/* Mothmr shell + plot-level satellite feature.
   Palette is the validated data-viz reference instance; layout uses logical
   properties throughout so RTL is a direction switch, not a second stylesheet. */

:root {
  color-scheme: light;
  --surface-1: #fcfcfb;
  --plane: #f9f9f7;
  --text-primary: #0b0b0b;
  --text-secondary: #52514e;
  --text-muted: #898781;
  --grid: #e1e0d9;
  --axis: #c3c2b7;
  --border: rgba(11, 11, 11, 0.10);

  /* Categorical series, assigned in this fixed order and never cycled. The
     multi-index chart on Crop monitoring draws up to eight at once, so two
     colours were not enough — and a palette picked by eye is the one thing
     the dataviz rules say never to do.
     
     Run against `validate_palette.js` on the light surface (#fcfcfb):
     lightness band PASS, chroma floor PASS, CVD separation PASS (worst
     adjacent pair ΔE 10.2 protan), normal-vision floor PASS (ΔE 18.2),
     contrast PASS except `--series-5` at 2.59:1 — which carries a WARN, and
     the relief the validator requires is present: every series is directly
     labelled at its last point and the same numbers are in the table below. */
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #0f9184;
  --series-4: #a05ac8;
  --series-5: #c99700;
  --series-6: #c2185b;
  --series-7: #0288a6;
  --series-8: #6f8b1e;

  --good: #0ca30c;
  --warning: #fab219;
  --critical: #d03b3b;

  /* Brand palette, sampled from the logo itself (k-means over its opaque
     pixels), not eyeballed from screenshots:
       leaf green #a3d153 · pin orange #fda401 · deep blue #0366a8
       mid blue   #027fb8 · white      #f9fcfa · teal      #05a9b6
     Each hue also gets an "-ink" step: the brand values are chosen for fills
     and are far too light to carry text on a light surface (white on the pin
     orange is only 1.98:1), so text and icons use the darker step while fills
     keep the brand value. */
  --brand-blue: #0366a8;
  --brand-blue-2: #027fb8;
  --brand-blue-ink: #024f80;
  --brand-teal: #05a9b6;
  --brand-teal-ink: #06767f;
  --brand-green: #a3d153;
  --brand-green-ink: #4f8118;
  --brand-orange: #fda401;
  --brand-orange-ink: #8a4f00;
  --brand-white: #f9fcfa;

  --brand: var(--brand-teal-ink);
  --accent: var(--brand-orange);
  /* orange is a fill colour; anything written on it must be dark ink */
  --on-accent: #26170a;

  --radius-card: 14px;
  --radius-control: 10px;
  --gap-xs: 4px;
  --gap-sm: 8px;
  --gap-md: 16px;
  --gap-lg: 24px;
  --shadow-card: 0 1px 2px rgba(11, 11, 11, .05), 0 6px 16px rgba(11, 11, 11, .05);
  /* Self-hosted (see `fonts.css`), so this is the face that renders rather
     than the face we hoped the operating system had. The system stack stays
     behind it as a genuine fallback. */
  --sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* The mobile bottom bar's own height — 6px padding, a 44px touch target,
     6px padding — named so anything sticking above it can say so rather than
     guess. Excludes the safe-area inset, which callers add themselves. */
  --bottomnav-h: 56px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --surface-1: #1a1a19;
    --plane: #0d0d0d;
    --text-primary: #ffffff;
    --text-secondary: #c3c2b7;
    --text-muted: #898781;
    --grid: #2c2c2a;
    --axis: #383835;
    --border: rgba(255, 255, 255, 0.10);
    /* Dark mode is *selected*, not an automatic flip: re-stepped from the
       same hues against the dark surface (#1a1a19) and validated there.
       Lightness band PASS (L 0.48-0.67), chroma floor PASS, CVD separation
       PASS (worst adjacent ΔE 9.7 deutan), normal-vision floor PASS (ΔE 19.0),
       contrast PASS on all eight. The light steps fail the band on this
       surface, which is why they are not simply reused. */
    --series-1: #4a90d9;
    --series-2: #dd6530;
    --series-3: #0d9488;
    --series-4: #9f62c6;
    --series-5: #b08a0a;
    --series-6: #cc3f74;
    --series-7: #1c8dab;
    --series-8: #789a28;
    /* on the dark surface the brand hues are already bright enough to carry
       text, so the "-ink" steps lighten rather than darken */
    --brand-blue-ink: #4aa6dd;
    --brand-teal-ink: #35c4cf;
    --brand-green-ink: #a3d153;
    --brand-orange-ink: #fdb733;
    --brand: var(--brand-teal-ink);
    --shadow-card: none;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--plane);
  color: var(--text-primary);
  /* 16px, set once and here. This said 14px and was overridden by a second
     `body { font-size: 16px }` nine hundred lines further down, so the
     declared size and the rendered size disagreed and editing this line did
     nothing at all. */
  font: 16px/1.5 var(--sans);
  padding-block-end: env(safe-area-inset-bottom);
  min-height: 100vh;
}

/* Agricultural backdrop.

   The source photograph is portrait, so scaling it to `cover` a landscape
   viewport magnifies it enormously - single blades of grass end up the size of
   a card. Instead the grass is used at its own scale as a band along the
   bottom, with the photograph's own sky tones continuing above it as a
   gradient. `grass.jpg` is the sun-free lower part of the photo mirrored onto
   itself, so repeating it horizontally leaves no visible seam at any width.

   Painted by fixed pseudo-elements rather than `background-attachment: fixed`,
   which is unreliable on mobile browsers. */
:root { --grass-h: clamp(96px, 19vh, 210px); --grass-top: calc(100% - var(--grass-h)); }

body::before {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: -2;
  /* The grass sits at its own scale along the bottom; a short fade blends its
     top edge into the sky instead of cutting hard. Nothing covers the
     photograph - readability comes from the opaque cards, app bar and
     navigation that sit on top of it. */
  background:
    linear-gradient(to bottom,
      rgba(250, 251, 244, 0) calc(var(--grass-top) - 64px),
      rgba(250, 251, 244, 0.80) calc(var(--grass-top) - 6px),
      rgba(250, 251, 244, 0) calc(var(--grass-top) + 30px)),
    url("grass.jpg") center bottom / auto var(--grass-h) repeat-x,
    linear-gradient(to bottom,
      #f7f8f3 0%, #fafbf4 44%, #f8fae2 76%, #eaeecb 100%);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body::before {
    background:
      /* blend the top edge, then damp the photo so it reads as a dark band */
      linear-gradient(to bottom,
        rgba(13, 13, 13, 0) calc(var(--grass-top) - 64px),
        rgba(13, 13, 13, 0.70) calc(var(--grass-top) - 6px),
        rgba(13, 13, 13, 0.45) calc(var(--grass-top) + 30px)),
      linear-gradient(to bottom,
        rgba(13, 13, 13, 0) var(--grass-top),
        rgba(16, 20, 14, 0.62) var(--grass-top),
        rgba(16, 20, 14, 0.72) 100%),
      url("grass.jpg") center bottom / auto var(--grass-h) repeat-x,
      linear-gradient(to bottom, #0d0d0d 0%, #101310 62%, #141810 100%);
  }
}

a { color: inherit; }

.skip {
  position: absolute; inset-inline-start: -9999px;
}
.skip:focus {
  inset-inline-start: var(--gap-md); inset-block-start: var(--gap-md);
  z-index: 50; background: var(--surface-1); padding: var(--gap-sm) var(--gap-md);
  border-radius: var(--radius-control);
}

/* ---------- app bar ---------- */
.appbar {
  position: sticky; inset-block-start: 0; z-index: 20;
  background: var(--surface-1);
  border-block-end: 1px solid var(--border);
  padding-block-start: env(safe-area-inset-top);
}
.appbar-inner {
  max-width: 1240px; margin-inline: auto;
  padding: var(--gap-sm) var(--gap-md);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap-md);
}
.brand {
  display: flex; align-items: center; gap: var(--gap-sm);
  text-decoration: none; color: inherit;
}
/* The account and the language selector, kept together at the end of the bar.
   Without this they are two more children of a `space-between` row, so they
   spread out and the signed-in name floats in the middle of the header.
   `margin-inline-start` rather than `margin-left`, so in Arabic the group ends
   up at the correct end of an RTL bar. */
.appbar-end {
  display: flex; align-items: center; gap: var(--gap-sm);
  margin-inline-start: auto; min-inline-size: 0;
}
/* U10 / §13 — the box is reserved by the `width`/`height`
   attributes on the tag, not here: browsers apply
   `aspect-ratio: attr(width)/attr(height)` to <img> from their own
   UA stylesheet, and typed `attr()` is not available to author
   stylesheets — written here it would be dropped silently while
   looking like it worked. `height: auto` then scales correctly
   instead of leaving the height unknown until the bytes arrive. */
.brand-mark { width: 30px; height: auto; display: block; }
.brand-name {
  font-weight: 700; letter-spacing: .01em; font-size: 18px;
  color: var(--brand-blue-ink);
}

.locale { display: flex; gap: var(--gap-xs); }
.locale-opt {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px; min-height: 34px;
  border: 1px solid var(--border); border-radius: 999px;
  text-decoration: none; color: var(--text-secondary);
  font-size: 13px;
}
.locale-opt.is-active {
  border-color: var(--brand-blue-ink); color: var(--brand-blue-ink);
  font-weight: 650;
}

/* ---------- shell ---------- */
.shell { max-width: 1240px; margin-inline: auto; display: block; }
.content { padding: var(--gap-md); padding-block-end: 96px; }

.sidenav {
  position: fixed; inset-inline: 0; inset-block-end: 0; z-index: 20;
  display: flex; justify-content: space-around;
  background: var(--surface-1);
  border-block-start: 1px solid var(--border);
  padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
}
.navitem {
  flex: 1; display: flex; flex-direction: column; align-items: center;
  gap: 2px; padding: 6px 2px; min-height: 44px;
  text-decoration: none; color: var(--brand-teal-ink); font-size: 11px;
  border-radius: var(--radius-control);
}
/* The brand orange is a fill colour and only reaches 1.98:1 as ink, so the
   active item is marked by a wash plus the darker orange step, never by the
   brand orange alone. */
.navitem.is-active {
  color: var(--brand-orange-ink); font-weight: 700;
  background: rgba(253, 164, 1, 0.16);
}
.navitem.is-disabled { opacity: .45; pointer-events: none; }
.navicon { font-size: 18px; line-height: 1; }

/* UI.md §16.4 — "Mobile: exactly four primary destinations — Today, Fields,
   Work, More." Reports is the fifth, so below the desktop breakpoint it leaves
   the bar and appears inside More instead. Five equal items plus More is six,
   which on a 320px screen is 53px each: too narrow for the 44px target once
   padding is counted, and far too narrow for the Arabic labels.

   Rendered in both places and hidden in one, rather than chosen in Jinja: the
   server cannot know the viewport, and guessing it from a user agent is how a
   tablet ends up with the wrong navigation. */
.navitem-wide { display: none; }
.navmore-narrow { display: list-item; }

@media (min-width: 1000px) {
  .navitem-wide { display: flex; }
  /* Gone from More once it has its own slot — the same destination in two
     places at once is how a menu stops being trustworthy. */
  .navmore-narrow { display: none; }
}

@media (min-width: 1000px) {
  .shell { display: grid; grid-template-columns: 232px 1fr; gap: var(--gap-lg); }
  /* Signed out there is no side navigation, and the grid was still reserving
     its 232px column — so the login, registration and 404 screens sat against
     an empty gutter on every desktop. `base.html` has marked this case since
     the shell was written; the rule was never added. Found by the class
     sweep, which is the whole reason that sweep exists (finding P2). */
  .shell.no-nav { grid-template-columns: 1fr; }
  .sidenav {
    position: sticky; inset-block: 64px auto; inset-inline: auto;
    align-self: start; flex-direction: column; justify-content: flex-start;
    gap: 2px; border: 1px solid var(--border);
    border-radius: var(--radius-card); padding: var(--gap-sm);
    margin-block-start: var(--gap-md); margin-inline-start: var(--gap-md);
  }
  .navitem {
    flex-direction: row; justify-content: flex-start; gap: var(--gap-sm);
    font-size: 14px; padding: 10px var(--gap-sm);
  }
  .content { padding-block-end: var(--gap-lg); }
}

/* ---------- cards & type ---------- */
.card {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  padding: var(--gap-md); margin-block-end: var(--gap-md);
}
.section-title {
  font-size: 15px; font-weight: 700; margin: 0 0 var(--gap-sm);
  color: var(--brand-green-ink);
}
.muted { color: var(--text-muted); }
.secondary { color: var(--text-secondary); }
.page-head { display: flex; align-items: baseline; gap: var(--gap-sm);
  flex-wrap: wrap; margin-block-end: var(--gap-md); }
.page-title { font-size: 22px; font-weight: 680; margin: 0; }
.backlink { text-decoration: none; color: var(--brand); font-size: 13px; }

/* ---------- KPI / hero ---------- */
.kpis { display: grid; gap: var(--gap-sm);
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.kpi {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-card); padding: var(--gap-md);
}
.kpi-label { font-size: 13px; color: var(--text-secondary); margin-block-end: 2px; }
.kpi-value { font-size: 26px; font-weight: 680; line-height: 1.15; }
.kpi-sub { font-size: 12px; color: var(--text-muted); margin-block-start: 2px; }
.hero .kpi-value { font-size: 44px; }

/* ---------- parcel cards ---------- */
.parcels { display: grid; gap: var(--gap-md);
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.parcel-name {
  font-size: 17px; font-weight: 700; margin: 0 0 2px;
  color: var(--brand-green-ink);
}
.parcel-meta { display: grid; gap: var(--gap-xs) var(--gap-md);
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  margin-block: var(--gap-sm); }
.meta-label { font-size: 11px; color: var(--text-muted); }
.meta-value { font-size: 14px; font-variant-numeric: tabular-nums; }
.cta {
  display: inline-block; margin-block-start: var(--gap-sm);
  padding: 9px 14px; border-radius: var(--radius-control);
  background: var(--accent); color: var(--on-accent); text-decoration: none;
  font-weight: 600; font-size: 13px; min-height: 40px; line-height: 22px;
}
/* The three per-parcel destinations. They wrap rather than scroll, so a narrow
   phone stacks them instead of hiding the third off-screen. */
.cta-row { display: flex; flex-wrap: wrap; gap: var(--gap-sm); }
.cta-row .cta { flex: 1 1 auto; text-align: center; }
.cta-row .cta + .cta {
  background: transparent; color: var(--brand);
  border: 1px solid var(--border);
}

/* ---------- status pills (never colour alone) ---------- */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: 999px; font-size: 12px;
  border: 1px solid var(--border); white-space: nowrap;
}
.pill::before { content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: var(--dot, var(--text-muted)); flex: none; }
.pill-full { --dot: var(--good); }
.pill-partial { --dot: var(--warning); }
.pill-none { --dot: var(--text-muted); }
.pill-pending { --dot: var(--series-1); }

/* ---------- day list ---------- */
.daylist { list-style: none; margin: 0; padding: 0; }
.day {
  border-block-end: 1px solid var(--grid); padding-block: var(--gap-sm);
  display: grid; gap: var(--gap-xs);
  grid-template-columns: minmax(120px, auto) 1fr; align-items: start;
}
.day:last-child { border-block-end: 0; }
.day-date { font-weight: 600; font-variant-numeric: tabular-nums; }
.day-facts { display: flex; gap: var(--gap-sm); flex-wrap: wrap;
  font-size: 12px; color: var(--text-secondary); margin-block-start: 2px; }
.shots { list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--gap-xs); }
.shot-link {
  display: inline-flex; align-items: center; gap: var(--gap-xs);
  padding: 5px 10px; min-height: 34px;
  border: 1px solid var(--border); border-radius: var(--radius-control);
  text-decoration: none; background: var(--plane);
  font-size: 12.5px; font-variant-numeric: tabular-nums;
}
.shot-link:hover { border-color: var(--brand); }
.shot-name { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.ndvi-chip { color: var(--text-secondary); }

@media (max-width: 560px) {
  .day { grid-template-columns: 1fr; }
}

/* ---------- coverage calendar ---------- */
.calendars {
  display: grid; gap: var(--gap-md);
  grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
}
.month-title {
  font-size: 13px; font-weight: 650; margin: 0 0 var(--gap-xs);
  color: var(--text-secondary);
}
table.cal { width: 100%; border-collapse: collapse; table-layout: fixed; }
table.cal th {
  font-size: 10px; font-weight: 600; color: var(--text-muted);
  padding-block-end: 4px; text-align: center;
}
.cal-day, .cal-pad {
  height: 34px; text-align: center; vertical-align: top;
  border: 1px solid transparent; border-radius: 6px; padding: 2px 0;
}
.cal-day { border-color: var(--grid); }
.cal-num {
  display: block; font-size: 10px; color: var(--text-muted);
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.cal-day.has-shots { background: var(--plane); }
.dots {
  display: flex; justify-content: center; align-items: center;
  gap: 2px; flex-wrap: wrap; margin-block-start: 2px;
}
/* dots are links; keep the hit target usable without inflating the grid */
a.dot { position: relative; }
a.dot::after {
  content: ""; position: absolute; inset: -6px; border-radius: 50%;
}
a.dot:hover, a.dot:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}
.dot {
  width: 9px; height: 9px; border-radius: 50%; display: inline-block;
  background: var(--dot, var(--text-muted));
}
.dot-full { --dot: var(--good); }
.dot-partial { --dot: var(--warning); }
.dot-none { --dot: var(--axis); }
.legend .dot { width: 9px; height: 9px; border-radius: 50%; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* ---------- pagination ---------- */
.pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap-sm); margin-block-start: var(--gap-md);
  padding-block-start: var(--gap-sm); border-block-start: 1px solid var(--grid);
  font-size: 13px; flex-wrap: wrap;
}
.pager a { text-decoration: none; }

/* ---------- filters ---------- */
.filters { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  align-items: flex-end; }
.field { display: flex; flex-direction: column; gap: 3px; }
/* 13px, not 11. This labels a control somebody is about to type into, which
   makes it the opposite of tertiary — and this product is read outdoors, in
   sunlight, at arm's length. The 10–12px band was set against a 14px body and
   never revisited when the body moved to 16. */
.field label { font-size: 13px; color: var(--text-secondary); }
.field select, .field input {
  font: inherit; color: inherit; background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  padding: 8px 10px; min-height: 40px; min-width: 120px;
}
.btn {
  font: inherit; font-weight: 600; min-height: 40px; padding: 8px 16px;
  border-radius: var(--radius-control); border: 1px solid var(--border);
  background: var(--brand); color: #fff; cursor: pointer;
}
.btn-ghost { background: transparent; color: var(--text-secondary); }
/* A secondary control that sits inline beside its subject — "Remove file" next
   to a thumbnail. Keeps the 44px field-use target of every other button (§13);
   only the horizontal padding and the type size come down. */
.btn-small { font-size: 13px; padding-inline: var(--gap-sm); }

/* U7 — the short "what is still missing" list, repeated inside the evidence
   capture card. Indented and quiet: it is a reminder of the full list above,
   not a second copy competing with it. */
.evneeded { margin-block-end: var(--gap-md);
  padding-inline-start: var(--gap-sm);
  border-inline-start: 3px solid var(--warning); }

/* ---------- tables ---------- */
.table-wrap { overflow-x: auto; }
table.data { width: 100%; border-collapse: collapse; font-size: 13px; }
table.data th, table.data td {
  text-align: start; padding: 7px 10px;
  border-block-end: 1px solid var(--grid);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
table.data th { color: var(--text-secondary); font-weight: 600; }

/* ---------- freshness and disclosure (U6) ----------
   Two shared components, extracted rather than styled inline, because U6 is
   the first of several priorities that need them and UI.md §14 asks for
   exactly this: extract as each priority ships, not a new design system.

   `.freshness` is deliberately quiet when fresh and only takes colour when
   stale — an age is background information right up until it stops being
   true. The stale state never relies on colour alone: the text says how old
   the reading is (§13, and the CVD case in the dataviz rules). */
.freshness { margin: var(--gap-sm) 0 0; font-size: 12px;
  color: var(--text-muted); display: flex; align-items: center;
  gap: var(--gap-xs); flex-wrap: wrap; }
.freshness.is-stale { color: var(--brand-orange-ink); font-weight: 600; }
.freshness.is-stale svg { flex: none; }

/* Progressive disclosure for provenance and technical detail (§12: move
   metadata, do not delete it). Sits *inside* a card, so it must not draw a
   second card border around itself. */
.disclosure { margin-block-start: var(--gap-md);
  border-block-start: 1px solid var(--grid); padding-block-start: var(--gap-sm); }
.disclosure > summary { cursor: pointer; font-size: 12px; font-weight: 600;
  color: var(--text-secondary); padding: var(--gap-xs) 0;
  /* 44px field-use target (§13) without moving the text off the baseline. */
  min-block-size: 24px; display: flex; align-items: center; }
.disclosure > summary:focus-visible { outline: 2px solid var(--brand);
  outline-offset: 2px; border-radius: var(--radius-control); }
.disclosure[open] > summary { margin-block-end: var(--gap-sm); }

/* ---------- definition grid ---------- */
.facts { display: grid; gap: var(--gap-sm) var(--gap-md);
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); margin: 0; }
.facts dt { font-size: 11px; color: var(--text-muted); }
.facts dd { margin: 0 0 var(--gap-sm); font-size: 14px;
  font-variant-numeric: tabular-nums; }

/* ---------- charts ---------- */
.chart { width: 100%; display: block; overflow: visible; }
.chart-head { display: flex; justify-content: space-between;
  align-items: baseline; gap: var(--gap-sm); flex-wrap: wrap; }
.legend { display: flex; gap: var(--gap-md); font-size: 12px;
  color: var(--text-secondary); flex-wrap: wrap; }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.viewtabs { display: inline-flex; gap: 2px; }
.viewtabs button {
  font: inherit; font-size: 12.5px; min-height: 34px; padding: 6px 12px;
  border: 1px solid var(--border); background: var(--surface-1);
  color: var(--text-secondary); cursor: pointer;
}
.viewtabs button:first-child {
  border-start-start-radius: var(--radius-control);
  border-end-start-radius: var(--radius-control);
}
.viewtabs button:last-child {
  border-start-end-radius: var(--radius-control);
  border-end-end-radius: var(--radius-control);
}
.viewtabs button[aria-pressed="true"] {
  background: var(--brand); color: #fff; border-color: var(--brand);
  font-weight: 600;
}
.tooltip {
  position: fixed; z-index: 40; pointer-events: none;
  background: var(--surface-1); color: var(--text-primary);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  box-shadow: var(--shadow-card); padding: 7px 10px; font-size: 12.5px;
  opacity: 0; transition: opacity .1s;
}
.tooltip.is-on { opacity: 1; }

/* ---------- notices ---------- */
.notice {
  border: 1px solid var(--border); border-inline-start: 3px solid var(--series-1);
  border-radius: var(--radius-control); padding: var(--gap-sm) var(--gap-md);
  background: var(--surface-1); font-size: 13px; color: var(--text-secondary);
  margin-block-end: var(--gap-md);
}
.empty { text-align: center; padding: var(--gap-lg); color: var(--text-secondary); }

.bandgrid { display: grid; gap: var(--gap-xs);
  grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); }
.bandgrid a {
  display: block; padding: 7px 9px; font-size: 12px;
  border: 1px solid var(--border); border-radius: var(--radius-control);
  text-decoration: none; background: var(--plane);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.bandgrid a:hover { border-color: var(--brand); }

#map { height: 320px; border-radius: var(--radius-control);
  border: 1px solid var(--border); }

/* ---------- growth stages ---------- */
.stages { list-style: none; margin: var(--gap-xs) 0 0; padding: 0;
  display: grid; gap: 2px; }
.stage {
  display: grid; grid-template-columns: 24px 1fr auto; gap: var(--gap-sm);
  align-items: center; padding: 5px 8px; border-radius: 6px;
  font-size: 13px; background: var(--plane);
}
.stage-seq {
  width: 22px; height: 22px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; font-size: 11px;
  background: var(--surface-1); border: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}
.stage.is-active {
  background: rgba(253, 164, 1, 0.16); font-weight: 650;
}
.stage.is-active .stage-seq {
  background: var(--brand-orange); color: var(--on-accent);
  border-color: transparent;
}
.stage-completed { opacity: .65; }
.stage-skipped { opacity: .45; text-decoration: line-through; }

/* ---------- work orders (spec v3.1 §75-§76) ---------- */
.tasks { list-style: none; margin: var(--gap-xs) 0 0; padding: 0;
  display: grid; gap: var(--gap-sm); }
.task {
  padding: var(--gap-sm) var(--gap-md); border-radius: 8px;
  background: var(--plane); border-inline-start: 3px solid var(--border);
}
.task-head { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--gap-sm); }
.task-title { font-weight: 650; font-size: 14px; }
.task-blocked { border-inline-start-color: var(--critical); }
.task-in_progress { border-inline-start-color: var(--brand-teal); }
.task-completed { border-inline-start-color: var(--brand-green); }
.task-verified { border-inline-start-color: var(--good); }
/* The status word is always rendered beside the dot, so none of these
   colours is load-bearing on its own. */
.pill-draft { --dot: var(--text-muted); }
.pill-planned { --dot: var(--series-1); }
.pill-assigned { --dot: var(--brand-blue); }
.pill-in_progress { --dot: var(--brand-teal); }
.pill-blocked { --dot: var(--critical); }
.pill-completed { --dot: var(--brand-green); }
.pill-verified { --dot: var(--good); }
.pill-cancelled { --dot: var(--text-muted); }
.pill-priority-low { --dot: var(--text-muted); }
.pill-priority-normal { --dot: var(--series-1); }
.pill-priority-high { --dot: var(--warning); }
.pill-priority-urgent { --dot: var(--critical); }
/* Advice ranks itself high/medium/low while a task is low/normal/high/urgent,
   so `medium` has no task equivalent and was undefined — invisible to the
   undefined-class sweep, because the class name is interpolated from the
   value rather than written out. */
.pill-priority-medium { --dot: var(--series-1); }
/* required evidence, attached evidence, and the activity trail (§78-§79) */
.req, .evidence, .trail { list-style: none; margin: var(--gap-xs) 0 var(--gap-sm);
  padding: 0; display: grid; gap: 2px; font-size: 13px; }
.req-item {
  display: grid; grid-template-columns: 1fr auto auto; gap: var(--gap-sm);
  align-items: center; padding: 4px 8px; border-radius: 6px;
  background: var(--surface-1); border-inline-start: 3px solid var(--warning);
}
.req-item.is-met { border-inline-start-color: var(--good); opacity: .7; }
.ev { display: flex; flex-wrap: wrap; gap: var(--gap-sm); align-items: center;
  padding: 3px 8px; border-radius: 6px; background: var(--surface-1); }
.ev-kind {
  font-size: 11px; padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--text-secondary); flex: none;
}
.trail li { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  padding: 2px 8px; }
/* attention queue (§80) and the parcel record (§79) */
.attn { list-style: none; margin: var(--gap-xs) 0 0; padding: 0;
  display: grid; gap: var(--gap-sm); }
.attn-item { padding: var(--gap-sm) var(--gap-md); border-radius: 8px;
  background: var(--plane); border-inline-start: 3px solid var(--border); }
.attn-head { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  align-items: center; }
.attn-title { font-weight: 650; }
.attn-why { margin: var(--gap-xs) 0 0; font-size: 13px;
  color: var(--text-secondary); }
.attn-critical { border-inline-start-color: var(--critical); }
.attn-high { border-inline-start-color: var(--critical); }
.attn-medium { border-inline-start-color: var(--warning); }
.attn-low { border-inline-start-color: var(--series-1); }
.attn-info { border-inline-start-color: var(--border); }
/* the severity word sits beside the dot, so colour is never the only cue */
.pill-sev-critical { --dot: var(--critical); }
.pill-sev-high { --dot: var(--critical); }
.pill-sev-medium { --dot: var(--warning); }
.pill-sev-low { --dot: var(--series-1); }
.pill-sev-info { --dot: var(--text-muted); }
.record { list-style: none; margin: var(--gap-xs) 0 0; padding: 0;
  display: grid; gap: 2px; }
.record-row { display: grid; gap: var(--gap-sm); align-items: baseline;
  grid-template-columns: auto auto 1fr auto; padding: 4px 8px;
  border-radius: 6px; font-size: 13px; }
.record-row:nth-child(odd) { background: var(--plane); }
.record-at { font-variant-numeric: tabular-nums; white-space: nowrap; }
.record-what { overflow-wrap: anywhere; }
@media (max-width: 560px) {
  .record-row { grid-template-columns: auto 1fr; }
}
.note-restricted {
  margin: var(--gap-sm) 0 0; padding: 6px 10px; border-radius: 6px;
  font-size: 12px; background: rgba(253, 164, 1, 0.14);
  border: 1px solid var(--border); color: var(--text-primary);
}

/* operational KPI row (§81) — links straight into the workload screen */
.kpis-ops .kpi-link { text-decoration: none; color: inherit; display: block; }
.kpis-ops .kpi-link:hover { border-color: var(--brand-teal-ink); }
.kpi.is-hot { border-color: var(--critical); }
.kpi.is-hot .kpi-value { color: var(--critical); }

/* reports: a print stylesheet, since PDF is produced by the browser */
.linkish { background: none; border: 0; padding: 0; font: inherit;
  color: var(--brand-blue-ink); text-decoration: underline; cursor: pointer; }
@media print {
  .appbar, .sidenav, .no-print, .skip { display: none !important; }
  body::before { display: none !important; }
  body { background: #fff; }
  .content { padding: 0; }
  .card { box-shadow: none; border: 1px solid #ccc; break-inside: avoid; }
  .table-wrap { overflow: visible; }
  a { text-decoration: none; color: inherit; }
}

/* import preview: status is named in its own column, colour only reinforces */
tr.imp-ok td { background: rgba(12, 163, 12, 0.06); }
tr.imp-duplicate td { background: rgba(250, 178, 25, 0.10); }
tr.imp-invalid td { background: rgba(208, 59, 59, 0.08); }

/* a read notification recedes but stays legible */
.attn-item.is-read { opacity: .62; }

/* §92.1.3 parcel thumbnail on the home card */
.parcel-thumb { display: block; margin-block: var(--gap-sm);
  border-radius: 10px; overflow: hidden; border: 1px solid var(--border);
  background: var(--plane); }
.parcel-thumb img { display: block; width: 100%; height: 132px;
  object-fit: cover; }

/* ---------- language switcher (five locales, so a disclosure) ---------- */
.locale { position: relative; }
.locale > summary {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 6px 10px; min-height: 34px; list-style: none;
  border: 1px solid var(--border); border-radius: 999px;
  color: var(--text-secondary); font-size: 13px;
}
.locale > summary::-webkit-details-marker { display: none; }
.locale[open] > summary,
.locale > summary:hover { border-color: var(--brand-blue-ink);
  color: var(--brand-blue-ink); }
.locale-caret { font-size: 10px; }
.locale[open] .locale-caret { transform: rotate(180deg); }
.locale-list {
  position: absolute; inset-block-start: calc(100% + 6px); inset-inline-end: 0;
  z-index: 40; min-width: 190px; margin: 0; padding: 4px;
  list-style: none; background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  box-shadow: var(--shadow-card);
}
.locale-list .locale-opt {
  display: flex; align-items: center; gap: var(--gap-sm);
  padding: 8px 10px; min-height: 40px; border-radius: 8px;
  text-decoration: none; color: var(--text-primary); font-size: 13px;
}
.locale-list .locale-opt:hover { background: var(--plane); }
.locale-list .locale-opt.is-active {
  color: var(--brand-blue-ink); font-weight: 650;
  background: rgba(3, 102, 168, 0.08);
}
.locale-name { margin-inline-start: auto; color: var(--text-secondary); }
.locale-list .locale-opt.is-active .locale-name { color: inherit; }

/* ---------- the account menu ----------
   Beside the language switcher and built from the same parts: a real
   <details>, keyboard reachable, closing on Escape, no JavaScript. Profile
   left the side navigation for this — the side navigation is the operating
   loop, and "which account am I, in which role" is not part of it.

   The name and role are visible **on the control itself** at desktop width,
   because that is the question being answered; on a phone the summary
   collapses to the avatar and the detail moves inside, where there is room
   for the phone number and workspace as well. */
.acct { position: relative; }
.acct > summary {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 4px 10px; min-height: 34px; list-style: none;
  border: 1px solid var(--border); border-radius: 999px;
  color: var(--text-secondary); font-size: 13px; max-inline-size: 220px;
}
.acct > summary::-webkit-details-marker { display: none; }
.acct[open] > summary,
.acct > summary:hover { border-color: var(--brand-blue-ink);
  color: var(--brand-blue-ink); }
.acct[open] .locale-caret { transform: rotate(180deg); }

.acct-avatar { display: inline-flex; flex: none; }
/* Hidden on narrow screens rather than truncated: a name cut to three
   characters identifies nobody, and the full name is in the panel below. */
.acct-who { display: none; min-inline-size: 0; }
.acct-name { font-weight: 650; color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-role { color: var(--text-muted); font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (min-width: 720px) {
  .acct-who { display: flex; flex-direction: column; line-height: 1.25;
    align-items: flex-start; }
}

.acct-list {
  position: absolute; inset-block-start: calc(100% + 6px); inset-inline-end: 0;
  z-index: 40; min-inline-size: 230px; padding: 4px;
  background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  box-shadow: var(--shadow-card);
}
.acct-detail {
  display: flex; flex-direction: column; gap: 2px;
  margin: 0; padding: 8px 10px;
  border-block-end: 1px solid var(--grid); font-size: 12px;
}
.acct-opt {
  display: flex; align-items: center; gap: var(--gap-sm); inline-size: 100%;
  padding: 8px 10px; min-height: 44px; border-radius: 8px;
  text-decoration: none; color: var(--text-primary); font-size: 13px;
  background: none; border: 0; font-family: inherit; text-align: start;
  cursor: pointer;
}
.acct-opt:hover { background: var(--plane); }
.acct-opt.is-active { color: var(--brand-blue-ink); font-weight: 650;
  background: rgba(3, 102, 168, 0.08); }
.acct-signout { color: var(--critical); }

/* ---------- authentication screens ----------
   Finding P2: login.html and register.html referenced `authwrap`, `auth-logo`,
   `authfield`, `btn-block` and nine more classes that had no definitions at
   all, so both screens rendered as an oversized logo above default browser
   inputs. This is the missing half, not a restyle.

   Acceptance target from the same finding: at 384x832 the fields and the
   submit action are reachable without scrolling, and labels stay legible
   after entry. */
.authwrap {
  max-width: 420px; margin-inline: auto;
  padding: var(--gap-md) var(--gap-md) var(--gap-lg);
  display: flex; flex-direction: column; align-items: stretch;
}
/* Compact on a phone, where the recording's full-width logo pushed the form
   below the fold; it may grow once there is room for it. */
.auth-logo {
  display: block; margin-inline: auto; margin-block-end: var(--gap-sm);
  width: clamp(72px, 22vw, 104px); height: auto;
}
.auth-logo-sm { display: block; margin-inline: auto; width: 64px; height: auto; }

.authcard { padding: var(--gap-md); margin-block-end: var(--gap-sm); }
.authcard-head { text-align: center; margin-block-end: var(--gap-sm); }
.auth-headline, .auth-title {
  font-size: 20px; line-height: 1.3; margin: 0 0 var(--gap-md);
  color: var(--text-primary);
}

/* Full-width fields with a visible focus ring. `inline-size: 100%` with
   border-box keeps them inside the card in both directions, which a plain
   `width: 100%` does not once padding is added. */
.authfield {
  font: inherit; box-sizing: border-box; inline-size: 100%;
  min-height: 44px;                     /* a comfortable touch target */
  padding: 10px 12px; margin-block-end: var(--gap-sm);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  background: var(--surface-1); color: var(--text-primary);
}
.authfield::placeholder { color: var(--text-muted); }
.authfield:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 1px;
  border-color: var(--brand);
}

.btn-block {
  display: block; inline-size: 100%; margin-block-start: var(--gap-sm);
  min-height: 44px;
}
.btn-accent { background: var(--accent); color: var(--on-accent); }
.btn-accent:focus-visible { outline: 2px solid var(--brand-orange-ink); }

/* Unavailable, and it must look unavailable rather than merely fail. Kept out
   of the primary action stack visually so it cannot be mistaken for a working
   way in (finding P3). */
.btn-apple {
  background: transparent; color: var(--text-muted);
  border: 1px dashed var(--border); cursor: not-allowed;
}
.btn-apple[disabled] { opacity: .65; }

.auth-register, .auth-help, .auth-demo {
  text-align: center; font-size: 13px; margin: var(--gap-sm) 0 0;
}
.auth-register a, .auth-help a { color: var(--brand); }
.auth-demo {
  margin-block-start: var(--gap-md); padding-block-start: var(--gap-sm);
  border-block-start: 1px solid var(--border); font-size: 12px;
}
.auth-actions { display: flex; gap: var(--gap-sm); margin-block-start: var(--gap-md); }
.auth-actions .btn { flex: 1 1 auto; }

.notice { border-radius: var(--radius-control); padding: 8px 12px;
          font-size: 13px; margin: 0 0 var(--gap-sm); }
.notice-good { background: rgba(12, 163, 12, .10); color: #0a6b0a;
               border: 1px solid rgba(12, 163, 12, .30); }
.notice-bad  { background: rgba(208, 59, 59, .10); color: #8f2020;
               border: 1px solid rgba(208, 59, 59, .30); }

/* ---------- draw a parcel (Add Farm) ---------- */
.drawwrap { position: relative; }
#drawmap {
  height: clamp(280px, 46vh, 460px); inline-size: 100%;
  border-radius: var(--radius-control); border: 1px solid var(--border);
  z-index: 0;                       /* keep Leaflet panes under the app bar */
}
.drawtools {
  display: flex; gap: var(--gap-sm); margin-block-start: var(--gap-sm);
}
.drawstat { font-weight: 600; margin-block: var(--gap-sm) 0; }
.fieldlabel {
  display: block; font-size: 13px; color: var(--text-secondary);
  margin-block: var(--gap-sm) 4px;
}
.pointlist { list-style: none; margin: var(--gap-sm) 0 0; padding: 0;
             max-height: 160px; overflow-y: auto; }
.pointlist li {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap-sm); padding: 4px 0; font-size: 13px;
  border-block-end: 1px solid var(--border);
}
/* Typing a corner, for a parcel that cannot be drawn by pointing at it
   (finding R4). Two numbers and a button, side by side where there is room. */
.pointadd {
  display: grid; gap: var(--gap-sm); margin-block-start: var(--gap-sm);
  grid-template-columns: 1fr 1fr auto; align-items: end;
}
.pointadd .fieldlabel { grid-column: auto; margin-block-end: 2px; }
@media (max-width: 480px) { .pointadd { grid-template-columns: 1fr 1fr; } }

.linkbtn {
  font: inherit; font-size: 12px; background: none; border: 0;
  color: var(--critical); cursor: pointer; padding: 4px 6px; min-height: 32px;
}

/* ---------- parcel card mini-map ----------
   The card used to carry a rendered satellite crop. A real map is more use:
   it places the field rather than only picturing it, and it keeps the parcel
   boundary legible at thumbnail size. Non-interactive on purpose — the card
   is a link, and a map that swallows drags would fight the page scroll. */
.parcel-map {
  display: block; margin-block: var(--gap-sm); height: 132px;
  border-radius: 10px; overflow: hidden; border: 1px solid var(--border);
  background: var(--plane); z-index: 0;
}
.parcel-map .leaflet-container { background: var(--plane); }

/* ---------- index overlay on the parcel map ---------- */
.indexmapwrap { position: relative; }
#indexmap {
  height: clamp(240px, 40vh, 420px); inline-size: 100%;
  border-radius: var(--radius-control); border: 1px solid var(--border);
  z-index: 0;
}
.indexpicker {
  display: flex; flex-wrap: wrap; gap: var(--gap-xs);
  margin-block-end: var(--gap-sm);
}
.indexpicker button {
  font: inherit; font-size: 12px; padding: 6px 10px; min-height: 34px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface-1); color: var(--text-secondary); cursor: pointer;
}
.indexpicker button[aria-pressed="true"] {
  border-color: var(--brand); color: var(--brand); font-weight: 650;
}
.indexlegend {
  display: flex; align-items: center; gap: var(--gap-sm);
  margin-block-start: var(--gap-sm); font-size: 12px;
  color: var(--text-secondary);
}
.indexramp {
  flex: 1 1 auto; height: 10px; border-radius: 999px;
  border: 1px solid var(--border);
}

/* ---------- home: compact summary, then parcels (finding P4) ----------
   The old order put eight KPI cards, today's work and the seasons list above
   the first parcel — measured at 1,066 px down a 384 px screen. This summary
   is one row, so a complete parcel card and its actions are reachable without
   scrolling. */
.summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--gap-md);
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-card); padding: var(--gap-sm) var(--gap-md);
  margin-block-end: var(--gap-md);
}
.summary-figure { display: flex; align-items: baseline; gap: 6px; }
.summary-value { font-size: 20px; font-weight: 700; line-height: 1.1; }
.summary-none { color: var(--text-muted); font-weight: 400; }
.summary-label { font-size: 13px; color: var(--text-secondary); }

.unitpick { display: flex; gap: 4px; margin-inline-start: auto; }
.unitopt {
  font-size: 12px; padding: 5px 9px; min-height: 32px;
  display: inline-flex; align-items: center;
  border: 1px solid var(--border); border-radius: 999px;
  text-decoration: none; color: var(--text-secondary);
}
.unitopt.is-active {
  border-color: var(--brand); color: var(--brand); font-weight: 650;
}

/* ---------- parcel card (finding P5) ---------- */
.parcel-card { padding: var(--gap-sm) var(--gap-md) var(--gap-md); }
.parcel-head { display: flex; gap: var(--gap-sm); align-items: flex-start; }
.parcel-head .parcel-map {
  flex: 0 0 auto; inline-size: 92px; height: 92px; margin-block: 0;
}
.parcel-map.is-empty { background: var(--plane); }

/* The parcel's own imagery, over the server-drawn shape. The outline paints
   with the HTML and the photograph covers it once rendered, so the card is
   never an empty box and never a layout shift — both fill the same frame. */
.parcel-map { position: relative; }
.parcel-shot {
  position: absolute; inset: 0; inline-size: 100%; block-size: 100%;
  object-fit: cover; display: block;
}

/* The server-drawn parcel shape. Fills its box and uses the brand orange, so
   a field with no usable reading yet still shows its own shape. */
.parcel-outline { display: block; inline-size: 100%; block-size: 100%; }
.parcel-outline polygon {
  fill: var(--brand-orange); fill-opacity: .28;
  stroke: var(--brand-orange); stroke-width: 2; stroke-linejoin: round;
}
.parcel-ident { min-width: 0; }
.parcel-ident .parcel-name {
  font-size: 17px; margin: 0 0 2px; line-height: 1.25;
}
.parcel-sub { font-size: 13px; color: var(--text-secondary); }
.parcel-sub .dot { margin-inline: 4px; color: var(--text-muted); }
.parcel-fresh { font-size: 13px; margin: var(--gap-sm) 0 0; }

.section-heading {
  font-size: 15px; color: var(--text-secondary); font-weight: 650;
  margin: var(--gap-lg) 0 var(--gap-sm);
}

/* ---------- Home, restructured ----------
   The action in the "due today" panel is its own row. It used to be an inline
   element following a paragraph inside a block container, so it sat tight
   against the text and its leading edge never lined up with the heading. */
.journey-focus-action { margin-block: var(--gap-md) 0 !important; }
.journey-focus-action .btn { display: inline-flex; align-items: center; }

/* The parcels header: the section's name on one side, the unit control it
   governs on the other. `.unitpick` already pushes itself to the end with
   `margin-inline-start: auto`, so this only has to be a row. */
.parcels-head {
  display: flex; align-items: baseline; gap: var(--gap-sm);
  flex-wrap: wrap; margin-block: var(--gap-lg) var(--gap-sm);
}
.parcels-head .section-heading { margin: 0; }

/* Compact cards. The whole card is one link to the field now — its own tabs
   carry satellite, weather and indices — so it needs the affordances a link
   inside a card used to provide: a hover edge, and no underline dragged
   across the name and the figures. */
.parcels-compact { gap: var(--gap-sm); }
.parcels-compact .parcel-card {
  display: block; text-decoration: none; color: inherit;
  padding: var(--gap-sm) var(--gap-md);
}
.parcels-compact .parcel-card:hover { border-color: var(--brand); }
.parcels-compact .parcel-head .parcel-map {
  inline-size: 64px; height: 64px;
}
.parcels-compact .parcel-name { font-size: 16px; }
.parcels-compact .parcel-fresh { margin-block-start: 6px; }

/* Parcel priorities, merged into the Fields table. Quiet: the row already
   names the field, so these are the exceptions hanging off it. */
.fieldissues { list-style: none; margin: var(--gap-xs) 0 0; padding: 0;
  display: grid; gap: var(--gap-xs); font-size: 13px; }
.fieldissues li { display: flex; align-items: center; gap: 6px;
  flex-wrap: wrap; }

/* The queue switcher on Work, including the personal queue that used to be a
   card on Home. */
.workviews { display: flex; flex-wrap: wrap; gap: var(--gap-xs);
  margin-block: var(--gap-sm) var(--gap-md); }
.workviews .cta.is-active {
  background: var(--brand); color: #fff; border-color: var(--brand);
}

/* ---------- Arabic and general legibility (finding P8) ----------
   12–13 px is reserved for genuinely secondary metadata rather than used for
   field values. Touch targets reach 44 px. (The body size itself is set once,
   at the top of this file — it used to be restated here, which meant the
   declaration up there had no effect and nobody could tell which one won.) */
.muted { font-size: 13px; }
.meta-label { font-size: 12px; }
.btn, .cta, .navitem, .locale-opt { min-height: 44px; }
.cta { display: inline-flex; align-items: center; justify-content: center; }
:where(a, button, summary, input, select):focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}

/* ---------- motion ----------
   The application had exactly one transition in ninety thousand lines, so
   every state change was instantaneous and unexplained: a pill changed colour,
   a row vanished, a count decremented, with nothing connecting the before to
   the after. That reads as unfinished more than any single screen does.

   Deliberately small. This is a tool used one-handed in a field, not a
   showcase: colour and opacity only, nothing that moves layout, nothing that
   delays a tap. `view-transition` is a progressive enhancement — browsers
   without it simply navigate as before. */
@view-transition { navigation: auto; }

:where(.btn, .cta, .navitem, .navmore-opt, .kpi-link, .locale-opt, .wsopt,
       .acct-opt, .crop-stage, .unitopt) {
  transition: background-color .15s ease, border-color .15s ease,
              color .15s ease, opacity .15s ease;
}

/* Every transition above, and the tooltip's, off in one place — which is the
   rule that was missing while there was nothing to turn off. */
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}
:root[lang="ar"], [dir="rtl"] {
  /* Self-hosted and identical to the public site's stack, so the marketing
     page and the product are set in the same face — they were not, and the
     landing page's Tahoma read as 1994 Windows to the market this product is
     aimed at. Noto stays as a fallback for anything that fails to load. */
  --sans: "IBM Plex Sans Arabic", "Noto Sans Arabic", "Segoe UI", system-ui, sans-serif;
  /* Arabic carries ascenders, descenders and diacritics that 1.5 crowds. The
     public site already sets its own looser figure; the product did not. */
  line-height: 1.7;
}

/* Tracking and casing are Latin typographic devices, and both are wrong here.
   Arabic is a connected script: positive `letter-spacing` forces the joins
   apart, so the text does not read as styled, it reads as broken. And Arabic
   has no case, so `text-transform: uppercase` does nothing at all — which
   leaves these labels with no distinguishing signal beyond being small and
   grey. `landing.css` already resets this for the public site; the
   application never did, and `.navgroup-label` is on every signed-in screen.

   `.idxcard-code` is deliberately absent: it sets index codes like NDVI,
   which are Latin in every locale and should keep their tracking. */
[dir="rtl"] .navgroup-label,
[dir="rtl"] .indexpicker-title,
[dir="rtl"] .brand-name {
  letter-spacing: normal;
  text-transform: none;
}

/* ---------- classes the templates already used and nothing defined ----------
   P2 found thirteen of these on the login screen and they were fixed there.
   The same sweep across all nineteen templates found twelve more, including
   the navigation's own labels and the logout button, so those screens have
   been rendering as unstyled browser defaults ever since. This is the missing
   half of markup that already shipped, not a restyle. */

/* Destructive, and it has to look it without shouting: outlined rather than a
   filled red block, because on the profile screen it is the only button and a
   solid red rectangle reads as an error state. */
.btn-danger {
  background: transparent; color: var(--critical);
  border-color: var(--critical); font-weight: 650;
}
.btn-danger:hover { background: rgba(208, 59, 59, .08); }
.btn-danger:focus-visible { outline-color: var(--critical); }

/* The sidenav label. `.navitem` already sizes the type for each breakpoint, so
   this only has to keep a long translation from breaking the five-across bar
   on a narrow phone. */
.navlabel {
  display: block; line-height: 1.15; text-align: center;
  max-inline-size: 100%; overflow-wrap: anywhere;
}
@media (min-width: 1000px) { .navlabel { text-align: start; } }

/* Screens with no `.page-head` wrapper: the title otherwise sits directly on
   top of the content below it, since `.page-title` carries no margin. */
.screen-title { margin-block-end: var(--gap-md); }

.plainlist { list-style: none; margin: 0; padding: 0; display: grid;
  gap: var(--gap-xs); }
.plainlist li { padding-block: 6px; border-block-end: 1px solid var(--grid); }
.plainlist li:last-child { border-block-end: 0; }

.profilewrap {
  max-width: 520px; margin-inline: auto;
  display: flex; flex-direction: column; gap: var(--gap-sm);
}
.profile-welcome { font-size: 18px; font-weight: 650; margin: 0; }
.profile-version {
  margin-block-start: var(--gap-lg); padding-block-start: var(--gap-sm);
  border-block-start: 1px solid var(--border); text-align: center;
}
.locale-center { justify-content: center; flex-wrap: wrap;
  margin-block: var(--gap-md); }

/* The map and its layer toggles. The toggles are an overlay, so the wrapper is
   the positioning context and the map gets a stacking context of its own —
   without one, Leaflet's panes (z-index 400) paint over the buttons. */
.mapwrap { position: relative; }
.mapwrap > div:first-child {
  position: relative; z-index: 0; inline-size: 100%;
  border: 1px solid var(--border); border-radius: var(--radius-control);
  background: var(--plane);
}
/* Found on the way past: the farms map had no size at all, so Leaflet drew it
   into a zero-height box. The parcel maps keep the heights they already have. */
#farmsmap { height: clamp(300px, 52vh, 560px); }
.maplayers {
  position: absolute; z-index: 1;
  inset-block-start: var(--gap-sm); inset-inline-end: var(--gap-sm);
  display: flex; flex-direction: column; gap: var(--gap-xs);
}
.maplayer {
  inline-size: 44px; min-height: 44px; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-1); color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  box-shadow: var(--shadow-card);
}
/* Pressed state is a wash plus the darker brand step, never the fill colour
   alone — the same rule the active nav item follows. */
.maplayer[aria-pressed="true"] {
  color: var(--brand-blue-ink); border-color: var(--brand-blue-ink);
  background: rgba(3, 102, 168, 0.10);
}

/* One month of the coverage calendar. It needs to be a visible block of its
   own: several sit side by side in `.calendars` and without a panel the day
   grids run into each other. */
.month {
  min-inline-size: 0; padding: var(--gap-sm);
  border: 1px solid var(--grid); border-radius: var(--radius-control);
  background: var(--surface-1);
}

/* ---------- the work list (§67, §76.3 — finding B1) ----------
   Buckets first, because the question this screen answers is "what needs me
   now", and every bucket stays on the page when it is empty: an absent
   heading would read as "nothing here" exactly like an empty one, and only
   one of those is true. */
.worklist { display: grid; gap: var(--gap-md); }
.workgroup {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  padding: var(--gap-sm) var(--gap-md) var(--gap-md);
}
.workgroup-head {
  display: flex; align-items: baseline; gap: var(--gap-sm); flex-wrap: wrap;
  margin: 0 0 var(--gap-xs); font-size: 15px; font-weight: 700;
  color: var(--brand-green-ink);
}
.workrow {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-sm);
}
/* The row itself is the link: on a phone the whole card is the target, not a
   title-sized strip of it. */
.workrow-main {
  flex: 1 1 200px; min-inline-size: 0; min-height: 44px;
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  text-decoration: none; color: inherit; padding-block: 4px;
  border-radius: 6px;
}
.workrow-main:hover .task-title { text-decoration: underline; }
.workrow-main .task-title { overflow-wrap: anywhere; }
.workrow-meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--gap-xs) var(--gap-sm); font-size: 13px;
  color: var(--text-secondary);
}

/* ---------- one work order (§78.1 — mobile first) ----------
   A field worker is holding a phone in a field, so the single column is the
   real layout and the desktop split is what inherits. The aside carries the
   reference material; everything you act on stays in the main column. */
.taskgrid { display: grid; gap: var(--gap-md); align-items: start; }
.taskmain, .taskaside { min-inline-size: 0; }
@media (min-width: 1000px) {
  .taskgrid { grid-template-columns: minmax(0, 1fr) 320px; }
}

.taskactions { display: flex; flex-wrap: wrap; gap: var(--gap-sm); }
.actionform { flex: 1 1 200px; }
/* Anything carrying a reason or a note takes the full width: a textarea at
   200 px is not writable on a phone. */
.actionform-wide { flex: 1 1 100%; }
.actionform .btn { inline-size: 100%; }
.actionform > summary {
  display: flex; align-items: center; justify-content: center;
  list-style: none; cursor: pointer;
}
.actionform > summary::-webkit-details-marker { display: none; }
.actionform[open] > summary { margin-block-end: var(--gap-sm); }
.actionform textarea {
  font: inherit; inline-size: 100%; min-height: 76px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-control);
  background: var(--surface-1); color: var(--text-primary);
}

/* What the work still needs before it can be completed (§78.2). `is-short`
   names the shortfall rather than only colouring it, so pressing Complete is
   never the first time a worker learns the photo is missing. */
.reqline {
  display: grid; grid-template-columns: 1fr auto auto; gap: var(--gap-sm);
  align-items: center; padding: 8px; border-radius: 6px;
  background: var(--plane); border-inline-start: 3px solid var(--axis);
  min-height: 44px;
}
.reqline.is-met { border-inline-start-color: var(--good); }
.reqline.is-short { border-inline-start-color: var(--warning); font-weight: 600; }
.reqline .meta-value { font-variant-numeric: tabular-nums; }

/* attached evidence */
.evlist { list-style: none; margin: 0; padding: 0; display: grid;
  gap: var(--gap-sm); }
.evitem {
  display: flex; flex-wrap: wrap; gap: var(--gap-sm); align-items: flex-start;
  padding: var(--gap-sm); border-radius: 8px; background: var(--plane);
}
.evthumb {
  flex: 0 0 auto; inline-size: 72px; block-size: 72px; display: block;
  border-radius: 8px; overflow: hidden; border: 1px solid var(--border);
  background: var(--surface-1);
}
.evthumb img { inline-size: 100%; block-size: 100%; object-fit: cover;
  display: block; }
.evbody { flex: 1 1 180px; min-inline-size: 0; display: grid; gap: 3px; }
.evmeta {
  display: flex; flex-wrap: wrap; gap: var(--gap-xs) var(--gap-sm);
  font-size: 12px; color: var(--text-muted); align-items: center;
}
.checkline { display: flex; gap: var(--gap-sm); align-items: baseline;
  font-size: 13px; }

/* the evidence form: six kinds, one set of fields at a time */
.evform { display: grid; gap: var(--gap-sm); }
.evform-row { display: flex; flex-wrap: wrap; gap: var(--gap-sm); }
.evform-row .evfield { flex: 1 1 140px; }
.evfield { display: flex; flex-direction: column; gap: 4px; }
.evfield > label { font-size: 13px; color: var(--text-secondary); }
.evfield input, .evfield select, .evfield textarea {
  font: inherit; inline-size: 100%; min-height: 44px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-control);
  background: var(--surface-1); color: var(--text-primary);
}
.evfield textarea { min-height: 92px; }
/* A consent question is a sentence with a box, not a field with a label: it
   has to read as a choice the worker makes (§78.2). */
.evfield-consent {
  flex-direction: row; align-items: flex-start; gap: var(--gap-sm);
  padding: var(--gap-sm); border-radius: 8px; background: var(--plane);
}
.evfield-consent input[type="checkbox"] {
  inline-size: 22px; min-height: 22px; block-size: 22px; margin: 2px 0 0;
  flex: none; accent-color: var(--brand);
}
.evfield-consent > label { font-size: 14px; color: var(--text-primary); }

/* ---------- assignment conflicts (§76.4) ----------
   Warnings, never refusals: they explain and then let an authorized user go
   ahead with a recorded reason. */
.conflicts { list-style: none; margin: var(--gap-xs) 0 0; padding: 0;
  display: grid; gap: var(--gap-sm); }
.conflict {
  padding: var(--gap-sm) var(--gap-md); border-radius: 8px;
  background: var(--plane); border-inline-start: 3px solid var(--warning);
  font-weight: 600;
}
.conflict-why { margin: var(--gap-xs) 0 0; font-size: 13px; font-weight: 400;
  color: var(--text-secondary); overflow-wrap: anywhere; }

/* ---------- workspace switcher (finding B5) ----------
   Same disclosure as the locale menu, but every option is a button in a POST
   form: switching workspace is a state change and must not be a GET link.
   The dropdown positioning is scoped to `.wsmenu`, so the same list renders
   as a plain stacked form on the profile screen. */
.wsmenu { position: relative; }
.wsmenu > summary {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 6px 10px; min-height: 44px; list-style: none;
  border: 1px solid var(--border); border-radius: 999px;
  color: var(--text-secondary); font-size: 13px;
}
.wsmenu > summary::-webkit-details-marker { display: none; }
.wsmenu[open] > summary, .wsmenu > summary:hover {
  border-color: var(--brand-blue-ink); color: var(--brand-blue-ink);
}
.wsmenu-list {
  margin: 0; padding: 4px; list-style: none; display: grid; gap: 2px;
}
.wsmenu .wsmenu-list {
  position: absolute; inset-block-start: calc(100% + 6px); inset-inline-end: 0;
  z-index: 40; min-inline-size: 220px; background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  box-shadow: var(--shadow-card);
}
.wsopt {
  font: inherit; text-align: start; inline-size: 100%; cursor: pointer;
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 10px; min-height: 44px; border-radius: 8px;
  background: none; border: 0; color: var(--text-primary);
}
/* A long organization name must not push the locale switcher off a narrow
   appbar, so it truncates in the summary and stays whole in the list. */
.wsmenu-name {
  max-inline-size: 12ch; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
.wsopt-name { font-weight: 600; }
.wsopt:hover { background: var(--plane); }
.wsopt.is-active {
  color: var(--brand-blue-ink); font-weight: 650;
  background: rgba(3, 102, 168, 0.08);
}

/* ---------- parcel activation (findings R3, N5) ----------
   Colour follows the *outcome*, not the fact that the job finished. `imaged`
   and `empty` are real answers and neither is success, so neither gets the
   success treatment; `failed` is the only one that reads as a problem. */
.ingest { border-inline-start: 3px solid var(--border); }
.ingest-running, .ingest-pending { border-inline-start-color: var(--brand-blue-ink); }
.ingest-ready { border-inline-start-color: var(--good); }
.ingest-imaged, .ingest-empty { border-inline-start-color: var(--warning); }
.ingest-failed { border-inline-start-color: var(--critical); }

/* ---------- team administration (§69.2) ---------- */
.teamrow {
  display: flex; flex-wrap: wrap; gap: var(--gap-sm); align-items: center;
  padding: var(--gap-sm) 0; border-block-end: 1px solid var(--grid);
}
.teamrow:last-child { border-block-end: 0; }

/* U9 — the parcel-scope chips are gone with the inline editor they belonged
   to. One member's parcels are now a list on that member's own screen, where
   removing one can be a named control rather than a 28px "x" that a gloved
   hand cannot hit. */
.scopelist { list-style: none; margin: 0 0 var(--gap-sm); padding: 0;
  display: grid; gap: var(--gap-xs); }
.scoperow {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap-sm); padding: var(--gap-sm) var(--gap-md);
  border-radius: var(--radius-control); background: var(--plane);
}

/* A search that sits above the thing it searches, on one line where there is
   room and stacked where there is not. */
.searchbar { display: flex; align-items: flex-end; flex-wrap: wrap;
  gap: var(--gap-sm); margin-block-end: var(--gap-md); }
.searchbar .authfield { flex: 1 1 16ch; min-inline-size: 0; }
.searchbar .fieldlabel { flex: 1 0 100%; margin: 0; }

/* U9 — the report chooser: type, scope, period, then two named actions. */
.reportform { margin-block-end: var(--gap-md); }
.reportactions { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  margin-block-start: var(--gap-md); }

/* U9 — notifications lead with the title; these two lines carry what was
   previously in front of it. */
.attn-facts { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  margin: var(--gap-xs) 0 0; font-size: 13px; }
.attn-field { display: inline-flex; align-items: center; gap: var(--gap-xs);
  color: var(--text-secondary); }
.attn-meta { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--gap-sm); margin: var(--gap-sm) 0 0; font-size: 12px; }
.inlineform { display: inline; }

/* ---------- long forms ----------
   Single column is the safe layout and the only one a 320 px screen gets. The
   two-column pass applies to grouped fields; a bare label or control that was
   written as a direct child spans the row instead of landing beside the wrong
   input. */
.formgrid { display: grid; gap: var(--gap-sm) var(--gap-md); }
.formgrid-wide { grid-column: 1 / -1; }
@media (min-width: 760px) {
  .formgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .formgrid > .fieldlabel, .formgrid > .authfield,
  .formgrid > input, .formgrid > select, .formgrid > textarea {
    grid-column: 1 / -1;
  }
}

/* ---------- weather warnings (finding P7) ----------
   These had no definitions at all. The required sweep could not see them,
   because it skips any `class` attribute containing `{{ }}` and this one is
   built from the severity — so the block P7 deliberately moved to the top of
   the weather screen was rendering as a bare browser `article`.

   Severity is carried by the edge and by the alert's own title text, never by
   the colour alone: `severe`, `warning` and `advisory` are the three values
   `weather.py` produces, and an unrecognised one keeps the neutral border
   rather than being quietly downgraded. */
.alert {
  padding: var(--gap-sm) var(--gap-md); margin-block-end: var(--gap-sm);
  border-radius: 8px; background: var(--plane);
  border-inline-start: 3px solid var(--border);
}
.alert:last-child { margin-block-end: 0; }
.alert h3 {
  font-size: 15px; font-weight: 700; margin: 0 0 var(--gap-xs);
  overflow-wrap: anywhere;
}
.alert p { margin: 0 0 var(--gap-xs); font-size: 14px; }
.alert p:last-child { margin-block-end: 0; }
.alert-severe { border-inline-start-color: var(--critical); }
.alert-warning { border-inline-start-color: var(--warning); }
.alert-advisory { border-inline-start-color: var(--series-1); }

/* ---------- chart readout (finding P7) ----------
   Written by the pointer, by a tap and by the arrow keys alike, so the value
   a sighted reader hovers and the value a screen reader announces are one
   line rather than two. It keeps a minimum height, because a row that appears
   and disappears under a chart shifts everything below it. */
.chart-readout {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--gap-xs) var(--gap-sm);
  margin: var(--gap-xs) 0 0; min-height: 26px;
  font-size: 13px; color: var(--text-secondary);
}
.chart-readout-name { color: var(--text-muted); }
.chart-readout-value {
  font-size: 15px; font-weight: 650; color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.chart-readout-when { font-variant-numeric: tabular-nums; }
.chart-readout-extra { font-size: 12px; color: var(--text-muted); }

/* The chart is focusable now, so it gets the ring every other control has.
   `.chart` is an SVG and the generic `:where(a, button, …)` rule above does
   not reach it. */
.chart:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 6px;
}

/* ---------- per-layer legend on the index map (finding P7) ----------
   A ramp with two numbers is not a legend: it never said which layer it
   belonged to, what the numbers are, when the reading was taken or where it
   came from. §86.3 — the two ends are the index's own definitional range,
   which is exactly what `render_overlay` normalises by, and nothing here may
   read as a calibrated soil or water measurement. */
.indexlegend-box {
  margin: var(--gap-sm) 0 0; padding: var(--gap-sm) var(--gap-md);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  background: var(--plane);
}
.indexlegend-title {
  font-size: 14px; font-weight: 650; color: var(--text-primary);
  margin-block-end: var(--gap-xs);
}
.indexlegend-scale {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--gap-xs) var(--gap-sm);
  margin: var(--gap-xs) 0 0; font-size: 13px; color: var(--text-secondary);
}
.indexlegend-range { font-variant-numeric: tabular-nums; }
.indexlegend-ticks {
  display: flex;
  justify-content: space-between;
  gap: 4px;
  margin-block: 8px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  overflow-x: auto;
}
.indexlegend-ticks > span { min-width: 30px; text-align: center; padding-top: 4px; }
.indexlegend-about { margin: var(--gap-xs) 0 0; font-size: 13px; }
.indexlegend-meta {
  display: grid; gap: var(--gap-xs) var(--gap-md); margin: var(--gap-sm) 0 0;
  grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr));
}
.indexlegend-meta dt { font-size: 12px; color: var(--text-muted); }
.indexlegend-meta dd {
  margin: 0; font-size: 13px; font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* ---------- density at 320 px (finding P8) ----------
   Measured, not asserted: headless Chrome at a 320x640 viewport, reporting
   `document.documentElement.scrollWidth` per screen. Only one thing actually
   pushed the page sideways — the metric tabs — but the defensive rules below
   cover the grids and scrollers that are one long translation away from doing
   the same. Wide content scrolls inside its own box; the page never does. */

/* The five metric buttons measured 338 px on a 320 px screen in English and
   340 px in Arabic, so the whole weather page scrolled sideways. They wrap
   now. Once they can sit on two rows the segmented shape is a lie about which
   ends are ends, so on a narrow screen each button carries its own radius. */
.viewtabs { flex-wrap: wrap; max-inline-size: 100%; }
@media (max-width: 480px) {
  .viewtabs button {
    border-radius: var(--radius-control); padding-inline: 10px;
  }
}

/* A table is allowed to be wider than the phone; the page is not. `contain`
   stops a swipe that reaches the end of the table from continuing into the
   page behind it. */
.table-wrap { max-inline-size: 100%; overscroll-behavior-x: contain; }

/* Every auto-fit/auto-fill track whose floor is wider than a 320 px column:
   at that width the track keeps its minimum and overflows the card instead of
   shrinking. `min()` lets the last column collapse to the space there is. */
.parcels { grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); }
.calendars { grid-template-columns: repeat(auto-fill, minmax(min(248px, 100%), 1fr)); }
.kpis { grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); }
.facts { grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); }
.parcel-meta { grid-template-columns: repeat(auto-fit, minmax(min(120px, 100%), 1fr)); }
.bandgrid { grid-template-columns: repeat(auto-fill, minmax(min(112px, 100%), 1fr)); }

/* Twelve facts in one column made the weather screen's definition list 634 px
   tall on its own and put the first chart 1,696 px down the page — the same
   complaint P4 and P6 measured elsewhere. The card is 270 px wide inside a
   320 px screen, so the floor has to leave room for two tracks and the 16 px
   between them: 116 px does, 128 px missed it by two pixels and silently
   stayed at one column. The desktop grid is left as it was. */
@media (max-width: 560px) {
  .facts { grid-template-columns: repeat(auto-fit, minmax(min(116px, 100%), 1fr)); }
  .indexlegend-meta {
    grid-template-columns: repeat(auto-fit, minmax(min(104px, 100%), 1fr));
  }
}

/* A long parcel or crop name has nowhere to go in a 92 px-plus-rest row, and
   a word that cannot break takes the card with it. */
.parcel-ident, .workrow-main, .attn-item, .task { min-inline-size: 0; }
.parcel-name, .parcel-sub, .attn-title { overflow-wrap: anywhere; }

/* The app bar carries brand, workspace and language on one line. At 320 px
   that is three things in 320 px, so it is allowed to use two. */
@media (max-width: 380px) {
  .appbar-inner { flex-wrap: wrap; gap: var(--gap-sm); }
  .page-title { font-size: 20px; }
  .content { padding-inline: var(--gap-sm); }
}

/* ---------- temperature colour scale (T1) ----------
   The chip never stands alone: `.degc` is the pair, and the number is the part
   that carries the reading. Sized to align in a definition list without
   pushing the row height around. */
.degc { display: inline-flex; align-items: center; gap: 6px; }
.degc-chip {
  inline-size: 12px; block-size: 12px; border-radius: 3px; flex: none;
  /* a hairline, so a pale chip is still a chip on the card's own surface */
  box-shadow: inset 0 0 0 1px rgba(11, 11, 11, .18);
}

.tempscale { margin: 0 0 var(--gap-md); }
.tempramp {
  display: flex; block-size: 14px; border-radius: 4px; overflow: hidden;
  margin-block: var(--gap-xs);
}
/* 76 steps, each an equal share — the ramp is the data, not a gradient that
   happens to look similar. */
.tempstep { flex: 1 1 0; }
.temptick {
  display: flex; justify-content: space-between;
  font-size: 11px; color: var(--text-muted);
}

/* ---------- restructured navigation (U2) ----------
   Four destinations plus More. `.navmore` is a real <details>, so it works
   with no JavaScript; the panel opens upward on the phone because the nav is
   pinned to the bottom there, and downward on the desktop rail. */
.navmore { position: relative; flex: 1; display: flex; }
.navmore > summary {
  list-style: none; cursor: pointer; inline-size: 100%;
}
.navmore > summary::-webkit-details-marker { display: none; }
.navmore-list {
  position: absolute; inset-block-end: calc(100% + 6px); inset-inline-start: 0;
  z-index: 40; margin: 0; padding: 4px; list-style: none;
  min-inline-size: 200px; background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  box-shadow: var(--shadow-card);
}
.navmore-opt {
  display: flex; align-items: center; gap: var(--gap-sm);
  padding: 10px; min-height: 44px; border-radius: 8px;
  text-decoration: none; color: var(--text-primary); font-size: 14px;
}
.navmore-opt:hover { background: var(--plane); }
.navmore-opt.is-active { color: var(--brand-orange-ink); font-weight: 650; }
/* The unread count follows Notifications into the menu, so it stays visible
   from anywhere rather than only from the screen that shows it. */
.navbadge {
  margin-inline-start: auto; min-inline-size: 20px; padding: 1px 6px;
  border-radius: 999px; background: var(--critical); color: #fff;
  font-size: 11px; font-weight: 700; text-align: center;
}

/* ---------- fields collection (U3) ---------- */
.fieldtable td { vertical-align: top; }
.fieldname { font-weight: 650; text-decoration: none; }
.fieldname:hover { text-decoration: underline; }
.pager {
  display: flex; align-items: center; gap: var(--gap-md);
  justify-content: center; margin-block: var(--gap-md);
}

/* ---------- the field's header and tabs (U5) ----------
   Persistent across Overview, Satellite, Weather, Work and History, so the
   field's identity and the way between its views never move. */
.fieldhead {
  display: flex; align-items: flex-start; gap: var(--gap-md);
  flex-wrap: wrap; margin-block-end: var(--gap-sm);
}
.fieldhead-main { flex: 1 1 18ch; min-inline-size: 0; }
.fieldhead-facts {
  display: flex; align-items: center; gap: var(--gap-sm);
  flex-wrap: wrap; color: var(--text-secondary); font-size: 14px;
}

/* A scrolling strip rather than a wrapping row: five tabs wrap to two lines at
   320 px, and a tab bar that changes height as you move along it is worse than
   one that scrolls. */
.fieldtabs {
  display: flex; gap: var(--gap-xs); overflow-x: auto;
  margin-block-end: var(--gap-md);
  border-block-end: 1px solid var(--border);
  scrollbar-width: thin;
}
.fieldtab {
  flex: none; padding: 10px 14px; min-height: 44px;
  display: inline-flex; align-items: center;
  text-decoration: none; color: var(--text-secondary); font-size: 15px;
  border-block-end: 3px solid transparent;
  white-space: nowrap;
}
.fieldtab:hover { color: var(--brand-blue-ink); }
/* The active tab is marked by weight and an underline as well as colour, so
   the current view is not carried by hue alone. */
.fieldtab.is-active {
  color: var(--brand-orange-ink); font-weight: 650;
  border-block-end-color: var(--brand-orange);
}

/* ---------- parcel advice (§95) ---------- */
.advicelist { list-style: none; margin: var(--gap-md) 0 0; padding: 0;
              display: grid; gap: var(--gap-md); }
.adviceitem {
  padding: var(--gap-md); border-radius: var(--radius-control);
  background: var(--plane); border-inline-start: 3px solid var(--border);
}
/* Priority is carried by the pill's own text as well as this edge, so the
   ranking never depends on colour alone. */
.advice-high { border-inline-start-color: var(--critical); }
.advice-medium { border-inline-start-color: var(--warning); }
.advice-low { border-inline-start-color: var(--brand-blue-ink); }
.advicetext { margin: var(--gap-sm) 0 0; }
.advicewhy summary { cursor: pointer; font-size: 13px; min-height: 32px;
                     display: flex; align-items: center; }
.advicewhy pre {
  overflow-x: auto; font-size: 12px; padding: var(--gap-sm);
  background: var(--surface-1); border-radius: 8px; margin-block-start: 4px;
}

/* ---------- the task's one primary action (§10) ----------
   Sized and placed so "what do I do now" is answered by looking, not reading.
   Everything else on the task screen is a secondary form behind a disclosure. */
.taskprimary { border-inline-start: 3px solid var(--brand-orange); }
.btn-primary-action {
  inline-size: 100%; min-height: 52px; font-size: 17px; font-weight: 650;
  margin-block-start: var(--gap-sm);
}
@media (min-width: 600px) { .btn-primary-action { inline-size: auto; min-inline-size: 16ch; } }

/* On a phone the task screen is long — instructions, required evidence,
   submitted evidence, the action, the sync list, the capture form, the
   sidebar, the history trail. The action sits above the capture form, which is
   the right reading order and the wrong reach: a worker who has scrolled down
   to attach three photos had to scroll back up to submit. Sticking it to the
   bottom keeps the one dominant action within thumb reach the whole way down,
   above the home indicator on a notched handset.

   `position: sticky` and not `fixed`: it stays in the document, so it cannot
   cover the content below it at the end of the scroll, and with the stylesheet
   unloaded the button is still simply where it was. */
   It has to clear the bottom navigation, which is `position: fixed` at
   `inset-block-end: 0` with `z-index: 20` — sticking this to 0 would simply
   park it underneath the nav bar. `--bottomnav-h` is that bar's own height:
   6px padding, a 44px touch target, 6px padding. */
@media (max-width: 599px) {
  .taskprimary {
    position: sticky;
    inset-block-end: calc(var(--bottomnav-h) + env(safe-area-inset-bottom, 0px)
                          + var(--gap-sm));
    z-index: 5;
    background: var(--surface-1);
    box-shadow: var(--shadow-card);
  }
}

/* ---------- self-onboarding (Stage V, spec §100) ----------
   Above everything on the Overview while it has something to say, and gone
   entirely once dismissed. Deliberately *not* a modal: rule 1 is that the
   wizard is never a gate, and a dialogue over the product is a gate however
   many ways it offers to close. */
.wizard {
  padding: var(--gap-md);
  margin-block-end: var(--gap-lg);
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--brand-teal);
  border-radius: var(--radius-card);
  background: var(--surface-1);
  box-shadow: var(--shadow-card);
}
.wizard-head { display: flex; justify-content: space-between;
  align-items: baseline; gap: var(--gap-sm); flex-wrap: wrap; }

.wizard-steps { list-style: none; margin: var(--gap-md) 0 0; padding: 0;
  display: grid; gap: var(--gap-sm); counter-reset: step; }
.wizard-step { display: flex; gap: var(--gap-md); align-items: flex-start;
  padding: var(--gap-sm); border-radius: var(--radius-control); }
.wizard-step.is-current { background: var(--plane); }

/* The marker carries a number or a tick. Both are `aria-hidden` — the step's
   state is in its text, because a tick glyph read aloud is noise. */
.wizard-mark {
  flex: none; inline-size: 26px; block-size: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; font-size: 13px; font-weight: 650;
  background: var(--plane); color: var(--text-secondary);
  border: 1px solid var(--border);
}
.wizard-step.is-done .wizard-mark {
  background: var(--good); color: #fff; border-color: transparent;
}
.wizard-step.is-current .wizard-mark {
  background: var(--brand-teal); color: #fff; border-color: transparent;
}
.wizard-body { min-inline-size: 0; flex: 1 1 auto; }
.wizard-steptitle { margin: 0 0 var(--gap-xs); font-size: 15px;
  font-weight: 650; }
.wizard-step.is-done .wizard-steptitle { color: var(--text-secondary);
  font-weight: 600; }
.wizard-actions { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  align-items: center; margin-block-start: var(--gap-sm); }

.wizard-foot { display: flex; justify-content: space-between;
  align-items: center; gap: var(--gap-md); flex-wrap: wrap;
  margin-block-start: var(--gap-md);
  padding-block-start: var(--gap-sm);
  border-block-start: 1px solid var(--grid); }
.wizard-resume { margin-block-end: var(--gap-md); }

@media (max-width: 380px) {
  .wizard { padding: var(--gap-sm); }
  .wizard-step { gap: var(--gap-sm); }
  .wizard-actions .btn { inline-size: 100%; }
}

/* ---------- organization-account note ----------
   Quiet on purpose. It is an explanation, not an alert: the accent border
   would say "something is wrong", and nothing is wrong — the account is
   working exactly as it should and will keep doing so. */
.orghint {
  padding: var(--gap-md);
  margin-block-end: var(--gap-lg);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--plane);
}
.orghint p { margin: var(--gap-sm) 0 0; }
.orghint-actions { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  align-items: center; margin-block-start: var(--gap-md); }
@media (max-width: 380px) {
  .orghint { padding: var(--gap-sm); }
  .orghint-actions .btn { inline-size: 100%; }
}

/* ---------- the secondary group: a menu on mobile, a list on desktop ----------
   Answering "why is there a More menu when the sidebar could hold everything?"
   — because on a phone it cannot. Below 1000px this is a bottom bar with four
   slots; above it, a column with room, so the group opens out and the summary
   goes away. */
.navgroup-label { display: none; }

/* The open sidebar group is hidden on a phone; the disclosure is hidden on a
   desktop. Exactly one is visible, and neither depends on overriding the
   browser's own `<details>` behaviour — an earlier version did, and every
   destination in the group silently disappeared from the sidebar. */
.navgroup { display: none; }

@media (min-width: 1000px) {
  .navmore { display: none; }
  .navgroup {
    display: block;
    margin-block-start: var(--gap-md);
    padding-block-start: var(--gap-sm);
    border-block-start: 1px solid var(--grid);
  }
  .navgroup-label {
    display: block; margin: 0 0 var(--gap-xs); padding-inline: var(--gap-sm);
    font-size: 12px; font-weight: 700; letter-spacing: .04em;
    text-transform: uppercase; color: var(--text-secondary);
  }
  .navgroup-list { list-style: none; margin: 0; padding: 0;
    display: grid; gap: 2px; }
}

/* UI.md §16.5 — the compact change statement on Crop monitoring. Quiet: it is
   context for an inspection prompt, not a headline, and §16.2 allows it only
   "when it explains a current inspection prompt". */
.monitor-change { margin: var(--gap-sm) 0 0; font-variant-numeric: tabular-nums; }
.backlink-row { margin: 0 0 var(--gap-md); }

/* UI.md §16.6 — a crossed work limit. One line each, stated as a measurement
   against a configured number, never as a verdict about a job. */
.worklimits { list-style: none; margin: 0 0 var(--gap-sm); padding: 0;
  display: grid; gap: var(--gap-xs); }
.worklimit { padding: var(--gap-sm) var(--gap-md);
  border-inline-start: 3px solid var(--warning);
  border-radius: var(--radius-control); background: var(--plane); }

/* UI.md §16.3 — the worker's next job. The only large thing on their screen,
   because it is the only thing they came for. */
.nexttask { border-inline-start: 3px solid var(--accent); }
.nexttask-title { display: block; font-size: 19px; font-weight: 700;
  margin-block: var(--gap-xs) var(--gap-sm); }
.nexttask-facts { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  align-items: center; margin: 0 0 var(--gap-sm); font-size: 14px; }

/* UI.md §16.5 — History's four named subviews. A second level below the field
   tabs, quieter than them: they are sections of one record, not destinations.
   Scrolls horizontally on a narrow screen rather than shrinking Arabic labels
   to fit four equal columns (§16.5 forbids exactly that). */
.subtabs { display: flex; gap: var(--gap-xs); overflow-x: auto;
  margin-block-end: var(--gap-md); padding-block-end: var(--gap-xs);
  -webkit-overflow-scrolling: touch; }
.subtab { flex: none; padding: 8px 12px; min-height: 40px;
  display: inline-flex; align-items: center; white-space: nowrap;
  border-radius: 999px; text-decoration: none; font-size: 13px;
  color: var(--text-secondary); background: var(--plane); }
.subtab:hover { color: var(--brand-blue-ink); }
.subtab.is-active { background: var(--brand-teal); color: #fff;
  font-weight: 650; }

/* ---------- half-width pairs ----------
   Two cards side by side where there is room, stacked where there is not.
   Below the breakpoint they are full width, which is the only sane answer on
   a phone — a "half" of a 320px screen is not a card, it is a column of
   single words. */
.halfgrid { display: grid; gap: 0 var(--gap-md); grid-template-columns: 1fr; }
@media (min-width: 900px) {
  /* `align-items: start` let each half size to its own content, so the pair
     sat at two different heights — most visibly when one of them had nothing
     to report and shrank to two lines beside a full one. Stretched, the row
     decides the height and both cards reach the same foot. */
  .halfgrid { grid-template-columns: 1fr 1fr; align-items: stretch; }
  .halfgrid > .half { display: flex; flex-direction: column; }
  /* The last line inside a half is pushed to the bottom, so the two cards
     agree at the foot as well as at the top. */
  .halfgrid > .half > .cardfoot { margin-block-start: auto; }
}
.cardfoot { margin-block-end: 0; font-size: 13px; }

/* ---------- parcel imagery: the picture, and the day that made it ----------
   Two halves of one card. The picker is narrower than the map because a list
   of dates does not need half a screen, and the map is the thing being
   looked at. */
@media (min-width: 900px) {
  .imagerygrid { grid-template-columns: minmax(0, 1.4fr) minmax(260px, 1fr); }
}
.imagerypick { display: grid; gap: var(--gap-xs);
  margin-block-end: var(--gap-md); }
.imagerypick .authfield { margin-block-end: var(--gap-sm); }
.imagery-shots { max-block-size: 320px; overflow-y: auto; }
.imagery-status { min-block-size: 1.2em; margin-block: var(--gap-sm) 0; }
/* A button that is an option, not the screen's primary action. */
.btn-quiet { font: inherit; font-size: 12px; font-weight: 650;
  padding: 6px 10px; min-block-size: 36px; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-control);
  background: var(--surface-1); color: var(--text-secondary); }
.btn-quiet[disabled] { opacity: .55; cursor: progress; }
/* A card that is half of a pair. It carries no width of its own — the grid
   decides that — but it exists as a class so a card can say what it is, and
   so the one-card case (no advice yet, say) still reads as a half rather than
   stretching across a row it was designed to share. */
.half { min-inline-size: 0; }
.half .table-wrap { overflow-x: auto; }

/* ---------- the four headline index cards ----------
   Crop, crop, soil, water. Small, because the value is the point and a big
   tile with one number in it is a poster, not a reading. */
.idxcards { display: grid; gap: var(--gap-sm);
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); }
.idxcard {
  padding: var(--gap-md); border-radius: var(--radius-control);
  background: var(--plane); border-inline-start: 3px solid var(--axis);
}
/* The group each index belongs to, as a quiet edge rather than a badge. Never
   the only signal: the code and the label both say which it is. */
.idxcard-plant { border-inline-start-color: var(--brand-green-ink); }
.idxcard-soil  { border-inline-start-color: var(--brand-orange-ink); }
.idxcard-water { border-inline-start-color: var(--brand-blue-ink); }

.idxcard-code { font-size: 11px; font-weight: 700; letter-spacing: .06em;
  color: var(--text-muted); }
.idxcard-value { font-size: 26px; font-weight: 700; line-height: 1.1;
  font-variant-numeric: tabular-nums; margin-block: 2px; }
.idxcard-label { font-size: 12px; color: var(--text-secondary); }
.idxcard-meta { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  margin-block-start: var(--gap-xs); font-size: 12px;
  font-variant-numeric: tabular-nums; }
/* A low usable share changes how the number should be read, so it is not
   muted away. Shown only when it is low — see the template. */
.idxcard-warn { margin-block-start: var(--gap-xs); font-size: 12px;
  color: var(--brand-orange-ink); font-weight: 600; }

/* ---------- "Weather for field work" (F-UX-09 / UI.md §19) ----------
   summary → timeline → selected-hour facts → disclosure. Nothing here paints
   an hour green or red: the card reports wind and gust, and a coloured band
   under a threshold would say the hours beneath it are safe to work in. */
.wxwork-sub { margin: -4px 0 var(--gap-sm); font-size: 13px; }
.wxwork-context { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--gap-xs); font-size: 12px; margin-block-end: var(--gap-sm); }
.wxwork-headline { margin: 0 0 var(--gap-sm); font-size: 15px;
  font-weight: 600; }
/* The state badge. Four words, one of them amber — and amber means a
   configured limit was crossed, not that work is forbidden. */
.wxstate { border-radius: 999px; padding: 2px 8px; font-weight: 650;
  border: 1px solid var(--border); background: var(--plane); }
.wxstate-threshold { color: var(--brand-orange-ink);
  border-color: var(--brand-orange-ink); }
.wxstate-stale, .wxstate-unavailable { color: var(--text-muted); }

.wxwork-chart { margin-block: var(--gap-sm); }
.wxwork-key { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--gap-xs); font-size: 12px; margin-block-end: var(--gap-sm); }
/* The legend keys repeat the line styles, because line style is what
   separates wind from gust — not colour. */
.wxkey { inline-size: 22px; block-size: 0; flex: none;
  border-block-start: 2px solid var(--series-1); }
.wxkey-gust { border-block-start-style: dashed; }

.wxwork-pick { display: flex; align-items: stretch; gap: var(--gap-xs); }
.wxscroll { flex: none; min-inline-size: 32px; min-block-size: 44px;
  font: inherit; border: 1px solid var(--border);
  border-radius: var(--radius-control); background: var(--surface-1);
  color: var(--text-secondary); cursor: pointer; }
/* The strip scrolls inside itself. The page must never scroll sideways, and
   twelve hours squeezed into a phone width are twelve unreadable labels. */
.wxhours { display: flex; gap: var(--gap-xs); overflow-x: auto;
  scroll-snap-type: x proximity; padding-block-end: 2px; }
.wxhour { flex: 0 0 auto; scroll-snap-align: start; display: grid;
  gap: 2px; padding: 6px 10px; min-block-size: 44px; min-inline-size: 56px;
  font: inherit; cursor: pointer; background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius-control); }
.wxhour[aria-pressed="true"] { border-color: var(--brand-blue-ink);
  color: var(--brand-blue-ink); font-weight: 650;
  background: rgba(3, 102, 168, .06); }
.wxhour-clock { font-variant-numeric: tabular-nums; font-size: 13px; }
.wxhour-day { font-size: 10px; color: var(--text-muted); }
/* An hour over the configured limit is marked on the control as well as in
   the table, by a rule at its foot rather than a fill: a filled chip reads
   as a status, and the status this card is allowed to report is "above the
   configured limit", which the text says. */
.wxhour.is-over { border-block-end-width: 3px;
  border-block-end-color: var(--brand-orange-ink); }

.wxwork-facts { margin-block-start: var(--gap-md); }
.wxwork-details { margin-block-start: var(--gap-md); }
.wxwork-details > summary { cursor: pointer; font-weight: 650;
  font-size: 13px; }
.wxwork-scope { margin-block-end: 0; font-size: 12px; }

/* ---------- hour-by-hour wind ---------- */
.calmruns { list-style: none; margin: 0 0 var(--gap-md); padding: 0;
  display: grid; gap: var(--gap-xs); }
.calmrun { display: flex; flex-wrap: wrap; gap: var(--gap-sm);
  align-items: baseline; padding: var(--gap-sm) var(--gap-md);
  border-inline-start: 3px solid var(--good);
  border-radius: var(--radius-control); background: var(--plane); }
.calmrun-when { font-weight: 700; font-variant-numeric: tabular-nums; }
.calmrun-len { font-weight: 600; color: var(--text-secondary); }

.windtable tr.is-over td { background: rgba(253, 164, 1, .12); font-weight: 600; }
/* The arrow points the way the wind is *going*, which is the way the spray
   drifts; the label beside it names where it comes from, which is how a
   forecast states it. Both, because either alone is read the wrong way round
   by somebody. */
.windarrow { display: inline-block; transform: rotate(var(--deg, 0deg));
  margin-inline-end: 4px; }

/* ---------- grouped index map picker + multi-series picker ---------- */
.indexpicker-group { margin-block-end: var(--gap-sm); }
.indexpicker-title { margin: 0 0 var(--gap-xs); font-size: 11px;
  font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-muted); }

.idxcards-tight .idxcard { padding: var(--gap-sm) var(--gap-md); }
.idxcards-tight .idxcard-value { font-size: 20px; }
/* The sign of the change, as a sign and nothing else. It was green for up and
   orange for down, which is a health claim: NDVI rising is vigour and MSI
   rising is stress, so no single colour rule is true of both (finding B19).
   The direction sentence that used to sit under it has moved to the map
   legend, where one index is named at a time. */
.idxdelta { font-weight: 700; color: var(--text-primary); }

.idxchart { margin-block-start: var(--gap-md); }
.idxpick { display: flex; flex-wrap: wrap; gap: var(--gap-xs);
  margin-block-end: var(--gap-sm); }
.idxpick-opt { display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px; min-height: 36px; font-size: 12px; font-weight: 650;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface-1); cursor: pointer; }
.idxpick-opt:has(input:checked) { border-color: var(--brand-blue-ink);
  color: var(--brand-blue-ink); background: rgba(3, 102, 168, .06); }
.idxpick-opt input { accent-color: var(--brand-blue-ink); }

/* ---------- one chart, every index (crop monitoring) ----------
   The picker is also the legend: a checked chip wears the colour its line is
   drawn in. The dot is a 2px-ringed swatch so it stays visible against both
   surfaces and against a chip it overlaps. Text keeps its ink token — the
   colour lives in the dot, never in the label. */
.idxpick-groups { display: grid; gap: var(--gap-sm);
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); }
.idxpick-group .idxpick { margin-block-end: 0; }
.idxpick-dot { inline-size: 10px; block-size: 10px; border-radius: 999px;
  box-shadow: 0 0 0 2px var(--surface-1); flex: none; }
/* Eight colours, seventeen indices: past the eighth line the remaining
   chips are unavailable rather than silently inert, and the note below the
   picker says why. */
.idxpick-opt.is-unavailable { opacity: .45; cursor: not-allowed; }
.idxpick-note { margin: 0 0 var(--gap-sm); font-size: 12px; }

/* ---------- the merged readings card ----------
   Plant, Soil and Water were three cards; they are three groups inside one,
   so the comparison that crosses them is on a single screen. */
.idxgroup + .idxgroup { margin-block-start: var(--gap-lg);
  padding-block-start: var(--gap-md); border-block-start: 1px solid var(--border); }
.idxgroup-title { display: flex; align-items: center; gap: var(--gap-xs);
  margin: 0 0 var(--gap-sm); font-size: 14px; font-weight: 700;
  color: var(--text-secondary); }
.idxgroup .table-wrap { margin-block-start: var(--gap-md); }

/* ---------- index map and its description, one row ----------
   The legend used to sit under the picture it explains, so on a short screen
   they were never both visible. Beside it at 900px and up; stacked below,
   where a "half" is a column of single words. */
.indexmaprow { display: grid; gap: var(--gap-md); grid-template-columns: 1fr;
  align-items: start; }
.indexmaprow > * { min-inline-size: 0; }
@media (min-width: 900px) {
  .indexmaprow { grid-template-columns: minmax(0, 1.7fr) minmax(250px, 1fr); }
}
/* It is a card in its own right now, not a footnote to the map. */
.indexmapaside { margin: 0; }
