/*
  Inter — the font Tabler's own demo uses, self-hosted.

  Tabler's CSS asks for "Inter Var" first but ships no font files and declares no
  @font-face, so without this the panel silently fell back to Segoe UI. Confirmed against
  preview.tabler.io, which loads Inter as a variable face (100-900) and renders every
  element in it.

  One 48KB file: the LATIN subset of the weight axis. That covers the whole 100-900 range
  in a single download, so the headings at 600 and the labels at 500 come from the same
  file as body text at 400 — no separate weight requests. Italic is omitted; the panel
  uses none, and a browser can synthesise it if something ever does.

  Absolute URL because this stylesheet is served from /<org>/<crop>/admin/ while the font
  lives at the origin root — a relative path would resolve per tenant and 404.

  font-display:swap so text paints in the fallback immediately rather than blocking; on a
  scale house's connection a blank table is worse than a brief font change.
*/
@font-face {
  font-family: "Inter Var";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/vendor/inter/inter-latin-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

/*
  HarvestLog over Tabler.

  Loaded AFTER vendor/tabler/tabler.min.css, so these same-specificity rules win. The
  panel should read as HarvestLog — the amber accent and warm dark surfaces from §11 of
  the design doc — not as a stock Tabler demo. Only variables and a few component tweaks
  live here; layout and components come from Tabler itself.

  Tabler is Bootstrap-based, so its dark palette hangs off [data-bs-theme=dark]. Every
  admin page sets that on <html>: the scale house is a dark room and the panel has never
  had a light mode.
*/

[data-bs-theme="dark"] {
  /* Surfaces — Tabler's stock dark is blue-grey (#111827); ours is warm neutral. */
  --tblr-body-bg:              #1a1a1c;
  --tblr-body-bg-rgb:          26, 26, 28;
  --tblr-body-color:           #f2f2f4;
  --tblr-body-color-rgb:       242, 242, 244;

  --tblr-bg-surface:           #242428;
  --tblr-bg-surface-primary:   #242428;
  --tblr-bg-surface-secondary: #2c2c31;
  --tblr-bg-surface-tertiary:  #2c2c31;

  --tblr-border-color:         #3a3a40;
  --tblr-border-color-translucent: #3a3a40;

  --tblr-secondary:            #b9b9c0;
  --tblr-secondary-color:      #b9b9c0;
  --tblr-secondary-bg:         #2c2c31;

  /* Accent. Amber is the primary action colour everywhere in this project. */
  --tblr-primary:              #f0a24a;
  --tblr-primary-rgb:          240, 162, 74;
  --tblr-success:              #3fb950;
  --tblr-success-rgb:          63, 185, 80;
  --tblr-danger:               #e5484d;
  --tblr-danger-rgb:           229, 72, 77;
  --tblr-warning:              #f0a24a;

  /* Inputs. Tabler's dark form background is its blue-grey gray-900, which reads as navy
     next to our warm neutral surfaces — the fields looked like they belonged to a
     different app. Sunk slightly below the card, as the pre-Tabler pages had them. */
  --tblr-bg-forms: var(--tblr-body-bg);

  /*
    Typography is Tabler's, deliberately NOT overridden.

    An earlier version of this file replaced --tblr-font-sans-serif with a system-ui
    stack, which silently discarded Tabler's own typography. Tabler's stack is
    "Inter Var", Inter, then -apple-system / Segoe UI / Roboto — so removing the override
    changes nothing visible until Inter is actually present, and everything the moment it
    is. Sizes come from --tblr-font-size-* (base 0.875rem, h1 1.5 → h6 0.625rem); no
    admin page should hardcode a pixel size.
  */
}

/*
  MIGRATION BRIDGE — the pre-Tabler variable names, aliased to the Tabler ones.

  The shared dialogs in shared.js style themselves with --surf / --acc / --t1 / --border,
  which used to come from a :root block in every page. A Tabler page has no such block,
  so without these the dialogs render transparent with a light border — invisible on a
  dark page. The other six admin pages still define and use these names directly, so the
  aliases must stay until all of them are converted AND shared.js is moved onto --tblr-*.

  Aliased rather than duplicated so there is still one source of truth for each colour.
*/
[data-bs-theme="dark"] {
  --bg:     var(--tblr-body-bg);
  --surf:   var(--tblr-bg-surface);
  --surf2:  var(--tblr-bg-surface-secondary);
  --border: var(--tblr-border-color);
  --t1:     var(--tblr-body-color);
  --t2:     var(--tblr-secondary-color);
  --t3:     #8a8a92;
  --acc:    var(--tblr-primary);
  --green:  var(--tblr-success);
  --red:    var(--tblr-danger);

  /*
    The sheet-number pill's red, deliberately NOT --red.

    --red is the danger colour and is used for errors on the page background, where it
    measures fine. The pill puts it on its own 15%-tinted background, and there the same
    hex came to 3.39:1 — under the 4.5:1 minimum, and the only one of the three pills that
    failed (green is 4.76:1, orange 5.47:1). Lightening it to #ff7a7a brings the pill to
    4.77:1 without touching the colour every error message on the panel depends on.
  */
  --pill-red: #ff7a7a;

  /* The pills' tinted grounds. Variables rather than literals in shared.js, so a theme
     can retune them — a 15% tint that reads as a soft wash on black is nearly invisible
     on white. */
  --pill-red-bg:   rgba(255,122,122,.15);
  --pill-green-bg: rgba(63,185,80,.15);
  --pill-acc-bg:   rgba(240,162,74,.15);

  /* Text ON a solid accent fill. The accent flips light-to-dark between themes, so
     anything sitting on it has to flip the other way — hardcoding either one leaves that
     text unreadable in the other mode. */
  --on-acc: #17130e;
}

/*
  ── LIGHT MODE ────────────────────────────────────────────────────────────────

  The scale house is a dark room and that is still the default, but the panel is also
  opened on an iPad in a truck cab at midday and on a laptop in an office with windows.
  Dark-on-bright is unreadable through a windscreen.

  Built as a MIRROR of the block above rather than as Tabler's stock light theme: the
  same warm neutral family, so the two modes are one product seen under different light
  rather than two designs. Every variable the dark block sets is set here, so nothing can
  fall through to a Bootstrap default that is blue-grey.

  THE ACCENT DARKENS. Amber at #f0a24a is 2.1:1 on white — fine as a large fill on a dark
  ground, illegible as text on a light one. #9a5a12 keeps the same hue and reaches 5.2:1,
  so links, focus rings and the accent text stay readable without becoming a different
  colour. Solid amber fills keep the light amber and take dark text, which is the same
  trade the dark mode already makes in reverse.
*/
[data-bs-theme="light"] {
  --tblr-body-bg:              #f7f6f3;
  --tblr-body-bg-rgb:          247, 246, 243;
  --tblr-body-color:           #23232a;
  --tblr-body-color-rgb:       35, 35, 42;

  --tblr-bg-surface:           #ffffff;
  --tblr-bg-surface-primary:   #ffffff;
  --tblr-bg-surface-secondary: #f0eeea;
  --tblr-bg-surface-tertiary:  #f0eeea;

  /*
    DARKER THAN LOOKS RIGHT ON A DIM SCREEN, because the screens this runs on are not dim.

    #dcd9d2 was picked against an ordinary monitor, where it reads as a quiet division
    between zones. On a high-brightness HDR panel in daylight the white surfaces bloom and
    a line four shades off white stops registering at all — the cards lose their edges and
    the page reads as one sheet. This is the same perceptual effect that made two buttons
    of identical colour look like different yellows.

    Bolder, not black: the line still has to be a division rather than a rule, and dark
    mode is untouched because a light line on a dark surface never had the problem.
  */
  --tblr-border-color:             #c6c1b6;
  --tblr-border-color-translucent: #c6c1b6;

  --tblr-secondary:            #5f5f6b;
  --tblr-secondary-color:      #5f5f6b;
  --tblr-secondary-bg:         #f0eeea;

  /*
    TWO AMBERS, BECAUSE THEY DO TWO JOBS.

    --tblr-primary is the amber as TEXT: links, focus rings, the accent word in a
    sentence. It has to be dark to be legible on white, which is why it is #9a5a12.

    --hl-fill-primary is the amber as a FILL, and a fill has no such problem — the text
    on it flips dark instead. Collapsing the two is what drained the panel in light mode:
    every gold button on the page turned a flat brown, and the buttons are most of what
    anyone looks at.
  */
  --tblr-primary:              #9a5a12;
  --tblr-primary-rgb:          154, 90, 18;
  --hl-fill-primary:           #eaa03f;
  --hl-fill-primary-hover:     #d8912f;
  /*
    Turned up, deliberately. The first pass picked each of these for text contrast on
    white and then used them for badges, borders and pills as well — which is how a
    green that measured 5:1 ended up looking grey next to the dark mode's. These are
    still above 4.5:1 as text and considerably closer to the colours the dark mode uses.
  */
  --tblr-success:              #12873a;
  --tblr-success-rgb:          18, 135, 58;
  --tblr-danger:               #cf2027;
  --tblr-danger-rgb:           207, 32, 39;
  --tblr-warning:              #a8630c;
  --tblr-warning-rgb:          168, 99, 12;

  /* Forms sit ON the card here rather than sunk below it: a white field on a white card
     needs its border to do the work, and a grey field would read as disabled. */
  --tblr-bg-forms: #ffffff;

  --bg:     var(--tblr-body-bg);
  --surf:   var(--tblr-bg-surface);
  --surf2:  var(--tblr-bg-surface-secondary);
  --border: var(--tblr-border-color);
  --t1:     var(--tblr-body-color);
  --t2:     var(--tblr-secondary-color);
  --t3:     #7c7c86;
  --acc:    var(--tblr-primary);
  --green:  var(--tblr-success);
  --red:    var(--tblr-danger);

  /* The pill's red, mirrored. On a light tint the problem inverts: the dark-mode
     #ff7a7a is 1.9:1 here, so the pill takes a DARKER red than the page's danger colour
     rather than a lighter one. 5.9:1 on its own tinted background. */
  --pill-red: #9e1f24;

  /*
    THE PILLS HAVE TO CARRY ON A WHITE PAGE.

    A tint that reads as a clear coloured ground on black is very nearly the page itself
    on white — the first pass raised these from the dark mode's 15% to 10-12%, which was
    the wrong direction, and the pills flattened into three grey chips with coloured
    text. On a light ground a wash needs roughly double, not less.

    The text darkens to match, so the contrast on the stronger ground goes up rather than
    down: these are read at a glance across a scale house, not studied.
  */
  --pill-red-bg:   rgba(158,31,36,.22);
  --pill-green-bg: rgba(18,135,58,.22);
  --pill-acc-bg:   rgba(154,90,18,.24);

  /* White on the darkened amber. */
  --on-acc: #ffffff;
}

/* Amber is a LIGHT accent, so white-on-amber fails contrast. Primary buttons and solid
   amber badges take the dark surface colour for their text instead. */
[data-bs-theme="dark"] .btn-primary,
[data-bs-theme="dark"] .btn-primary:hover,
[data-bs-theme="dark"] .btn-primary:focus,
[data-bs-theme="dark"] .btn-warning,
[data-bs-theme="dark"] .badge.bg-primary {
  color: #1a1a1c;
}

/*
  THE GOLD BUTTON SURVIVES THE LIGHT MODE.

  It used to become the darkened amber with white text — legible, and the wrong call. The
  amber IS this product: it is the Save button on every page, the pill on every link, the
  thing the eye goes to. Rendered brown it stopped reading as the same application, and
  the panel lost most of its colour in one substitution, because buttons are most of what
  is on a page.

  A fill does not need the text contrast a link does; it needs contrast with what sits ON
  it, and that flips dark. So the button takes the same gold as the dark mode with dark
  text, exactly the trade the dark mode already makes in reverse. --tblr-primary stays
  dark for anything that is genuinely text.
*/
[data-bs-theme="light"] .btn-primary,
[data-bs-theme="light"] .btn-warning,
[data-bs-theme="light"] .badge.bg-primary,
[data-bs-theme="light"] .badge.bg-warning {
  --tblr-btn-bg: var(--hl-fill-primary);
  --tblr-btn-border-color: var(--hl-fill-primary);
  --tblr-btn-color: #23232a;
  --tblr-btn-hover-bg: var(--hl-fill-primary-hover);
  --tblr-btn-hover-border-color: var(--hl-fill-primary-hover);
  --tblr-btn-hover-color: #23232a;
  --tblr-btn-active-bg: var(--hl-fill-primary-hover);
  --tblr-btn-active-border-color: var(--hl-fill-primary-hover);
  --tblr-btn-active-color: #23232a;
  --tblr-btn-disabled-bg: var(--hl-fill-primary);
  --tblr-btn-disabled-border-color: var(--hl-fill-primary);
  --tblr-btn-disabled-color: #23232a;
  background-color: var(--hl-fill-primary);
  border-color: var(--hl-fill-primary);
  color: #23232a;
}
[data-bs-theme="light"] .btn-primary:hover,
[data-bs-theme="light"] .btn-primary:focus,
[data-bs-theme="light"] .btn-primary:active,
[data-bs-theme="light"] .btn-warning:hover,
[data-bs-theme="light"] .btn-warning:focus,
[data-bs-theme="light"] .btn-warning:active {
  background-color: var(--hl-fill-primary-hover);
  border-color: var(--hl-fill-primary-hover);
  color: #23232a;
}

/*
  THE SOFT BADGES, TURNED UP.

  Tabler's -lt badges are a ~10% tint of the colour with the colour as text. On a dark
  surface that lands as a clearly coloured chip; on white the same tint is all but the
  page itself, so Done, Shared, Approved and every status on the panel faded to grey
  text in a grey box. Stronger ground, darker text, and they read as the same chips the
  dark mode has.
*/
/* !important, because Tabler's own -lt rules carry it. Without matching it the
   overrides parse, apply to nothing, and the chips stay pale. */
[data-bs-theme="light"] .bg-green-lt  { background: rgba(18,135,58,.16)  !important; color: #0d6b2c !important; }
[data-bs-theme="light"] .bg-red-lt    { background: rgba(207,32,39,.14)  !important; color: #a0161c !important; }
[data-bs-theme="light"] .bg-yellow-lt { background: rgba(216,145,47,.24) !important; color: #7d4a08 !important; }
[data-bs-theme="light"] .bg-blue-lt   { background: rgba(32,107,196,.14) !important; color: #17509a !important; }

/*
  NO MONOSPACE ANYWHERE IN THE PANEL.

  Weights, ticket numbers and device ids used to be set in var(--tblr-font-monospace),
  on the reasoning that they are read as data rather than prose. The flaw is that the
  value is a STACK, not a font — Consolas on Windows, Courier New on an iPhone, Monaco
  on a Mac — so the same number was a different typeface on every device, and on a phone
  it landed on the weakest face of the three.

  Everything is Inter now. Alignment was the only thing monospace was really buying, and
  tabular figures give that without a second typeface: every digit takes the same width,
  so a column of weights still lines up exactly.

  These class names are kept and simply re-pointed, so any markup still carrying .mono or
  .text-mono gets tabular Inter rather than silently reverting to a monospace face.
*/
.mono, .num, td.num, th.num, input.num, .text-mono, .font-monospace,
code, kbd, samp, pre {
  font-family: var(--tblr-font-sans-serif) !important;
}

/*
  Numbers are set exactly like the words around them — no tabular figures.

  Removing monospace, this first asked for tabular-nums so a column of weights would still
  line up. That solved alignment and reintroduced the original complaint: Inter's tabular
  digits are much wider than its normal ones (11111 measures 130px tabular against 81px
  proportional), so a gross weight still did not look like the rest of the page. Numbers
  now render identically to body text.

  The cost is real and deliberate: gross, tare and net no longer line up digit-for-digit
  down a column. They stay right-aligned, so the ones, tens and hundreds columns still
  end at the same place, which is most of what made them comparable.

  The pills keep their tabular figures — a sheet number is a fixed-width code (YY-G-NNN)
  in a column of other sheet numbers, and ragged ones read as broken rather than as prose.
*/

/*
  Form controls inherit Inter's character variants.

  Tabler sets font-feature-settings "cv03","cv04","cv11" on the body, but browsers do not
  pass that into <input>, <select> or <textarea> — they compute `normal`. So the same
  digits looked one way in a table and another way in the box you typed them into. This
  is a browser default, not a Tabler choice, and it has to be undone explicitly.
*/
input, select, textarea, button {
  font-feature-settings: inherit;
}
/* <code> is used here for literal values a person types — a CSV header, a filename.
   Without the monospace face it needs some other mark, or it disappears into the
   sentence around it. */
code { background: var(--tblr-bg-surface-secondary); border-radius: 3px; padding: 1px 5px; }
td.num, th.num { text-align: right; }
/* Weight and time inputs. Right-aligned so the digits of gross, tare and net line up
   vertically — the scaler is comparing them at a glance, not reading them as words. */
input.num { text-align: right; }
/* A read-only field is showing a derived value (net, acreage), not inviting a change. */
.form-control[readonly] { color: var(--tblr-secondary-color); }

/*
  The domain pills stay exactly as they were — sheet-no red, sheet green, link gold.
  Tabler has badges, but these encode meaning specific to this system and are already
  consistent across every page via shared.js. Kept, not replaced (design doc §12).
*/

/* Tables inside cards: let a wide table scroll rather than crush its columns on a
   phone. `.table-responsive` is Tabler's wrapper; this is the floor below which the
   columns stop being legible. */
.table-responsive > table { min-width: 600px; }

/*
  iOS: stop wide tables dragging the whole page sideways.

  Tabler's .card is a COLUMN flex container, so its children are flex items and default
  to min-width:auto — "never shrink below your content". Chromium still constrains a
  scrolling child to the card, but WebKit honours min-width:auto and lets the
  .table-responsive stretch to the full width of its table. On an iPhone that pushed the
  table out past the card's border and the page off the side of the screen, while the
  same page measured perfectly clean in Chromium.

  min-width:0 is the standard flex-item fix; max-width:100% is belt and braces. Applied
  to every card child, not just tables, so anything wide added later behaves too.
*/
.card > *, .card-body > * { min-width: 0; }
.card > .table-responsive, .card-body > .table-responsive { max-width: 100%; }
/* A card must never be wider than the column it sits in, whatever it contains. */
.page-body .card { max-width: 100%; }

/*
  The top bar carries farm, crop and season. On a narrow phone the farm name is the part
  that can give way — it truncates — while the two switchers keep their full width, since
  losing them costs the user the ability to change context at all.
*/
#hl-navbar .navbar-brand { min-width: 0; flex-shrink: 1; overflow: hidden; }
/*
  The crop and season switchers need air between them.

  Tabler zeroes .nav-link horizontal padding while the navbar is collapsed, so on a phone
  the two dropdowns sat flush against each other and read as one run-on string —
  "Potato ⌄Season 2026 ⌄". They are two separate controls and have to look like it.
  A gap rather than padding, so the space is only ever BETWEEN them and never adds a
  dead margin at the right edge of a narrow screen.
*/
#hl-navbar .navbar-nav.flex-row { flex-shrink: 0; gap: .875rem; }
/* Keeps the pair off the very edge of the screen on a phone. */
#hl-navbar .navbar > .container-xl { padding-right: .5rem; }

/*
  A flush table sitting at the edge of a card must follow the card's rounded corners.

  .table-responsive scrolls, so its overflow already clips — but with square corners, so
  the header band and the last row painted straight over the card's rounded outline. It
  is most obvious on the coloured cards, where the band visibly cuts the orange or green
  border at each corner. `inherit` picks up whichever radius the card is using.
*/
.card > .table-responsive:first-child { border-top-left-radius: inherit; border-top-right-radius: inherit; }
.card > .table-responsive:last-child  { border-bottom-left-radius: inherit; border-bottom-right-radius: inherit; }

/* The action column on every table: right-aligned and never wrapped, so a row's buttons
   stay on one line and stay together. It is what overflows first on a narrow screen,
   which is exactly why the wrapper above has to scroll. */
td.acts { white-space: nowrap; text-align: right; }

/*
  A row you can act on should say so under the cursor.

  Rows that open a sheet, a load or a link are clickable, but nothing marked them out —
  on a wide table the eye loses which line it is on somewhere around the fifth column,
  and clicking the wrong row opens the wrong ticket. Whole-row highlight, not cell.

  Pointer devices only: on a touch screen :hover sticks after a tap and leaves a row
  looking selected when it is not.
*/
@media (hover: hover) and (pointer: fine) {
  tbody tr.pick:hover > td,
  tbody tr.ld:hover > td,
  tbody tr[data-id]:hover > td,
  tbody tr[data-fcard]:hover > td {
    /*
      background-COLOR, never the shorthand. The shorthand resets background-image too,
      and the source-tinted columns (.jd, .wx) carry their tint as an image precisely so
      this highlight can slide underneath it. The shorthand here is what made a hovered
      row's green and blue columns go flat surface grey — black, in dark mode.
    */
    background-color: var(--tblr-bg-surface-secondary);
  }
}

/*
  Sortable column headings.

  `.sortable` is added by the shared sorter, so a heading only looks interactive when it
  actually is. The arrow is reserved with a fixed-width slot rather than appended, so a
  heading does not jump sideways the first time it is sorted.
*/
/*
  Reorderable column headings — see hlColumns. These were scoped to #allloads in browse.html
  while the Loads table was the only one that could be reordered; they belong with the other
  heading rules now that any table can take the behaviour.

  `grab` on the heading and not on the grip: the grip at the right edge is hlResizable's and
  keeps its own col-resize cursor, so the two gestures look as different as they behave.
*/
thead th.over { box-shadow: inset 3px 0 0 var(--tblr-primary); }
thead th[draggable="true"] { cursor: grab; }

th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable::after {
  content: '';
  display: inline-block;
  width: 0.85em;
  text-align: right;
  color: var(--tblr-primary);
}
/*
  THE ARROW POINTS THE WAY THE LIST READS, not at the end it starts from.

  A on top and Z at the bottom shows a DOWN arrow: the eye travels down the column and the
  values go forward, so the arrow is the direction of travel. Z on top shows an up arrow.

  This was the other way round — ascending drew an up arrow, which is the commoner convention
  and is defensible as "the arrow points at the small end". It reads backwards on a table you
  are scanning rather than one you are reasoning about, and the dates make the case: a sheet
  list opening newest-first is the view you want, and it was marking itself with a down arrow
  while the newest thing sat at the top.

  Chosen by the user, and it is a convention rather than a correctness matter, so it is
  written down here where the next person to feel it is wrong will find the reason.
*/
th.sortable[data-sort="asc"]::after  { content: '\2193'; }
th.sortable[data-sort="desc"]::after { content: '\2191'; }
@media (hover: hover) and (pointer: fine) {
  th.sortable:hover { color: var(--tblr-primary); }
}

/*
  The working sheet's pills sat directly against the table header below them, so the
  green pill's border and the header rule touched and read as one boxed-in strip.

  The margin alone did nothing: the pills live in a `.card-body pb-0`, whose zero bottom
  padding is what actually closes the gap. The space has to come from the card body.
*/
.sheethead { margin-bottom: 4px; }
#sheetzone > .card-body.pb-0 { padding-bottom: 1rem !important; }

/*
  Dialog text at the panel's normal reading size.

  Tabler's .modal inherits the body's 0.875rem, which is fine for a dense table and small
  for a dialog you are reading a decision out of — the assign-to-link dialog especially,
  where the whole content is one sentence and one control.
*/
.modal-body, .modal-body .form-label, .modal-body .form-control, .modal-body .form-select {
  font-size: 1rem;
}
.modal-title { font-size: 1.15rem; }

/* A date input centred its text while every other field left-aligns. WebKit does that by
   default; nothing had overridden it. */
input[type="date"], input[type="time"], input[type="datetime-local"] {
  text-align: left;
}

/* Icon + label inside a button. Tabler's .btn is already a flex row with a gap, so this
   only needs to stop an icon being squashed when the button is tight. */
.btn > svg { flex-shrink: 0; }

/* The nav's active page should be unmistakable at a glance in a busy room. */
.navbar .nav-link.active,
.navbar .nav-item.active > .nav-link {
  color: var(--tblr-primary) !important;
  font-weight: 600;
}

/*
  Columns that came from John Deere, in John Deere green.

  The Fields zone puts machine-measured figures next to scale-house ones, and the two
  are true at different moments — the combine knows when the pass ended, the scale not
  until the truck is weighed. The tint is what stops them being read as one table of
  equally current numbers. Deliberately faint: this is a source marking, not a warning,
  and it sits under text that still has to be legible in both themes.
*/
/*
  THE COLOUR IS THE PLATFORM'S, so it is a variable rather than green typed thirty times.

  John Deere is green today. Case IH is red, Trimble is blue, and a farm may one day run
  two of them at once — at which point the marking has to say WHICH machine reported,
  not merely that a machine did. Setting --hl-src-rgb on a table (or on the page) retunes
  every tint below it; nothing else has to change.

  The class is still `.jd`, because renaming it would touch every admin page for no
  behaviour, and the day a second platform lands it can gain a sibling.
*/
:root { --hl-src-rgb: 54, 124, 43; }        /* John Deere green */
[data-bs-theme="dark"] { --hl-src-rgb: 124, 200, 90; }

/*
  THE SECOND SOURCE: sky blue, for the weather.

  Its own variable rather than a shade of the first. Operations Center and the sky are
  two outside services that answer at different moments and fail independently, and the
  whole point of tinting a figure is to say WHICH outside thing said it. A weather
  reading in Deere green would be a lie told in a colour.
*/
:root { --hl-wx-rgb: 2, 132, 199; }         /* sky blue */
[data-bs-theme="dark"] { --hl-wx-rgb: 56, 189, 248; }
/* The mail account's own colour, a third service that fails independently of the other
   two — violet, because green and sky blue are taken and the strip says which is which
   before a pane is opened. Same pair as the others: darker for light mode, brighter for
   dark, so the filled tab keeps its contrast both ways. */
:root { --hl-mail-rgb: 124, 58, 237; }      /* violet */
[data-bs-theme="dark"] { --hl-mail-rgb: 167, 139, 250; }
/* The texting account — teal, the fourth service and the fourth colour. Not a blue,
   deliberately: the weather already owns sky blue and iMessage's own blue sits close
   enough to it that the two tabs would read as one service at a glance, which is the
   single thing this strip exists to prevent. */
:root { --hl-bloo-rgb: 13, 148, 136; }      /* teal */
[data-bs-theme="dark"] { --hl-bloo-rgb: 45, 212, 191; }

/*
  ── THE TAB STRIP ────────────────────────────────────────────────────────────────

  On CONNECTIONS each tab wears its own service's colour, so the strip says what is behind
  each pane before it is opened. Selected is a filled tab; the rest are outlines of the
  same hue.

  On BROWSE there is no such thing to say. Its four panes are Fields, Sheets, Grades and
  Loads — one database, four views of it, with no outside service behind any of them —
  so a strip of four different hues would be inventing a distinction to colour. They take
  the panel's own amber instead, `.hl-tab-hl`, which is what every other control that means
  "this product" already wears.

  They are BUTTONS, not links: they switch a pane rather than going anywhere. Pressing the
  open one does nothing on either page — the strip always has exactly one tab open, since a
  tab that closes to an empty page reads as broken when the tab IS the table.
*/
.hl-tabs { display: flex; flex-wrap: wrap; gap: .5rem; }
.hl-tab {
  --hl-tab-rgb: var(--hl-src-rgb);
  border: 2px solid rgba(var(--hl-tab-rgb), .85);
  background: transparent;
  color: rgb(var(--hl-tab-rgb));
  font-weight: 600;
  border-radius: 8px;
  padding: .45rem 1.1rem;
  line-height: 1.3;
}
.hl-tab:hover { background: rgba(var(--hl-tab-rgb), .14); }
/* Filled when its pane is open. White text on both themes: the fill is dark green or
   dark blue in light mode and bright in dark mode, and the darker of the two decides. */
.hl-tab.is-on { background: rgb(var(--hl-tab-rgb)); color: #fff; }
[data-bs-theme="dark"] .hl-tab.is-on { color: #10151a; }
.hl-tab-wx { --hl-tab-rgb: var(--hl-wx-rgb); }
.hl-tab-mail { --hl-tab-rgb: var(--hl-mail-rgb); }
.hl-tab-bloo { --hl-tab-rgb: var(--hl-bloo-rgb); }

/*
  THE VERIFIED MARK — a check inside a filled circle, wherever a load or a sheet is tabled.

  Its own colour rather than Tabler's green, so the mark means one thing across the panel
  and cannot drift when a badge class is restyled. Sized in em so it tracks the table's own
  text; a fixed pixel circle looked right on Browse and wrong in the Sheet Inbox, which
  runs a smaller row.

  A load with no mark renders NOTHING — see verifiedTick in shared.js for why absence is
  the honest answer rather than a dash.
*/
/*
  The mark is an inline SVG — see verifiedTick in shared.js — so this only sizes it and sits
  it on the text's baseline. The colours are the SVG's own, one green and one pale fill in
  both themes, so the mark is the same object on a light page and a dark one rather than a
  colour scheme that shifts with it.
*/
.hl-verified {
  display: inline-block; width: 1.45em; height: 1.45em;
  vertical-align: -.32em;
}
/*
  The panel's own amber, for a strip whose panes are all this database — see above.

  IT TAKES BOTH AMBERS, one for each job, which is the whole reason the file keeps two.

  The OUTLINE and the TEXT are --tblr-primary: the amber that has to stay legible on white,
  #9a5a12 in light mode. That is what an unselected tab is made of.

  The FILL is --hl-fill-primary, and the first version of this rule got it wrong in exactly
  the way the note beside those two variables warns about. Driving the fill from
  --tblr-primary turned the selected tab into a flat brown block in light mode — "every gold
  button on the page turned a flat brown" is that note's own sentence, and this reproduced it
  on the tab strip. Dark mode hid it, because there the two ambers are the same colour.

  The fallbacks are the dark theme. --hl-fill-primary and its hover are declared only in the
  light block, deliberately: dark mode's own amber is already bright enough to be a fill, so
  there is nothing for them to fix there. `var(…, X)` resolves to X exactly when they are
  absent, which is exactly in dark mode.

  THE RING IS THE HOVER AMBER, a step deeper than the fill. Without it the selected tab is a
  single flat colour with no edge — the border, the background and the strip all agree — and
  what reads as a tab is precisely the edge. Dark text on both, because white on #eaa03f is
  about 1.9:1 and cannot be read at all.
*/
.hl-tab-hl {
  --hl-tab-rgb: var(--tblr-primary-rgb);
  --hl-tab-fill: var(--hl-fill-primary, #f0a24a);
  --hl-tab-ring: var(--hl-fill-primary-hover, #c07f2c);
  /*
    ONLY THE OPEN TAB IS GOLD. The Connections strip can leave every tab in its own colour
    because each names a different service; four tabs of one hue just read as a band of
    amber with one of them slightly darker, and the eye has to work out which. So the
    closed ones go neutral and the gold means "this one" rather than "these are tabs".

    Both variables are declared in both themes, so this needs no colour of its own.
  */
  border-color: var(--tblr-border-color);
  color: var(--tblr-secondary-color);
}
/*
  Hover previews the selection — the amber wash it inherits, plus the amber text to go with
  it, so a closed tab says what it will become rather than merely lighting up.
*/
.hl-tab-hl:hover { color: rgb(var(--hl-tab-rgb)); }
.hl-tab-hl.is-on {
  background: var(--hl-tab-fill);
  border-color: var(--hl-tab-ring);
  /*
    Dark ink, because white on #eaa03f is about 1.9:1 and cannot be read. Light mode is set
    here; dark mode keeps the strip's existing `[data-bs-theme="dark"] .hl-tab.is-on` rule,
    which is more specific and already puts near-black on the fill. Two different darks on
    two different golds, each picked for the gold under it.
  */
  color: #412402;
}
/*
  WEATHER COLUMNS, the sky-blue counterpart of .jd.

  Same specificity fight, same answer — see the note on .jd above. These out-specify
  Tabler's own cell rule on purpose; a bare .wx is one class and loses.

  A third source needs a third colour: the air is neither Deere's figures nor the scale
  house's, and tinting it green would say a machine reported it.
*/
/* Image, not color, so the row hover shows through — see the note on .jd above. */
.wx { background-image: linear-gradient(rgba(var(--hl-wx-rgb), .10), rgba(var(--hl-wx-rgb), .10)); }
.table tbody td.wx,
.table tbody th.wx { background-image: linear-gradient(rgba(var(--hl-wx-rgb), .10), rgba(var(--hl-wx-rgb), .10)); }
.table thead th.wx { background: rgba(var(--hl-wx-rgb), .16); }
[data-bs-theme="light"] .wx { background-image: linear-gradient(rgba(var(--hl-wx-rgb), .16), rgba(var(--hl-wx-rgb), .16)); }
[data-bs-theme="light"] .table tbody td.wx,
[data-bs-theme="light"] .table tbody th.wx { background-image: linear-gradient(rgba(var(--hl-wx-rgb), .16), rgba(var(--hl-wx-rgb), .16)); }
[data-bs-theme="light"] .table thead th.wx { background: rgba(var(--hl-wx-rgb), .26); }
thead th.wx { border-bottom: 3px solid rgba(var(--hl-wx-rgb), .75); }

/* The weather card's own heading row, the sky-blue counterpart of .jd-card. */
.wx-card thead th {
  background: rgba(var(--hl-wx-rgb), .18);
  border-bottom: 2px solid rgba(var(--hl-wx-rgb), .55);
}
[data-bs-theme="light"] .wx-card thead th { background: rgba(var(--hl-wx-rgb), .22); }

/*
  GREEN COLUMNS. ONE FLAT TINT, EVERY ROW.

  SPECIFICITY IS THE WHOLE STORY HERE, and getting it wrong twice is what produced first
  a banded column and then no colour at all.

  Tabler paints every cell with `.table > :not(caption) > * > *`, which counts as one
  class and one element. A bare `.jd` is one class and loses, so the tint never applied
  from that rule at all — what used to paint it was an accident: a `nth-child(3n)` rule
  specific enough to win, which is why the green appeared in stripes. Removing that rule
  removed the only thing painting the cells.

  So the cell rules now out-specify Tabler on purpose and say so. No nth-child, no theme
  attribute needed to win — the colour comes from --hl-src-rgb, which the theme changes.
*/
/*
  The tint is a BACKGROUND-IMAGE, not a background-color — the same two-layer trick the
  sticky headings use, for the same reason from the other side. The row hover paints a
  background-color on every cell; as a color it slides UNDER this translucent gradient,
  so a hovered row highlights and the column stays its colour. When the tint was itself
  a background-color, hover simply replaced it — a green or blue cell went flat surface
  grey, which in dark mode reads as the column going black under the pointer.
*/
.jd { background-image: linear-gradient(rgba(var(--hl-src-rgb), .10), rgba(var(--hl-src-rgb), .10)); }
.table tbody td.jd,
.table tbody th.jd { background-image: linear-gradient(rgba(var(--hl-src-rgb), .10), rgba(var(--hl-src-rgb), .10)); }
.table thead th.jd { background: rgba(var(--hl-src-rgb), .16); }
/*
  A FARM WITH NO OPERATIONS CENTER CREDENTIALS SEES NO JOHN DEERE COLUMNS.

  Set on <body> from the bootstrap flag. Every such column already carries `.jd` to tint
  it, so hiding by the same class takes the headings and the cells together and cannot
  fall out of step with the tint — a column that were hidden in one and not the other
  would shift every cell after it one place left.

  `!important` for the same specificity reason the tints above have to fight: those rules
  are `.table tbody td.jd`, two classes and an element, so a bare `.no-jd .jd` loses to
  them on background and would lose here too if display were contested.
*/
/*
  `.jd-only` hides with the John Deere columns without being tinted like one.

  `.jd` means two things at once — "this came off a machine", which paints it green, and
  "this is meaningless without Operations Center", which hides it. That is fine on a
  table cell and wrong on a control: the Refresh button wore the column tint as a green
  background, which made a button look like a piece of Deere's data. Anything that only
  needs the second meaning carries this instead.
*/
body.no-jd .jd, body.no-jd .jd-only { display: none !important; }

/*
  ── THE PANEL USES THE MONITOR IT IS ON ─────────────────────────────────────────

  Tabler's .container-xl stops at 1320px, which is a sensible cap for a page of prose and
  the wrong one for this: these are wide tables, and a Browse row carries a dozen columns
  that were being squeezed into two thirds of a widescreen while the other third sat
  empty. Every page's header, tabs and body use the same container, so they are widened
  together — widening only the body would step the header in from the content beneath it.

  Only above 1400px, so every one of Tabler's own breakpoints below that is untouched and
  the iPad in the scale house is unaffected.

  Capped rather than edge to edge. 95vw keeps a margin so the cards do not fuse to the
  bezel, and the 2200px ceiling is there because on an ultrawide a table row stops being
  readable long before it stops fitting — following a row across three feet of glass is a
  head movement, not a glance.
*/
/*
  AND IT GROWS SMOOTHLY, rather than in steps.

  This applied only above 1400px, so between roughly 1200 and 1400 the container sat at
  Tabler's fixed 1320 while the window kept growing — the page stayed put, margins grew,
  and then it snapped wide the moment 1400 was crossed. That dead stretch is the gap.

  The cap is a single expression at every width instead, so the page tracks the window
  continuously. 95vw still keeps the cards off the bezel and 2200px is still the ceiling,
  for the reason below; what is gone is the breakpoint in the middle.
*/
.container-xl { max-width: min(95vw, 2200px); }

/*
  ── A ZONE IS DRAGGED FROM ITS BOTTOM-RIGHT CORNER ──────────────────────────────

  How many rows a zone should show is a different answer on every farm, every screen and
  every morning: four sheets waiting or forty, a laptop or the scale house's widescreen.
  A fixed cap is right for one of those and wrong for the rest.

  `resize` needs a non-visible overflow, which every scroll zone already has, so the
  browser draws the handle and no JS is needed to move it. The second rule is what makes
  it work in both directions: resize sets an inline HEIGHT, a max-height would cap that,
  and a zone that can be dragged shorter but never taller reads as a broken handle. Once
  there is a height — dragged, or restored on load — the cap steps out of the way.

  The cap therefore has to live in a stylesheet rather than a style attribute: an inline
  max-height outranks any rule trying to lift it.
*/
/*
  ── THE FIELD LIST'S THREE STATES ───────────────────────────────────────────────

  Green is in this crop's database, red is set aside, and a plain row has not been
  decided. Carried as background-IMAGES for the same reason the source tints are: the row
  hover is a background-colour, and it slides underneath rather than replacing them, so a
  green row stays green under the pointer instead of going flat grey.

  Faint. This is a state marking on a table somebody reads across, not a warning — at
  full strength forty green rows are a wall, and the one red row stops being the thing
  the eye goes to.

  The farm band is a heading, not a state, so it gets weight rather than colour. Traceability's
  Move Loads dialog bands its setups the same way (hl-band), so one heading row reads as
  covering every group under it.
*/
.oc-included > td { background-image: linear-gradient(rgba(47,158,68,.13), rgba(47,158,68,.13)); }
.oc-excluded > td { background-image: linear-gradient(rgba(214,57,57,.13), rgba(214,57,57,.13)); }
.oc-excluded > td:nth-child(3) { text-decoration: line-through; text-decoration-color: rgba(214,57,57,.6); }
tr.oc-farm > td, tr.hl-band > td {
  font-weight: 700; letter-spacing: .04em;
  background: var(--tblr-bg-surface-secondary);
  border-top: 1px solid var(--tblr-border-color);
}

/*
  An armed alert bell, filled rather than outlined.

  `fill` on the path, not a colour on the button: the icons are stroked outlines, so a
  text colour alone changes the outline and leaves the middle empty — which at a glance is
  the same shape as the silent one. Filling the body is what makes "this will make a
  sound" readable without looking closely.
*/
/*
  ── WHICH CROP, WHICH SEASON ────────────────────────────────────────────────────

  These two decide what every figure on every page means, and they were nav links in the
  same ink and weight as Browse and Reports — so the only thing distinguishing a potato
  tab from a wheat one was set to look like somewhere to go rather than a fact about
  where you are.

  Amber and bold, with the season a shade quieter than the crop: the crop chooses the
  database, the season chooses a year within it, and the type should say which of those
  is the larger statement. Both stay dropdowns — they are also how you switch.

  Uppercase for the crop only. "POTATO" beside "Season 2026" reads as a label and a
  qualifier; both shouted would be two headlines competing.
*/
.nav-link.hl-crop .nav-link-title {
  color: var(--tblr-primary);
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.nav-link.hl-season .nav-link-title {
  color: var(--tblr-primary);
  font-weight: 600;
}
/* The archive marker keeps its own colour — it is a warning, not part of the label. */
.nav-link.hl-season .nav-link-title .text-orange { font-weight: 500; }

.hl-bell-on { color: var(--tblr-primary); }
.hl-bell-on svg { fill: currentColor; fill-opacity: .35; }

/*
  A FLOOR ON THE DRAG.

  `resize: vertical` moves the BOTTOM edge, which can never rise above the element's own
  top — that would be a negative height. So on a zone whose top sits partway down the
  page, dragging upward stops responding once the cursor passes that top edge while the
  mouse carries on moving, which reads as the resizer dying halfway up the screen.

  Nothing in CSS can lift that: the bottom edge cannot outrank the top. What a floor does
  is make it stop at a size that still shows something, instead of collapsing toward a
  sliver first and only then going dead. Roughly three rows and the header.
*/
.hl-resize { overflow: auto; resize: vertical; min-height: 8rem; }
/*
  THE GRIP IS DRAWN, because the browser's own is built for a white page.

  Chrome's default resizer is a pale diagonal graphic on a transparent ground — on the
  panel's dark surfaces it reads as a small white notch cut out of the corner of the card,
  which looks like a rendering fault rather than a handle.

  Replaced with two strokes in the border colour, so it is the same grey as every other
  division on the page and works in both themes without a second rule. Sized in the
  gradient rather than by a border, which the resizer does not honour.

  Firefox draws its own themed grip and ignores this; Safari uses the WebKit pseudo like
  Chrome. No browser is left without a visible handle either way.
*/
.hl-resize::-webkit-resizer {
  background-color: transparent;
  background-image: linear-gradient(
    135deg,
    transparent 0 42%,
    var(--tblr-border-color) 42% 54%,
    transparent 54% 66%,
    var(--tblr-border-color) 66% 78%,
    transparent 78%
  );
}
/*
  !important, and not for convenience. The caps it has to overrule are written at every
  specificity the panel uses — `.zonescroll` and `.inboxscroll` are classes, but the Links
  zone sets its 560px through `#links-scroll`, and an ID beats any number of classes. A
  zone whose cap happened to be written as an ID would otherwise be the one zone that
  could be dragged shorter and never taller, which is a bug nobody would connect to a
  selector.
*/
.hl-resize[style*="height"] { max-height: none !important; }

/*
  ── A ROW THAT ANSWERS THE FINGER ───────────────────────────────────────────────

  Rows that open something — the sheet inbox, the loads list, a field's card — showed a
  hover on a desktop and nothing at all under a finger. A tap that opens a dialog a
  moment later, with no acknowledgement in between, reads as a tap that missed, so the
  scaler taps again and the second one lands on whatever the dialog put there.

  Applied to the CELLS rather than the row, because a <tr> is not a painting surface in
  a table: a background on it is drawn under the cells' own and never seen. Carried as
  background-color so the source tints above — which are background-IMAGES precisely so
  a highlight can slide beneath them — keep their colour while the row is held.

  The mouse gets it too. It is the same statement to both, and a button that depresses
  under a pointer is not a touch affordance, it is feedback.
*/
tr.pick:active > td,
tr.ld:active > td,
tr[data-fcard]:active > td {
  background-color: var(--tblr-bg-surface-secondary);
}
/*
  iOS paints its own grey flash over anything tappable, on its own schedule and in its
  own colour. Turned off so the rule above is the only answer, rather than two of them
  fighting over one row.
*/
tr.pick, tr.ld, tr[data-fcard] { -webkit-tap-highlight-color: transparent; }

/*
  ── THE FILTER BOX AND ITS CLEAR ────────────────────────────────────────────────

  Every zone that filters gets the same box and the same X, from hlFilterBox() — the
  panel had a "Clear" button beside some lists, nothing at all beside others, and no way
  to empty a filter except selecting the text. One shape, defined once, so they cannot
  drift apart the next time a zone gains a filter.

  LEFT, not right, and inside the field. The right-hand end of these inputs is where the
  text ends up as it is typed, so a control there is under the cursor exactly when it is
  most likely to be hit by accident. The left is empty whatever has been typed.

  Shown only when there is something to clear: an X on an empty box is a control that
  does nothing, and the row of them across a page reads as clutter.
*/
.hl-filterbox { position: relative; }
/*
  Padded only while the X is actually there. A constant 2.1rem indented every placeholder
  in the panel to make room for a control that appears once something has been typed — so
  an empty box looked wrongly aligned against every other input on the page, which is most
  of the time a filter box is looked at.

  The text shifting when the X appears is the right kind of movement: it happens at the
  moment the control arrives, so it reads as making room rather than as a jump.
*/
.hl-filterbox.has-value > .form-control { padding-left: 2.1rem; }
.hl-filterbox > .hl-filterclear {
  position: absolute; left: .3rem; top: 50%; transform: translateY(-50%);
  display: none; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; padding: 0; line-height: 1;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--tblr-secondary); font-size: 1.15rem; cursor: pointer;
}
.hl-filterbox.has-value > .hl-filterclear { display: flex; }
.hl-filterbox > .hl-filterclear:hover { background: var(--tblr-bg-surface-secondary); color: var(--tblr-body-color); }
/* The small variant used in Setup's reference lists, where the input is form-control-sm. */
.hl-filterbox.has-value > .form-control-sm { padding-left: 1.9rem; }
.hl-filterbox > .form-control-sm ~ .hl-filterclear { width: 1.3rem; height: 1.3rem; font-size: 1rem; }

/* A whole ROW the pull's filters claimed, in the full pulled-fields view — the same
   tint as .jd, carried as an image so the hover colour slides underneath it. */
.table tbody tr.src-match > td { background-image: linear-gradient(rgba(var(--hl-src-rgb), .10), rgba(var(--hl-src-rgb), .10)); }

/* A fault on a page nobody is looking at, said in the menu. Deliberately a dot and not
   a count: there is one John Deere connection per farm, so a number could only ever
   be 1. */
/*
  THE CORNER, NOT THE END OF THE LINE.

  It used to sit inline after the label, vertically centred — which puts a coloured dot
  exactly where a full stop goes, so it read as punctuation on the end of the word rather
  than as a mark on the item. A notification badge belongs at the top right, clear of the
  text, which is where every other application puts one and therefore where the eye
  already looks for it.

  Positioned against `.nav-link-title.has-badge` rather than the link: the link is as wide
  as its padding makes it, so the dot would drift away from the word on a short label and
  sit under it on a long one. The title box is the word.

  A little room is reserved on the right so the dot has somewhere to be that is not the
  next menu item — it overhangs the text, but only into the link's own padding.
*/
.nav-link-title.has-badge { position: relative; padding-right: .5rem; }
.nav-badge {
  position: absolute;
  top: -.1rem;
  right: -.1rem;
  width: .5rem; height: .5rem;
  border-radius: 50%;
  background: var(--tblr-danger, #d63939);
}
.nav-badge.d-none { display: none !important; }

/*
  BLUE, BECAUSE NOTHING IS BROKEN.

  Red is a fault and orange is a warning; a driver waiting to be let in is neither. It is
  somebody standing at the window, and the only wrong response is not noticing.

  Same bare dot as the one beside it — the dot's job is to send somebody to the page, and
  the page says who and how many. A count here would be a second place for the same fact
  to be told, and the one that goes stale.
*/
.nav-badge-blue { background: #2f6fd0; }
[data-bs-theme="dark"] .nav-badge-blue { background: #4a90e2; }

/*
  ── BUTTONS: ONE RULE, SO A PAGE STOPS LOOKING LIKE FOUR PAGES ──────────────────

  Ghost buttons are gone. A control with no outline reads as text until it is hovered,
  which is how a page ends up with things that look like labels and things that look
  like buttons doing the same kind of job.

    in a card HEADER      btn-sm         primary action btn-primary,
                                         others btn-outline-secondary / -danger
    in a card BODY        full size      same three
    inside a .lrow        btn-sm         btn-outline-primary

  One btn-primary per card: the thing you came to that card to do.

  ── GROUPS ──────────────────────────────────────────────────────────────────────

  Fields and buttons that belong to each other sit inside one outline, with a small
  heading. A card body that is a flat run of inputs and buttons makes the reader work
  out the grouping from spacing alone, and spacing is exactly what gets lost when a
  panel is redrawn or read on a phone.
*/
.group {
  border: 1px solid var(--tblr-border-color);
  border-radius: var(--tblr-border-radius, 4px);
  padding: .875rem;
  margin-bottom: 1rem;
}
.group:last-child { margin-bottom: 0; }
.group > .group-title {
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--tblr-secondary-color);
  margin-bottom: .75rem;
}
/* The row of buttons that acts on the group it closes. */
.group-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-top: .875rem;
}

/*
  A bordered list of rows, each with something on the left and a control on the right.

  These classes were invented in setup.html and their rules stayed in that page's own
  <style> block. The Connections page then used the same markup and got NOTHING — no
  flex, no padding, no border, no gap — so the organization name and its button sat
  against each other with nowhere to breathe. Rules that two pages use belong here.

  Wraps rather than squeezes: on a phone the button drops under the name instead of
  crushing it to two characters and an ellipsis.
*/
.list {
  border: 1px solid var(--tblr-border-color);
  border-radius: var(--tblr-border-radius, 4px);
}
.list .lrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem 1rem;
  padding: .625rem .75rem;
  border-bottom: 1px solid var(--tblr-border-color);
}
.list .lrow:last-child { border-bottom: none; }
/* The name takes the slack so the control is pushed to the right edge. */
.list .lrow > .form-check { flex: 1 1 12rem; min-width: 0; }

/*
  TEN ROWS, THEN SCROLL.

  A contractor's John Deere account can hold hundreds of clients, and the whole list
  rendered inline pushed every step after it — crop types, fields, the pull button —
  off the bottom of the screen. Ten is enough to recognise the account and short
  enough to leave the sequence visible.

  Derived from the row height rather than a round number of pixels, so a change to
  .lrow padding cannot quietly turn this into nine and a half. The variable is the
  single place to correct if that measurement ever drifts: content line (1.25rem) +
  padding (.625rem twice) + the 1px rule between rows.
*/
.list.rows-10 {
  --hl-lrow-h: calc(1.25rem + 1.25rem + 1px);
  max-height: calc(var(--hl-lrow-h) * 10);
  overflow-y: auto;
}

/*
  Coloured panels need a THICKER edge in light mode.

  A tinted background carries the meaning well enough on a dark surface, but on a white
  one a 1px pale border and a 6%-opacity fill are nearly the same colour as the page —
  the panel reads as a plain box and the green or amber does no work at all. A 4px bar
  down the leading edge is the cheapest way to make the colour survive a bright screen,
  and it stays out of the way of the text.
*/
.alert { border-left-width: 4px; }
.alert-success { border-left-color: var(--tblr-success); }
.alert-warning { border-left-color: var(--tblr-warning); }
.alert-danger  { border-left-color: var(--tblr-danger); }
.alert-info    { border-left-color: var(--tblr-info); }

/* The same problem on the Fields zone's machine-reported columns: a 10% tint vanishes
   on white. The heading keeps a solid rule under it so the group is still readable as a
   group even where the fill is faint. Colour from --hl-src-rgb, like the rest. */
[data-bs-theme="light"] .jd { background-image: linear-gradient(rgba(var(--hl-src-rgb), .16), rgba(var(--hl-src-rgb), .16)); }
[data-bs-theme="light"] .table tbody td.jd,
[data-bs-theme="light"] .table tbody th.jd { background-image: linear-gradient(rgba(var(--hl-src-rgb), .16), rgba(var(--hl-src-rgb), .16)); }
[data-bs-theme="light"] .table thead th.jd { background: rgba(var(--hl-src-rgb), .26); }
[data-bs-theme="light"] .table tbody tr.src-match > td { background-image: linear-gradient(rgba(var(--hl-src-rgb), .16), rgba(var(--hl-src-rgb), .16)); }
thead th.jd { border-bottom: 3px solid rgba(var(--hl-src-rgb), .75); }

/*
  AN OUTLINED CARD IS ONE THAT WANTS SOMETHING.

  The rim used to say what KIND of thing a card held — blue for links, green for sheets —
  which meant almost every card on the page had one, and a marking that is everywhere
  marks nothing. It says one thing now: there is work waiting in here. Sheets to place,
  drivers asking to pair. A card nobody has to act on is left plain.

  No exceptions. The John Deere cards used to carry one as a SOURCE marking and it was
  wrong twice over — it was not the platform's green once Tabler had toned it, and a
  results table demands nothing of anybody. They carry a tinted heading row instead.

  2px, and the whole way round rather than a bar down one edge: these are peers on a page,
  not alerts stacked in a column, and a left-edge rule reads as "this one is special" when
  each is simply itself.
*/
.card.border-primary,
.card.border-success,
.card.border-warning,
.card.border-danger,
.card.border-info,
.card.border-azure {
  border-width: 2px;
}

/*
  A CARD WHOSE WHOLE CONTENTS CAME FROM THE MACHINE.

  Where a table mixes sources — Browse's HarvestLog Fields zone — the green marks the
  COLUMNS that are Deere's, because the point is telling them from the scale's. Where
  everything in the card came from one pull, marking a column says nothing and quietly
  implies the others are ours. So the HEADING ROW carries it.

  The heading only, and no rim: an outlined card on this panel means work is waiting in
  it, and a table of results asks for nothing. Faint, because this is a source marking
  rather than a warning, and it sits under text that stays legible in both themes.
*/
.jd-card thead th {
  background: rgba(var(--hl-src-rgb), .18);
  border-bottom: 2px solid rgba(var(--hl-src-rgb), .55);
}
[data-bs-theme="light"] .jd-card thead th { background: rgba(var(--hl-src-rgb), .22); }

/*
  The little "i" that opens a zone's explanation.

  Defined here rather than on one page because two pages now carry one, and a third
  will: an affordance that means "there is something to read here" has to look identical
  everywhere or it stops being recognised as the same thing.
*/
/*
  GOLD, on the user's call — the panel's accent, the colour of its primary buttons. It was the
  secondary grey on a border-grey ring, the colour of text that is not meant to be pressed, so
  nobody could tell it opened anything. The hover fills it, as a pressable thing does.
*/
.hl-info {
  cursor: pointer;
  color: var(--tblr-primary);
  border: 1px solid var(--tblr-primary);
  background: transparent;
  border-radius: 5px;   /* a rounded square, on the user's call; it was a circle */
  /* 16px, on the user's call — at 19 in gold it stood out more than the heading beside it. */
  width: 16px;
  height: 16px;
  padding: 0;
  line-height: 14px;   /* the height inside the border, which centres the i */
  text-align: center;
  font-size: .68rem;
  font-style: italic;
  font-weight: 700;
  font-family: Georgia, 'Times New Roman', serif;   /* a serif i reads as an i, not an l */
  display: inline-block;
  flex-shrink: 0;
}
.hl-info:hover, .hl-info:focus-visible {
  color: var(--tblr-primary-fg, #10151a);
  background: var(--tblr-primary);
}
/*
  Beside the heading, its BOTTOM LEVEL WITH THE BOTTOM OF THE WORDS, on the user's call. It was
  centred on the line and hung below the text. A button's baseline is the baseline of the "i"
  inside it, which sits a few px above the square's bottom edge. Standing exactly on the
  baseline read as too high beside the text; with its bottom 3px under the letters it reads
  as level. At 16px that takes a 1px raise (it was 3px at 19px) — measured, bottom 3px below
  the baseline either way. Change the size and this number has to be measured again.
*/
.card-title .hl-info { margin-left: .5rem; vertical-align: 1px; }

/*
  A short farm address, set apart from a farm that has a real name. The letters were
  spaced out — "W H B" — and are not any more; the italic is what marks it now.

  The padding is not decoration. An italic glyph leans PAST its advance width, so the
  last letter's tail sits outside the box the browser measured — and a "B" at the end of
  a slug lost its corner wherever the container clipped or the next element began. Two
  pixels is the lean; padding gives it somewhere to be.
*/
.org-slug {
  font-style: italic;
  padding-right: 2px;
}

/*
  ── UNDER THE NOTCH ─────────────────────────────────────────────────────────────

  The panel's pages declare viewport-fit=cover, so on a phone the page owns the whole
  screen including the strip behind the Dynamic Island. A tall dialog — the field card is
  the tallest — is centred in that whole screen and its top slid up behind the island,
  taking the field name and the close button with it.

  Insetting the DIALOG rather than the page: the backdrop should still cover the screen
  edge to edge, because a dark overlay that stops short of the island looks like a bug of
  its own. Only the content it holds needs to keep clear.

  A centred modal also needs its available height reduced by the same amount, or the
  padding pushes the bottom off instead of shortening the box.
*/
@media (max-width: 1199.98px) {
  .modal .modal-dialog {
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .modal .modal-dialog-centered {
    min-height: calc(100% - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  }
  /* The field card scrolls inside itself rather than growing past the screen. */
  .modal .modal-content { max-height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 16px); }
  .modal .modal-body { overflow-y: auto; }
}

/*
  ── THE HAMBURGER'S CONTROL BLOCKS ──────────────────────────────────────────────

  Appearance, Crop and Season are three button groups stacked in the collapsed menu, and
  they were three different heights: the theme buttons carry an icon beside their label
  and the others carry text alone, so each group sized itself to its own content. Stacked,
  that reads as three unrelated controls rather than one set.

  So the buttons share a height and their labels centre in it, whether or not there is an
  icon. The spacing between blocks is set here too rather than on each host, so adding a
  fourth needs no new margin.

  Phone widths only — above MD these live on the bar and in the profile menu, where they
  are laid out by their own rules.
*/
/* Follows the header: the tabs now collapse at lg, so the menu they collapse INTO is
   styled below lg too. */
@media (max-width: 991.98px) {
  #hl-theme-menu, #hl-tenant-menu, #hl-season-menu { margin-top: 1rem !important; }
  #hl-theme-menu .btn,
  #hl-tenant-menu .btn,
  #hl-season-menu .btn {
    min-height: 2.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .35rem;
  }
  /* NOT full width — see the note in hlPaintThemeControls. Stretched across a phone each
     button gets an inch of empty space around a two-word label, and the group sits wider
     than the page titles above it. They size to their labels; the shared height is what
     makes them a set. */
  /* One rhythm between blocks: the crop group carried its own mb-2, so the gap under Crop
     was larger than the one under Appearance. The host margin is the only spacing now. */
  #hl-tenant-menu .btn-group { margin-bottom: 0 !important; }
  /*
    THE SELECTED BUTTON DRAWS ITS OWN EDGE, and draws it on top.

    Buttons in a group overlap by a pixel on purpose — Bootstrap pulls each one back over
    its neighbour so the shared border is a single line rather than two. Whichever button
    is painted LAST wins that pixel, so the filled yellow one at the right-hand end laid
    its background over the outline of the button beside it and the group looked like it
    had lost a border.

    Lifting the active button above its neighbours means its own border is the one that
    survives the overlap, which is the line that should win: it is the edge of the thing
    currently chosen. `position: relative` because z-index does nothing to a static box.
  */
  #hl-theme-menu .btn-group > .btn.btn-primary,
  #hl-tenant-menu .btn-group > .btn.btn-primary,
  #hl-season-menu .btn-group > .btn.btn-primary {
    position: relative;
    z-index: 2;
  }
  /* The little grey caption above each group, on one rhythm. */
  #hl-theme-menu > .text-secondary,
  #hl-tenant-menu > .text-secondary,
  #hl-season-menu > .text-secondary { margin-bottom: .35rem !important; }
}

/*
  ── THE MARK, CENTRED ON A SMALL SCREEN ────────────────────────────────────────

  Below md the top bar carries three things: the hamburger on the left, the account chip
  on the right, and the mark. In normal flow the mark sits immediately after the toggle,
  so it reads as a label hanging off the menu button rather than as the identity of the
  page — and because the two ends are different widths, no amount of auto margin puts it
  in the middle.

  Taken OUT of the flow and pinned to the centre of the bar instead, which is the only way
  to centre against two unequal neighbours. pointer-events stays on so it remains
  clickable, but it takes no width, so the toggle and the chip lay out as though it were
  not there and cannot be pushed around by it.

  Above md it returns to the flow untouched — see the note in shared.js about the mark
  anchoring the right-hand end of the bar at desktop widths.
*/
/*
  ── WHAT A SELECTION CAN DO DROPS TO ITS OWN LINE RATHER THAN OFF THE CARD ──────

  Every zone on this panel puts its selection's actions in the card header, top right (see
  .card-actions). The header is a flex ROW and the actions carry `margin-left:auto`, which
  is what holds them beside the title — and what ran them off the card as soon as the
  window was narrow enough: a select and four buttons cannot shrink past their own content,
  so they kept the width they needed and the card's right edge arrived first. Half a bar sat
  over the rows underneath or outside the zone entirely, and the page scrolled sideways.

  NOT A PHONE RULE, and that is the point. It began wherever the bar stopped fitting — a
  half-width desktop window reaches it as surely as a handset — so the header simply wraps
  at every width. A row of actions that fits stays where it always was; one that does not
  takes the line below the title, and the bar's own `flex-wrap` (already on .bulkbar) folds
  the buttons into as many lines as they need.

  min-width:0 is what lets a select or a filter box shrink inside that line instead of
  setting the width of it. A flex item defaults to min-width:auto, which is the whole reason
  the overflow could not resolve itself.

  EVERYTHING EXCEPT A BUTTON. min-width:0 was on every child, and a box shrinks gracefully
  where a button does not: there is nothing inside a button to reflow, so squashing one below
  its label just pushes the words out over its own edge — "Insert load after" hanging into
  the space beside it, in a box too small to hold it. A button keeps the width of what it
  says and the bar folds around it instead, which is why the actions row wraps here and not
  only in the header: two of these bars wrap an inner .bulkbar that already wrapped, and the
  rest — the working sheet's among them — are a plain row that did not.

  row-gap, not gap. A wrapped second row needs air above it; a column gap here would land on
  top of the margin utilities the markup already spaces these with and double every one.
*/
.card-header { flex-wrap: wrap; row-gap: .5rem; }
.card-header .card-actions { row-gap: .5rem; }
.card-header .card-actions > *:not(.btn) { min-width: 0; }
/*
  A PICKER INSIDE A GROUP IS NOT A DIRECT CHILD, and the rule above only reaches direct
  children. The Sheet Inbox's link picker and Browse's live in a wrapper — `#ib_bulk`, a
  `.bulkbar` — so they kept their min-content width and went on setting the row's, which is
  the exact thing min-width:0 is here to stop. Selectors read the DOM, not the flex tree, so
  dissolving the wrapper with `display: contents` does not make its children direct ones.
*/
.card-header .card-actions .form-select,
.card-header .card-actions .form-control { min-width: 0; }
.card-header .card-actions .btn { flex: none; white-space: nowrap; }
.card-header .form-select, .card-header .form-control { max-width: 100%; }

/*
  ONE HEIGHT FOR EVERY CONTROL ON A CARD HEADER, so a row of them reads as a row.

  A btn-sm is 27px and a form-select-sm is 31px, four pixels Tabler never has to reconcile
  because its own headers hold buttons OR a control, not both. These hold both: a link picker
  with an Assign beside it, a filter box with a Clear. Centred against each other, two
  controls that do the same job in the same bar sat at two different tops and two different
  bottoms, and the row read as things that had been put near each other.

  2rem is the select's height, the taller of the two, so nothing has to shrink to meet it and
  no label gets tighter than it was. Height only — the padding, the type and the radius are
  Tabler's.
*/
.card-header .card-actions .btn-sm,
.card-header .card-actions .form-select-sm,
.card-header .card-actions .form-control-sm { height: 2rem; }

/*
  AND THE ZONE'S NAME STAYS IN THE TOP-LEFT CORNER OF THE HEADER.

  Tabler centres the header's items against each other, which is invisible while everything
  in it is one line high and wrong the moment the actions wrap: a two-row bar is 70px tall,
  so "Sheet Inbox · 7 waiting" slid halfway down the card and sat level with the gap between
  the two rows of buttons. The name of the zone is the one thing on that header with a fixed
  place, and it is the corner.

  The icon that some headers put beside the title — the working sheet's load-order switch —
  goes with it, because it reads as part of the heading rather than as one of the actions.
*/
.card-header > .card-title,
.card-header > .btn { align-self: flex-start; }

/*
  A GROUP INSIDE THE ACTIONS ENDS AT THE SAME EDGE AS EVERYTHING ELSE.

  The Sheet Inbox's assign group is a flex row of its own, so the justification on the
  actions positions the GROUP and has nothing to say about what is inside it. Once the group
  wrapped, its second line packed to the left of a box whose right-hand end was somewhere
  under the first line — Move Loads alone under the middle of the header, while every other
  line on the card ended at the right margin.
*/
.card-header .card-actions .bulkbar,
.card-header .card-actions #ib_bulk,
.card-header .card-actions .jd-only { justify-content: flex-end; }

/*
  ── AND REFRESH IS ONE OF THE BUTTONS, NOT A BUTTON BESIDE THEM ─────────────────

  The Sheet Inbox's assign group is a box, and Refresh is that box's SIBLING — so the two
  never shared a line even when there was room for one. The group wrapped inside its own
  width and Refresh stood off to the right of the whole thing, level with nothing, reading
  as a control that belonged to the card rather than to the row of buttons it sits in.

  `display: contents` drops the box and hands its buttons straight to the actions row. Then
  there is one wrapping row for all of them — count, picker, Assign, Export, Move, Refresh —
  packed to the right and broken only where the width actually runs out, which is both the
  compact answer and the one where Refresh behaves like everything beside it.

  The gap and the centring are unchanged: the wrapper carried the same `gap-2` and
  `align-items-center` the actions row does, so what it hands over lands identically spaced.

  `:not(.d-none)` because the group is absent until something is ticked, and `.d-none` is
  display:none with !important — this rule carries !important too and would otherwise put an
  empty bar on the header of a queue nobody has touched.

  The actions row has to be able to wrap for any of that to work. The utility on the markup
  only says `d-flex`. Where a card asks for `flex-nowrap` outright — Browse's Fields header,
  which holds its Ops Center group on one line by design — that utility still wins.
*/
/*
  Justified to the right HERE rather than in the md block, because it is true at every width.
  A group that has not wrapped is content-sized and Tabler's auto margin already holds it
  against the edge; the moment it wraps, the box is wider than any one of its lines and only
  this decides where those lines end. Between sm and md there was no rule at all, so the
  inbox's buttons packed left the instant Refresh dropped to a second line.
*/
.card-header > .card-actions { flex-wrap: wrap; justify-content: flex-end; }
.card-header > .card-actions > #ib_bulk:not(.d-none) { display: contents !important; }

/*
  ── AND IT SHARES THE TITLE'S LINE RATHER THAN CLAIMING ONE ─────────────────────

  The wrap above is what a bar does when it does not FIT. It was firing when it merely did
  not fit WHOLE: flex decides where to break by each item's natural width, before it lets
  anything shrink, so a seven-control selection bar took the line below the title, then
  shrank to the card and wrapped again inside it. Three rows — a heading with 600px of empty
  space beside it, then the bar in two — where two rows held the same controls.

  flex-basis 0 is the whole fix. The group stops declaring a natural width, so it never
  triggers the header's break; it takes whatever the title leaves and folds its own contents
  into as many rows as they need. That is the SECOND row, and it is the only extra one.

  JUSTIFIED TO THE RIGHT, both ways, because the group is now a stretched box rather than a
  shrink-wrapped one that Tabler's auto margin held against the edge. Which of the two does
  the work depends on the card: some of these regions carry `d-flex` in the markup and some
  are plain blocks of inline-block buttons, and one declaration cannot serve both. The
  children take `start` back so a right-aligned box does not right-align the text inside a
  select or a label.

  ABOVE md ONLY. Below it the title and a bar genuinely cannot share a line, and a group with
  no natural width would stop wrapping at the exact width it has to — the overflow the note
  above describes, back again. There it keeps its natural basis and the breakpoints below say
  what happens next.
*/
@media (min-width: 768px) {
  .card-header > .card-actions {
    flex: 1 1 0%;
    min-width: 0;
    text-align: right;
  }
  .card-header > .card-actions > * { text-align: start; }
}

@media (max-width: 767.98px) {
  /*
    THE ACTIONS KEEP TO THE RIGHT, at this width as at every other.

    They used to be pulled to the left edge here — Tabler's auto margin overridden to 0 and
    the bars justified to flex-start — on the reading that a row of buttons held against the
    right edge of a phone reads as cut off. On the user's call it does not. What it reads as
    is the corner the actions occupy on every wider screen, and jumping them across at one
    arbitrary width is the thing that makes the same page look like two.

    max-width rather than width: a group stays on the title's line while it fits there and
    takes a line of its own only when it genuinely cannot.
  */
  .card-header > .card-actions { max-width: 100%; }
  .card-header .bulkbar { justify-content: flex-end; max-width: 100%; }

  .navbar > .container-xl,
  .navbar > .container-fluid { position: relative; }

  #hl-brand {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    margin: 0;
    padding: 0;
    /* The farm name is already d-none below xl; this keeps the box to the icon itself so
       the centring is the ICON's centre and not a text box's. */
    width: auto;
    max-width: 40vw;
  }
}

/*
  ── THE FIELD CARD ──────────────────────────────────────────────────────────────

  These rules were in browse.html, which was the only page the card could be opened
  from. It opens from the dashboard now too — the card itself moved to shared.js — and
  a page-local stylesheet cannot follow it. So they live here, with everything else the
  panel shares.

  Nothing about them changed in the move. Left alone deliberately, so that if the card
  ever looks wrong on one page and right on another, it is not because the styling was
  quietly rewritten on its way across.
*/

/*
  THE FIELD CARD'S SECTIONS, kept apart.

  The card was one continuous run of tiles and lines: acreages, rates, sky, storage and
  Deere's own figures all at the same rhythm, so nothing said where one subject ended
  and the next began. Each block now carries a quiet heading and real space beneath it,
  and a hairline divides them — a rule rather than a bigger gap, because whitespace
  alone at this density reads as an accident.

  The last block takes no rule or margin, so the card does not end on a line pointing at
  the table below it.
*/
.fc-block { padding-bottom:14px; margin-bottom:14px; border-bottom:1px solid var(--tblr-border-color); }
.fc-block:last-of-type { padding-bottom:0; margin-bottom:16px; border-bottom:0; }
.fc-head {
  font-size:11px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  color:var(--tblr-secondary); margin-bottom:10px;
}
/* The tiles inside a block sit closer to their heading than the blocks do to each
   other, or the grouping the heading creates is undone by the spacing. */
.fc-block .row > div { margin-bottom:10px; }
/*
  A PILL IS THE SAME SIZE AS A PLAIN FIGURE.

  `.badge` carries its own font-size, so every number wearing the green source pill
  printed a step smaller than the ones beside it in plain type. Deere's figures then
  looked like footnotes to the scale house's — a difference in importance, when the
  only difference is where the number came from.

  `inherit` rather than a fixed size, so it tracks .fs-3 if that ever changes. The grey
  unit suffix beside a value stays .fs-5: that IS subordinate, and correctly smaller.
*/
.fc-block .fs-3 .badge {
  font-size:inherit; font-weight:700; vertical-align:baseline; padding:.1em .45em;
}
/*
  THE FIELD CARD'S TABLES KEEP THEIR HEADINGS IN VIEW. Each scrolls inside a 300px box, and a
  column of numbers scrolled past its heading is a column nobody can name — Net, Pulp, Amb and
  Hum are all plain figures. The same sticky rule Browse's zones wear.

  A SOLID background under every heading, the modal's own surface, or rows show through as they
  pass beneath. The weather headings keep their tint as an IMAGE over that surface: theme.css
  paints them with the `background` shorthand, which on its own would put back a translucent
  colour and the see-through heading this is here to stop — the same fault Browse's zones had.
*/
.fc-scroll thead th {
  position:sticky; top:0; z-index:2;
  background:var(--tblr-modal-bg, var(--tblr-bg-surface));
  box-shadow:inset 0 -1px 0 var(--tblr-border-color);
}
[data-bs-theme="light"] .fc-scroll thead th.wx {
  background-color:var(--tblr-modal-bg, var(--tblr-bg-surface));
  background-image:linear-gradient(rgba(var(--hl-wx-rgb), .26), rgba(var(--hl-wx-rgb), .26));
}
[data-bs-theme="dark"] .fc-scroll thead th.wx {
  background-color:var(--tblr-modal-bg, var(--tblr-bg-surface));
  background-image:linear-gradient(rgba(var(--hl-wx-rgb), .16), rgba(var(--hl-wx-rgb), .16));
}
/*
  THE FIELD'S IDENTITY IN OPERATIONS CENTER, printed as a reference.

  Monospace because it is an identifier and not prose — a UUID set in a proportional
  face is unreadable back to somebody checking it against a screen in Operations Center.
  `user-select:all` so one click takes the whole of it and not a word between hyphens.

  It also happens to be the fastest way to tell a real field from a seeded one: Deere's
  ids are UUIDs, and a demo farm's are not.

  THE FAMILY IS SET HERE rather than left to Bootstrap's `font-monospace` utility, which
  was tried first and lost: the computed face came back as Inter, the panel's proportional
  body font, so the id printed in exactly the type this rule exists to avoid. Naming the
  stack in the rule that owns the element settles it without depending on which of two
  frameworks' utilities wins.
*/
.fc-jdid {
  font-family: var(--tblr-font-monospace, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace);
  font-size:11px; letter-spacing:.02em; user-select:all;
}
/*
  A NAMED FACT UNDER THE TILES — the ones that are a list or an identifier rather than a
  figure, and so cannot be a tile.

  The varieties and the machines used to print as one grey run joined by a dot, with
  nothing saying which half was which: "Russet Burbank · ploeger 25-4" reads as one list of
  four things unless you already know what you are looking at. Now each says what it is.

  The key column is a FIXED WIDTH so the three labels stack into a column. Ragged labels
  are barely better than none — the eye has to read each one to find the row it wants,
  which is the work the label was supposed to save.

  The value is body ink against a secondary label, so the fact reads as the content and the
  label as its name. The whole line is small: these sit under the figures and are reference,
  not headline.
*/
.fc-line { display:flex; gap:10px; align-items:baseline; margin-top:6px; font-size:12px; }
/*
  88px, and the number is measured rather than guessed: HARVESTERS is the longest of the
  three and renders 81px at this size, weight and letter-spacing. At 76px it overflowed its
  own column and pushed its value five pixels right of the other two — a fixed column that
  the longest label does not fit in is just a ragged column with extra rules.

  nowrap so a narrow card breaks the VALUE onto a second line and never the label: a label
  wrapping to two lines drags its own row out of the column it is there to make.
*/
.fc-line > .k {
  flex:0 0 88px; white-space:nowrap; color:var(--tblr-secondary);
  font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
}
.fc-line > .v { color:var(--tblr-body-color); min-width:0; overflow-wrap:anywhere; }

/*
  ── A CARD HEADER ON A PHONE ────────────────────────────────────────────────────

  A Tabler card header is one flex row: the title, then .card-actions pushed right by an
  auto margin. On a desktop that is the whole story. On a phone it was the reason the panel
  ran off the side of the screen, and the mechanism is worth writing down because it looks
  like a table problem and is not.

  A selection bar is six or seven buttons and a link picker — about 420px of controls that
  cannot shrink, because a button is as wide as its label. A flex item will not go below its
  own min-content, so the row stayed 420px wide, the CARD had to be at least that wide to
  hold it, and the page had to be at least as wide as the card. The screen is 393px. So the
  page itself scrolled sideways, the card's right-hand edge sat off it, and the buttons at
  that end — Export CSV, Clear, Move Loads, Pull data — were somewhere past the glass. The
  tables were scrolling correctly inside their own boxes all along.

  Below sm the actions therefore take a row of their own, full width, and lay out as
  justified text: every control grows to share the line it lands on, so each row is flush
  against both edges of the card and a lone control at the end takes the width rather than
  leaving a gap. A link picker takes a whole line on its own — it holds a field and a bay
  and a variety, which is a sentence, not a word.

  THE WRAPPERS DISSOLVE. Several of these bars are a <span> or a <div> around a group of
  buttons — the bulk bars, the Operations Center pair, the inbox's assign group — and a
  wrapper that stays a box is one unshrinkable 420px item again, just one level down.
  `display: contents` drops the box and hands its buttons to the row. Never with
  !important where the wrapper is hidden by .d-none, and `:not(.d-none)` where the wrapper
  carries a d-flex utility, so a bar that is supposed to be absent still is.
*/
@media (max-width: 575.98px) {
  .card-header { flex-wrap: wrap !important; row-gap: .5rem; }
  /* auto, not 100%: a small icon button beside the title — the load-order switch on the
     working sheet — still belongs on the title's line. Only the actions take a row. */
  .card-header > .card-title { flex: 1 1 auto; min-width: 0; margin-right: 0; }

  /*
    :not(.d-none) IS LOAD-BEARING, and it was learned the hard way on the page.

    A bar that is absent until it has something to act on — the working sheet's, the grades
    page's — is hidden by .d-none, which is display:none with !important. This rule is
    display:flex with !important too, and it carries two classes to .d-none's one, so it won
    the cascade and put an empty selection bar under the heading of every card that has one.
    The same trap is set for the wrappers below, by the id selector.
  */
  /*
    max-width, NOT width — on the user's call: a bar stays on the title's line for as long as
    it fits there, and takes a row of its own only when it genuinely cannot.

    width:100% claimed the row whatever was in it, so "+ New Link" beside a two-word heading
    dropped under it on a screen with room for both. The header wraps, so an actions group
    too wide for what is left of the line moves down as a block by itself — which is the
    same rule, applied when it is actually true.
  */
  /*
    HELD RIGHT, not left — the same call as the md block above, and the auto margin Tabler
    puts on .card-actions is what does it, so the override that used to zero it has gone.
  */
  .card-header .card-actions:not(.d-none) {
    max-width: 100%;
    min-width: 0;
    display: flex !important;
    flex-wrap: wrap !important;
    align-items: stretch;
    justify-content: flex-end;
    gap: .5rem;
  }

  /* See above. The three wrappers this panel has, named rather than matched by shape:
     display:contents on the wrong element turns a button into loose text. */
  .card-header .card-actions .bulkbar.on,
  .card-header .card-actions #ib_bulk:not(.d-none),
  .card-header .card-actions .jd-only:not(.d-none) { display: contents !important; }

  /*
    A BUTTON KEEPS ITS OWN WIDTH AND WRAPS, like the ones in Browse's bulk bar.

    These used to be justified — `flex: 1 1 auto`, every control growing to share the line it
    landed on — so that a row was flush against both edges of the card. What that actually
    does to a bar holding ONE button is stretch it across the whole card: Refresh on the Sheet
    Inbox, Recall Sheet on Start a Sheet. A button the width of the screen reads as the
    primary action of the page rather than a small thing in a corner, and it is the one thing
    on that header nobody was asking to be bigger.

    So they neither grow nor shrink — `flex: none`, set once at the top of this file for every
    width, because a button squashed below its label is the same fault as a button stretched
    past it. Only the margins are this breakpoint's business: the utilities that spaced these
    out in a nowrap row would be a ragged gap inside a gapped one.
  */
  .card-header .card-actions .btn { margin: 0 !important; }
  /*
    A GHOST BUTTON GETS AN EDGE HERE, and only here.

    Ghost means no border and no fill: the quiet end of the bar, right where Clear and
    Export CSV belong when they sit at the end of a row of solid ones. Stretched to a share
    of a phone's line and stacked under the others, the same button is a word in the middle
    of an empty cell with nothing to say it can be pressed — the row reads as two buttons
    and a caption. The hairline is the card's own border colour, so the bar squares off
    without any of them starting to compete with the solid buttons above.
  */
  .card-header .card-actions .btn[class*="btn-ghost"] { border-color: var(--tblr-border-color); }
  /*
    THE PICKER SHRINKS TO A FLOOR AND SHARES ITS LINE.

    It used to take a line of its own, whatever else was on the bar — `flex: 1 1 100%`, with
    the element's own min and max widths overridden away. Two controls that could have sat
    side by side became two rows, and the header grew by the height of a button for nothing.

    9rem is the floor, which is the width the inbox's own picker has always declared for
    itself, and it is about as narrow as a link reads at — the number, the field and the bay
    are still there. Below that it is not a shrunken dropdown, it is a box with a chevron in
    it. It takes what is left of a line above the floor and drops to the next line below it.
  */
  .card-header .card-actions .form-select,
  .card-header .card-actions .form-control {
    flex: 1 1 9rem;
    width: auto !important;
    min-width: 9rem;
    max-width: 100%;
    margin: 0 !important;
  }
  /*
    "3 selected", "1 of 40 sheets" — it USED TO TAKE THE WHOLE LINE, as a caption for the row
    beneath it. It is four words; a row of its own for four words is the single biggest piece
    of height on a narrow header, and it bought nothing that the same words at the head of the
    bar do not. Inline now, and never broken across two lines of its own.
  */
  .card-header .card-actions b,
  .card-header .card-actions span.small { flex: 0 0 auto; white-space: nowrap; margin: 0 !important; }
  /* Most of those captions are empty most of the time, and an empty item still takes a
     gap — which reads as the bar having lost its top line. */
  .card-header .card-actions b:empty,
  .card-header .card-actions span.small:empty { display: none; }

  /*
    THE ZONE STRIP, ON ONE LINE IF IT WILL GO.

    Four tabs at desktop padding wrap three-and-one, which reads as a row that broke rather
    than a set of four. They share the width instead — the same justified rule as the bar
    above, and the strip squares off with the card beneath it.
  */
  .hl-tabs { gap: .4rem; }
  .hl-tab { flex: 1 1 auto; padding-left: .5rem; padding-right: .5rem; text-align: center; }
}
