/* Material-inspired styling, mobile-first (crew uses phones in the field).
   Hand-rolled — no framework, no CDN; served by whitenoise. */

:root {
  --primary: #2d6a33;
  --primary-dark: #1e4b23;
  --on-primary: #ffffff;
  --primary-container: #b0d3ae;
  --surface: #ffffff;
  --background: #f2f5f0;
  --on-surface: #1a1c19;
  --muted: #5c6459;
  --outline: #c3ccc0;
  --outline-light: #dde4da;
  --error: #a3271b;
  /* Alarm, not error. --error is tuned for message text and goes quiet at
     border width; --flag is for the one thing on a page that must be seen at
     arm's length. Currently the scan reviewer's uncertain cells. */
  --flag: #e11d0c;
  --flag-tint: #ffe9e6;
  --flag-ink: #7a1409;
  /* Hazelnuts, dark enough to carry white text. The tints they pair with are
     literals at each use (#f3ead9 on the board, #f0e2c8 on the tiles); this
     one is shared because "selected" has to look identical in the palette and
     the grid or the two read as different states. */
  --hazel-deep: #7d6118;
  --elev-1: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 3px 1px rgba(0, 0, 0, 0.15);
  --elev-2: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 6px 2px rgba(0, 0, 0, 0.15);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 1rem;
  color: var(--on-surface);
  background: var(--background);
}

/* ---- App bar ---- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  /* Row gap, not just column gap: the bar wraps to two or three rows below
     about 700px, and at 0.25rem the brand sat 4px off the nav — close enough
     to read as one crushed block rather than two lines. */
  gap: 0.5rem 1rem;
  padding: 0.7rem 1rem;
  background: var(--primary);
  color: var(--on-primary);
  box-shadow: var(--elev-2);
}
.brand {
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
  color: var(--on-primary);
  text-decoration: none;
}
.nav {
  display: flex;
  gap: 0.5rem 1rem;
  align-items: center;
  flex-wrap: wrap;
}
.nav a, .nav .linklike {
  color: #ddebdb;
  text-decoration: none;
  font-weight: 500;
  min-height: 2.5rem;
  display: inline-flex;
  align-items: center;
}
.nav a:hover, .nav .linklike:hover { color: var(--on-primary); }
/* Nav dropdown menus (Irrigation / Crop Estimates) */
.nav-menu { position: relative; }
.nav-menu > summary {
  list-style: none;
  cursor: pointer;
  color: #ddebdb;
  font-weight: 500;
  min-height: 2.5rem;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}
.nav-menu > summary::-webkit-details-marker { display: none; }
.nav-menu > summary::after { content: "▾"; font-size: 0.7em; opacity: 0.85; }
.nav-menu > summary:hover,
.nav-menu[open] > summary { color: var(--on-primary); }
.nav-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 0.25rem;
  min-width: 11rem;
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--elev-2);
  padding: 0.35rem;
  display: flex;
  flex-direction: column;
  z-index: 30;
}
.nav-dropdown a {
  color: var(--on-surface);
  min-height: auto;
  padding: 0.55rem 0.75rem;
  border-radius: 6px;
  font-weight: 500;
}
.nav-dropdown a:hover { background: #e4ede2; color: var(--on-surface); }
.nav-user {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-left: auto;
  flex-wrap: wrap;
}
.inline { display: inline; }
.linklike {
  background: none; border: none; padding: 0; font: inherit; cursor: pointer;
}

/* ---- Layout ---- */
.container { max-width: 64rem; margin: 1rem auto 3rem; padding: 0 1rem; }
h1 { font-size: 1.6rem; font-weight: 500; margin: 0.75rem 0; }
h2 { font-size: 1.15rem; font-weight: 500; margin: 0.25rem 0 0.75rem; }
h3 { font-size: 1rem; font-weight: 500; }
.muted { color: var(--muted); }
.small { font-size: 0.85rem; }
.page-head {
  display: flex; justify-content: space-between; align-items: center;
  gap: 0.5rem 1rem; flex-wrap: wrap;
}
/* The action group beside a page title. A bare inline <span> put its buttons
   flush against each other the moment they wrapped -- two pills touching with
   0px between them, which reads as one misshapen control. */
.page-head > span {
  display: inline-flex; flex-wrap: wrap; gap: 0.4rem; align-items: center;
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  gap: 1rem;
}
.card {
  background: var(--surface);
  border-radius: 12px;
  box-shadow: var(--elev-1);
  padding: 1rem;
}
/* Stacked cards need the same air between them as the grid puts beside them —
   without this they butt together and read as one long box. Rows inside
   .cards already have the gap, so the grid's own siblings reset to zero. */
.card + .card,
.card + .cards,
.cards + .card,
.cards + .cards { margin-top: 1rem; }
.cards > .card + .card { margin-top: 0; }

/* ---- Buttons (Material filled / text) ---- */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.6rem;
  padding: 0.55rem 1.4rem;
  border: none;
  border-radius: 999px;
  background: var(--primary);
  color: var(--on-primary);
  font: inherit;
  font-weight: 500;
  font-size: 0.95rem;
  text-decoration: none;
  cursor: pointer;
}
.button:hover { background: var(--primary-dark); box-shadow: var(--elev-1); }
/* A button that won't act has to stop looking like one, or the page reads as
   broken rather than as waiting for something. The note beside it says what. */
.button:disabled, .button:disabled:hover {
  background: var(--outline); color: var(--muted);
  cursor: not-allowed; box-shadow: none;
}
.button.small { min-height: 2.1rem; padding: 0.25rem 0.9rem; font-size: 0.85rem; }
.linklike-dark {
  background: none; border: none; padding: 0.4rem 0.5rem; font: inherit;
  cursor: pointer; color: var(--primary); font-weight: 500;
  border-radius: 999px; text-decoration: none;
}
.linklike-dark:hover { background: #e4ede2; }

/* ---- Chips / badges ---- */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.2rem 0.7rem;
  border-radius: 8px;
  font-size: 0.8rem;
  font-weight: 600;
  vertical-align: middle;
}
.badge.draft { background: #f4e5b3; color: #574a13; }
.badge.final { background: #cfe8d1; color: #1e4b23; }

/* ---- Messages ---- */
.message { padding: 0.7rem 1rem; border-radius: 8px; margin-bottom: 1rem; box-shadow: var(--elev-1); }
.message.warning { background: #faf1d3; color: #574a13; }
.message.error { background: #f8ded9; color: var(--error); }
.message.success { background: #d8eeda; color: #1e4b23; }

/* ---- Tables ---- */
/* Wide tables scroll SIDEWAYS inside their own box. The page carries their
   length — no height cap here, and none anywhere else either. A box that
   scrolls vertically inside a page that also scrolls is unusable on an iPad:
   a drag over the table moves the table, a drag beside it moves the page, and
   neither reaches the end of the other.

   The frozen header row went with the cap, and could not be kept. overflow-x
   makes this a scroll container on BOTH axes — CSS computes an overflow-y of
   visible back to auto whenever the other axis isn't visible — so a sticky
   header can only stick to this box, and a box exactly as tall as its table
   has nothing to stick against. The first column's sideways stick is on the
   axis that does scroll, and still works; on the wide grids that is the half
   that matters, since it keeps the block number beside its row. */
.scroll-x {
  overflow-x: auto;
  border-radius: 12px;
  box-shadow: var(--elev-1);
}
.scroll-x table.data { box-shadow: none; }
table.data {
  border-collapse: collapse;
  width: 100%;
  background: var(--surface);
  border-radius: 12px;
  /* no overflow:hidden here — it would break position:sticky cells */
  box-shadow: var(--elev-1);
}
table.data th, table.data td {
  border-bottom: 1px solid var(--outline-light);
  padding: 0.6rem 0.75rem;
  text-align: left;
  white-space: nowrap;
}
table.data th { background: #e7ede4; font-weight: 600; font-size: 0.85rem; }
table.data tr:last-child td { border-bottom: none; }
table.data tr.totals th { background: #d8eeda; }
tr.superseded td { color: #8a978c; background: #fafbfa; }
td.missing { background: #faf1d3; }

/* Grid + board: keep the first column visible while scrolling sideways */
table.grid th:first-child, table.grid td:first-child,
table.board th:first-child, table.board td:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--surface);
  box-shadow: 1px 0 0 var(--outline-light);
}
/* Restores the header tint the sticky rule above overwrote with --surface. */
table.grid th:first-child, table.board th:first-child {
  background: #e7ede4;
}
table.grid input.frac { width: 5rem; text-align: right; }
table.grid input.reason { width: 16rem; max-width: 60vw; }
table.grid input.reason.needed {
  border-color: #b98c00;
  background: #fdf6dd;
}

/* ---- Forms ---- */
input, textarea, select {
  font: inherit;
  font-size: 1rem; /* 16px: stops iOS zoom-on-focus */
}
form p label { display: block; font-weight: 500; margin-bottom: 0.25rem; }
/* NOTE ON SPECIFICITY: `table.grid input.frac` here is (0,2,2), so this rule
   outranks any single-class component rule written later — `.scan-review
   input.needed` is (0,2,1) and lost border and background to it while its own
   colour still applied, leaving the flag rendering half-styled. A component
   restyling these inputs has to match the element and both classes to win. */
form p input, form p textarea, form p select,
.inline-form input, table.grid input.frac, .grid-save textarea {
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--outline);
  border-radius: 8px;
  background: var(--surface);
}
form p input, form p textarea, form p select { width: 100%; max-width: 22rem; }
form p input[type="date"] {
  width: 11rem; /* dates are short; full-width date inputs also misbehave on iOS */
}
input:focus, textarea:focus, select:focus {
  outline: 2px solid var(--primary);
  outline-offset: -1px;
}
form .helptext { display: block; color: var(--muted); font-size: 0.85rem; }
form .errorlist { color: var(--error); margin: 0.2rem 0; padding-left: 1rem; }
.inline-form { display: inline-flex; gap: 0.3rem; align-items: center; flex-wrap: wrap; }
.inline-form input { width: 5.5rem; }
/* Set-every-block control above the estimate grid.
   Margins are its own because .card carries none — a card only gets air from
   .card + .card and friends, and this one sits between a paragraph and a
   table, matching no sibling rule. So it butted against both.
   The input needs its own width too: table.grid input.frac is scoped to the
   table, and this one sits outside it. */
.bulk-set {
  display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;
  margin: 1rem 0;
}
.bulk-set input.frac { width: 5.5rem; }
.bulk-set .muted { flex: 1 1 20rem; }
.grid-save { margin-top: 1rem; }
.grid-save textarea { width: 100%; max-width: 42rem; display: block; margin: 0.5rem 0; }
.form-narrow { max-width: 26rem; }
.finalize { margin-top: 1rem; }

/* ---- Misc ---- */
.login-box { max-width: 26rem; margin: 3rem auto; text-align: center; }
.obs { border-top: 1px solid var(--outline-light); padding: 0.6rem 0; overflow-wrap: anywhere; }
.obs:first-of-type { border-top: none; }
/* Season roster editor */
.roster-bulk { font-size: 0.8rem; font-weight: 400; }
.roster-check { display: inline-flex; align-items: center; gap: 0.4rem; min-height: 2.2rem; }
.roster-check input { width: 1.15rem; height: 1.15rem; }
.roster-crop { width: 9rem; }
.roster-note { width: 12rem; }
tr.needs-fix td { background: #faf1d3; }

/* Season-by-season farmed strip on the block page */
.season-strip { display: flex; gap: 0.35rem; flex-wrap: wrap; margin: 0.1rem 0 0.9rem; }
.season-flag {
  padding: 0.1rem 0.55rem; border-radius: 999px; font-size: 0.75rem;
  font-weight: 600; font-variant-numeric: tabular-nums;
}
.season-flag.on { background: #dcebd8; color: #1e4b23; }
.season-flag.off { background: #e8e8e6; color: var(--muted); }
.status-chip {
  display: inline-block; padding: 0.05rem 0.55rem; border-radius: 999px;
  font-size: 0.75rem; font-weight: 600; vertical-align: 1px;
}
.status-chip.open { background: #f8ded9; color: var(--error); }
.status-chip.done { background: #cfe8d1; color: #1e4b23; }
.cards > .card { min-width: 0; }
.card { overflow-wrap: anywhere; }
.hours-whole { font-variant-numeric: tabular-nums; }
/* Numeric columns read as numbers: right-aligned, aligned digits, and sized
   to their content (unlike td.small, which is for prose columns). */
table.data th.num, table.data td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Selects in a data row size to their widest option, so a long label can push
   later columns off screen. Cap them like input[type=date] and .roster-crop —
   and keep option labels short; the full wording belongs in help text. */
table.data td select { max-width: 11rem; }

/* Tables inside cards: contained horizontal scroll, lighter chrome, and
   long text columns wrap instead of forcing the table wide. */
.card .scroll-x.card-table {
  box-shadow: none;
  border: 1px solid var(--outline-light);
  border-radius: 8px;
  margin-bottom: 0.5rem;
}
.card .scroll-x.card-table table.data { border-radius: 0; }
/* Prose columns (justifications, notes, who/when) wrap and claim room rather
   than forcing the table wide. Keyed on td.small ONLY: .muted is a colour
   class, and having it here silently bought 11rem for any greyed cell —
   including numbers. Every genuine prose cell already carries .small too. */
table.data td.small {
  white-space: normal;
  min-width: 11rem;
  max-width: 20rem;
}

/* ---- Block tiles (Blocks overview) ---- */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6.2rem, 1fr));
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}
.tile {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.6rem 0.7rem;
  border-radius: 10px;
  text-decoration: none;
  color: var(--on-surface);
  box-shadow: var(--elev-1);
  min-height: 3.6rem;
}
.tile:hover { box-shadow: var(--elev-2); }
.tile-num { font-weight: 700; font-size: 1.05rem; }
.tile-crop { font-size: 0.72rem; color: var(--muted); line-height: 1.2; }
.tile.grapes { background: #dcebd8; }
.tile.hazel { background: #f0e2c8; }
.tile.fallow { background: #e8e8e6; opacity: 0.75; }
.tile.fallow .tile-num { color: var(--muted); }
.legend { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.tile-chip {
  padding: 0.2rem 0.7rem; border-radius: 8px; font-size: 0.8rem; font-weight: 600;
}
.tile-chip.grapes { background: #dcebd8; }
.tile-chip.hazel { background: #f0e2c8; }
.tile-chip.fallow { background: #e8e8e6; color: var(--muted); }
.block-edit { margin: 1rem 0; }
.block-edit summary { cursor: pointer; min-height: 2.5rem; display: flex; align-items: center; }
.block-edit form { margin-top: 0.75rem; }

.season-divider {
  display: flex; align-items: baseline; justify-content: space-between;
  /* space-between holds these apart on a wide screen and nothing did on a
     narrow one, so the heading and its summary ran together — "Union5 blocks".
     The gap only ever applies once they stop being pushed to the edges. */
  gap: 0.15rem 0.75rem; flex-wrap: wrap;
  margin-top: 1.75rem; padding-bottom: 0.3rem;
  border-bottom: 2px solid var(--primary-container);
}
/* Sits directly under .page-head, which is a flex row -- so its own margin is
   the only thing between the year strip and the buttons above it. Without one
   they touch on any width where those buttons wrap. */
.season-nav { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-top: 0.5rem; }

/* ---- Schedule board ---- */
.palette-card { margin-bottom: 1rem; }
.palette { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.6rem 0 0.8rem; }
.chip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  border: 1px solid var(--outline); background: var(--surface);
  border-radius: 999px; padding: 0.35rem 0.7rem; font: inherit;
  font-weight: 600; cursor: pointer; min-height: 2.4rem;
}
.chip.selected { background: var(--primary); color: var(--on-primary); border-color: var(--primary); }
.chip-remaining {
  background: #e7ede4; border-radius: 999px; padding: 0 0.45rem;
  font-size: 0.8rem; color: var(--muted);
}
.chip.selected .chip-remaining { background: rgba(255,255,255,0.25); color: #fff; }
.chip-remaining.done { background: #cfe8d1; color: #1e4b23; }
.chip-remaining.over { background: #f8ded9; color: var(--error); }
table.board td.slot { min-width: 7rem; cursor: pointer; }
table.board td.slot.has-selected { background: #eaf3e8; }
/* Chips wrap at 300px. `table.data` is width:100%, so six days split whatever
   the page is wide and a column settled near 150px — at which a busy hour's
   sixteen block numbers stack five rows deep and the day reads as a wall. The
   pair below fixes the wrap point at 300 from both sides: the cell won't be
   narrower, the chips won't run wider. The week scrolls sideways in .scroll-x
   instead of growing downward.

   The cap lives on an inner box because max-width on a table cell is
   advisory — auto layout sizes columns to content and overrules it. */
.slot-body {
  display: flex; flex-wrap: wrap; gap: 0.1rem; align-items: center;
  max-width: 300px;
}
@media (min-width: 641px) {
  table.board td.slot { min-width: 300px; }
}
/* The hour's demand on the pump, on its own line under the numbers, so the
   column can be scanned for peaks without adding anything up. Bottom left:
   it is the sum of what is above it, and a total belongs beneath its terms.
   `order: 1` rather than a second element, so the flex row keeps one child
   list and the JavaScript that rebuilds a cell after a tap can emit it first. */
.slot-flow {
  order: 1; flex: 0 0 100%;
  font-size: 0.7rem; font-weight: 600; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.slot-flow.over { color: var(--error); }
table.board .hour-label { font-weight: 600; white-space: nowrap; }
.slot-chip {
  display: inline-block; background: #dcebd8; border-radius: 6px;
  padding: 0.05rem 0.35rem; margin: 0.05rem; font-size: 0.85rem; font-weight: 600;
}
.slot-chip.active { background: var(--primary); color: var(--on-primary); }
/* Hazelnuts keep their tan once they land on an hour. The grid is where the
   crew actually reads the week, and a block number is the same digit whatever
   is planted in it — the tint is the only thing saying which planting an hour
   belongs to. Same pair as the palette chip above. */
.slot-chip.hazel { background: #f3ead9; color: #574a13; }
.slot-chip.hazel.active { background: var(--hazel-deep); color: #fff; }

.site-tabs { display: flex; gap: 0.5rem; margin: 0.5rem 0 0.75rem; }
.site-tab {
  padding: 0.45rem 1.1rem; border-radius: 999px; text-decoration: none;
  font-weight: 600; color: var(--primary); background: #e4ede2;
  min-height: 2.4rem; display: inline-flex; align-items: center;
}
.site-tab.active { background: var(--primary); color: var(--on-primary); }
.board-actions { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; }
.cellclear {
  border: none; background: #e4ede2; color: var(--muted); cursor: pointer;
  border-radius: 999px; width: 1.4rem; height: 1.4rem; line-height: 1;
  font-size: 0.95rem; margin-left: 0.3rem; vertical-align: middle;
}
.cellclear:hover { background: #f8ded9; color: var(--error); }
.todo-day { margin-bottom: 1rem; }
.todo-day h3 { margin: 0.75rem 0 0.4rem; }
table.todo .todo-time { font-weight: 700; white-space: nowrap; width: 4.5rem; }
.todo-line { padding: 0.1rem 0; }
.todo-line.on strong { color: #1e4b23; }
.todo-line.off strong { color: var(--error); }
.todo-check { margin-right: 0.35rem; }

@media print {
  .topbar, .no-print { display: none !important; }
  .container { max-width: none; margin: 0; }
  .scroll-x, table.data { box-shadow: none; }
  table.data th, table.data td { padding: 2px 4px; font-size: 9pt; }
}

/* ---- Phone tweaks ---- */
@media (max-width: 640px) {
  /* The wrapped nav is tall on phones; a sticky bar would eat a third of
     the screen. (It used to hide the frozen table headers too — those are
     gone now, but the screen-space reason stands on its own.) */
  .topbar { position: static; padding: 0.6rem 0.75rem; }
  .container { padding: 0 0.75rem; margin-top: 0.75rem; }
  h1 { font-size: 1.35rem; }
  table.data th, table.data td { padding: 0.55rem 0.6rem; font-size: 0.9rem; }
  /* Full-width action buttons need a full-width group to be 100% OF. The
     group is inline-flex, so without this they measure 100% of their own
     shrink-to-fit width and stop short of the screen edge. */
  .page-head > span { width: 100%; }
  .page-head .button { width: 100%; }
  .login-box { margin-top: 2rem; }
  .nav-group + .nav-group { border-left: none; padding-left: 0; }
  .nav-user { margin-left: 0; }
}

/* ---- Week quick-nav (Hours / ET % / Schedule) ---- */
.week-nav { display: flex; gap: 0.4rem; margin: 0.25rem 0 0.9rem; }
.week-tab {
  display: inline-flex; align-items: center; min-height: 2.2rem;
  padding: 0.3rem 0.9rem; border-radius: 999px; text-decoration: none;
  font-weight: 600; font-size: 0.9rem; color: var(--primary);
  border: 1px solid var(--outline); background: var(--surface);
}
.week-tab:hover { background: #e4ede2; }
.week-tab.active { background: var(--primary); color: var(--on-primary); border-color: var(--primary); }
.week-dates { font-weight: 600; white-space: nowrap; }
.week-actions { display: flex; gap: 0.3rem; margin-top: 0.3rem; }
.week-actions .week-tab { min-height: 1.9rem; padding: 0.15rem 0.7rem; font-size: 0.85rem; }

/* Formula block on the crop methodology page — reads as arithmetic, not code.
   (--panel/--accent aren't in this design system; using the real tokens.) */
pre.formula {
  background: var(--background);
  border-left: 3px solid var(--primary);
  padding: 0.7rem 0.9rem;
  overflow-x: auto;
  font-size: 0.9rem;
  line-height: 1.6;
  white-space: pre;
}

/* Crop workflow: the manager's six steps, with the current one called out. */
ol.workflow { list-style: none; margin: 0 0 1.2rem; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
ol.workflow li { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0.8rem; border: 1px solid var(--outline-light); border-radius: 0.5rem; background: var(--surface); }
ol.workflow li.done { opacity: 0.65; }
ol.workflow li.current { border-color: var(--primary); box-shadow: var(--elev-1); }
ol.workflow .step-mark { flex: none; width: 1.75rem; height: 1.75rem; border-radius: 50%; display: grid; place-items: center; font-weight: 700; font-size: 0.9rem; background: var(--background); color: var(--muted); }
ol.workflow li.done .step-mark { background: var(--primary); color: var(--on-primary); }
ol.workflow li.current .step-mark { background: var(--primary-container); color: var(--primary-dark); }
ol.workflow .step-body { display: flex; flex-direction: column; gap: 0.1rem; flex: 1 1 auto; min-width: 0; }
ol.workflow .step-body .muted { font-size: 0.85rem; }

/* Scan review: the page image beside the numbers read off it. */
.scan-review { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1rem; align-items: start; }
.scan-review .scan-image img { width: 100%; height: auto; border: 1px solid var(--outline-light); border-radius: 0.4rem; }
.scan-review .scan-fields { display: flex; flex-direction: column; gap: 0.8rem; min-width: 0; }
@media (max-width: 900px) { .scan-review { grid-template-columns: minmax(0, 1fr); } }

/* Scan review: cells the OCR wasn't sure about.

   The highlight IS the feature — it's the only thing telling a reviewer which
   2 of 40 numbers to check against the image, so it has to survive a glance at
   arm's length. Deliberately NOT --error (#a3271b, a dark brick red tuned for
   message text): at input-border size that reads as "slightly different" rather
   than "look here". It has its own alert colour, --flag, up with the palette. */

/* Outranks `table.grid input.frac` in the shared input rule above, which is
   (0,2,2) and was quietly winning border and background while this rule's
   colour and shadow applied -- the flag rendered half-styled and looked merely
   "slightly different" instead of alarming. */
.scan-review table.grid input.needed {
  border-width: 2px;
  border-style: solid;
  border-color: var(--flag);
  background-color: var(--flag-tint);
  color: var(--flag-ink);
  font-weight: 700;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--flag) 30%, transparent);
}
.scan-review table.grid input.needed:focus {
  outline: 2px solid var(--flag);
  outline-offset: 1px;
  background: #fff;
}
/* A marker in the row itself, so the flag survives even if the input's own
   styling is missed at a glance down a column of twenty. */
.scan-review tr:has(input.needed) td:first-child { box-shadow: inset 3px 0 0 var(--flag); }
.scan-review .needs-review-note { color: var(--flag); font-weight: 700; }

/* Scan stack: pages are read in the background, so rows show live progress. */
.spinner { display: inline-block; width: 0.85em; height: 0.85em; vertical-align: -0.1em;
  border: 2px solid var(--outline-light); border-top-color: var(--primary);
  border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 3s; } }
.failed { color: var(--error); font-weight: 600; }
.scan-review input.tiny { width: 4.5rem; }
.scan-review tr.spare input { background: var(--background); }
.short-count { color: var(--flag); font-weight: 700; }

/* Sortable table headers: the whole cell is the control, so it has to look and
   focus like one. */
table.sortable th { cursor: pointer; user-select: none; position: relative; padding-right: 1.25rem; }
table.sortable th:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
table.sortable th::after { content: "⇅"; position: absolute; right: 0.4rem; opacity: 0.35; font-size: 0.8em; }
table.sortable th[aria-sort="ascending"]::after { content: "↑"; opacity: 1; }
table.sortable th[aria-sort="descending"]::after { content: "↓"; opacity: 1; }
table.sortable th.nosort { cursor: default; padding-right: 0.7rem; }
table.sortable th.nosort::after { content: none; }
button.linklike.danger { color: var(--error); }
/* The one fixed height left in the app, and deliberately so: a PDF <embed> has
   no intrinsic height, and its viewer scrolls internally whatever we give it.
   This is a document viewer, not a card of app content — not one we missed. */
.scan-review .scan-pdf { width: 100%; height: 70vh; border: 1px solid var(--outline-light); border-radius: 0.4rem; }

/* Schedule board: crops are grouped, and named. The board is printed in black
   and white for the crew, so the heading carries the distinction and the tint
   is only reinforcement — a colour-only cue would vanish on paper. */
.palette-group + .palette-group { margin-top: 0.7rem; }
.palette-crop {
  margin: 0 0 0.35rem;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.chip.chip-hazel {
  background: #f3ead9;
  border-color: #b98c00;
}
/* Selected, not active: the palette toggles `selected` on its chips and the
   grid toggles `active` on its own. Naming the wrong one left this rule dead,
   so a picked hazelnut kept its tan background from the line above and took
   white text from `.chip.selected` — unreadable, and the one state the crew
   has to see at arm's length. Dark tan rather than the vine green so the crop
   still reads once it's picked. */
.chip.chip-hazel.selected {
  background: var(--hazel-deep);
  border-color: var(--hazel-deep);
  color: #fff;
}

/* Week page: the Calculation card now stacks three things — the computed
   numbers, the per-crop base hours, and this week's adjustments. Without
   headings and air they read as one undifferentiated column of inputs, and
   the two Kc controls (a crop's for the season, grapes' for the week) look
   like the same field twice. */
.section-head {
  margin: 1.5rem 0 0.6rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--outline-light);
  font-size: 0.95rem;
  font-weight: 600;
}
.card > .section-head:first-child { margin-top: 0; padding-top: 0; border-top: none; }
/* Kc edited in the row it belongs to, rather than in a separate form that
   restates which crop it is about. */
.kc-inline { display: inline-flex; align-items: center; gap: 0.35rem; }
.kc-inline input[type="number"] { width: 5rem; text-align: right; }
/* The provenance is a sentence, so it wraps under the crop rather than
   forcing a fourth column wider than the card it sits in. */
.kc-source { white-space: normal; max-width: 26rem; margin-top: 0.15rem; }

/* ---- Sales report ---- */
/* A buyer is TWO stacked tables — what they contracted, then the blocks that
   fill it — where every other page in the app puts one. .scroll-x carries no
   margin of its own, so the pair butted together and read as a single table
   with a strange header wedged into the middle of it. Same reasoning, and the
   same fix, as .card + .card up in Layout. */
.sales-group .scroll-x + .scroll-x { margin-top: 1.1rem; }
/* Buyers need to come apart harder than the tables within one do, or the
   groups themselves stop being legible as groups. season-divider's own
   margin-top is the gap between a table and the NEXT heading; this is the one
   the eye actually reads as "new buyer". */
.sales-group + .sales-group { margin-top: 2.5rem; }
/* The line under a heading ("Fruit nobody has contracted") sits between the
   rule and the table, so it needs air on both sides rather than hugging one. */
.sales-group .sales-note { margin: 0.7rem 0 0.9rem; }
/* Elsewhere the year strip is followed by a paragraph that gives it room. Here
   a card comes straight after and pinched it against the heading. */
.sales-seasons { margin-bottom: 0.9rem; }
/* Contracted tons on the roster: a tonnage is four or five characters, so the
   box is sized to that rather than stretching over the column. */
.contract-tons { width: 7rem; text-align: right; }
/* Two levels of subtotal inside one table, so they cannot look alike: site
   bands carry the heavier container colour, variety bands keep the standard
   totals green and step in under them. */
.sales-group tr.sales-site th { background: var(--primary-container); }
.sales-group tr.sales-variety th:first-child { padding-left: 1.6rem; font-weight: 500; }
/* Name and its download sit together at the left of the rule; the summary
   line stays pushed to the right by the divider's own space-between. */
.sales-buyer { display: inline-flex; align-items: center; gap: 0.6rem; }

/* ---- Irrigation methodology page ----
   Everything here is scoped under .method so this page's step/bar/gauge names
   can't reach the rest of the app through the one global stylesheet. Colours
   are the ones already in use: crop tints from the block tiles, and the
   amber pair from the %ET grid's "reason needed" input. */
.method {
  --grapes-tint: #dcebd8;
  --grapes-ink: var(--primary);
  --hazel-tint: #f0e2c8;
  --hazel-ink: #7d6118;
  --decide-bg: #fdf6dd;
  --decide-line: #b98c00;
  --decide-ink: #574a13;
  /* Shared inch scale for every bar on the page. */
  --inch: 7.2rem;
}
.method .grapes { --tint: var(--grapes-tint); --ink: var(--grapes-ink); }
.method .hazel { --tint: var(--hazel-tint); --ink: var(--hazel-ink); }

/* Reference ET: one figure, whole farm */
.method .eto { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1.5rem; }
.method .eto-num {
  font-size: 2.6rem; font-weight: 500; line-height: 1;
  color: var(--primary); font-variant-numeric: tabular-nums;
}
.method .eto-body { flex: 1 1 22rem; }
.method .eto-body p:last-child { margin-bottom: 0; }

/* A block's chain, ETo down to gallons */
.method .blk { display: flex; flex-direction: column; gap: 0.75rem; }
.method .blk-head {
  display: flex; flex-direction: column; gap: 0.15rem;
  padding-bottom: 0.6rem; border-bottom: 2px solid var(--tint);
}
.method .blk-head h3 { color: var(--ink); font-weight: 600; margin: 0; }
/* Two-line floor on the spec and every step label, so the same step sits at
   the same height in all three columns whichever crop needs longer wording. */
.method .blk-spec {
  font-size: 0.78rem; color: var(--muted); line-height: 1.35;
  font-variant-numeric: tabular-nums; min-height: 2.7em;
}

/* Row cross-section: shaded width, then wetted strip, over the row spacing */
.method .rowstrip {
  display: flex; height: 1.5rem; border: 1px solid var(--outline);
  border-radius: 4px; overflow: hidden;
  background: repeating-linear-gradient(45deg, transparent 0 5px, var(--outline-light) 5px 6px);
}
.method .rowstrip .shade { background: var(--ink); width: var(--shade, 0); }
.method .rowstrip .wet { background: var(--primary-container); width: var(--wet, 0); }

.method .step { display: flex; flex-direction: column; gap: 0.2rem; }
.method .step-name { font-size: 0.8rem; color: var(--muted); line-height: 1.25; min-height: 2.55em; }
.method .step-fig { font-size: 1.25rem; font-weight: 500; line-height: 1.2; font-variant-numeric: tabular-nums; }
.method .step-fig .unit,
.method .out-fig .unit { font-size: 0.72rem; color: var(--muted); font-weight: 400; margin-left: 0.25rem; }

/* The arithmetic behind each figure, deliberately quiet */
.method .calc {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.68rem; line-height: 1.5; color: var(--muted);
  overflow-x: auto; white-space: nowrap;
}

/* Inches, on one scale across all three blocks. Capped so a high %ET can't
   push the bar out of its card — past ~2.4 in it saturates and the figure
   carries the value. */
.method .bar {
  height: 0.5rem; border-radius: 2px; background: var(--ink);
  width: calc(var(--inch) * var(--v, 0)); min-width: 2px; max-width: 100%;
}

/* %ET — the one input on this page a person sets */
.method .step-decide {
  background: var(--decide-bg); border-left: 3px solid var(--decide-line);
  border-radius: 0 6px 6px 0; padding: 0.5rem 0.7rem;
}
.method .step-decide .step-name { color: var(--decide-ink); }
.method .decide-tag {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--decide-line);
}
.method .slider-row { display: flex; align-items: center; gap: 0.6rem; margin: 0.15rem 0 0.35rem; }
.method .slider-row input[type="range"] {
  flex: 1; min-width: 0; height: 1.5rem; cursor: pointer;
  accent-color: var(--decide-line);
}
.method .slider-row input[type="range"]:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 4px;
}
.method .slider-val {
  font-size: 0.95rem; font-weight: 600; min-width: 3.2rem; text-align: right;
  color: var(--decide-ink); font-variant-numeric: tabular-nums;
}
/* Past 100% the number is no longer a deficit; the caption says so. */
.method .over-note { font-size: 0.7rem; line-height: 1.3; font-weight: 600; color: var(--decide-line); }
.method .over-note:empty { display: none; }

.method .rule {
  display: flex; align-items: center; gap: 0.6rem; white-space: nowrap;
  font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}
.method .rule::before, .method .rule::after {
  content: ""; flex: 1; height: 1px; background: var(--outline-light);
}

.method .out {
  background: var(--tint); border-radius: 8px; padding: 0.6rem 0.75rem;
  display: flex; flex-direction: column; gap: 0.1rem;
}
.method .out-name { font-size: 0.72rem; font-weight: 600; color: var(--ink); }
.method .out-fig { font-size: 1.6rem; font-weight: 500; line-height: 1.1; font-variant-numeric: tabular-nums; }
.method .out-check { background: transparent; border: 1px dashed var(--ink); }

/* Where applied water lands when there is more than one emitter per plant —
   six per tree on the hazelnuts, so "per tree" is an acre figure over the
   tree count, not what the tree drinks. */
.method .split-note {
  display: flex; flex-direction: column; gap: 0.15rem;
  margin-top: 0.4rem; padding-top: 0.4rem; border-top: 1px dashed var(--outline);
  font-size: 0.74rem; line-height: 1.35; color: var(--muted);
}
.method .split-note b { color: var(--on-surface); font-variant-numeric: tabular-nums; }

/* Root zone: hours to fill against hours needed */
.method .gauge { display: flex; flex-direction: column; gap: 0.55rem; margin: 0.5rem 0 0.75rem; }
.method .gauge-row { display: flex; flex-direction: column; gap: 0.2rem; }
.method .gauge-top {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 0.5rem; font-size: 0.8rem; color: var(--muted);
}
.method .gauge-top b { color: var(--on-surface); font-variant-numeric: tabular-nums; white-space: nowrap; }
.method .gauge-track { height: 0.7rem; background: var(--background); border-radius: 3px; overflow: hidden; }
.method .gauge-fill { height: 100%; background: var(--ink); }
.method .gauge-fill.over { background: var(--decide-line); }
.method .verdict { font-size: 0.85rem; margin: 0; padding: 0.55rem 0.7rem; border-radius: 8px; background: var(--tint); }
.method .verdict.split { background: var(--decide-bg); border-left: 3px solid var(--decide-line); }
/* Section heading between cards needs air above it; h2's own top margin is
   tuned for sitting inside a card. */
.method h2.sec { margin-top: 1.5rem; }
.method .tail { margin-bottom: 0; }

/* %ET past 100 is over-replacement, not a deficit. Sits above the input on the
   grid so the number is never read as a percentage of something withheld. */
.over-full {
  display: block;
  font-size: 0.7rem;
  font-weight: 600;
  color: #b98c00;
  min-height: 1rem;
  line-height: 1;
}
.over-full:empty { visibility: hidden; }
/* Applied gal/plant/day carries a mark where there is more than one emitter to
   a plant, because then it is the acre's water over the plant count and not
   what the plant gets. Title on the cell has the detail. */
.split-flag { color: var(--muted); font-weight: 700; margin-left: 0.1rem; }

/* ---- Blocks that can't run together ---- */
.conflict-sides { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
.conflict-side { flex: 1 1 12rem; min-width: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.conflict-side select { width: 100%; padding: 0.4rem; border: 1px solid var(--outline); border-radius: 8px; }
.conflict-join { flex: 0 0 auto; text-align: center; font-size: 0.85rem; color: var(--muted); line-height: 1.3; }
.conflict-rule {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 1rem; padding: 0.7rem 0; border-top: 1px solid var(--outline-light);
}
.conflict-rule:first-of-type { border-top: none; }

/* ---- %ET grid ----
   Headers wrap rather than forcing the table wide: a column of numbers is
   narrow and its name should not be what sets the width. Units go on their own
   line under the name, so "To water" reads as a heading and "whole hr" as the
   unit it is. */
table.grid th { white-space: normal; vertical-align: bottom; }
table.grid th.num { min-width: 4.5rem; }
table.data th .unit,
table.grid th .unit {
  display: block;
  font-weight: 400;
  font-size: 0.72rem;
  color: var(--muted);
  line-height: 1.2;
}

/* Board warnings, grouped by kind. Five types in one flat list is noise, and
   noise is what teaches a manager to scroll past the flow warning too. */
.board-warnings .warning-group + .warning-group { margin-top: 0.6rem; }
.board-warnings .warning-kind {
  font-weight: 600; font-size: 0.85rem; margin-bottom: 0.15rem;
}
.board-warnings ul { margin: 0; padding-left: 1.1rem; }
/* The bulk reason sits inside the cutover banner, where it is read before the
   save rather than found after a page of refused rows. */
.bulk-reason { margin: 0.5rem 0 0; }
.bulk-reason label { display: block; font-weight: 500; margin-bottom: 0.2rem; }
.bulk-reason input { width: 100%; max-width: 34rem; }

/* A closed season reads. Inputs stay on the page — the numbers are the record
   and worth seeing — but they cannot be typed into, and the save buttons are
   gone rather than present and refusing. */
.read-only input,
.read-only textarea,
.read-only select {
  background: var(--background);
  color: var(--muted);
  pointer-events: none;
}
