/* ── FIELD APP ────────────────────────────────────────────────────────────
   Loaded ONLY by /field, after every other stylesheet. Everything here is
   additive: the tokens, buttons, forms, cards and modals all come from the
   shared design system, so the field app cannot drift into looking like a
   different product.

   Scope new rules under .field-app (set on <body>) whenever a selector could
   match something the desktop also uses. Read DESIGN_SYSTEM.md first — no raw
   hex, use the tokens.
   ───────────────────────────────────────────────────────────────────────── */

/* ── LAYOUT ─────────────────────────────────────────────────────────────── */

/* The desktop's .main padding is sized for a mouse and a wide screen; on a
   phone it costs a fifth of the usable width. */
.field-app .main {
  padding: var(--space-4);
  /* Clear the fixed tab bar AND, on an iPhone, the home indicator beneath it.
     Without this the last card is unreachable — it sits under the bar. */
  padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--space-4));
}

/* responsive.css hides #user-name below 640px because the desktop header is
   competing with the "Viewing as" switcher and the notification bell. The field
   header carries neither, so there is room — and "who is this phone signed in
   as" matters more on a shared yard handset than on someone's own desktop.
   Outranks the id selector via the body class. */
.field-app #user-name { display: inline; }

.field-app .header {
  /* The status bar is drawn over the app in standalone mode
     (apple-mobile-web-app-status-bar-style: black-translucent), so the header
     has to pay for its own clearance or the logo sits under the clock. */
  height: calc(var(--header-h) + env(safe-area-inset-top));
  padding-top: env(safe-area-inset-top);
}

:root { --tabbar-h: 56px; }

/* ── TAB BAR ────────────────────────────────────────────────────────────── */

.tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 40;
  display: flex;
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
}

/* A tab carries .nav-item so applyPermissions() — shared verbatim with /app —
   can show and hide it by the same selector the sidebar uses. That means it
   also inherits the sidebar's LOOK: base.css gives .nav-item a margin, pill
   padding, a rounded corner, and .nav-item.active a tinted background and a
   3px red left border. In a five-across bar that reads as one tab having been
   dragged out of place. Undo it here rather than dropping the class. */
.tabbar .tab {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  margin: 0;
  padding: 0;
  border-radius: 0;
  border-left: 0;
  background: none;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  cursor: pointer;
  color: var(--color-text-muted);
  /* The tab bar is the one control people use in gloves, at arm's length, on a
     moving forklift. Whole-height targets, no padding games. */
  min-height: var(--tabbar-h);
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* Same specificity fight as above: .nav-item:hover and .nav-item.active both
   set a background, and both must lose here. */
.tabbar .tab:hover { background: none; color: var(--color-text-muted); }

/* The .tabbar .tab block above sets -webkit-tap-highlight-color: transparent,
   throwing away the ONLY press feedback a touch device gives for free — and
   nothing replaced it. This is the control that block describes as used in
   gloves, at arm's length, on a moving forklift, so it is the last one that
   should feel dead.
   A tint rather than a scale: the tab is a full-height bar segment, and
   shrinking it would fight the "whole-height target" it is built to be. */
.tabbar .tab:active { background: var(--color-surface-hover); }
.tabbar .tab.active,
.tabbar .tab.active:hover {
  background: none;
  border-left: 0;
  color: var(--color-primary);
  font-weight: var(--weight-semibold);
}

/* A bar rather than a background fill: with five tabs, a filled active state
   reads as a pressed button and people tap it again expecting something. */
.tabbar .tab.active::before {
  content: "";
  position: absolute;
  top: 0;
  width: 28px;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--color-primary);
}
.tabbar .tab { position: relative; }

.tab-icon  { font-size: 19px; line-height: 1; }
.tab-label { font-size: 11px; font-weight: var(--weight-bold); letter-spacing: .01em; }

/* ── BOTTOM BANNERS: "ADD TO HOME SCREEN" AND "A NEW VERSION IS READY" ───── */

/* Sits directly on top of the tab bar rather than over the content, so it never
   covers a scan button or a result someone is reading. It pushes nothing: the
   .main padding below already reserves the tab bar's height, and the banner
   overlaying the last few pixels of a scroll area is preferable to the whole
   page reflowing the moment Chrome decides to fire beforeinstallprompt.

   One block for both banners, not two copies: they are never on screen at the
   same time — showUpdateBanner() hides the install offer and
   maybeShowInstallBanner() yields to the update banner — so any difference
   between them would only ever show up as one of them sitting wrong. Colour is
   the deliberate exception, overridden just below. */
.install-banner,
.update-banner {
  position: fixed;
  left: 0; right: 0;
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  z-index: 45;                      /* above the tab bar (40), below modals */
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  box-shadow: 0 -6px 16px rgba(0, 0, 0, .08);
}

/* [hidden] is only `display: none` in the UA stylesheet, so any rule that sets
   a display wins over it. All three of these carry one — the banners from the
   rule above, the button from .btn — and without these the banner shows when
   it should not, and iOS gets an Install button that cannot install. */
.install-banner[hidden],
.update-banner[hidden] { display: none; }
.install-actions .btn[hidden] { display: none; }

/* The update banner alone gets warn colours. Ignoring the install offer costs
   nothing; ignoring an update left a yard phone on a three-week-old build
   hitting a bug the server had already fixed — on a quiet grey banner, that is
   what happened. Same bold-tint-plus-warn-text pairing as the admin badge
   (--color-access-admin-*), so contrast is already proven. Layout stays in the
   shared block above. */
.update-banner {
  background: var(--color-warn-bg-bold);
  border-top: 2px solid var(--color-warn);
}
.update-banner .install-text strong,
.update-banner .install-text span { color: var(--color-warn-text); }

/* Chrome's Desktop site notice (field/945-desktop-site.js). It only ever shows
   on a page Chrome has laid out ~980px wide and shrunk onto a phone, so every
   size here is in vw: px would render as small as the rest of the screen, and
   the one thing this banner must be is readable. Warn colours, like the update
   banner, because the app is unusable until the switch is turned off. */
.desktop-site-banner {
  position: fixed;
  left: 0; right: 0; top: 0;
  z-index: 45;                      /* same layer as the banners above */
  display: flex;
  align-items: center;
  gap: 3vw;
  padding: 3vw 4vw;
  background: var(--color-warn-bg-bold);
  border-bottom: 0.5vw solid var(--color-warn);
  box-shadow: 0 6px 16px rgba(0, 0, 0, .08);
}
.desktop-site-banner[hidden] { display: none; }
.desktop-site-banner .install-text strong { font-size: 4vw; color: var(--color-warn-text); }
.desktop-site-banner .install-text span   { font-size: 3.4vw; color: var(--color-warn-text); }
.desktop-site-banner .btn { font-size: 3.6vw; padding: 2vw 4vw; }

.install-text   { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.install-text strong { font-size: var(--text-sm); color: var(--color-text-strong); }
.install-text span   { font-size: var(--text-xs); color: var(--color-text-muted); }

/* Buttons never shrink — on a 320px phone the hint text gives up width first,
   because a wrapped "Not now" that pushes Install off-screen is unusable. */
.install-actions { display: flex; gap: var(--space-3); flex: 0 0 auto; }

/* The login screen's own install offer is NOT here. It is the same component on
   /app and /field now, so it lives in components.css with the rest of the login
   card — this file may only add what is genuinely field-shaped. */

/* ── SEARCH ─────────────────────────────────────────────────────────────── */

.field-search { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-3); }

.field-search input {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  /* 16px exactly. Anything smaller and iOS Safari zooms the page on focus, which
     on this screen means the tab bar scrolls off and cannot be got back to
     without pinching. responsive.css makes the same fix for the same reason. */
  font-size: 16px;
  background: var(--color-surface);
}
.field-search input:focus { outline: 2px solid var(--color-primary); outline-offset: -1px; }

/* The store filter: the phone version of the desktop's "📍 Stores (All)"
   dropdown. Its own container, NOT .field-search (that class styles every
   descendant input to width:100% for the search boxes) — and it anchors the
   absolute .dropdown-panel. Rows are sized for a thumb, not a mouse: the
   yard could not see the desk-sized checkbox at all before tapping
   (reported 2026-09-03). Scoped under .field-stores so the desktop's own
   dropdown rows keep their size. */
.field-stores { position: relative; display: inline-block; margin-bottom: var(--space-3); }
.field-stores .dropdown-option-plain {
  display: flex; align-items: center; gap: var(--space-3);
  font-size: 16px; padding: var(--space-2) var(--space-2);
}
.field-stores .dropdown-option-plain input { width: 22px; height: 22px; }

.field-meta { font-size: var(--text-xs); color: var(--color-text-muted); margin-bottom: var(--space-3); min-height: 1em; }

/* ── RESULT CARDS ───────────────────────────────────────────────────────── */

.field-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-3);
}

/* G1 one-screen flow: the single stop the driver is working right now, lifted
   out of the list so the phone answers "where am I" at a glance. Emphasis only
   — every control inside the card is unchanged. */
.field-card.field-focus {
  border-left: 3px solid var(--color-primary);
  background: var(--color-primary-bg);
  padding: var(--space-5);
}

/* Delivered / not-delivered stops, folded under the active + upcoming ones so
   a long run stays short. Native <details>; the summary is a quiet muted row.
   Same "done stays correctable" spirit as .field-card.is-done below. */
.field-done-fold { margin-bottom: var(--space-3); }
.field-done-fold > summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  padding: var(--space-2) 0;
}

.field-card-top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }

.field-rnum  { font-weight: var(--weight-bold); color: var(--color-primary); font-size: var(--text-base); }
.field-price { font-weight: var(--weight-bold); color: var(--color-text-strong); font-size: var(--text-base); }

/* How the order leaves the yard, tagged beside its number (TRUCK1, Local
   Pickup, FedExSmartPost). `margin-right: auto` is what keeps the price at the
   far edge: it takes the slack the top row's space-between used to hold, so
   this sits against the number as one "#1234 TRUCK2" group.
   The value is free text from Powerlink, up to 20 characters, so it is capped
   and clipped rather than allowed to shove the price off a 320px screen —
   shrinking before the price does, never after. */
.field-via {
  flex: 0 1 auto;
  margin-right: auto;
  min-width: 0;
  max-width: 12ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 1px var(--space-2);
  border: 1px solid var(--color-primary-border);
  border-radius: var(--radius-sm);
  background: var(--color-primary-bg);
  color: var(--color-primary-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

.field-card-title { font-weight: var(--weight-bold); color: var(--color-text-strong); margin-top: 2px; }
.field-card-sub   { font-size: var(--text-sm); color: var(--color-text-muted); margin-top: 1px; }

.field-card-facts { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); margin-top: var(--space-3); }
.field-fact   { font-size: var(--text-xs); color: var(--color-text-muted); }
.field-fact b { color: var(--color-text-strong); font-weight: var(--weight-bold); margin-right: 3px; }

.field-more { display: flex; justify-content: center; padding: var(--space-2) 0 var(--space-6); }

/* ── PULLER (My Pulls) ───────────────────────────────────────────────────────
   The read-only cards above have no controls; a pull card does. Same field-card
   family, plus a condition note and a pull toggle sized for a thumb in a glove.
   A pulled card is tinted green so a puller glancing down the list sees at once
   what is done and what is left. */
.field-card.is-pulled { border-color: var(--color-success-text); background: var(--color-success-bg); }
.field-card-status { font-weight: var(--weight-bold); font-size: var(--text-sm); white-space: nowrap; }
.field-card-status.assigned { color: var(--color-text-muted); }
.field-card-status.pulled   { color: var(--color-success-text); }
.field-note {
  width: 100%;
  margin-top: var(--space-3);
  padding: 10px 12px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  /* 16px exactly — same iOS no-zoom-on-focus reason as .field-search input. */
  font-size: 16px;
  background: var(--color-surface);
}
.field-card-actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
.field-card-actions .btn { flex: 1 1 auto; }

/* ── LOADING / EMPTY / NOTICE ───────────────────────────────────────────── */

.field-card.skel { height: 96px; background: var(--color-border); opacity: .45; animation: field-pulse 1.2s ease-in-out infinite; }
@keyframes field-pulse { 0%,100% { opacity: .25 } 50% { opacity: .5 } }

/* Someone who has set "reduce motion" should not get a pulsing block. */
@media (prefers-reduced-motion: reduce) {
  .field-card.skel { animation: none; }
}

.field-empty { text-align: center; color: var(--color-text-muted); font-size: var(--text-sm); padding: var(--space-9) var(--space-4); }
.field-empty.error { color: var(--color-danger, #dc2626); }

.field-notice { text-align: center; color: var(--color-text-muted); font-size: var(--text-base); line-height: 1.5; padding: var(--space-9) var(--space-5); }

/* Puller picking checklist (field My Pulls) */
.field-checklist { margin-top: var(--space-3); border-top: 1px solid var(--color-border); padding-top: var(--space-2); display: flex; flex-direction: column; gap: var(--space-2); }
.field-ck-title { font-size: var(--text-sm); color: var(--color-text-muted); }
/* The two required questions, as boxes side by side on the phone. */
.field-ck-boxes { display: flex; gap: var(--space-2); }
.field-ck-box { flex: 1; min-width: 0; border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-2); background: var(--color-surface); }
.field-ck-box.needs-answer { border-color: var(--color-danger); background: var(--color-danger-bg); }
.field-ck-q { font-size: var(--text-sm); font-weight: var(--weight-bold); margin-bottom: var(--space-2); }
.field-ck-opts { display: flex; gap: var(--space-1); }
.field-ck-opt { flex: 1; min-height: 44px; border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-surface); color: var(--color-text); font-size: var(--text-sm); font-weight: var(--weight-bold); cursor: pointer; }
.field-ck-opt:active { transform: scale(.98); transition: transform 90ms ease-out; }
@media (prefers-reduced-motion: reduce) {
  /* AFTER the rule above, not before: identical specificity means the later
     declaration wins, so an override placed higher in the file does nothing.
     Not "no feedback" either — the movement becomes a tint. */
  .field-ck-opt:active { transform: none; background: var(--color-surface-hover); }
}
.field-ck-opt.is-on { background: var(--color-primary-bg-bold); color: var(--color-primary-text-bold); border-color: var(--color-primary-border-bold); }
/* Staged-photo tray (puller + driver). */
.field-ph-strip { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.field-ph-strip:empty { display: none; }
.field-ph-thumb { position: relative; width: 56px; height: 56px; }
.field-ph-thumb img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--radius-md); border: 1px solid var(--color-border); }
.field-ph-x { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; border-radius: 50%; border: none; background: var(--color-danger); color: var(--color-surface); font-size: 13px; line-height: 1; cursor: pointer; }
.field-ph-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.field-ph-count { font-size: var(--text-sm); color: var(--color-text-muted); }
.field-ph-title { font-size: var(--text-sm); font-weight: var(--weight-bold); margin-top: var(--space-2); }
.field-ph-thumb img { cursor: pointer; }

/* Driver delivery capture (field My Route): WO notes, receiver + signature pad */
.field-status-fail { color: var(--color-danger); }
.field-wo-notes { font-size: var(--text-sm); background: var(--color-primary-bg); border: 1px solid var(--color-primary-border); border-radius: var(--radius-md); padding: var(--space-2); margin-top: var(--space-2); }
/* What we wrote back about a feature request, on the phone's own list. Same
   shape as .field-wo-notes above — a tinted box for text that came from
   somewhere else — but its own name because it is a different somewhere: that
   one is the customer's work-order note, this one is our answer to the person
   holding the phone. It is the most important line on a settled card, so it is
   a box rather than another muted .field-card-sub. */
.field-answer { font-size: var(--text-sm); background: var(--color-surface-sunken); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-2); margin-top: var(--space-3); }
.field-lbl { display: block; font-size: var(--text-sm); color: var(--color-text-muted); margin-top: var(--space-2); }
.field-sig { margin-top: var(--space-2); }
.field-sig-hd { display: flex; align-items: center; justify-content: space-between; font-size: var(--text-sm); color: var(--color-text-muted); margin-bottom: var(--space-1); }
.field-sig-pad { width: 100%; height: 140px; border: 1px dashed var(--color-border); border-radius: var(--radius-md); background: var(--color-surface); touch-action: none; }

/* Orders / Delivery: tap a card to expand its parts (prices, qty) in place. */
.field-doc-head { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.field-doc-head:active { opacity: .7; transition: opacity 90ms ease-out; }
/* A Tab stop since js/050m-key-activation.js reached /field. */
.field-doc-head:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
.field-chev { display: inline-block; margin-right: 4px; color: var(--color-text-muted); transition: transform 120ms ease; }
.field-doc-head.is-open .field-chev { transform: rotate(90deg); }
.field-items { margin-top: var(--space-3); border-top: 1px solid var(--color-border); padding-top: var(--space-2); display: flex; flex-direction: column; gap: var(--space-2); }
.field-li { display: flex; align-items: flex-start; gap: var(--space-2); }
.field-li-body { flex: 1 1 auto; min-width: 0; }
.field-li-top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.field-li-name { flex: 1 1 auto; min-width: 0; font-weight: var(--weight-bold); color: var(--color-text-strong); }
.field-li-total { flex: 0 0 auto; font-weight: var(--weight-bold); color: var(--color-text-strong); white-space: nowrap; }
.field-li-foot { display: flex; justify-content: space-between; border-top: 1px solid var(--color-border); margin-top: var(--space-1); padding-top: var(--space-2); font-size: var(--text-sm); color: var(--color-text-muted); }
.field-pick-count { font-weight: var(--weight-bold); color: var(--color-text-strong); }

/* The picker's tick box beside each part — a glove-sized tap target that turns
   green when the part has been pulled off the shelf. Shared across devices via
   /workorders|delivery-tickets/{id}/picks, so it comes up already ticked. */
.field-pick { flex: 0 0 auto; min-width: 44px; min-height: 44px; display: flex; align-items: center; justify-content: center; padding: 2px 0 0; margin: 0; background: none; border: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.field-pick:active { transform: scale(.9); transition: transform 90ms ease-out; }
/* An UNTICKED box is the one a picker has to find, and it was the harder of the
   two states to see (reported 2026-09-08, Orders and Delivery): the ticked box
   is a solid green block, while this one was a 24px square of --color-border —
   a card hairline — outlining white on white. Same box, but it read as a speck
   beside the part name. So it is 28px now, and outlined in --color-border-strong
   (the input-outline weight) rather than the hairline. The ticked rule below
   only overrides the two colours, so both states stay exactly the same size.
   28px sits inside the 44px target above with room to spare; the target is the
   button, not this box. */
.field-pick-box { width: 28px; height: 28px; border: 2px solid var(--color-border-strong); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: var(--weight-bold); line-height: 1; color: var(--color-surface); }
.field-li.is-picked .field-pick-box { background: var(--color-success-text); border-color: var(--color-success-text); }
.field-li.is-picked .field-li-name { color: var(--color-text-muted); text-decoration: line-through; }

/* ── PAGER + FINISHED ORDERS ──────────────────────────────────────────────
   Both lists page 40 at a time (FIELD_PAGE_SIZE in field/910-screens.js). The
   pager renders only when there IS a second page, so a short list is unchanged.
   Rendered by _fieldPager(). */
.field-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-4) var(--space-6);
}

/* An order whose every part is ticked. It has already moved to the bottom of
   the list (_fieldSinkIfDone); this is what says WHY it moved, so a card at the
   bottom does not read as one the picker simply has not reached yet.
   Deliberately faded rather than hidden — a mis-tick has to stay correctable,
   and on a phone you cannot un-tick what is not on screen. */
.field-card.is-done {
  opacity: .55;
}
