/* ===================================================================================
   Gusta Warehouse — design system
   -----------------------------------------------------------------------------------
   Self-hosted and dependency-free, replacing the Tailwind Play CDN. Three reasons that
   matters here more than it would elsewhere:

     1. Offline. The service worker can only precache same-origin files. With the CDN,
        a phone that lost wifi rendered the app shell completely unstyled.
     2. Speed. The Play CDN ships a compiler and rebuilds the sheet on every page load,
        on warehouse handsets, over warehouse wifi.
     3. CSP. One less script origin to allow.

   Everything below is scoped to plain classes, so pages stay readable as HTML.
   Brand tokens are shared with gusta-discount-cards / gusta-repack-app.
   =================================================================================== */

:root {
  /* --- Brand ------------------------------------------------------------------- */
  --cream:        #FBF3EC;
  --cream-deep:   #F3E8DC;
  --panel:        #FFFFFF;
  --panel-sunk:   #FDFAF7;
  --apricot:      #E4A884;
  --terracotta:   #A05828;
  --terracotta-d: #8F4B20;
  --charcoal:     #58585A;

  /* Ink is a shade darker than the sibling apps': warehouse phones get read at arm's
     length under mixed lighting, so body copy runs at ~13:1 against the cream. */
  --ink:          #2C2C33;
  --ink-soft:     #4A4A54;
  --muted:        #6B6B75;
  --line:         #E6DCD2;
  --line-strong:  #D8C9B9;

  /* --- Semantic ---------------------------------------------------------------- */
  --ok:        #3B6D11;  --ok-soft:   #E7F0DC;  --ok-ink:   #2F570D;
  --warn:      #854F0B;  --warn-soft: #FBEEDA;  --warn-ink: #6B3F08;
  --bad:       #A32D2D;  --bad-soft:  #FAE7E7;  --bad-ink:  #791F1F;
  --info:      #0C447C;  --info-soft: #E6F1FB;  --info-ink: #093358;

  /* --- Module accent (overridden per page by [data-module]) --------------------- */
  --accent:      var(--terracotta);
  --accent-d:    var(--terracotta-d);
  --accent-on:   #FFFFFF;
  --accent-soft: #F7E9DD;
  --accent-sink: #7A4220;

  /* --- Type -------------------------------------------------------------------- */
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", "Helvetica Neue", sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* 13px is the floor. The previous build used 10–12px for bin codes and counts, which
     is the exact text someone reads while holding a box. */
  --fs-xs:   0.8125rem;
  --fs-sm:   0.875rem;
  --fs-base: 1rem;
  --fs-md:   1.0625rem;
  --fs-lg:   1.25rem;
  --fs-xl:   1.5rem;
  --fs-2xl:  2rem;

  /* --- Metrics ----------------------------------------------------------------- */
  --r-sm: 8px;  --r: 12px;  --r-lg: 16px;  --r-xl: 20px;  --r-pill: 999px;
  --tap: 48px;        /* minimum interactive height */
  --tap-lg: 56px;     /* primary / gloved-hand actions */
  --gap: 12px;
  --pad: 16px;
  --shell: 34rem;     /* phone-shaped reading column */
  --shell-wide: 68rem;

  --shadow-sm: 0 1px 2px rgba(58, 42, 28, .06), 0 1px 3px rgba(58, 42, 28, .05);
  --shadow:    0 2px 4px rgba(58, 42, 28, .05), 0 6px 16px rgba(58, 42, 28, .07);
  --shadow-lg: 0 10px 32px rgba(58, 42, 28, .14);

  --safe-b: env(safe-area-inset-bottom, 0px);
  color-scheme: light;
}

/* Module identity. Staff learn the colour of a screen before they read its title, so the
   four workflows stay distinguishable — but all four now sit in the same warm family
   rather than the stock indigo/amber/emerald/slate they had before. */
[data-module="receiving"] { --accent:#A05828; --accent-d:#8F4B20; --accent-soft:#F7E9DD; --accent-sink:#7A4220; }
[data-module="inbound"]   { --accent:#55508C; --accent-d:#48437A; --accent-soft:#EAE8F4; --accent-sink:#443F76; }
[data-module="picking"]   { --accent:#3B6D11; --accent-d:#315B0D; --accent-soft:#E7F0DC; --accent-sink:#2F570D; }
[data-module="admin"]     { --accent:#58585A; --accent-d:#47474A; --accent-soft:#ECECEE; --accent-sink:#3F3F44; }

/* =========================== reset =========================== */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--cream);
  color: var(--ink);
  font: 400 var(--fs-base)/1.55 var(--font);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-wrap: anywhere;      /* long SKUs never push the layout sideways */
}

h1, h2, h3 { margin: 0; line-height: 1.25; font-weight: 700; letter-spacing: -0.01em; }
h1 { font-size: var(--fs-lg); }
h2 { font-size: var(--fs-md); }
h3 { font-size: var(--fs-base); }
p  { margin: 0; }
img { max-width: 100%; display: block; }
a  { color: var(--accent-sink); }

:where(button, a, input, select, textarea, summary):focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* =========================== layout =========================== */

.app-bar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--accent);
  color: var(--accent-on);
  box-shadow: var(--shadow-sm);
  padding-top: env(safe-area-inset-top, 0px);
}
.app-bar__inner {
  max-width: var(--shell-wide);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px var(--pad);
  min-height: var(--tap);
}
.app-bar__back {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  margin-left: -8px;
  border-radius: var(--r-pill);
  color: inherit;
  text-decoration: none;
  font-size: var(--fs-lg);
  line-height: 1;
  background: rgba(255,255,255,.14);
}
.app-bar__back:hover { background: rgba(255,255,255,.24); }
.app-bar__titles { min-width: 0; flex: 1; }
.app-bar__titles h1 { font-size: var(--fs-md); font-weight: 700; }
.app-bar__sub {
  font-size: var(--fs-xs);
  color: rgba(255,255,255,.82);
  margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-bar__actions { flex: none; display: flex; align-items: center; gap: 8px; }

.shell {
  max-width: var(--shell);
  margin: 0 auto;
  padding: var(--pad);
  padding-bottom: calc(var(--pad) + var(--safe-b));
}
.shell--wide { max-width: var(--shell-wide); }

/* Pages with a fixed action bar reserve room for it. */
.has-actionbar { padding-bottom: calc(112px + var(--safe-b)); }

/* Vertical rhythm. `stack` is a grid with an explicit minmax(0,1fr) column — an `auto`
   track sizes to its items' max-content, so a single `.truncate` child (white-space:
   nowrap) used to blow the whole list out past the card it lived in. Pinning the column
   makes that failure impossible anywhere in the app. */
.stack        { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap); }
.stack--tight { gap: 8px; }
.stack--loose { gap: 20px; }

.row { display: flex; align-items: center; gap: var(--gap); min-width: 0; }
.row--start { align-items: flex-start; }
.row--between { justify-content: space-between; }
.row__grow { flex: 1; min-width: 0; }
.row__fixed { flex: none; }

.cols-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap); }
.cols-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; align-items: end; }
.cols-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }

/* =========================== surfaces =========================== */

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--pad);
  box-shadow: var(--shadow-sm);
}
.card--flat { box-shadow: none; }
.card--sunk { background: var(--panel-sunk); box-shadow: none; }
.card--ok   { border-color: #B9CFA0; background: #F4F9EC; }
.card--warn { border-color: #E4C48C; background: #FEF8EE; }
.card--bad  { border-color: #E2AFAF; background: #FDF2F2; }

.card__title {
  font-size: var(--fs-md);
  font-weight: 700;
  display: flex; align-items: center; gap: 8px;
}
.card__note {
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.5;
}

.section-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.hr { height: 1px; background: var(--line); border: 0; margin: 4px 0; }

/* =========================== type helpers =========================== */

.t-xs   { font-size: var(--fs-xs); }
.t-sm   { font-size: var(--fs-sm); }
.t-md   { font-size: var(--fs-md); }
.t-lg   { font-size: var(--fs-lg); }
.t-xl   { font-size: var(--fs-xl); }
.t-2xl  { font-size: var(--fs-2xl); }
.t-bold { font-weight: 700; }
.t-black{ font-weight: 800; letter-spacing: -.015em; }
.t-muted{ color: var(--muted); }
.t-soft { color: var(--ink-soft); }
.t-center { text-align: center; }
.t-right  { text-align: right; }
.t-warn { color: var(--warn); }
.t-bad  { color: var(--bad); }
.t-ok   { color: var(--ok); }

/* Codes are the thing people squint at, so they get real size and tracking. */
.mono {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: .02em;
  color: var(--ink-soft);
}

.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Product titles get two full lines before clipping — one truncated line loses the size
   or colour that distinguishes two otherwise-identical variants. */
.clamp-2 {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
}

.hidden { display: none !important; }

/* =========================== buttons =========================== */

.btn {
  appearance: none;
  font: 600 var(--fs-base)/1.2 var(--font);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 12px 18px;
  border: 1px solid transparent;
  border-radius: var(--r);
  background: var(--accent);
  color: var(--accent-on);
  cursor: pointer;
  text-decoration: none;
  text-align: center;
  transition: background .14s ease, transform .06s ease, border-color .14s ease;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.btn:hover  { background: var(--accent-d); }
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[aria-disabled="true"] {
  background: var(--cream-deep);
  color: #9A9088;
  cursor: default;
  transform: none;
  border-color: var(--line);
}

.btn--lg    { min-height: var(--tap-lg); font-size: var(--fs-md); font-weight: 700; border-radius: var(--r-lg); }
.btn--sm    { min-height: 38px; padding: 8px 12px; font-size: var(--fs-sm); border-radius: var(--r-sm); }
.btn--block { display: flex; width: 100%; }

.btn--quiet  { background: var(--panel); color: var(--ink); border-color: var(--line-strong); }
.btn--quiet:hover { background: var(--panel-sunk); border-color: var(--accent); }
.btn--ghost  { background: transparent; color: var(--muted); border-color: var(--line); }
.btn--ghost:hover { background: var(--panel); color: var(--ink); }
.btn--soft   { background: var(--accent-soft); color: var(--accent-sink); }
.btn--soft:hover { background: var(--cream-deep); }
.btn--go     { background: var(--ok); color: #fff; }
.btn--go:hover { background: var(--ok-ink); }
.btn--danger { background: var(--bad); color: #fff; }
.btn--danger:hover { background: var(--bad-ink); }
.btn--warnsoft { background: var(--warn-soft); color: var(--warn-ink); border-color: #EBD3A8; }
.btn--warnsoft:hover { background: #F7E4C4; }
.btn--badsoft  { background: var(--bad-soft); color: var(--bad-ink); border-color: #EDC5C5; }
.btn--badsoft:hover { background: #F5D9D9; }
.btn--link {
  background: none; border: 0; color: var(--muted); min-height: 36px;
  padding: 6px 8px; font-size: var(--fs-sm); text-decoration: underline;
}
.btn--link:hover { background: none; color: var(--ink); }

/* Square steppers for quantity fields. */
.btn--step {
  width: var(--tap); min-width: var(--tap); min-height: var(--tap); padding: 0;
  font-size: var(--fs-lg); font-weight: 700;
  background: var(--cream-deep); color: var(--ink); flex: none;
}
.btn--step:hover { background: var(--line); }

/* =========================== segmented control =========================== */

.segmented {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 4px;
  padding: 4px;
  background: var(--cream-deep);
  border-radius: var(--r);
}
.segmented__btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font: 600 var(--fs-sm)/1.2 var(--font);
  min-height: 42px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  transition: background .14s ease, color .14s ease;
  -webkit-tap-highlight-color: transparent;
}
.segmented__btn[aria-selected="true"] {
  background: var(--panel);
  color: var(--accent-sink);
  box-shadow: var(--shadow-sm);
}

/* Four segments across a 375px handset broke "Transfers" into "Transfer / s". Opt-in wrap
   to a 2x2 grid on narrow screens; every tab stays visible and full-size. */
@media (max-width: 560px) {
  .segmented--wrap {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* =========================== forms =========================== */

.field { display: grid; gap: 6px; min-width: 0; }
.label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--muted);
}
.hint { font-size: var(--fs-xs); color: var(--muted); line-height: 1.45; }

.input, .select, .textarea {
  width: 100%;
  min-width: 0;
  font: 400 var(--fs-base)/1.4 var(--font);
  /* 16px minimum, or iOS Safari zooms the whole page on focus. */
  min-height: var(--tap);
  padding: 11px 13px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-radius: var(--r);
  transition: border-color .14s ease, box-shadow .14s ease;
}
.input::placeholder, .textarea::placeholder { color: #A8A29C; }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent);
  /* Fallback first: color-mix() needs a 2023+ browser and some shed handsets are older. */
  box-shadow: 0 0 0 3px rgba(160, 88, 40, .18);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.input--mono { font-family: var(--mono); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
.input--num  { text-align: center; font-weight: 700; font-size: var(--fs-md); }
.input--big  { font-size: var(--fs-xl); font-weight: 800; text-align: center; min-height: var(--tap-lg); }
.textarea { min-height: 140px; font-family: var(--mono); font-size: var(--fs-sm); line-height: 1.6; resize: vertical; }

/* Hide the spinner arrows — they are unhittable with a glove on and steppers exist. */
.input--num::-webkit-outer-spin-button,
.input--num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.input--num { -moz-appearance: textfield; }

/* type="search" draws its own clear button in WebKit/Blink, which sat next to ours — two
   ✕ controls in one field, the native one too small to hit reliably. Ours stays. */
.input[type="search"]::-webkit-search-cancel-button,
.input[type="search"]::-webkit-search-decoration,
.input[type="search"]::-webkit-search-results-button,
.input[type="search"]::-webkit-search-results-decoration {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}

.search {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}
.search .input { padding-left: 40px; padding-right: 40px; }
.search__icon {
  position: absolute; left: 13px;
  font-size: var(--fs-sm); color: var(--muted); pointer-events: none; line-height: 1;
}
.search__clear {
  position: absolute; right: 6px;
  width: 34px; height: 34px; border: 0; border-radius: var(--r-pill);
  background: var(--cream-deep); color: var(--ink-soft);
  font-size: var(--fs-sm); cursor: pointer; display: none;
  align-items: center; justify-content: center;
}
.search[data-filled="true"] .search__clear { display: flex; }
.search__clear:hover { background: var(--line); }

/* Live-search spinner sits where the clear button would be. */
.search[data-busy="true"] .search__clear { display: none; }
.search__spin {
  position: absolute; right: 14px; display: none;
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--line-strong); border-right-color: var(--accent);
  animation: gspin .7s linear infinite;
}
.search[data-busy="true"] .search__spin { display: block; }
@keyframes gspin { to { transform: rotate(360deg); } }

/* =========================== badges & chips =========================== */

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--fs-xs); font-weight: 700; line-height: 1.3;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--cream-deep); color: var(--ink-soft);
  white-space: nowrap;
}
.badge--ok   { background: var(--ok-soft);   color: var(--ok-ink); }
.badge--warn { background: var(--warn-soft); color: var(--warn-ink); }
.badge--bad  { background: var(--bad-soft);  color: var(--bad-ink); }
.badge--info { background: var(--info-soft); color: var(--info-ink); }
.badge--accent { background: var(--accent-soft); color: var(--accent-sink); }
/* Grid children stretch by default, which turns a pill into a full-width bar. */
.stack > .badge, .stack > .bin { justify-self: start; }

/* Bin codes. Deliberately the loudest small element on the page — it is the answer to
   "where do I walk", which is the whole point of the app. */
.bin {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--mono);
  font-size: var(--fs-sm); font-weight: 700; letter-spacing: .04em;
  padding: 5px 10px;
  border-radius: var(--r-sm);
  background: var(--cream-deep); color: var(--ink);
  white-space: nowrap;
}
.bin--retail   { background: var(--info-soft); color: var(--info-ink); }
.bin--primary  { background: var(--accent-soft); color: var(--accent-sink); }
.bin--overflow { background: var(--cream-deep); color: var(--ink-soft); }
.bin--none     { background: var(--warn-soft); color: var(--warn-ink); font-family: var(--font); letter-spacing: 0; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* Pick route: an ordered walk, first stop emphasised. */
.route { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 0; margin: 0; list-style: none; }
.route__step { display: flex; align-items: center; gap: 6px; }
.route__pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-sm); font-weight: 600;
  padding: 6px 10px; border-radius: var(--r-sm);
  background: var(--cream-deep); color: var(--ink-soft);
}
.route__pill code { font-family: var(--mono); font-weight: 700; letter-spacing: .04em; }
.route__step:first-child .route__pill { background: var(--accent); color: var(--accent-on); }
.route__arrow { color: var(--line-strong); font-weight: 700; }

/* =========================== media =========================== */

.thumb {
  flex: none;
  border-radius: var(--r);
  object-fit: cover;
  background: var(--cream-deep) center/28px no-repeat
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23C9BCAE' stroke-width='1.6'%3E%3Crect x='3' y='3' width='18' height='18' rx='3'/%3E%3Cpath d='m4 16 4.5-4.5 3.5 3.5 3-3L20 16'/%3E%3Ccircle cx='9' cy='8.5' r='1.4'/%3E%3C/svg%3E");
}
.thumb--sm { width: 44px; height: 44px; }
.thumb--md { width: 56px; height: 56px; }
.thumb--lg { width: 68px; height: 68px; }

/* =========================== tappable list rows =========================== */

.tile {
  display: block; width: 100%; text-align: left;
  font: inherit; color: inherit; text-decoration: none;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--pad);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: border-color .14s ease, background .14s ease, transform .06s ease;
  min-width: 0;
}
.tile:hover  { border-color: var(--accent); background: var(--panel-sunk); }
.tile:active { transform: translateY(1px); }

.tile--sunk { background: var(--panel-sunk); box-shadow: none; }
.tile--sunk:hover { background: var(--accent-soft); }

/* A tile is often ALSO a row or a stack. `.tile`, `.row` and `.stack` are all single-class
   selectors, so whichever is written last in this file would silently win the `display`
   declaration — which is how a batch row lost its flex layout and dropped its delete button
   onto its own line. Stating the combinations makes the layout role win regardless of order. */
.tile.row   { display: flex; }
.tile.stack { display: grid; grid-template-columns: minmax(0, 1fr); }

/* Home screen destinations. */
.nav-tile {
  display: flex; align-items: center; gap: 14px;
  padding: 18px var(--pad);
  border-radius: var(--r-lg);
  background: var(--accent); color: var(--accent-on);
  text-decoration: none;
  box-shadow: var(--shadow);
  transition: transform .1s ease, box-shadow .14s ease;
}
.nav-tile:hover  { box-shadow: var(--shadow-lg); }
.nav-tile:active { transform: translateY(1px); }
.nav-tile__icon {
  flex: none; width: 52px; height: 52px;
  display: grid; place-items: center;
  font-size: var(--fs-xl);
  background: rgba(255,255,255,.16);
  border-radius: var(--r);
}
/* These are spans (so the whole tile stays one link); without display:block the name and
   description ran together on a single line. */
.nav-tile__text { min-width: 0; flex: 1; }
.nav-tile__name { display: block; font-size: var(--fs-md); font-weight: 700; }
.nav-tile__desc { display: block; font-size: var(--fs-sm); color: rgba(255,255,255,.84); margin-top: 1px; }

/* =========================== states =========================== */

.empty {
  text-align: center;
  padding: 36px 20px;
  color: var(--muted);
  font-size: var(--fs-sm);
  background: var(--panel);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
}
.empty__icon { font-size: var(--fs-xl); display: block; margin-bottom: 8px; opacity: .7; }

.skeleton { display: grid; gap: 10px; }
.skeleton__bar {
  height: 14px; border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--cream-deep), #F1E4D6, var(--cream-deep));
  background-size: 200% 100%;
  animation: gshimmer 1.3s ease-in-out infinite;
}
.skeleton__bar:nth-child(2n) { width: 62%; }
@keyframes gshimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

.notice {
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.5;
  padding: 11px 14px;
  border-radius: var(--r);
  background: var(--cream-deep);
  color: var(--ink-soft);
}
.notice--ok   { background: var(--ok-soft);   color: var(--ok-ink); }
.notice--warn { background: var(--warn-soft); color: var(--warn-ink); }
.notice--bad  { background: var(--bad-soft);  color: var(--bad-ink); }
.notice--info { background: var(--info-soft); color: var(--info-ink); }

/* =========================== scanning =========================== */

/* "The gun is live" indicator. The keyboard-wedge scanner works with no visible UI, so
   staff had no way to tell a dead page from a dead scanner. */
.scan-live {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--fs-xs); color: var(--muted);
}
.scan-live__dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ok);
  animation: gpulse 2.2s ease-out infinite;
}
@keyframes gpulse {
  0%   { box-shadow: 0 0 0 0 rgba(59, 109, 17, .55); }
  70%  { box-shadow: 0 0 0 7px rgba(59, 109, 17, 0); }
  100% { box-shadow: 0 0 0 0 rgba(59, 109, 17, 0); }
}

.camera { position: relative; border-radius: var(--r); overflow: hidden; background: #14110E; }
.camera video { width: 100%; display: block; aspect-ratio: 4 / 3; object-fit: cover; }
/* Framing reticle — people aim at the middle of a rectangle, not at nothing. */
.camera__reticle {
  position: absolute; inset: 18% 12%;
  border: 2px solid rgba(255,255,255,.85);
  border-radius: var(--r);
  box-shadow: 0 0 0 100vmax rgba(0,0,0,.32);
  pointer-events: none;
}
.camera__hint {
  position: absolute; left: 0; right: 0; bottom: 0;
  text-align: center; font-size: var(--fs-xs); font-weight: 600;
  color: #fff; background: rgba(0,0,0,.5); padding: 6px;
  pointer-events: none;
}

/* =========================== toasts =========================== */

/* A stack, not one absolutely-positioned node per message — three toasts used to land
   on the same coordinates and overprint each other into mush. */
.toast-stack {
  position: fixed;
  left: 50%; transform: translateX(-50%);
  bottom: calc(96px + var(--safe-b));
  z-index: 70;
  display: flex; flex-direction: column-reverse; align-items: center; gap: 8px;
  width: min(92vw, var(--shell));
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  max-width: 100%;
  padding: 11px 16px;
  border-radius: var(--r);
  font-size: var(--fs-sm); font-weight: 600; line-height: 1.45;
  text-align: center;
  color: #fff;
  background: var(--ink);
  box-shadow: var(--shadow-lg);
  animation: gtoast-in .18s ease-out;
}
.toast--ok    { background: var(--ok); }
.toast--warn  { background: var(--warn); }
.toast--error { background: var(--bad); }
.toast--out   { animation: gtoast-out .18s ease-in forwards; }
@keyframes gtoast-in  { from { opacity: 0; transform: translateY(8px); } }
@keyframes gtoast-out { to   { opacity: 0; transform: translateY(6px); } }

/* =========================== status bar (offline / syncing) =========================== */

.status-bar {
  position: fixed; top: 0; left: 0; right: 0;
  z-index: 60;
  text-align: center;
  font-size: var(--fs-xs); font-weight: 700;
  padding: 5px 12px;
  padding-top: calc(5px + env(safe-area-inset-top, 0px));
  color: #fff;
}
.status-bar--offline { background: var(--warn); }
.status-bar--syncing { background: var(--info); }

/* =========================== fixed action bar =========================== */

.action-bar {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: 45;
  /* Opaque fallback: without it, an unsupported color-mix() leaves the bar transparent
     and the page scrolls visibly under the submit button. */
  background: var(--panel);
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--line);
  padding: 12px var(--pad) calc(12px + var(--safe-b));
}
.action-bar__inner { max-width: var(--shell); margin: 0 auto; display: grid; gap: 6px; }

/* =========================== overlays =========================== */

.overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(44, 44, 51, .5);
  backdrop-filter: blur(2px);
  display: flex; justify-content: center;
  padding: var(--pad);
  animation: gfade .15s ease-out;
}
.overlay--center { align-items: center; }
.overlay--sheet  { align-items: flex-end; padding: 0; }
@keyframes gfade { from { opacity: 0; } }

.dialog {
  background: var(--panel);
  border-radius: var(--r-xl);
  padding: 22px;
  width: 100%;
  max-width: 26rem;
  box-shadow: var(--shadow-lg);
  display: grid; gap: 14px;
  animation: gpop .18s ease-out;
}
.dialog__title { font-size: var(--fs-md); font-weight: 700; white-space: pre-line; line-height: 1.45; }
@keyframes gpop { from { opacity: 0; transform: scale(.97) translateY(6px); } }

.sheet {
  background: var(--panel);
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  padding: 22px var(--pad) calc(22px + var(--safe-b));
  box-shadow: var(--shadow-lg);
  display: grid; gap: 14px;
  animation: gslide .2s ease-out;
}
@keyframes gslide { from { transform: translateY(14%); } }

/* Full-screen WRONG PRODUCT block. */
.blocker {
  position: fixed; inset: 0; z-index: 120;
  background: var(--bad);
  color: #fff;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px;
  padding: 28px 22px calc(28px + var(--safe-b));
  text-align: center;
  overflow-y: auto;
}
.blocker__mark { font-size: 3.5rem; line-height: 1; }
.blocker__head { font-size: var(--fs-2xl); font-weight: 800; letter-spacing: .01em; margin-bottom: 6px; }
.blocker__label { font-size: var(--fs-sm); opacity: .88; }
.blocker__code {
  font-family: var(--mono); font-size: var(--fs-xl); font-weight: 700;
  letter-spacing: .05em; margin-bottom: 10px;
}
.blocker .btn { margin-top: 18px; background: #fff; color: var(--bad-ink); min-width: 15rem; }
.blocker .btn:hover { background: var(--cream); }

/* =========================== identity chip =========================== */

.whoami {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--fs-xs);
  color: rgba(255,255,255,.9);
}
.whoami__email { max-width: 34vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.whoami__out {
  appearance: none; border: 0; cursor: pointer;
  background: rgba(255,255,255,.18); color: #fff;
  font: 600 var(--fs-xs)/1 var(--font);
  padding: 9px 12px; border-radius: var(--r-pill);
  min-height: 34px;
}
.whoami__out:hover { background: rgba(255,255,255,.3); }

/* =========================== stat cells =========================== */

.stat { text-align: center; min-width: 0; }
.stat__n { font-size: var(--fs-xl); font-weight: 800; line-height: 1.1; letter-spacing: -.02em; }
/* No tracking and no wrapping: in a four-up tally on a 375px handset, "EXPECTED" was
   breaking into "EXPECTE / D". The number carries the emphasis, the label just names it. */
.stat__k {
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0;
  color: var(--muted); margin-top: 2px; white-space: nowrap;
}
.stat--dim .stat__n { color: var(--line-strong); }

/* =========================== admin: labels & log =========================== */

.label-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 14px; }
.label-cell {
  border: 1px solid var(--line-strong); border-radius: var(--r);
  padding: 12px; text-align: center; background: #fff; break-inside: avoid;
}
.label-cell svg, .label-cell canvas { max-width: 100%; height: auto; margin: 0 auto; }
.label-cell__code { font-family: var(--mono); font-weight: 800; font-size: var(--fs-lg); margin-top: 6px; }

.logline {
  background: var(--panel-sunk);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 11px 13px;
  min-width: 0;
}
.logline__json {
  font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted);
  margin: 6px 0 0; white-space: pre-wrap; overflow-wrap: anywhere;
}

details.disclosure { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 14px var(--pad); }
details.disclosure > summary {
  cursor: pointer; font-weight: 700; font-size: var(--fs-sm); color: var(--ink-soft);
  list-style: none; display: flex; align-items: center; gap: 8px; min-height: 28px;
}
details.disclosure > summary::-webkit-details-marker { display: none; }
details.disclosure > summary::before { content: "▸"; color: var(--muted); transition: transform .15s ease; }
details.disclosure[open] > summary::before { transform: rotate(90deg); }

/* =========================== warehouse map =========================== */

/* The map is a CSS Grid of whole cells. Nothing here positions a rack in pixels or percent —
   a rack spans grid tracks, so alignment is a property of the layout engine rather than
   something JavaScript has to compute and keep correct during a drag. */

.wmap-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.wmap-bar__gap { flex: 1 1 auto; }
/* A hairline between groups: floor | build | view | save. Eight controls in one undivided row
   read as eight equally-likely next steps, which is not what any of them are. */
.wmap-bar__sep {
  width: 1px;
  align-self: stretch;
  min-height: 28px;
  background: var(--line);
}
.wmap-group { display: inline-flex; align-items: center; gap: 6px; }

/* An empty rack is a real state (just added, not filled in yet) and has to look deliberate
   rather than broken or invisible. */
.wrack--empty {
  border-style: dashed;
  background: color-mix(in srgb, var(--info-soft) 45%, transparent);
}
.wrack__empty {
  font-size: clamp(8px, calc(var(--cellpx, 20px) * 0.28), 13px);
  font-weight: 600;
  opacity: .6;
  pointer-events: none;
}
.wgrid--compact .wrack__empty { display: none; }

/* First run: an empty floor says what to do instead of showing bare graph paper. */
.wgrid__empty {
  grid-column: 1 / -1;
  grid-row: 1 / -1;
  display: grid;
  place-content: center;
  gap: 4px;
  text-align: center;
  color: var(--muted);
  font-size: var(--fs-sm);
  pointer-events: none;
}
.wgrid__empty b { color: var(--ink-soft); font-size: var(--fs-base); }

.wmap-dirty {
  font-size: 12px;
  font-weight: 700;
  color: var(--warn-ink);
}

/* Editing controls live under the floor, not beside it, so the floor keeps the full card width.
   Admins do this at a laptop; the two panels only split once there is room for both. */
/* The rack panel now has the full width to itself — the library moved up beside the map. */
.wmap-below {
  display: grid;
  gap: 12px;
  grid-template-columns: 1fr;
  margin-top: 12px;
}

/* Floor and bin library, side by side once there is room for both. */
.wmap-work {
  display: grid;
  gap: 12px;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 1080px) {
  .wmap-work {
    grid-template-columns: minmax(0, 1fr) 264px;
    align-items: start;
  }
  /* 264px is not enough for a field and a button side by side without truncating the field. */
  .wmap-rail #map-newbin-form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; }
  /* Sticky so the bins stay put while the floor scrolls past them. */
  .wmap-rail {
    position: sticky;
    top: 12px;
    max-height: calc(100vh - 24px);
    overflow-y: auto;
  }
}

.wgrid__heading {
  font-size: 13px;
  font-weight: 700;
  margin: 10px 0 4px;
}

.wgrid {
  --cols: 28;
  --rows: 16;
  width: 100%;
  /* Cells stay square because the grid's own box carries the cols:rows ratio. */
  aspect-ratio: var(--cols) / var(--rows);
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  grid-template-rows: repeat(var(--rows), 1fr);
  background-color: var(--panel-sunk);
  /* One hairline per cell, drawn by the background rather than by 448 empty elements. */
  background-image:
    linear-gradient(to right, rgba(0, 0, 0, .07) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0, 0, 0, .07) 1px, transparent 1px);
  background-size: calc(100% / var(--cols)) calc(100% / var(--rows));
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
}

/* Without this a drag on a touch screen scrolls the page instead of moving the rack. */
.wgrid--editable { touch-action: none; }

.wrack {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  padding: 2px;
  background: var(--info-soft);
  border: 1px solid var(--info);
  border-radius: 6px;
  font-size: 11px;
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
}
.wgrid--editable .wrack { cursor: grab; }
.wgrid--editable .wrack.is-dragging { cursor: grabbing; opacity: .85; }

.wrack__label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
/* A one-cell-wide rack cannot show a horizontal label, so turn it along the rack instead of
   truncating every rack on the floor to a single letter. */
.wrack--tall .wrack__label {
  writing-mode: vertical-rl;
  text-orientation: mixed;
}

.wrack__count {
  position: absolute;
  top: 1px;
  right: 3px;
  font-size: clamp(10px, calc(var(--cellpx, 20px) * 0.34), 17px);
  font-weight: 700;
  opacity: .65;
  pointer-events: none;
}

/* Sits fully inside the rack on purpose. The rack clips its overflow so long labels cannot spill
   across the floor, which means a pin hung off the corner would be silently cut in half —
   leaving colour as the only cue that this is the rack you want. */
.wrack__pin {
  position: absolute;
  top: 0;
  left: 2px;
  font-size: 13px;
  line-height: 1;
  pointer-events: none;
}

.wrack.is-hit {
  background: var(--ok-soft);
  border: 2px solid var(--ok);
  z-index: 2;
}
.wrack.is-dim { opacity: .35; }

/* Overlapping racks hide one another; the outline says so without blocking the move. */
.wrack.is-overlap {
  outline: 2px dashed var(--bad);
  outline-offset: -2px;
}

.wrack.is-selected {
  outline: 2px solid var(--accent-sink);
  outline-offset: 1px;
  z-index: 3;
}
.wrack:focus-visible {
  outline: 2px solid var(--accent-sink);
  outline-offset: 1px;
}

/* Resize grip. Deliberately smaller than the 48px touch target the rest of the app uses: a 48px
   grip would cover a 1x1 rack completely and make it unmovable. The properties panel carries
   numeric X/Y/W/H, so every change here is reachable without the grip on a phone. */
.wrack__handle {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 14px;
  height: 14px;
  cursor: nwse-resize;
  background:
    linear-gradient(135deg, transparent 50%, var(--accent-sink) 50%);
  border-bottom-right-radius: 5px;
}

/* Zoom. Fit mode lets the grid shrink to the card; zoomed mode gives every cell an explicit
   pixel size and lets the scroll container handle the overflow, which is what makes a dense
   floor readable without shrinking the building. */
.wmap-scroll {
  overflow: auto;
  max-height: 72vh;
  border-radius: var(--r-sm);
  overscroll-behavior: contain;
}
.wgrid--zoom {
  width: calc(var(--cols) * var(--cell));
  aspect-ratio: auto;
  height: calc(var(--rows) * var(--cell));
  max-width: none;
}
.wmap-zoom { display: inline-flex; gap: 4px; }

/* Full screen: the section becomes the whole viewport, so let the map use the height it gains. */
#tab-map:fullscreen {
  background: var(--cream);
  padding: var(--pad);
  overflow: auto;
}
#tab-map:fullscreen .wmap-scroll { max-height: none; }

/* Bin codes drawn inside the rack. A rack whose bins are its shelf levels then reads as those
   levels top-to-bottom, instead of a count you have to click to expand. */
.wrack__bins {
  position: absolute;
  inset: calc(3px + clamp(13px, calc(var(--cellpx, 20px) * 0.56), 28px)) 2px 2px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 1px;
  overflow: hidden;
  pointer-events: none;
}
.wrack__bin {
  font-size: clamp(10px, calc(var(--cellpx, 20px) * 0.34), 18px);
  font-weight: 700;
  line-height: 1.35;
  text-align: center;
  border-radius: 3px;
  background: rgba(255, 255, 255, .72);
  color: var(--info-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wrack__bin.is-hit { background: var(--ok); color: #fff; }

/* On a tall rack the codes divide its height, so three bins read as three shelf levels at the
   heights they actually sit at rather than as a list bunched under the label. */
.wrack--tall .wrack__bin {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
}

/* Wide-and-short racks read better with the codes in a row than in a column. */
.wrack:not(.wrack--tall) .wrack__bins { flex-direction: row; flex-wrap: wrap; align-content: flex-start; justify-content: center; }
.wrack:not(.wrack--tall) .wrack__bin { padding: 0 3px; }

/* Too small to read a code, or too many codes for the rack's size: fall back to the count. */
.wgrid--compact .wrack__bins,
.wrack--overflow .wrack__bins { display: none; }
.wgrid:not(.wgrid--compact) .wrack:not(.wrack--overflow) .wrack__count { display: none; }
.wrack--overflow .wrack__count {
  position: static;
  align-self: center;
  font-size: clamp(10px, calc(var(--cellpx, 20px) * 0.34), 17px);
  opacity: .9;
}
.wrack--overflow .wrack__count::after { content: " bins"; }
.wgrid:not(.wgrid--compact) .wrack--tall .wrack__label { writing-mode: horizontal-tb; }
.wgrid:not(.wgrid--compact) .wrack__label {
  position: absolute; top: 1px; left: 0; right: 0; text-align: center;
  font-size: clamp(11px, calc(var(--cellpx, 20px) * 0.46), 24px);
  font-weight: 700;
}

.wmap-levels { display: grid; grid-template-columns: 2fr 1fr auto; gap: 6px; align-items: center; }

.linkish {
  font: inherit;
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent-sink);
  text-decoration: underline;
  cursor: pointer;
}

.wmap-pool__btn--new { border-color: var(--ok); color: var(--ok-ink); font-weight: 700; }

.wgrid-legend {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 8px;
  font-size: 12px;
}
.wgrid-legend__item { display: inline-flex; align-items: center; gap: 6px; }
.wgrid-legend__swatch {
  width: 14px;
  height: 14px;
  border-radius: 4px;
  background: var(--info-soft);
  border: 1px solid var(--info);
}
.wgrid-legend__swatch--hit {
  background: var(--ok-soft);
  border: 2px solid var(--ok);
}

/* X / Y / W / H inputs, four across. */
.wmap-nums {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.wmap-nums label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  font-weight: 700;
  opacity: .8;
}

.wmap-bins {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  /* Without these the chips inherit align-items:stretch and grow to the height of whatever
     container they sit in, which turned the unplaced-bin list into full-height vertical bars. */
  align-items: flex-start;
  align-content: flex-start;
}
.wmap-bins__chip {
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  padding: 6px 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
}
.wmap-bins__chip:hover { border-color: var(--bad); color: var(--bad); }
.wmap-bins__chip--plain { cursor: default; opacity: .75; }
.wmap-bins__chip--plain:hover { border-color: var(--line); color: inherit; }

/* Draggable bin chip. The grip cursor and the ✕ together say "move me or remove me" without
   a caption; the ✕ is what keeps every action reachable on a touch screen, where HTML5 drag
   events never fire at all. */
.wmap-chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 0 2px 0 9px;
  min-height: 34px;
  border-radius: var(--r-pill);
  border: 1px solid var(--info);
  background: var(--info-soft);
  color: var(--info-ink);
  font-size: var(--fs-xs);
  font-weight: 700;
  cursor: grab;
  user-select: none;
}
.wmap-chip:active { cursor: grabbing; }
.wmap-chip.is-dragging { opacity: .4; }
.wmap-chip__code { pointer-events: none; }
/* In the library the code is the "put it on the selected rack" button. */
.wmap-chip__code--btn {
  font: inherit;
  font-weight: 700;
  color: inherit;
  background: none;
  border: 0;
  padding: 6px 2px 6px 0;
  margin: 0;
  cursor: pointer;
  pointer-events: auto;
}
.wmap-chip__code--btn:hover { text-decoration: underline; }
.wmap-chip__x {
  font: inherit;
  font-size: 13px;
  line-height: 1;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-pill);
  background: none;
  color: inherit;
  opacity: .55;
  cursor: pointer;
}
.wmap-chip__x:hover { opacity: 1; background: rgba(0, 0, 0, .08); }
/* Not in the bin master yet — it is created on Save, so it should not look identical to one
   that already exists. */
.wmap-chip--new {
  border-style: dashed;
  border-color: var(--ok);
  background: var(--ok-soft);
  color: var(--ok-ink);
}

/* Anywhere a bin can be dropped. Empty ones keep their height so the target does not appear
   only once you are already dragging. */
.wmap-drop {
  min-height: 56px;
  max-height: 232px;
  overflow-y: auto;
  padding: 8px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  align-content: flex-start;
}
.wmap-drop.is-over,
.wrack.is-over {
  border-color: var(--ok);
  outline: 2px solid var(--ok);
  outline-offset: -2px;
  background: var(--ok-soft);
}

.wmap-trash {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 52px;
  padding: 8px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  color: var(--muted);
  font-size: var(--fs-xs);
  text-align: center;
}
.wmap-trash.is-over {
  border-color: var(--bad);
  outline: 2px solid var(--bad);
  outline-offset: -2px;
  background: var(--bad-soft);
  color: var(--bad-ink);
}

.wmap-pool {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  max-height: 190px;
  overflow-y: auto;
}
.wmap-pool__btn {
  font: inherit;
  font-size: 12px;
  padding: 6px 9px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
}
.wmap-pool__btn:hover { border-color: var(--info); color: var(--accent-sink); }

/* =========================== responsive =========================== */

/* Tablet and desktop. The app is built for a phone in one hand; on a bigger screen it
   stays a single readable column but gains breathing room, and the pages that genuinely
   benefit from two columns opt in with `.split`. */
@media (min-width: 720px) {
  :root { --pad: 20px; --gap: 14px; }
  .shell { padding: 24px 20px; }
  .app-bar__titles h1 { font-size: var(--fs-lg); }
  .whoami__email { max-width: 18rem; }
  .toast-stack { bottom: calc(24px + var(--safe-b)); }
  .action-bar__inner { max-width: var(--shell); }
  .overlay--sheet { align-items: center; padding: var(--pad); }
  .sheet { border-radius: var(--r-xl); max-width: 26rem; }
}

@media (min-width: 960px) {
  .split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 24px;
    align-items: start;
  }
  .split > * { min-width: 0; }
  /* Opt-in rather than `.shell:has(.split)`: :has() is unsupported on older Android
     Chrome, where the two columns would then be crammed into the narrow shell. */
  .shell--split { max-width: var(--shell-wide); }
  .split__sticky { position: sticky; top: 88px; }
  .nav-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Very small handsets — the older scanner phones still in the shed. */
@media (max-width: 360px) {
  :root { --pad: 12px; --gap: 10px; }
  .cols-4 { gap: 6px; }
  .stat__n { font-size: var(--fs-lg); }
}

/* =========================== print (shelf labels) =========================== */

@media print {
  body { background: #fff; }
  body * { visibility: hidden; }
  #print-area, #print-area * { visibility: visible; }
  #print-area { position: absolute; inset: 0; padding: 0; }
  .label-cell { border-color: #999; }
}
