/* ==========================================================================
   BikesOnline WMS

   Self-contained. No Bootstrap, no AdminKit, nothing from /usa/. Powermeter is
   a BI tool for analysts on desktops; this is an operator app for someone
   holding a rugged handheld in a warehouse, often wearing gloves. The two have
   almost no styling requirements in common, and sharing a stylesheet would mean
   every dashboard theme tweak lands on the pick screen.

   Operator rules, which are not preferences:
     - 48px minimum hit target. Gloves.
     - No hover-only affordances. Touch devices have no hover.
     - High contrast. Warehouses have bad light and dusty screens.
     - Numbers big enough to read at arm's length while holding a box.
     - Air between everything. Two controls that touch are one fat-fingered tap
       away from being the wrong control, and --stack/--stack-card below are
       what keep that from happening by accident.
   ========================================================================== */

:root {
    --bg:        #f1f5f9;
    --surface:   #ffffff;
    --border:    #dbe3ee;
    --muted:     #64748b;
    --text:      #0f172a;
    --primary:   #2563eb;
    --primary-d: #1d4ed8;
    --ok:        #16a34a;
    --warn:      #d97706;
    --danger:    #dc2626;
    --ink:       #0f172a;
    --radius:    12px;
    --tap:       48px;

    /* The two spacing rhythms. Everything vertical uses one of them, so
       nothing has to remember a margin of its own. */
    --stack:      14px;   /* between the parts of a screen */
    --stack-card: 12px;   /* between the parts of one card */

    /* Where the top bar stops, and therefore where the desk rail starts.
       SPELLED OUT RATHER THAN MEASURED: it is --tap plus .wms-top's own padding,
       top and bottom, which is what the bar is pinned to. Writing 68px here and
       hoping the bar agrees is how the two drifted apart and left a white band
       between the header and the sidebar. The safe-area term matters on an iPad
       held in desk mode, where the bar grows into the notch and the rail has to
       follow it down. */
    --top-h: calc(var(--tap) + 4px + max(4px, env(safe-area-inset-top)));
}

* { box-sizing: border-box; }

html, body {
    margin: 0; padding: 0;
    font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
    background: var(--bg); color: var(--text);
    -webkit-text-size-adjust: 100%;
    overscroll-behavior-y: none;   /* no pull-to-refresh mid-scan */
}

body.wms { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }

/* ---- Top bar ---- */

/* THE HEIGHT IS A CONTRACT, NOT A COINCIDENCE. The desk rail is sticky beneath
   this bar and has to know where it stops (--top-h, below), and there is no CSS
   way to read a sibling's height — so the bar is pinned to exactly the value the
   rail is told to expect, and both come from the same token.

   Getting this wrong is visible: the bar used to be sized only by its contents,
   which on the floor is the 48px back arrow but on a desk — where that arrow is
   hidden — is the 38px chip row. The bar came out ten pixels shorter than the
   rail's offset, and those ten pixels showed as a white band across the top of
   the page between the header and the sidebar. */
.wms-top {
    display: flex; align-items: center; gap: 12px;
    min-height: var(--top-h);
    background: var(--ink); color: #fff;
    padding: 4px 12px; padding-top: max(4px, env(safe-area-inset-top));
    position: sticky; top: 0; z-index: 50;
}
.wms-top__back {
    display: flex; align-items: center; justify-content: center;
    width: var(--tap); height: var(--tap); flex-shrink: 0;
    color: #fff; text-decoration: none; border-radius: 10px;
    background: rgba(255,255,255,.08);
}
.wms-top__back:active { background: rgba(255,255,255,.2); }
/* Occupies the back arrow's slot on a page with nowhere to go, so the title and
   the chips keep their positions. No background and no :active state — it must
   not look like something that rewards a tap. */
.wms-top__mark {
    display: flex; align-items: center; justify-content: center;
    width: var(--tap); height: var(--tap); flex-shrink: 0;
    color: #64748b;
}
.wms-top__title { flex: 1; min-width: 0; }
/* One line, like the subtitle under it. A page label is a name, not prose, and a
   title that wraps grows the bar past --top-h — which on a desk reopens the gap
   between the header and the rail, and on a handheld shoves the whole screen
   down by a line. */
.wms-top__title h1 { margin: 0; font-size: 1.05rem; font-weight: 700; letter-spacing: -.01em;
                     overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* One line, always. The subtitle is the wave code and what is left of it, and
   it changes on every pick — letting it wrap moves the whole screen down by a
   line and back again while the operator is scanning against it. */
.wms-top__title small { color: #94a3b8; font-size: .74rem; display: block;
                        overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wms-top__title small.is-critical { color: #fca5a5; font-weight: 800; }
.wms-top__right { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* A name rather than an address, so it can afford to be read at bench distance.
   Still shrinks before the page title does: on a 390px handheld the operator is
   the least useful thing in the bar, and letting it push an alert off the right
   edge would hide the one indicator that matters. */
.wms-who { display: flex; align-items: center; gap: 4px; min-width: 0; flex-shrink: 1;
           color: #cbd5e1; font-size: .84rem; font-weight: 700; }
.wms-who .material-icons { font-size: 1.15rem; color: #94a3b8; flex-shrink: 0; }
.wms-who__name { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.wms-alert {
    display: flex; align-items: center; gap: 4px;
    min-width: 40px; height: 38px; padding: 0 10px;
    background: rgba(255,255,255,.08); border: 0; border-radius: 10px;
    color: #cbd5e1; cursor: pointer; text-decoration: none;
}
.wms-alert .material-icons { font-size: 1.2rem; }
.wms-alert em { font-style: normal; font-size: .74rem; font-weight: 800; }
.wms-alert.is-attention { color: #fff; background: var(--danger); }
.wms-alert.is-unknown { color: #94a3b8; }

/* The printer chip uses the same compact shell shape as the dispatch alert. */
.wms-print {
    display: flex; align-items: center; gap: 4px;
    min-width: 40px; height: 38px; padding: 0 10px;
    background: rgba(255,255,255,.08); border: 0; border-radius: 10px;
    color: #f87171; cursor: pointer;
}
.wms-print .material-icons { font-size: 1.15rem; }
.wms-print em { font-style: normal; font-size: .74rem; font-weight: 700; }
.wms-print.is-ready   { color: #4ade80; }
.wms-print.is-queued  { color: #fbbf24; }
.wms-print.is-offline { color: #f87171; }
/* Something is waiting on a decision. Outranks the other three, and is the one
   state that gets a filled background — a colour change alone is too easy to
   miss on a dusty screen in bad light. */
.wms-print.is-attention { color: #fff; background: var(--danger); }
/* Before the first answer arrives. The chip is also [hidden] until then, so
   this only shows if a page paints it early. */
.wms-print.is-unknown { color: #94a3b8; }

/* ---- Main ---- */

.wms-main { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.wms--operator .wms-main { padding: 14px; gap: var(--stack); max-width: 720px; width: 100%; margin: 0 auto; }

/* The admin shell used to be `padding: 0`, which was really the map editor's
   requirement written down in the wrong place: every other admin page — sync
   settings, exceptions, dispatch — inherited it and rendered flush against both
   edges of the screen, with the cards' rounded corners sliced off. The map says
   so for itself below; everything else gets a padded, centred column. */
.wms--admin .wms-main { padding: 20px; gap: var(--stack); max-width: 1240px; width: 100%; margin: 0 auto; }
body[data-route="map"] .wms-main { padding: 0; max-width: none; }

/* ---- The screen is a stack ----

   Every page renders into a single <div id="app">, so the gap on .wms-main
   above only ever had one child to space out and did nothing. Without this,
   a card sat flush on the button under it, two stacked buttons shared an edge,
   and two inline-flex buttons ended up side by side touching. Making #app the
   stack fixes all of it in one place, and means no screen has to remember to
   put a margin on the thing it just rendered.

   A bare <div> at the top of a screen is a grouping wrapper — pick's individual
   order list, look-up's results — never a visual element, so it stacks too. */
#app,
#app > div:not([class]) { display: flex; flex-direction: column; gap: var(--stack); }

/* A slot rendered ahead of the thing that will fill it — receive's pendingSlot,
   the hub's stats strip on a quiet morning — is not a gap's worth of nothing
   until it has something in it. */
#app > div:not([class]):empty,
.wms-main > div:not([class]):empty { display: none; }

/* On a handheld a top-level action fills the width: it is the biggest possible
   target for a gloved hand and it lands in the same place every time. On a
   desktop that would be a button the width of the browser, so admin keeps them
   at their natural size. */
.wms--admin #app > .btn,
.wms--admin #app > div:not([class]) > .btn { align-self: flex-start; }

/* ---- Task hub ---- */

.wms-group { margin-bottom: 8px; }   /* + the stack gap = the 22px this always was */
.wms-group h2 {
    font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
    color: var(--muted); margin: 0 0 10px;
}
.wms-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.wms-tile {
    display: flex; flex-direction: column; gap: 6px;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 16px; text-decoration: none; color: var(--text);
    min-height: 108px; box-shadow: 0 1px 2px rgba(15,23,42,.05);
}
.wms-tile:active { border-color: var(--primary); transform: scale(.985); }
.wms-tile .material-icons { font-size: 1.7rem; color: var(--primary); }
.wms-tile strong { font-size: .96rem; font-weight: 700; }
.wms-tile span.blurb { font-size: .74rem; color: var(--muted); line-height: 1.4; }

/* The exception banner.
   Exceptions used to be the last tile in the last group on the hub — under
   Setup, next to the warehouse map — which is where you put something nobody
   is expected to look at. Every row in that queue is a stopped order, a
   negative bin or a write that never reached Brightpearl, so it goes first and
   in red, and it is not on the hub at all when the queue is empty. */
.home-alert {
    display: flex; align-items: center; gap: 14px;
    background: #fef2f2; border: 2px solid var(--danger); border-radius: var(--radius);
    padding: 14px 16px; text-decoration: none; color: var(--text);
    box-shadow: 0 0 0 3px rgba(220,38,38,.08);
}
.home-alert:active { transform: scale(.99); }
.home-alert .material-icons { font-size: 2rem; color: var(--danger); flex-shrink: 0; }
.home-alert__count { font-size: 1.7rem; font-weight: 800; color: #991b1b; line-height: 1; }
.home-alert__text { flex: 1; min-width: 0; }
.home-alert__text strong { display: block; font-size: 1rem; font-weight: 800; color: #991b1b; }
.home-alert__text span { font-size: .78rem; color: #7f1d1d; }
.home-alert__go { color: var(--danger); }

/* ---- Cards, fields, buttons ---- */

/* A card is a stack too, for the same reason the screen is: a scan field
   landing directly on the button under it is how a gloved thumb submits a pick
   it meant to type into. Anything inside a card that used to carry its own
   margin has had it removed below — a gap and a margin together read as a
   mistake, because it is one. */
.card {
    display: flex; flex-direction: column; gap: var(--stack-card);
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); padding: 16px;
}
.card h3, .card h2 { margin: 0; font-size: .95rem; font-weight: 700; }
.card h2 { font-size: 1.15rem; }
.card > p { margin: 0; }
.card > .field { margin-bottom: 0; }
/* A bare <div> grouping part of a card — the pack bench keeps a carton's
   measurements, serials and rates in one — is a stack for the same reason the
   card is. */
.card > div:not([class]) { display: flex; flex-direction: column; gap: var(--stack-card); }
/* A card whose whole content is one row — the staging note, a heading with an
   icon beside it. It sets display:flex for its own reasons and would otherwise
   inherit the column direction from above and stack the icon on the text. */
.card.staging-note, .card.section-heading { flex-direction: row; }
/* <details> loses its open/closed behaviour as a flex container. */
details.card { display: block; }
details.card > summary { margin-bottom: var(--stack-card); }

/* Buttons fill the width of an operator's card and sit at their natural size on
   an admin one — the same split as the screen-level rule above. */
.wms--admin .card > .btn { align-self: flex-start; }

.field { margin-bottom: 14px; }
.field > label, .field > span {
    display: block; font-size: .7rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .05em; color: var(--muted); margin-bottom: 6px;
}
/* Selected by class as well as by wrapper. The counting screen puts its scan
   box straight into the card rather than into a .field, so under the old
   `.field input` selector the primary scan target of that screen rendered as a
   bare native input: 21px tall, on a device operated with gloves. An input is
   the same size whoever's parent it is. */
.field input, .field select, .field textarea,
input.scan, input.qty {
    width: 100%; min-height: var(--tap); padding: 10px 14px;
    font-size: 1.05rem; font-family: inherit; color: var(--text);
    border: 2px solid var(--border); border-radius: 10px; background: #fff;
}
.field input:focus, .field select:focus, input.scan:focus, input.qty:focus {
    outline: 0; border-color: var(--primary); box-shadow: 0 0 0 4px rgba(37,99,235,.14);
}
/* Scan targets read as scannable, and the value is unmistakable at arm's length. */
input.scan {
    font-size: 1.35rem; font-weight: 700; letter-spacing: .04em;
    text-transform: uppercase; border-color: var(--primary);
    background: #eff6ff;
}
input.qty { font-size: 1.9rem; font-weight: 800; text-align: center; letter-spacing: .05em; }
/* The placeholder is a sentence of instructions, not scanned data, so it does
   not take the field's scan sizing — at 1.35rem uppercase, "Bin label, barcode
   or SKU" ran off the end of the box and read as "BIN LABEL, BARCODE OR S". */
input.scan::placeholder {
    font-size: .95rem; font-weight: 500; letter-spacing: 0; text-transform: none;
}

/* A scan that has landed. The picking screen has asked for this on both of its
   scan fields since it was written, and nothing had ever drawn it — so the one
   confirmation that the bin scan registered was the small tick above it. */
.field--ok > span { color: var(--ok); }
.field--ok input { border-color: var(--ok); background: #f0fdf4; }

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    min-height: var(--tap); padding: 0 20px;
    font-family: inherit; font-size: 1rem; font-weight: 700;
    border: 0; border-radius: 10px; cursor: pointer;
    background: var(--primary); color: #fff;
}
.btn:active { background: var(--primary-d); transform: scale(.985); }
.btn[disabled] { opacity: .45; pointer-events: none; }
.btn--block { width: 100%; }
.btn--lg { min-height: 60px; font-size: 1.1rem; }
.btn--ghost { background: transparent; color: var(--text); border: 2px solid var(--border); }
.btn--danger { background: var(--danger); }
.btn--ok { background: var(--ok); }
/* The default already is the primary blue; named so that a screen asking for it
   explicitly gets what it asked for instead of nothing. */
.btn--primary { background: var(--primary); color: #fff; }
/* Used in nine places across packing, dispatch, picking and products and never
   defined, so every "small" button rendered at the full 48px operator size —
   which is what made a Remove sit larger than the carton heading beside it and
   a Void dominate its table row. Small still clears 36px: this variant only
   ever appears on the admin shell and in a card header, never under a glove. */
.btn--sm { min-height: 36px; padding: 0 12px; font-size: .82rem; }
.btn--sm .material-icons { font-size: 1rem; }
/* Square, for a button whose whole label is one icon. */
.btn--icon { width: var(--tap); min-width: var(--tap); padding: 0; }
.btn--icon.btn--sm { width: 36px; min-width: 36px; }
/* Buttons are <a> as often as <button> here — "Open order", "Pack station",
   "Open PO #88421 in Brightpearl". Without this they render underlined. */
a.btn { text-decoration: none; }
.btn .material-icons { font-size: 1.2rem; }
.btn-row { display: flex; gap: 10px; }
.btn-row .btn { flex: 1; }

/* ---- Lists ---- */

.list { display: flex; flex-direction: column; gap: 8px; }
.row {
    display: flex; align-items: center; gap: 12px;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 10px; padding: 12px 14px; min-height: 56px;
    text-decoration: none; color: var(--text); width: 100%; text-align: left;
    font-family: inherit; font-size: 1rem; cursor: pointer;
}
.row:active { border-color: var(--primary); }
.row__main { flex: 1; min-width: 0; }
/* A button in a list row is secondary to the row itself, so it takes the space
   it needs and no more. At full operator size the trash button on an empty
   receipt wrapped the PO name onto three lines and the row stopped being
   readable, which is the opposite of what the button was for. The layout
   variants below are not list rows and keep their buttons full size. */
.row:not(.row--between):not(.row--wrap) > .btn {
    flex-shrink: 0; min-height: 40px; padding: 0 12px; font-size: .84rem;
}
.row:not(.row--between):not(.row--wrap) > .btn--icon { width: 40px; min-width: 40px; padding: 0; }
/* A native checkbox renders at 13px. Selecting variances to correct is a real
   decision and the box is the only thing that shows it was made. */
.row > input[type="checkbox"] { width: 22px; height: 22px; flex-shrink: 0; }
.row__main strong { display: block; font-weight: 700; font-size: .98rem; }
.row__main small { color: var(--muted); font-size: .76rem; }
.row__qty { font-size: 1.3rem; font-weight: 800; }
.row.is-done {
    /* Complete lines stay fully legible — an operator scanning a long PO needs
       to read what is already done to be sure of it, not squint past it. The
       tint and the tick carry the meaning instead of dimming. */
    background: #f0fdf4;
    border-color: #bbf7d0;
}
.row.is-done .row__qty { color: var(--ok); }
.row.is-done .row__main strong { color: #166534; }
.row__tick { color: var(--ok); font-size: 1.4rem; }

/* A line where more arrived than was ordered. */
.row.is-over { background: #fffbeb; border-color: #fde68a; }
.row.is-over .row__qty { color: var(--warn); }

.empty { text-align: center; padding: 40px 16px; color: var(--muted); }
.empty .material-icons { font-size: 2.6rem; opacity: .3; }
.empty p { font-size: .9rem; margin: 10px 0 0; }

/* ---- Badges and stats ---- */

.badge {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 3px 10px; border-radius: 20px;
    font-size: .72rem; font-weight: 700;
    background: #e2e8f0; color: var(--muted);
}
.badge--ok   { background: #dcfce7; color: #15803d; }
.badge--warn { background: #fef3c7; color: #b45309; }
.badge--bad  { background: #fee2e2; color: #b91c1c; }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: 10px; }
.stat { background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
        padding: 12px; text-align: center; }
.stat b { display: block; font-size: 1.5rem; font-weight: 800; }
.stat span { font-size: .68rem; color: var(--muted); text-transform: uppercase;
             letter-spacing: .05em; font-weight: 600; }

.hint { font-size: .78rem; color: var(--muted); line-height: 1.5; }
.muted { color: var(--muted); }
.warn { color: #b45309; font-weight: 600; }
.error { color: var(--danger); font-weight: 600; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.num--warn { color: #b45309; font-weight: 700; }

/* Layout variants are intentionally borderless. `.row` is also the standard
   tappable list row; without these overrides, using it as a flex helper inside
   a card draws a second fake card and makes the planner look nested and noisy. */
.row.row--between, .row.row--wrap {
    min-height: 0; padding: 0; border: 0; border-radius: 0;
    background: transparent; cursor: default;
}
.row--between { justify-content: space-between; }
/* The text side of a between-row shrinks; the number or badge on the right does
   not. A flex item will not go below its content width unless it is told it
   may, so on a 390px screen the product name held the row open and pushed the
   quantity — the one thing the picker needs off that row — past the edge. */
.row--between > *:first-child { min-width: 0; }
/* Except a heading, which is a label rather than prose and reads badly broken
   across two lines. What sits beside it wraps underneath instead, below. */
.row--between > h3:first-child { min-width: max-content; }
.row--wrap { flex-wrap: wrap; }
.field--sm { flex: 1 1 150px; min-width: 130px; margin-bottom: 0; }

.card--warn { border-color: #fcd34d; background: #fffbeb; }
.card--ok { border-color: #86efac; background: #f0fdf4; }
.card--active { border-color: #93c5fd; box-shadow: 0 0 0 3px rgba(37,99,235,.1); }
.stat--warn { border-color: #fcd34d; background: #fffbeb; }

.table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.table th { padding: 9px 10px; text-align: left; color: var(--muted); font-size: .68rem;
            text-transform: uppercase; letter-spacing: .05em; border-bottom: 1px solid var(--border); }
.table td { padding: 11px 10px; border-bottom: 1px solid #eef2f7; vertical-align: middle; }
.table tbody tr:last-child td { border-bottom: 0; }
.table--tight td { padding-top: 8px; padding-bottom: 8px; }
.row--flagged td { background: #fff7ed; }
.row--done td { background: #f0fdf4; }

.tabs { display: flex; align-items: center; gap: 6px; padding: 5px;
        border: 1px solid var(--border); border-radius: 12px; background: #e9eef5; width: fit-content; }
.tab { display: inline-flex; align-items: center; justify-content: center; gap: 7px;
       min-height: 42px; padding: 0 16px; border: 0; border-radius: 8px; background: transparent;
       color: var(--muted); font: 700 .85rem/1 Inter, sans-serif; cursor: pointer; }
.tab .material-icons { font-size: 1.05rem; }
.tab--on { color: var(--text); background: #fff; box-shadow: 0 1px 3px rgba(15,23,42,.12); }
.tab__count { min-width: 20px; padding: 3px 6px; border-radius: 12px; background: #e2e8f0;
              color: #475569; font-size: .68rem; }
.tab--on .tab__count { background: #dbeafe; color: #1d4ed8; }

/* Wraps once the filter row outgrows a handheld's width, rather than
   overflowing off the side of the screen where the last filter is unreachable. */
/* `width: auto` is what lets it wrap, but in the screen stack that also makes
   it stretch the full width of a desktop page — a tab bar with three tabs and
   nine hundred pixels of empty strip after them. */
.tabs--wrap { flex-wrap: wrap; width: auto; align-self: flex-start; max-width: 100%; }

/* Used by the pack station and the product editor. Both referenced these long
   before either had a rule, so they rendered as bare divs. */
.chip { display: inline-flex; align-items: center; padding: 4px 9px; border-radius: 20px;
        background: #e2e8f0; color: #475569; font-size: .7rem; font-weight: 700; }
.chip--ok { background: #dcfce7; color: #15803d; }

/* The step number on the counting screen. Same story as .chip above: written
   into count.js from the start, never given a rule, so all three steps
   rendered as a stray "1", "2" and "3" floating above their headings. */
.step {
    display: grid; place-items: center; width: 26px; height: 26px; flex-shrink: 0;
    border-radius: 50%; background: var(--primary); color: #fff;
    font-size: .8rem; font-weight: 800;
}

/* A tick inside a table cell, where the icon IS the answer. */
.material-icons.ok { color: var(--ok); }

.lookup-presets { margin-top: 0; }
.lookup-presets > small { display: block; color: var(--muted); margin-bottom: 8px; }
.lookup-presets__buttons { display: flex; flex-wrap: wrap; gap: 8px; }
.lookup-presets__buttons .btn { min-height: 44px; }

.settings-statuses { display: grid; gap: 8px; }
/* Three columns since the colour moved into the name: the 12px dot column is
   gone and the pill is the name. */
.settings-status { display: grid; grid-template-columns: 24px 1fr auto; align-items: center;
  gap: 9px; min-height: var(--tap); padding: 11px 12px; border: 1px solid var(--border);
  border-radius: 10px; cursor: pointer; }
.settings-status input { width: 22px; height: 22px; }
.settings-status--disabled { opacity: .58; }
.settings-status small { color: var(--muted); }

/* A Brightpearl workflow status, painted the way Brightpearl paints it.
   justify-self:start keeps the pill the width of its own text — stretched to
   the column it would read as a row highlight rather than as a label. The text
   colour is computed per status in settings.js; there is no sensible default
   here, because the palette spans #FCEBEB to #5F5E5A. */
.status-pill { justify-self: start; max-width: 100%; padding: 4px 10px; border-radius: 999px;
  font-size: .82rem; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere;
  border: 1px solid rgba(15, 23, 42, .12); }
.status-pill--plain { background: var(--surface); color: var(--text); }
/* A grouping inside a card, and a stack in its own right — the rate panel is
   a price, a mismatch warning, a buy button and a table, and every one of them
   used to carry its own margin to keep off the next. */
.panel { display: flex; flex-direction: column; gap: 10px;
         padding: 13px 14px; border: 1px solid var(--border);
         border-radius: 11px; background: #f8fafc; }
.panel > p { margin: 0; }

/* ---- Pack bench ---- */
.carton { border-left: 4px solid var(--primary); }

/* Three axes across one line, sharing the width evenly so none of them looks
   more important than the others. Weight gets its own line beneath. */
.dims-row { display: flex; gap: 10px; align-items: flex-start; }
.dims-row .field { flex: 1 1 0; min-width: 0; margin-bottom: 0; }
.dims-weight .field { max-width: 200px; margin-bottom: 0; }

.move-select { min-height: 38px; padding: 4px 8px; border: 1px solid var(--border);
               border-radius: 8px; background: #fff; font-size: .78rem; color: var(--muted); }
.ok-note { margin: 0; padding: 10px 12px; border-radius: 9px;
           background: #f0fdf4; color: #15803d; font-size: .82rem; font-weight: 600; }

/* A box that is waiting for a frame serial.
   The amber left edge replaces the usual blue, so a packer scanning down a
   multi-carton order sees which box is asking for something before reading a
   word of it. */
.carton--gated { border-left-color: var(--warn); }

/* The gate itself. Deliberately the only interactive thing in the card while it
   is up — the measurements, the rates and the buy button are not rendered at
   all rather than disabled, because a greyed-out price still reads as "nearly
   done" and this step is not nearly anything. */
.serial-gate { border-color: #fcd34d; background: #fffbeb; }
.serial-gate .section-heading__icon { background: #fef3c7; color: var(--warn); }
.serial-gate .field { margin-bottom: 0; }
.serial-gate input { border-color: var(--warn); background: #fff; }
.serial-gate input:focus { outline: 3px solid #fde68a; outline-offset: 1px; }

/* A serial that has been read, stated back as a fact rather than left sitting
   in a box that invites typing over it. */
.serial-recorded { display: flex; align-items: center; gap: 10px;
                   padding: 9px 12px; border-radius: 9px; background: #f0fdf4; }
.serial-recorded > div { flex: 1 1 auto; min-width: 0; }
.serial-recorded small { display: block; color: var(--muted); font-size: .72rem; }
.serial-recorded strong { font-variant-numeric: tabular-nums; letter-spacing: .02em;
                          word-break: break-all; }

.rates { border-color: #bfdbfe; background: #f8fbff; }

.rates .row { gap: 10px; align-items: center; }
.rate-price b { font-size: 1.5rem; }


/* The carrier's own logo. Constrained by HEIGHT, because the four marks have
   very different aspect ratios — the UPS shield is taller than it is wide, the
   USPS lockup is six times wider than tall — and sizing on width would make one
   of them a postage stamp next to a billboard. */
.carrier-logo { height: 22px; width: auto; max-width: 124px; object-fit: contain;
                flex-shrink: 0; display: block; }
.table .carrier-logo { height: 18px; max-width: 104px; }

/* Fallback for a carrier with no file yet: its name, plainly. */
.carrier { display: inline-flex; align-items: center; padding: 5px 9px; border-radius: 7px;
           font-size: .72rem; font-weight: 800; letter-spacing: .01em;
           color: var(--muted); background: #e2e8f0; white-space: nowrap; }
/* The code is what came back from ShipStation — `fedex_walleted`, `tusk` — so
   it is shown as it arrived rather than title-cased into a company that does
   not exist under that name. */
.carrier--text { text-transform: uppercase; }

.loading-labels { display: flex; flex-direction: row; align-items: center; gap: 14px;
                  border-color: #bfdbfe; background: #f8fbff; }
.spinner { flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%;
           border: 3px solid #bfdbfe; border-top-color: var(--primary);
           animation: wms-spin .8s linear infinite; }
@keyframes wms-spin { to { transform: rotate(360deg); } }

/* Warehouse screens are often left on for a whole shift. A reduced-motion
   setting is a request not to animate, not a request to hide the state. */
@media (prefers-reduced-motion: reduce) {
    .spinner { animation-duration: 2.4s; }
}

/* ---- Product catalogue ---- */
.coverage-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.coverage-grid > .card { margin: 0; }
.coverage-facts { gap: 22px; }
.coverage-fact b { display: block; font-size: 1.3rem; }
.coverage-fact small { color: var(--muted); font-size: .68rem; text-transform: uppercase;
                       letter-spacing: .05em; }
.product-table th,
.product-table tbody tr:not(.product-editor) > td { text-align: center; vertical-align: middle; }
.product-table th:first-child,
.product-table tbody tr:not(.product-editor) > td:first-child { text-align: left; }
.product-table tbody tr.row--warn td { background: #fffbeb; }
.product-table .muted { font-size: .74rem; }
.product-editor td { background: #f8fafc; }
.product-editor .panel { margin: 0; background: #fff; }
.panel-rule { border: 0; border-top: 1px solid var(--border); margin: 18px 0; }
.barcode-value, .barcode-readable { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                                    font-variant-numeric: tabular-nums; letter-spacing: .06em; }
.barcode-value { font-size: .75rem; white-space: nowrap; }
.barcode-readable { margin: 3px 0; font-size: 1.25rem; font-weight: 800; }
.barcode-editor { display: flex; align-items: center; justify-content: space-between; gap: 18px; }
.barcode-editor h4 { margin: 0 0 4px; }
.barcode-editor p:last-child { margin-bottom: 0; }
.barcode-actions { display: flex; align-items: end; flex-wrap: wrap; gap: 9px; }
.barcode-actions .field { margin: 0; }
.barcode-actions input { min-width: 190px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                         letter-spacing: .08em; }
.barcode-or { align-self: center; color: var(--muted); font-size: .72rem; text-transform: uppercase; }

/* The dispatch stop is a physical-safety instruction, not an ordinary amber
   information card. It is rendered first on the page and echoed in the sticky
   header so changing the date or scrolling the shipment table cannot hide it. */
.dispatch-cancellation-alert { border: 2px solid var(--danger); background: #fef2f2;
                               box-shadow: 0 0 0 3px rgba(220,38,38,.08); }
.dispatch-cancellation-alert__title { display: flex; align-items: center; gap: 9px; min-width: 0; }
.dispatch-cancellation-alert__title .material-icons { color: var(--danger); font-size: 1.6rem; }
.dispatch-cancellation-alert__title h2 { margin: 0; color: #991b1b; }
.dispatch-cancellation-alert .dispatch-cancellation-alert__badge {
    flex: 0 0 auto; background: var(--danger); color: #fff;
}

/* ---- Alert cards ----
   One card shape for every "a person must decide something about this order"
   surface: the dispatch stop list and the exception queue. They were formatted
   differently, and the dispatch one crammed an order number, a stage, a source,
   a tracking number and four separate upstream findings into two runs of small
   grey text — which reads as a paragraph and is scanned as noise. Each fact
   gets its own line, and the findings get one row each. */

.alert-list { display: flex; flex-direction: column; gap: 10px; }
.alert-card {
    background: var(--surface); border: 1px solid var(--border);
    border-left: 5px solid var(--muted); border-radius: 10px;
    padding: 12px 14px; display: flex; flex-direction: column; gap: 10px;
}
.alert-card--critical { border-left-color: var(--danger); background: #fffbfb; }
.alert-card--warn     { border-left-color: var(--warn); }
.alert-card--info     { border-left-color: var(--primary); }
.alert-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.alert-card__ref { min-width: 0; }
.alert-card__ref strong { display: block; font-size: 1.05rem; font-weight: 800; letter-spacing: -.01em; }
.alert-card__ref span { color: var(--muted); font-size: .78rem; }
/* Stage, source, tracking: each a labelled chip, so "picking" and "detected by
   multiple" stop running into the first sentence of the reason. */
.alert-facts { display: flex; flex-wrap: wrap; gap: 6px; }
.alert-fact {
    display: inline-flex; align-items: baseline; gap: 5px;
    background: #f1f5f9; border-radius: 7px; padding: 4px 8px;
    font-size: .74rem; color: var(--text); max-width: 100%; min-width: 0;
}
.alert-fact b { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.alert-fact span { color: var(--muted); text-transform: uppercase; letter-spacing: .04em; font-size: .64rem; }
/* One finding per row. A four-sentence blob is unreadable on a handheld and
   worse on a bench monitor, because nothing tells you it is four facts. */
.alert-reasons { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.alert-reasons li {
    display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 8px;
    align-items: start; font-size: .86rem; line-height: 1.35;
}
/* The Material Icons CODEPOINT for error_outline, not its ligature name.
   Generated content is not reliably ligated in every engine, and where it is
   not the row reads "error_outline" in words. */
.alert-reasons li::before {
    content: '\e001'; font-family: 'Material Icons'; font-size: 1rem;
    font-weight: normal; font-style: normal; letter-spacing: normal;
    text-transform: none; color: var(--danger); line-height: 1.25;
}
.alert-card__actions { display: flex; flex-wrap: wrap; gap: 8px; }
.alert-card__actions .btn { flex: 1 1 160px; }

/* ---- Dialogs ----
   Replaces window.confirm/prompt. Anchored to the bottom on a narrow screen so
   the buttons sit under the thumb and the on-screen keyboard does not cover
   them; centred once there is room. */

.wms-modals { position: fixed; inset: 0; z-index: 1000; pointer-events: none; }
.wms-modal {
    position: absolute; inset: 0; pointer-events: auto;
    background: rgba(15,23,42,.55);
    display: flex; align-items: flex-end; justify-content: center;
    padding: 12px; padding-bottom: max(12px, env(safe-area-inset-bottom));
}
.wms-modal__panel {
    background: var(--surface); border-radius: var(--radius);
    width: 100%; max-width: 520px; max-height: 88vh; overflow-y: auto;
    padding: 16px; display: flex; flex-direction: column; gap: var(--stack-card);
    box-shadow: 0 18px 50px rgba(15,23,42,.35);
    animation: modal-in .14s ease-out;
}
@keyframes modal-in { from { transform: translateY(14px); opacity: 0; } }
.wms-modal__panel--danger { border-top: 5px solid var(--danger); }
.wms-modal__panel--warn   { border-top: 5px solid var(--warn); }
.wms-modal__head { display: flex; align-items: center; gap: 10px; }
.wms-modal__head h2 { margin: 0; font-size: 1.1rem; }
.wms-modal__head .material-icons { font-size: 1.6rem; color: var(--muted); }
.wms-modal__panel--danger .wms-modal__head .material-icons { color: var(--danger); }
.wms-modal__panel--warn .wms-modal__head .material-icons { color: var(--warn); }
.wms-modal__body { display: flex; flex-direction: column; gap: var(--stack-card); }
.wms-modal__body p { margin: 0; font-size: .9rem; line-height: 1.4; }
.wms-modal__lines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px;
                    font-size: .86rem; }
.wms-modal__lines li { padding-left: 12px; border-left: 3px solid var(--border); }
.wms-modal__error { color: var(--danger); font-weight: 700; font-size: .84rem; }
.wms-modal__actions { display: flex; gap: 10px; }
.wms-modal__actions .btn { flex: 1; }
@media (min-width: 560px) {
    .wms-modal { align-items: center; }
}

.kpi-badge { display: inline-flex; align-items: center; padding: 5px 10px; border-radius: 20px;
             font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .04em;
             background: #e2e8f0; color: #475569; }
.badge-ok { background: #dcfce7; color: #15803d; }
.badge-risk { background: #fef3c7; color: #b45309; }
.badge-flagged { background: #fee2e2; color: #b91c1c; }

/* ---- Picker choice screens ---- */
/* minmax(0,1fr), not the implicit `auto`.
 *
 * An `auto` grid track is at least as wide as its items' min-content, and
 * .individual-order__items is one `white-space: nowrap` run of part numbers —
 * a six-line order reads "1× 7164999, 1× 470-510-3110-005-ML, 1× ..." with no
 * break in it. So the track grew to 768px whatever the screen was, every order
 * card in the list with it, and on a handheld the cards ran to twice the width
 * of the device. The ellipsis on that line was written to prevent exactly this
 * and never got the chance, because the box grew instead of clipping.
 *
 * Every other list here already had a minmax(0,…) column — .wave-order,
 * .order-line, .exception-panel__orders. These two were the ones that wrap
 * their content in a card rather than laying it out in grid columns, so there
 * was no column to clamp and the guard was missed. */
.pick-choice-list, .individual-order-list,
.alternate-bin-list, .order-line-list, .settings-statuses {
    grid-template-columns: minmax(0, 1fr);
}
.pick-choice-list, .individual-order-list { display: grid; gap: 10px; }
.pickcard { width: 100%; padding: 16px; text-align: left; border: 1px solid var(--border);
            border-radius: 12px; background: #fff; color: var(--text); font: inherit; cursor: pointer; }
.pickcard:active { border-color: var(--primary); transform: scale(.99); }
.pickcard--taken { border-color: #fcd34d; background: #fffbeb; }
.pickcard__big { margin: 10px 0 3px; font-size: 1.7rem; font-weight: 800; }
.search-row { display: flex; align-items: end; gap: 10px; }
.search-row .field { flex: 1; margin: 0; }
.section-heading { display: flex; align-items: flex-start; gap: 12px; }
.section-heading__icon { padding: 9px; border-radius: 10px; background: #dbeafe; color: var(--primary); }
.section-heading h3 { margin-bottom: 3px; }
.section-heading p { margin: 0; }

.individual-order { display: block; width: 100%; padding: 14px; text-align: left; border: 1px solid var(--border);
                    border-radius: 12px; background: #fff; color: var(--text); font: inherit; cursor: pointer; }
.individual-order:active { border-color: var(--primary); }
.individual-order--blocked { border-color: #fecaca; background: #fff7f7; }
.individual-order__head, .individual-order__foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.individual-order__head strong { font-size: 1rem; }
.individual-order__meta { margin-top: 6px; color: var(--muted); font-size: .78rem; }
.individual-order__items { margin: 8px 0; color: #334155; font-size: .8rem; white-space: nowrap;
                           overflow: hidden; text-overflow: ellipsis; }
.individual-order__foot { padding-top: 8px; border-top: 1px solid #eef2f7; font-size: .75rem; color: var(--muted); }
.individual-order__foot b { color: var(--text); }
.order-detail-head h2 { margin: 3px 0 4px; }
.order-detail-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 10px; }
.order-detail-grid > div { padding: 11px; border-radius: 9px; background: #f8fafc; }
.order-detail-grid small, .order-detail-grid strong { display: block; }
.order-detail-grid small { margin-bottom: 4px; color: var(--muted); font-size: .65rem; text-transform: uppercase; }
.order-detail-grid strong { font-size: .78rem; line-height: 1.4; }
.order-line-list { display: grid; }
.order-line { display: grid; grid-template-columns: minmax(0,1fr) auto 45px; align-items: center; gap: 12px;
              padding: 11px 0; border-bottom: 1px solid #eef2f7; }
.order-line:last-child { border-bottom: 0; }
.order-line strong, .order-line small { display: block; }
.order-line small { margin-top: 2px; color: var(--muted); }
.order-line > b { text-align: right; }
.staging-note { display: flex; align-items: flex-start; gap: 12px; border-color: #bfdbfe; background: #eff6ff; }
.staging-note > .material-icons { color: var(--primary); }
.staging-note p { margin: 4px 0 0; }
.alternate-bins { border-color: #93c5fd; }
.alternate-bins h3 { margin-bottom: 3px; }
.alternate-bins p { margin: 0; }
/* A chaotic-storage SKU can sit in twenty bins — verified live: AIGPX24SDJ1AG1
   is in exactly twenty. All of them stay in the list, but the list scrolls
   inside the card instead of pushing the scan fields off a handheld screen. */
.alternate-bin-list { display: grid; gap: 7px; max-height: 46vh; overflow-y: auto; }
/* The bin code sizes to itself and the counts take what is left: the code is
   what the operator walks to, and the old `1fr auto` broke OVERSTOCK-02 across
   two lines so that "9 on hand — not a pick face" could stay on one. */
.alternate-bin { display: grid; grid-template-columns: auto minmax(0,1fr) 24px; align-items: center; gap: 10px; width: 100%;
                 min-height: 48px; padding: 8px 11px; border: 1px solid var(--border); border-radius: 9px;
                 background: #fff; color: var(--text); text-align: left; cursor: pointer; }
/* The counts/reason column only. Scoped to a direct child so the "current"
   badge inside the first column keeps its own colour. */
.alternate-bin > span:not(.material-icons) { color: var(--muted); font-size: .75rem; text-align: right; }
.alternate-bin .material-icons { color: var(--primary); font-size: 1.1rem; }
.alternate-bin > div { display: flex; align-items: center; gap: 6px; }

/* A bin that exists and holds stock but cannot be picked from. Still legible —
   the quantity is the reason it is on screen — but plainly not a button. */
.alternate-bin--blocked { background: #f8fafc; border-style: dashed; cursor: default; }
.alternate-bin--blocked strong { color: var(--muted); font-weight: 600; }
.alternate-bin--blocked .material-icons { color: #94a3b8; }

.progress { height: 7px; overflow: hidden; border-radius: 8px; background: #dbe3ee; }
.progress__bar { height: 100%; border-radius: inherit; background: var(--primary); }
.stop { padding: 22px; text-align: center; border: 2px solid #93c5fd; border-radius: 14px; background: #eff6ff; }
.stop__label, .stop__hint { display: block; color: #64748b; font-size: .72rem; font-weight: 700;
                            text-transform: uppercase; letter-spacing: .08em; }
.stop__bin { display: block; margin: 4px 0; color: #1e3a8a; font-size: clamp(2.6rem, 13vw, 4.5rem);
             font-weight: 800; letter-spacing: -.04em; }
.stop__ok { display: inline-flex; align-items: center; gap: 5px; color: #15803d; font-size: .78rem; font-weight: 700; }
.stop__ok .material-icons { font-size: 1rem; }
.qty-need { text-align: center; }
.qty-need b { display: block; font-size: 1.8rem; }
.qty-need span { color: var(--muted); font-size: .68rem; text-transform: uppercase; }
.sku { font-size: 1.05rem; }

/* Unit tally on a multi-unit line. Sized to be readable at arm's length,
   because the operator checks it between scans without stopping walking. */
.scan-tally { display: flex; align-items: baseline; gap: 8px; padding: 9px 12px;
              border: 1px solid var(--border); border-radius: 9px; background: #f8fafc; }
.scan-tally b { font-size: 1.35rem; }
.scan-tally span { color: var(--muted); font-size: .74rem; text-transform: uppercase; letter-spacing: .05em; }
.scan-tally--done { border-color: #86efac; background: #f0fdf4; }
.scan-tally--done b { color: #15803d; }

.input--big { font-size: 1.25rem !important; font-weight: 700; }
.btn--xl { min-height: 66px; width: 100%; font-size: 1.15rem; }
.btn--warn { background: #d97706; }

/* ---- Wave planner ----
   The width and centring this page used to declare for itself are now what the
   admin shell does for every admin page; only the roomier padding is its own. */
body[data-route="waves"] .wms-main { padding: 24px; }
.wave-hero { display: flex; align-items: center; gap: 16px; padding: 20px 22px;
             border: 1px solid #bfdbfe; border-radius: 14px;
             background: linear-gradient(135deg,#eff6ff 0%,#fff 62%); }
.wave-hero__icon { padding: 13px; border-radius: 13px; background: var(--primary); color: #fff; font-size: 1.6rem; }
.wave-hero__copy { flex: 1; min-width: 0; }
.wave-hero h2, .section-title h2 { margin: 2px 0 4px; color: var(--ink); font-size: 1.25rem; }
.wave-hero p { margin: 0; color: var(--muted); font-size: .82rem; line-height: 1.5; }
.wave-hero--compact { margin-top: 2px; }
.eyebrow { color: var(--primary); font-size: .65rem; font-weight: 800; text-transform: uppercase; letter-spacing: .1em; }
.wave-planner-layout { display: grid; grid-template-columns: minmax(0,1fr) 310px; gap: 22px; align-items: start; }
.wave-planner-main { min-width: 0; }
.wave-planner-side { position: sticky; top: 84px; display: grid; gap: 12px; }
.section-title { display: flex; justify-content: space-between; align-items: end; gap: 12px; margin: 0 0 12px; }
.section-title h2 { margin-bottom: 0; }
.planner-controls__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.planner-controls .field--sm { min-width: 0; }
.planner-controls .field--sm:first-child { grid-column: 1 / -1; }
.planner-controls small { display: block; margin-top: 5px; color: var(--muted); font-size: .66rem; line-height: 1.35; }
.planner-summary { grid-template-columns: repeat(3,1fr); }
.planner-summary .stat { padding: 10px 6px; }
.planner-summary .stat b { font-size: 1.25rem; }
.planner-commit p { margin-top: 0; }

.wave-preview { overflow: hidden; margin-bottom: 12px; border: 1px solid var(--border); border-radius: 13px; background: #fff; }
.wave-preview__head { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-bottom: 1px solid #e8edf3; }
.wave-preview__number { display: grid; place-items: center; width: 34px; height: 34px; flex: 0 0 34px;
                        border-radius: 10px; background: #dbeafe; color: #1d4ed8; font-weight: 800; }
.wave-preview--pa_only .wave-preview__number { background: #dcfce7; color: #15803d; }
.wave-preview--mixed .wave-preview__number { background: #ede9fe; color: #6d28d9; }
.wave-preview__title { flex: 1; min-width: 0; }
.wave-preview__title h3 { margin: 0; font-size: .92rem; }
.wave-preview__title span { color: var(--muted); font-size: .7rem; }
.wave-preview__metrics { display: flex; gap: 16px; }
.wave-preview__metrics > span { text-align: center; }
.wave-preview__metrics b { display: block; font-size: .95rem; }
.wave-preview__metrics small { color: var(--muted); font-size: .62rem; text-transform: uppercase; }
/* The order list under a proposed wave. Its rows draw their own separators, so
   this is only here to stop the last one's border running into the card edge. */
.wave-preview__orders > .wave-order:last-child { border-bottom: 0; }
.wave-order { display: grid; grid-template-columns: minmax(0,1fr) auto 48px; align-items: center; gap: 12px;
              padding: 10px 16px; border-bottom: 1px solid #f0f3f7; }
.wave-order:last-child { border-bottom: 0; }
.wave-order__main { min-width: 0; }
.wave-order__main strong { display: block; font-size: .82rem; }
.wave-order__main span { display: block; overflow: hidden; color: var(--muted); font-size: .69rem;
                         text-overflow: ellipsis; white-space: nowrap; }
.wave-order__age { color: var(--muted); font-size: .68rem; }
.wave-order__units { text-align: right; font-size: .78rem; }

.exception-panel { margin-top: 14px; overflow: hidden; border: 1px solid var(--border); border-radius: 13px; background: #fff; }
.exception-panel--warn { border-color: #fcd34d; background: #fffbeb; }
.exception-panel > header { display: flex; gap: 10px; padding: 14px 16px; }
.exception-panel > header > .material-icons { color: #64748b; }
.exception-panel--warn > header > .material-icons { color: #d97706; }
.exception-panel h3 { margin: 0 0 2px; font-size: .88rem; }
.exception-panel p { margin: 0; color: var(--muted); font-size: .72rem; }
.exception-panel__orders > div { display: grid; grid-template-columns: 130px minmax(0,1fr) auto; gap: 12px;
                                padding: 8px 16px; border-top: 1px solid rgba(148,163,184,.2); font-size: .72rem; }
.exception-panel__orders span { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.exception-panel__orders b:last-child { color: #b45309; text-align: right; }

.live-wave-grid { display: grid; grid-template-columns: repeat(auto-fit,minmax(330px,1fr)); gap: 14px; }
.live-wave-card { padding: 16px; border: 1px solid var(--border); border-radius: 13px; background: #fff; }
.live-wave-card__head { display: flex; align-items: start; justify-content: space-between; gap: 12px; }
.live-wave-card__head h3 { margin: 0 0 3px; font-size: 1rem; }
.live-wave-card__facts { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; margin: 14px 0; }
.live-wave-card__facts > span { display: flex; align-items: center; justify-content: center; gap: 4px;
                                padding: 9px 6px; border-radius: 9px; background: #f8fafc; }
.live-wave-card__facts .material-icons { color: #64748b; font-size: 1rem; }
.live-wave-card__facts small { color: var(--muted); font-size: .68rem; }
.live-wave-card__owner { display: flex; flex-wrap: wrap; gap: 12px; padding: 10px 0; border-top: 1px solid #eef2f7;
                         color: var(--muted); font-size: .72rem; }
.live-wave-card__owner > span { display: inline-flex; align-items: center; gap: 5px; }
.live-wave-card__owner .material-icons { font-size: 1rem; }
.live-wave-card__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.live-wave-card__actions .btn { min-height: 40px; padding: 0 13px; font-size: .78rem; }

@media (max-width: 850px) {
    body[data-route="waves"] .wms-main { padding: 14px; }
    .wave-planner-layout { grid-template-columns: 1fr; }
    .wave-planner-side { position: static; grid-row: 1; }
    .wave-hero { align-items: flex-start; flex-wrap: wrap; }
    .wave-hero .btn { width: 100%; }
}
@media (max-width: 560px) {
    /* 20px a side is a fifth of a handheld's width. An admin page opened on
       one — a supervisor checking exceptions from the floor — gets the
       operator shell's padding instead. */
    .wms--admin .wms-main { padding: 12px; }
    /* Dispatch's shipped table is seven columns; on a phone it made the whole
       page wider than the screen, so the day filter and the totals scrolled
       sideways along with it. The table scrolls inside its own card instead.
       :has() is the enhancement here — where it is unsupported this rule is
       simply dropped and the page behaves as it did before. */
    .wms--admin .card:has(> .table) { overflow-x: auto; }
    .tabs { width: 100%; }
    .tab { flex: 1; padding: 0 8px; }
    /* "Carton 1" with its chips beside it. The heading cannot shrink, so at
       this width the chips go underneath rather than squeezing it onto two
       lines. Only this row: flex-wrap makes a row wrap INSTEAD of shrinking,
       which everywhere else costs more than it buys. */
    .carton > .row--between { flex-wrap: wrap; }
    .wave-preview__metrics { gap: 8px; }
    .wave-preview__metrics > span:nth-child(3) { display: none; }
    .wave-order { grid-template-columns: minmax(0,1fr) 42px; }
    .wave-order__age { display: none; }
    .exception-panel__orders > div { grid-template-columns: 1fr auto; }
    .exception-panel__orders span { display: none; }
    .search-row { align-items: stretch; flex-direction: column; }
    .coverage-grid { grid-template-columns: 1fr; }
    .barcode-editor { align-items: stretch; flex-direction: column; }
    .barcode-actions { align-items: stretch; flex-direction: column; }
    .barcode-actions .btn { width: 100%; }
    .barcode-or { align-self: center; }
    .order-detail-grid { grid-template-columns: 1fr; }
    .order-line { grid-template-columns: minmax(0,1fr) auto; }
    .order-line > span { grid-column: 1 / -1; grid-row: 2; }
}

/* ---- Transfers, serial history and operational reports ---- */

.move-progress {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
}
.move-step {
    display: flex; align-items: center; gap: 8px; min-width: 0;
    padding: 10px; border: 1px solid var(--border); border-radius: 10px; background: #fff;
}
.move-step > .material-icons { color: #94a3b8; font-size: 1.1rem; }
.move-step > div { min-width: 0; }
.move-step small, .move-step strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.move-step small { color: var(--muted); font-size: .62rem; text-transform: uppercase; letter-spacing: .05em; }
.move-step strong { font-size: .78rem; }
.move-step.is-current { border-color: var(--primary); background: #eff6ff; }
.move-step.is-current > .material-icons { color: var(--primary); }
.move-step.is-done > .material-icons { color: var(--ok); }
.transfer-summary { display: flex; align-items: center; justify-content: center; gap: 18px;
                    padding: 18px; border-radius: 10px; background: #f8fafc; font-size: 1.2rem; }
.transfer-summary .material-icons { color: var(--primary); }
.result-card { text-align: center; align-items: center; }
.result-card > .material-icons { color: var(--ok); font-size: 3rem; }
.result-card .btn-row { width: 100%; }

.timeline { position: relative; display: flex; flex-direction: column; gap: 0; }
.timeline__event { display: grid; grid-template-columns: 42px minmax(0,1fr); gap: 12px; position: relative; }
.timeline__event:not(:last-child)::before { content: ''; position: absolute; left: 20px; top: 39px; bottom: 0;
                                              width: 2px; background: var(--border); }
.timeline__icon { display: grid; place-items: center; align-self: start; width: 42px; height: 42px;
                  border-radius: 50%; background: #dbeafe; color: #1d4ed8; font-size: 1.15rem; z-index: 1; }
.timeline__body { background: #fff; border: 1px solid var(--border); border-radius: 11px; padding: 13px 15px;
                  margin-bottom: 12px; min-width: 0; }
.timeline__body strong, .timeline__body small { display: block; }
.timeline__body small { color: var(--muted); font-size: .68rem; margin-top: 2px; }
.timeline__body p { margin: 7px 0 0; font-size: .8rem; }
.timeline__body a { display: inline-block; margin-top: 8px; font-size: .75rem; }

.report-filters { display: flex; flex-direction: row; flex-wrap: wrap; align-items: end; }
.report-filters > .field { flex: 1 1 170px; margin: 0; }
.report-filters > .report-agent-filter { flex-grow: 1.5; }
.report-filters > .btn { flex: 0 0 auto; }
.report-kpis { grid-template-columns: repeat(auto-fit, minmax(145px, 1fr)); }
.report-kpis .stat small, .stat > small { display: block; margin-top: 3px; color: var(--muted); font-size: .62rem; }
.report-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px,1fr)); gap: var(--stack); }
.report-grid--charts { grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); align-items: stretch; }
.report-grid--charts > .card { min-width: 0; }
.report-section-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.report-section-title h2, .report-section-title h3, .report-section-title p { margin: 0; }
.report-section-title > small { color: var(--muted); white-space: nowrap; }
.agent-detail { display: contents; }
.agent-detail__header { border-left: 4px solid var(--primary); }
.agent-detail__header h2 { margin-top: 2px; }
.report-selection { display: flex; align-items: center; gap: 9px; padding: 9px 12px;
                    border-radius: 10px; color: #1e3a8a; background: #dbeafe; }
.report-selection > .btn { margin-left: auto; }
.age-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 8px; }
.bar-chart { display: flex; flex-direction: column; gap: 9px; }
.bar-chart__row { display: grid; grid-template-columns: 58px minmax(80px,1fr) 54px; gap: 10px; align-items: center; }
.bar-chart__row > span { color: var(--muted); font-size: .7rem; }
.bar-chart__row > b { text-align: right; font-size: .78rem; }
.bar-chart__track { height: 12px; overflow: hidden; border-radius: 8px; background: #e2e8f0; }
.bar-chart__track > i { display: block; height: 100%; border-radius: inherit; background: var(--primary); }
.chart-block { min-width: 0; }
.chart-legend { display: flex; flex-wrap: wrap; gap: 7px 13px; margin: 8px 0 12px; }
.chart-legend > span { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); font-size: .65rem; }
.chart-legend i, .donut-legend i { display: inline-block; width: 9px; height: 9px; border-radius: 3px; }
.chart-scroll { min-width: 0; overflow-x: auto; padding: 4px 0 2px; }
.stacked-chart { display: grid; grid-template-columns: repeat(var(--chart-days), minmax(32px, 1fr));
                 min-width: calc(var(--chart-days) * 34px); height: 225px; gap: 5px; align-items: end; }
.stacked-chart__column { display: grid; grid-template-rows: 18px 180px 18px; min-width: 0; text-align: center; }
.stacked-chart__column > b { align-self: end; font-size: .62rem; }
.stacked-chart__column > span { padding-top: 4px; color: var(--muted); font-size: .58rem; white-space: nowrap; }
.stacked-chart__well { display: flex; align-items: end; justify-content: center; border-bottom: 1px solid var(--border);
                       background: repeating-linear-gradient(to top, transparent 0, transparent 44px, #eef2f7 45px); }
.stacked-chart__bar { display: flex; flex-direction: column-reverse; width: min(72%, 28px); min-height: 3px;
                      overflow: hidden; border-radius: 4px 4px 0 0; transition: height .18s ease; }
.stacked-chart__bar > i { flex-basis: 0; min-height: 2px; }
.stacked-chart__bar.is-empty { background: #e2e8f0; opacity: .55; }
.donut-layout { display: grid; grid-template-columns: minmax(130px, .8fr) minmax(150px, 1fr);
                 gap: 22px; align-items: center; min-height: 230px; }
.donut-chart { display: grid; place-items: center; width: min(100%, 190px); aspect-ratio: 1; margin: auto;
               border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(15,23,42,.05); }
.donut-chart > div { display: grid; place-items: center; width: 56%; aspect-ratio: 1; border-radius: 50%;
                     background: var(--surface, #fff); box-shadow: 0 0 0 1px rgba(15,23,42,.04); }
.donut-chart b, .donut-chart span { display: block; }
.donut-chart b { font-size: 1.4rem; line-height: 1; }
.donut-chart span { color: var(--muted); font-size: .65rem; }
.donut-legend { display: flex; flex-direction: column; gap: 9px; }
.donut-legend > div { display: grid; grid-template-columns: 10px minmax(0,1fr) auto 34px; align-items: center; gap: 7px;
                      font-size: .72rem; }
.donut-legend small { color: var(--muted); text-align: right; }
.chart-toolbar { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 14px; }
.chart-toolbar .btn.is-active { color: #fff; border-color: var(--primary); background: var(--primary); }
.chart-hint { margin: 11px 0 0; }
.agent-chart { display: flex; flex-direction: column; gap: 7px; }
.agent-chart__row { display: grid; grid-template-columns: minmax(150px, .9fr) minmax(120px, 2fr) 62px;
                    gap: 12px; align-items: center; width: 100%; padding: 7px 9px; border: 1px solid transparent;
                    border-radius: 9px; color: inherit; background: transparent; text-align: left; cursor: pointer; }
.agent-chart__row:hover, .agent-chart__row:focus-visible { border-color: #bfdbfe; background: #eff6ff; }
.agent-chart__row.is-selected { border-color: var(--primary); background: #eff6ff; }
.agent-chart__row > span { min-width: 0; }
.agent-chart__row strong, .agent-chart__row small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agent-chart__row strong { font-size: .76rem; }
.agent-chart__row small { color: var(--muted); font-size: .6rem; }
.agent-chart__row > i { height: 13px; overflow: hidden; border-radius: 8px; background: #e2e8f0; }
.agent-chart__row > i > u { display: block; height: 100%; border-radius: inherit; background: var(--primary); text-decoration: none; }
.agent-chart__row > b { text-align: right; font-size: .75rem; }
.table-scroll { width: 100%; overflow-x: auto; }
.report-table { min-width: 600px; }
.report-table th:not(:first-child), .report-table td:not(:first-child) { text-align: right; }
.event-ledger th:first-child, .event-ledger td:first-child { white-space: nowrap; }
.event-ledger td:last-child { text-align: left; white-space: nowrap; }
.reference-chip { display: inline-block; margin-right: 6px; padding: 2px 5px; border-radius: 5px;
                  color: #334155; background: #e2e8f0; font-size: .58rem; text-transform: uppercase; }
.link-button { display: flex; flex-direction: column; align-items: flex-start; padding: 0; border: 0;
               color: var(--primary); background: transparent; text-align: left; cursor: pointer; }
.link-button small { color: var(--muted); font-size: .6rem; }
.agent-profile { border-left: 4px solid var(--primary); }
.agent-profile > div:first-child h2, .agent-profile > div:first-child p { margin: 2px 0 0; }
.agent-profile > .report-filters { margin-top: 14px; padding: 0; border: 0; box-shadow: none; }
.activity-ledger { min-width: 0; }
.activity-records { display: flex; flex-direction: column; gap: 4px; margin-top: 14px; }
.activity-record { overflow: hidden; border: 1px solid var(--border); border-radius: 11px; background: #fff; }
.activity-record__head { display: grid; grid-template-columns: 125px 90px minmax(170px,1.1fr) minmax(180px,1.4fr) 18px;
                         gap: 10px; align-items: center; min-height: 40px; padding: 0 13px;
                         background: #fff; cursor: pointer; list-style: none; }
.activity-record__head::-webkit-details-marker { display: none; }
.activity-record__head:hover { background: #f8fafc; }
.activity-record__head time { color: var(--muted); font-size: .68rem; white-space: nowrap; }
.activity-record__head > strong, .activity-record__context { min-width: 0; overflow: hidden;
                                                              text-overflow: ellipsis; white-space: nowrap; }
.activity-record__head > strong { font-size: .74rem; }
.activity-record__context { color: var(--muted); font-size: .64rem; }
.activity-record__chevron { color: var(--muted); font-size: 1rem; transition: transform .15s ease; }
.activity-record[open] .activity-record__chevron { transform: rotate(180deg); }
.activity-record__expanded { border-top: 1px solid #e2e8f0; background: #f8fafc; }
.activity-badge { display: inline-flex; justify-content: center; padding: 3px 6px; border-radius: 999px;
                  color: #1e3a8a; background: #dbeafe; font-size: .58rem; font-weight: 700; text-transform: uppercase; }
.activity-badge--putaway { color: #075985; background: #e0f2fe; }
.activity-badge--move { color: #5b21b6; background: #ede9fe; }
.activity-badge--pick { color: #92400e; background: #fef3c7; }
.activity-badge--pack { color: #9d174d; background: #fce7f3; }
.activity-badge--ship { color: #065f46; background: #d1fae5; }
.activity-badge--count { color: #334155; background: #e2e8f0; }
.activity-badge--quarantine { color: #991b1b; background: #fee2e2; }
.activity-record__items { display: flex; flex-direction: column; padding: 3px 13px; }
.activity-item { display: grid; grid-template-columns: minmax(180px,1.6fr) 45px minmax(145px,1fr) auto;
                 gap: 10px; align-items: center; min-width: 0; padding: 9px 0; border-bottom: 1px solid #eef2f7; }
.activity-item:last-child { border-bottom: 0; }
.activity-item > div { min-width: 0; }
.activity-item strong, .activity-item small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.activity-item strong { font-size: .74rem; }
.activity-item small { color: var(--muted); font-size: .62rem; }
.activity-item > b { font-size: .72rem; text-align: right; }
.activity-item__movement, .activity-item__count { color: #334155; font-size: .68rem; }
.activity-item__count { grid-column: 3 / -1; }
.activity-facts { display: flex; flex-wrap: wrap; gap: 7px; padding: 8px 13px 12px; }
.activity-facts > span { min-width: 90px; padding: 5px 8px; border-radius: 7px; background: #f1f5f9; }
.activity-facts small, .activity-facts b { display: block; }
.activity-facts small { color: var(--muted); font-size: .53rem; text-transform: uppercase; letter-spacing: .04em; }
.activity-facts b { margin-top: 2px; font-size: .66rem; overflow-wrap: anywhere; }
.activity-record__ref { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 7px 13px;
                        border-top: 1px solid #eef2f7; color: var(--muted); font-size: .58rem; }
.activity-record__ref code { font-size: .58rem; overflow-wrap: anywhere; }
.report-definition { flex-direction: row; align-items: flex-start; }
.report-definition > .material-icons { color: var(--primary); }

.audit-filters > .audit-search { flex-grow: 2; min-width: 260px; }
.audit-records { display: flex; flex-direction: column; gap: 3px; }
.audit-record { padding: 0; overflow: hidden; }
.audit-record__summary { display: grid; grid-template-columns: 146px minmax(125px,.9fr) minmax(110px,.7fr)
                        minmax(260px,2fr) minmax(140px,.9fr) 84px 18px; gap: 9px; align-items: center;
                        min-height: 38px; padding: 0 11px; background: #fff; cursor: pointer; list-style: none; }
.audit-record > .audit-record__summary { margin-bottom: 0; }
.audit-record__summary::-webkit-details-marker { display: none; }
.audit-record__summary:hover { background: #f8fafc; }
.audit-record__summary > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.audit-record__summary time { color: var(--muted); font-size: .64rem; }
.audit-record__summary strong { font-size: .7rem; }
.audit-record__subject, .audit-record__operator { color: #334155; font-size: .65rem; }
.audit-record__key-facts { display: flex; align-items: center; gap: 12px; }
.audit-record__key-facts > span { display: inline-flex; align-items: baseline; gap: 4px; min-width: 0; }
.audit-record__key-facts small { flex-shrink: 0; color: var(--muted); font-size: .5rem;
                                 font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
.audit-record__key-facts b { overflow: hidden; text-overflow: ellipsis; font-size: .65rem; }
.audit-record__key-facts em { color: var(--muted); font-size: .6rem; font-style: normal; }
.audit-record__summary > .badge { justify-self: start; }
.audit-record__chevron { color: var(--muted); font-size: 1rem; transition: transform .15s ease; }
.audit-record[open] .audit-record__chevron { transform: rotate(180deg); }
.audit-record__mount:empty { display: none; }
.audit-record__expanded { border-top: 1px solid #e2e8f0; background: #f8fafc; }
.audit-facts { display: flex; flex-wrap: wrap; gap: 7px; padding: 11px 14px; }
.audit-facts > span { min-width: 110px; max-width: 260px; padding: 6px 8px; border-radius: 7px; background: #f1f5f9; }
.audit-facts small, .audit-facts b { display: block; }
.audit-facts small { color: var(--muted); font-size: .52rem; text-transform: uppercase; letter-spacing: .04em; }
.audit-facts b { margin-top: 2px; overflow-wrap: anywhere; font-size: .65rem; }
.audit-detail { border-top: 1px solid #eef2f7; }
.audit-detail h4 { margin: 0; padding: 9px 14px; color: var(--primary); font-size: .68rem; }
.audit-detail pre { max-height: 420px; margin: 0; padding: 12px 14px; overflow: auto; color: #dbeafe;
                    background: #0f172a; font: .66rem/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; }
.audit-pager { display: flex; align-items: center; justify-content: center; gap: 14px; color: var(--muted); font-size: .72rem; }

@media (max-width: 900px) {
    .report-grid--charts { grid-template-columns: minmax(0,1fr); }
}

@media (max-width: 560px) {
    .move-step { padding: 8px 6px; flex-direction: column; text-align: center; gap: 3px; }
    .move-step > .material-icons { font-size: 1rem; }
    .age-grid { grid-template-columns: repeat(2,1fr); }
    .report-filters > .field { flex-basis: 120px; }
    .report-filters > .btn { width: 100%; }
    .report-section-title { flex-wrap: wrap; }
    .donut-layout { grid-template-columns: 1fr; gap: 14px; }
    .donut-chart { width: 150px; }
    .agent-chart__row { grid-template-columns: minmax(95px,.9fr) minmax(70px,1fr) 48px; gap: 7px; padding: 7px 4px; }
    .agent-chart__row small { display: none; }
    .activity-record__head { grid-template-columns: minmax(85px,.8fr) 78px minmax(0,1.2fr) 16px;
                             gap: 6px; padding: 0 8px; }
    .activity-record__context { display: none; }
    .activity-item { grid-template-columns: minmax(0,1fr) 40px; }
    .activity-item__movement, .activity-item__count { grid-column: 1 / -1; }
    .audit-record__summary { grid-template-columns: 88px minmax(90px,.8fr) minmax(0,1.2fr) 72px 16px;
                             gap: 6px; min-height: 36px; padding: 0 8px; }
    .audit-record__subject, .audit-record__operator { display: none; }
    .audit-record__summary time { direction: rtl; text-overflow: clip; }
    .audit-record__key-facts { gap: 7px; }
    .audit-record__key-facts > span:nth-child(n+3) { display: none; }
    .audit-record__key-facts small { display: none; }
    .audit-filters > .audit-search { min-width: 100%; }
}

/* ---- Toasts ---- */

.wms-toasts {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 900;
    padding: 14px; padding-bottom: max(14px, env(safe-area-inset-bottom));
    display: flex; flex-direction: column; gap: 8px; pointer-events: none;
}
.toast {
    background: var(--ink); color: #fff; padding: 14px 16px; border-radius: 10px;
    font-size: .92rem; font-weight: 600; display: flex; align-items: center; gap: 10px;
    box-shadow: 0 10px 30px rgba(15,23,42,.3); pointer-events: auto;
    animation: toast-in .16s ease-out;
}
@keyframes toast-in { from { transform: translateY(10px); opacity: 0; } }
.toast--ok    { background: #15803d; }
.toast--error { background: var(--danger); }
.toast--warn  { background: var(--warn); }
.toast .material-icons { font-size: 1.2rem; }

@media (prefers-reduced-motion: reduce) {
    * { animation: none !important; transition: none !important; }
}


/* Map-editor palette. Aliases onto the shell tokens above rather than defining
   a second, slightly-different set of greys — the map is part of the same app
   and drifting palettes are how it stops looking like it. The rack/blocked
   colours are map-specific and have no shell equivalent. */
:root {
    --wms-bg:        var(--bg);
    --wms-surface:   var(--surface);
    --wms-border:    var(--border);
    --wms-muted:     var(--muted);
    --wms-text:      var(--text);
    --wms-primary:   var(--primary);
    --wms-ok:        var(--ok);
    --wms-warn:      var(--warn);
    --wms-danger:    var(--danger);
    --wms-rack:      #93c5fd;
    --wms-rack-edge: #2563eb;
    --wms-blocked:   #94a3b8;
    --wms-area:      #c7d2fe;
    --wms-radius:    10px;
}

/* ==========================================================================
   Map editor
   ========================================================================== */

.wms-map {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    background: var(--wms-bg);
}

/* ---- Top bar ---- */

.wms-map__bar {
    display: flex;
    align-items: center;
    gap: 18px;
    padding: 10px 16px;
    background: var(--wms-surface);
    border-bottom: 1px solid var(--wms-border);
    flex-wrap: wrap;
}

.wms-map__title { display: flex; align-items: center; gap: 10px; min-width: 210px; }
.wms-map__title > .material-icons {
    font-size: 1.6rem; color: var(--wms-primary);
    background: rgba(59,125,221,.1); border-radius: 10px; padding: 8px;
}
.wms-map__title h5 { margin: 0; font-weight: 600; font-size: .98rem; color: var(--wms-text); }
.wms-map__title small { color: var(--wms-muted); font-size: .76rem; }

.wms-tools { display: flex; gap: 4px; flex: 1; flex-wrap: wrap; }

.wms-tool {
    display: flex; flex-direction: column; align-items: center; gap: 1px;
    min-width: 62px; padding: 6px 8px;
    background: transparent; border: 1px solid transparent; border-radius: 8px;
    color: var(--wms-muted); cursor: pointer;
    transition: background .12s, color .12s, border-color .12s;
}
.wms-tool .material-icons { font-size: 1.15rem; }
.wms-tool em { font-style: normal; font-size: .68rem; font-weight: 600; letter-spacing: .02em; }
.wms-tool:hover { background: #eef2f7; color: var(--wms-text); }
.wms-tool.is-active {
    background: rgba(59,125,221,.12);
    border-color: rgba(59,125,221,.35);
    color: var(--wms-primary);
}

.wms-map__actions { display: flex; align-items: center; gap: 8px; }
.wms-map__actions .btn { display: inline-flex; align-items: center; gap: 5px; }
.wms-map__actions .material-icons { font-size: 1rem; }

.wms-route-picker {
    display: inline-flex; align-items: center; gap: 5px; min-width: 230px;
    padding: 4px 7px; border: 1px solid var(--wms-border); border-radius: 8px;
    background: #fff; color: var(--wms-primary);
}
.wms-route-picker select {
    width: 100%; min-width: 0; border: 0; outline: 0; background: transparent;
    color: var(--wms-text); font: 600 .74rem/1.2 Inter, sans-serif; cursor: pointer;
}
.wms-route-picker select:disabled { color: var(--wms-muted); cursor: wait; }
.wms-route-summary { max-width: 210px; color: var(--wms-muted); font-size: .7rem; line-height: 1.25; }

.wms-dirty {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: .74rem; font-weight: 600; color: var(--wms-warn);
    background: rgba(245,158,11,.12); padding: 4px 9px; border-radius: 20px;
}
.wms-dirty .material-icons { font-size: .85rem; }

/* ---- Body ---- */

.wms-map__body { display: flex; flex: 1; min-height: 0; }

/* ---- Tray ---- */

.wms-tray {
    width: 230px; flex-shrink: 0;
    background: var(--wms-surface);
    border-right: 1px solid var(--wms-border);
    padding: 12px; display: flex; flex-direction: column; min-height: 0;
}
.wms-tray__head { display: flex; align-items: center; justify-content: space-between; }
.wms-tray__head h6 { margin: 0; font-size: .78rem; font-weight: 700; text-transform: uppercase;
                     letter-spacing: .05em; color: var(--wms-muted); }
.wms-count {
    background: #eef2f7; color: var(--wms-muted); border-radius: 20px;
    padding: 1px 8px; font-size: .72rem; font-weight: 700;
}
.wms-hint { font-size: .72rem; color: var(--wms-muted); margin: 8px 0; line-height: 1.45; }

.wms-tray__list { overflow-y: auto; flex: 1; margin: 0 -4px; padding: 0 4px; }

.wms-chip {
    display: flex; align-items: center; justify-content: space-between; gap: 6px;
    width: 100%; margin-bottom: 4px; padding: 7px 9px;
    background: #f8fafc; border: 1px solid var(--wms-border); border-radius: 8px;
    font-size: .8rem; font-weight: 600; color: var(--wms-text);
    cursor: pointer; text-align: left;
}
.wms-chip:hover { border-color: var(--wms-primary); background: #eff6ff; }
.wms-chip.is-armed {
    border-color: var(--wms-primary); background: rgba(59,125,221,.12);
    box-shadow: 0 0 0 3px rgba(59,125,221,.12);
}
.wms-chip small { font-weight: 500; color: var(--wms-muted); font-size: .68rem; }
.wms-tray__done { font-size: .76rem; color: var(--wms-ok); font-weight: 600; padding: 10px 2px; }

/* ---- Canvas ---- */

.wms-canvas { position: relative; flex: 1; overflow: hidden; background: #eef2f7; min-width: 0; }
.wms-canvas svg { width: 100%; height: 100%; display: block; cursor: default; }
.wms-canvas.is-panning svg { cursor: grabbing; }
.wms-canvas.is-drawing svg { cursor: crosshair; }
.wms-canvas.is-placing svg { cursor: copy; }

/* Bays */
.bay rect.body {
    fill: var(--wms-rack); stroke: var(--wms-rack-edge);
    stroke-width: 1.5; rx: 2;
}
.bay text { font-family: Inter, sans-serif; font-weight: 700; fill: #0f172a; pointer-events: none; }
.bay text.meta { font-weight: 500; fill: #1e40af; }
.bay.is-selected rect.body { stroke: #1d4ed8; stroke-width: 3; }
.bay.is-unreachable rect.body { fill: #fecaca; stroke: var(--wms-danger); stroke-dasharray: 4 2; }
.bay.is-clashing rect.body { stroke: var(--wms-warn); stroke-width: 3; }
.bay.is-route-stop rect.body { stroke: #ef4444; stroke-width: 3; }
.bay .shelfline { stroke: rgba(37,99,235,.35); stroke-width: .75; pointer-events: none; }

/* Shapes */
.shape rect.body { stroke-width: 1.5; rx: 2; }
.shape[data-type="blocked"]  rect.body { fill: url(#blockedHatch); stroke: var(--wms-blocked); }
.shape[data-type="wall"]     rect.body { fill: #64748b; stroke: #475569; }
.shape[data-type="obstacle"] rect.body { fill: #cbd5e1; stroke: #64748b; }
.shape[data-type="office"]   rect.body { fill: #e2e8f0; stroke: #94a3b8; }
.shape[data-type="dock"]     rect.body { fill: #fde68a; stroke: #d97706; }
.shape[data-type="area"]     rect.body { fill: var(--wms-area); stroke: #6366f1;
                                         fill-opacity: .35; stroke-dasharray: 6 3; }
.shape[data-type="note"]     rect.body { fill: transparent; stroke: transparent; }
.shape text { font-family: Inter, sans-serif; font-size: 11px; font-weight: 600;
              fill: #334155; pointer-events: none; }
.shape.is-selected rect.body { stroke: var(--wms-primary); stroke-width: 3; }

/* Resize handles */
.handle { fill: #fff; stroke: var(--wms-primary); stroke-width: 1.5; cursor: nwse-resize; }
.handle.ne, .handle.sw { cursor: nesw-resize; }

/* Origin marker */
.origin circle { fill: var(--wms-ok); stroke: #fff; stroke-width: 2; }
.origin text { font-family: Inter, sans-serif; font-size: 10px; font-weight: 700; fill: #166534; }

/* Marquee while drawing */
.marquee { fill: rgba(59,125,221,.15); stroke: var(--wms-primary); stroke-width: 1.5; stroke-dasharray: 5 3; }

/* A selected order wave, routed by the same walk grid used for sequencing. */
.wave-route__halo { fill: none; stroke: rgba(255,255,255,.92); stroke-width: 8;
                    stroke-linejoin: round; stroke-linecap: round; }
.wave-route__line { fill: none; stroke: #ef4444; stroke-width: 3.5;
                    stroke-linejoin: round; stroke-linecap: round; }
.wave-route__stop circle { fill: #fff; stroke: #dc2626; stroke-width: 3; }
.wave-route__stop text { fill: #991b1b; font: 800 8px/1 Inter, sans-serif; }
.wave-route__stop text.wave-route__label { paint-order: stroke; stroke: #fff; stroke-width: 3px;
                                           stroke-linejoin: round; font-size: 10px; }
.wave-route__stop.is-unreachable circle { fill: #fee2e2; stroke-dasharray: 3 2; }

/* Zoom + legend */
.wms-zoom {
    position: absolute; right: 14px; bottom: 14px;
    display: flex; align-items: center; gap: 2px;
    background: var(--wms-surface); border: 1px solid var(--wms-border);
    border-radius: 8px; padding: 3px; box-shadow: 0 2px 8px rgba(15,23,42,.08);
}
.wms-zoom button {
    display: flex; border: 0; background: transparent; border-radius: 6px;
    padding: 4px; color: var(--wms-muted); cursor: pointer;
}
.wms-zoom button:hover { background: #eef2f7; color: var(--wms-text); }
.wms-zoom span { font-size: .72rem; font-weight: 600; color: var(--wms-muted);
                 min-width: 42px; text-align: center; }

.wms-legend {
    position: absolute; left: 14px; bottom: 14px;
    display: flex; gap: 12px; align-items: center;
    background: rgba(255,255,255,.94); border: 1px solid var(--wms-border);
    border-radius: 8px; padding: 6px 10px; font-size: .7rem; color: var(--wms-muted);
}
.wms-legend span { display: inline-flex; align-items: center; gap: 5px; }
.sw { width: 11px; height: 11px; border-radius: 2px; display: inline-block; border: 1px solid; }
.sw-bay     { background: var(--wms-rack);  border-color: var(--wms-rack-edge); }
.sw-blocked { background: #e2e8f0;          border-color: var(--wms-blocked); }
.sw-area    { background: var(--wms-area);  border-color: #6366f1; }
.sw-route   { background: #ef4444;          border-color: #b91c1c; }
.sw-warn    { background: #fecaca;          border-color: var(--wms-danger); }

/* ---- Properties panel ---- */

.wms-props {
    width: 265px; flex-shrink: 0;
    background: var(--wms-surface); border-left: 1px solid var(--wms-border);
    padding: 12px; overflow-y: auto;
}
.wms-props__empty { text-align: center; padding: 26px 8px; color: var(--wms-muted); }
.wms-props__empty .material-icons { font-size: 2rem; opacity: .35; }
.wms-props__empty p { font-size: .78rem; margin: 8px 0 14px; }

.wms-keys { list-style: none; padding: 0; margin: 0; text-align: left;
            font-size: .72rem; line-height: 2; }
.wms-keys kbd {
    background: #eef2f7; border: 1px solid var(--wms-border); border-bottom-width: 2px;
    border-radius: 4px; padding: 1px 5px; font-size: .68rem; color: var(--wms-text);
}

.wms-field { margin-bottom: 11px; }
.wms-field label {
    display: block; font-size: .68rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .05em; color: var(--wms-muted); margin-bottom: 4px;
}
.wms-field input[type="text"], .wms-field input[type="number"], .wms-field select {
    width: 100%; padding: 6px 9px; font-size: .82rem;
    border: 1px solid var(--wms-border); border-radius: 7px; background: #fff;
}
.wms-field input:focus, .wms-field select:focus {
    outline: 0; border-color: var(--wms-primary); box-shadow: 0 0 0 3px rgba(59,125,221,.15);
}
.wms-field .row2 { display: flex; gap: 7px; }
.wms-check { display: flex; align-items: center; gap: 7px; font-size: .8rem; color: var(--wms-text); }

.wms-props h6 {
    font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
    color: var(--wms-muted); margin: 0 0 10px;
}
.wms-props__code {
    font-size: 1rem; font-weight: 700; color: var(--wms-text); margin-bottom: 2px;
}
.wms-props__sub { font-size: .72rem; color: var(--wms-muted); margin-bottom: 12px; }

.wms-swatches { display: flex; gap: 5px; flex-wrap: wrap; }
.wms-swatch { width: 22px; height: 22px; border-radius: 5px; border: 2px solid transparent; cursor: pointer; }
.wms-swatch.is-active { border-color: #0f172a; }

/* ---- Stats ---- */

.wms-stats { border-top: 1px solid var(--wms-border); margin-top: 14px; padding-top: 12px; }
.wms-stat { display: flex; justify-content: space-between; align-items: baseline;
            font-size: .78rem; padding: 3px 0; }
.wms-stat span:first-child { color: var(--wms-muted); }
.wms-stat span:last-child  { font-weight: 700; color: var(--wms-text); }
.wms-stat.is-warn span:last-child { color: var(--wms-warn); }
.wms-stat.is-bad  span:last-child { color: var(--wms-danger); }
.wms-note { font-size: .7rem; color: var(--wms-muted); line-height: 1.5; margin-top: 8px; }

/* ---- Toast ---- */

.wms-toast {
    position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
    background: #0f172a; color: #fff; padding: 10px 18px; border-radius: 9px;
    font-size: .84rem; z-index: 2000; box-shadow: 0 8px 26px rgba(15,23,42,.28);
    display: flex; align-items: center; gap: 8px; max-width: 640px;
}
.wms-toast .material-icons { font-size: 1.05rem; }
.wms-toast.is-error { background: var(--wms-danger); }
.wms-toast.is-ok    { background: #15803d; }

@media (max-width: 1200px) {
    .wms-tray, .wms-props { width: 200px; }
    .wms-tool em { display: none; }
    .wms-tool { min-width: 40px; }
    .wms-route-picker { min-width: 190px; }
    .wms-route-summary { display: none; }
}

/* ---- Print station ----
   A desktop page that is left open all day, usually minimised behind the
   operator's real work. So the state has to be readable from across a bench at
   a glance, not read carefully: one big coloured band that says ready, busy or
   broken, and everything else secondary to it. */

.station-hero {
    display: flex; align-items: center; gap: 16px;
    padding: 18px 20px; border-radius: var(--radius);
    background: var(--surface); border: 1px solid var(--border);
    border-left: 6px solid var(--muted);
}
.station-hero__icon { font-size: 2.4rem; color: var(--muted); }
.station-hero__body { flex: 1; min-width: 0; }
.station-hero__body h2 { margin: 0; font-size: 1.3rem; font-weight: 700; }
.station-hero__body p  { margin: 3px 0 0; font-size: .84rem; color: var(--muted); }
.station-hero__count { text-align: right; }
.station-hero__count strong { display: block; font-size: 1.9rem; line-height: 1; }
.station-hero__count span   { font-size: .72rem; color: var(--muted); }

.station-hero--ok   { border-left-color: var(--ok); }
.station-hero--ok   .station-hero__icon { color: var(--ok); }
.station-hero--wait { border-left-color: var(--warn); }
.station-hero--wait .station-hero__icon { color: var(--warn); }
.station-hero--bad  { border-left-color: var(--danger); background: #fff5f5; }
.station-hero--bad  .station-hero__icon { color: var(--danger); }

.station-facts { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin: 0; }
.station-facts dt { font-size: .78rem; color: var(--muted); }
.station-facts dd { margin: 0; font-size: .82rem; font-weight: 600; }

.station-log { list-style: none; margin: 0; padding: 0; display: flex;
               flex-direction: column; gap: 8px; }
.station-log li { display: flex; align-items: flex-start; gap: 9px;
                  padding-bottom: 8px; border-bottom: 1px solid var(--border); }
.station-log li:last-child { border-bottom: 0; padding-bottom: 0; }
.station-log .material-icons { font-size: 1.1rem; }
.station-log li.is-ok  .material-icons { color: var(--ok); }
.station-log li.is-bad .material-icons { color: var(--danger); }
.station-log strong { display: block; font-size: .84rem; }
.station-log small  { font-size: .72rem; color: var(--muted); }

.station-footnote { font-size: .72rem; color: var(--muted); line-height: 1.5;
                    text-align: center; margin: 4px 0 0; }

/* Two ways to print one label, so they must not read as one wide button. */
.barcode-print-actions { display: flex; flex-wrap: wrap; gap: 8px; }

/* ---- Print queue panel ----
   Opened from the printer chip, on any screen. Anchored under the header rather
   than centred as a modal: it belongs to the chip that opened it, and an
   operator holding a handheld one-handed can reach the top-right corner. */

.print-queue {
    position: fixed; z-index: 1500;
    top: calc(var(--tap) + 18px); right: 10px;
    width: min(420px, calc(100vw - 20px));
    max-height: calc(100vh - var(--tap) - 38px);
    max-height: calc(100dvh - var(--tap) - 38px); overflow-y: auto;
    background: var(--surface); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: 0 14px 44px rgba(15,23,42,.26);
    padding: 12px 14px;
    display: flex; flex-direction: column; gap: 10px;
}
.print-queue__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.print-queue__head strong { font-size: .95rem; }
.print-queue__close {
    display: flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; border: 0; border-radius: 8px;
    background: transparent; color: var(--muted); cursor: pointer;
}
.print-queue__close:active { background: var(--bg); }

.print-queue__station { display: flex; align-items: flex-start; gap: 8px; margin: 0;
                        font-size: .82rem; line-height: 1.4; }
.print-queue__station .material-icons { font-size: 1.05rem; flex-shrink: 0; }
.print-queue__station.is-ok  { color: var(--ok); }
.print-queue__station.is-bad { color: var(--danger); }

.print-queue__loading, .print-queue__empty, .print-queue__hint {
    margin: 0; font-size: .8rem; color: var(--muted); line-height: 1.5;
}
.print-queue__error { margin: 0; font-size: .82rem; color: var(--danger); line-height: 1.45; }

.print-queue__list, .print-queue__stations { list-style: none; margin: 0; padding: 0;
                                             display: flex; flex-direction: column; gap: 8px; }

.print-queue__job { display: flex; align-items: flex-start; justify-content: space-between;
                    gap: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
.print-queue__job:last-child { border-bottom: 0; padding-bottom: 0; }
.print-queue__job-main { min-width: 0; flex: 1; }
.print-queue__job-main strong { display: block; font-size: .84rem; }
.print-queue__job-main small  { display: block; font-size: .72rem; color: var(--muted); }
.print-queue__job-main em     { display: block; font-size: .72rem; color: var(--danger);
                                font-style: normal; line-height: 1.4; margin-top: 2px; }
.print-queue__who { font-weight: 600; }
.print-queue__job-actions { display: flex; gap: 6px; flex-shrink: 0; }
/* The status is carried by a coloured left edge as well as the words, so a
   failed label is findable by scanning down the list rather than by reading it. */
.print-queue__job.is-failed    { border-left: 3px solid var(--danger); padding-left: 8px; }
.print-queue__job.is-claimed   { border-left: 3px solid var(--warn);   padding-left: 8px; }
.print-queue__job.is-cancelled { opacity: .6; }

.print-queue__pick {
    display: flex; align-items: center; gap: 10px; width: 100%;
    min-height: var(--tap); padding: 8px 10px; text-align: left;
    background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
    color: var(--text); cursor: pointer;
}
.print-queue__pick:active { background: #e8edf5; }
.print-queue__pick .material-icons { color: var(--muted); }
.print-queue__pick-body strong { display: block; font-size: .86rem; }
.print-queue__pick-body small  { display: block; font-size: .72rem; color: var(--muted); }

/* Queue rows on the station page. The station is read from across a bench, so
   rows are taller than the panel's and the action buttons keep a real tap
   target rather than shrinking to fit. */
.station-log__body { flex: 1; min-width: 0; }
.station-log__body em { display: block; font-size: .74rem; color: var(--danger);
                        font-style: normal; line-height: 1.4; margin-top: 2px; }
.station-log__who { font-weight: 600; color: var(--text) !important; }
.station-log__actions { display: flex; gap: 6px; flex-shrink: 0; }
.station-log li.is-wait .material-icons { color: var(--warn); }

/* Separate printer selection from setup instructions. */
.print-queue__section { display: flex; flex-direction: column; gap: 8px;
                        border-top: 1px solid var(--border); padding-top: 10px; }
.print-queue__section > strong,
.print-queue__section-head strong { font-size: .78rem; text-transform: uppercase;
                                    letter-spacing: .04em; color: var(--muted); }
.print-queue__section-head { display: flex; align-items: center;
                             justify-content: space-between; gap: 10px; }
/* Setup instructions are a short checklist, separate from everyday launch. */
.print-setup { display: grid; gap: 12px; }
.print-setup__install { display: grid; gap: 12px; padding: 14px;
    background: var(--bg); border: 1px solid var(--border); border-radius: 12px; }
.print-setup__heading { display: grid; gap: 3px; }
.print-setup__heading strong, .print-setup__open > strong { font-size: .9rem; }
.print-setup__heading > span { font-size: .75rem; color: var(--muted); }
.print-setup__download { width: 100%; }
.print-setup__steps { counter-reset: setup-step; list-style: none; margin: 0;
    padding: 0; display: grid; gap: 14px; }
.print-setup__steps li { counter-increment: setup-step; position: relative;
    padding-left: 36px; min-height: 26px; }
.print-setup__steps li::before { content: counter(setup-step); position: absolute;
    left: 0; top: 0; width: 25px; height: 25px; border-radius: 50%; display: grid;
    place-items: center; background: var(--surface); border: 1px solid var(--border);
    color: var(--primary); font-size: .78rem; font-weight: 700; }
.print-setup__steps strong { display: block; font-size: .82rem; line-height: 1.5; }
.print-setup__steps p { margin: 3px 0 0; font-size: .8rem; line-height: 1.5;
    color: var(--muted); overflow-wrap: anywhere; }
.print-setup__help { border-top: 1px solid var(--border); padding-top: 10px; }
.print-setup__help summary { cursor: pointer; color: var(--muted); font-size: .78rem;
    padding: 4px 0; }
.print-setup__help[open] p { margin-top: 8px; }
.print-setup__open { display: grid; gap: 8px; padding: 2px 2px 0; }

/* The chosen printer. A border alone is too subtle next to a radio glyph that
   already carries the state, so it gets both. */
.print-queue__pick.is-active { border-color: var(--primary); background: #eff6ff; }
.print-queue__pick.is-active .material-icons { color: var(--primary); }

/* ── Three-way stock comparison ──────────────────────────────────────────
   Shopify, Brightpearl and the WMS side by side on one row. The WMS column is
   the reference — it is the only figure that is a fact — so it is the one
   drawn plainly and the other two are coloured only when they disagree with
   it. Colouring all three would make every interesting row a wall of red and
   leave nothing for the eye to anchor on. */
.trio { display: grid; grid-template-columns: repeat(3, minmax(52px, 1fr));
        gap: 4px; text-align: right; flex: 0 0 auto; }
.trio__cell { padding: 2px 6px; border-radius: 8px; background: var(--surface);
              border: 1px solid var(--border); }
.trio__cell b { display: block; font-size: 1.05rem; font-weight: 800; line-height: 1.2; }
.trio__cell span { font-size: .58rem; color: var(--muted); text-transform: uppercase;
                   letter-spacing: .04em; }

/* The shelf. Never flagged, because it is what the others are measured against. */
.trio__cell.is-truth { background: #f8fafc; border-color: #cbd5e1; }
.trio__cell.is-truth b { color: #0f172a; }

/* Below the shelf count: this system is selling stock we do not have. */
.trio__cell.is-short { background: #fef2f2; border-color: #fecaca; }
.trio__cell.is-short b { color: var(--danger); }

/* Above the shelf count: stock this system cannot sell. Costs money slowly
   rather than immediately, so it is warned rather than alarmed. */
.trio__cell.is-long { background: #fffbeb; border-color: #fde68a; }
.trio__cell.is-long b { color: var(--warn); }

/* ── Live progress for a refresh or a correction ─────────────────────────
   A long operation an operator is watching has to say what it is doing, how
   far along it is, and what it decided about each line — a spinner and a final
   count leaves them guessing whether anything happened at all. */
.runlog__head { display: flex; justify-content: space-between; align-items: baseline;
                gap: 10px; margin-bottom: 6px; }
.runlog__head b { font-size: .92rem; }
.runlog__head span { font-size: .74rem; color: var(--muted); }
.runlog__lines { margin-top: 10px; max-height: 260px; overflow-y: auto;
                 border: 1px solid var(--border); border-radius: 10px; }
.runlog__line { display: flex; align-items: center; gap: 8px; padding: 6px 10px;
                border-bottom: 1px solid var(--border); font-size: .78rem; }
.runlog__line:last-child { border-bottom: 0; }
.runlog__line strong { font-weight: 700; }
.runlog__line small { color: var(--muted); margin-left: auto; text-align: right; }
.runlog__line .material-icons { font-size: 1rem; }
.runlog__line.is-ok .material-icons { color: var(--ok); }
.runlog__line.is-skip .material-icons { color: var(--muted); }
.runlog__line.is-bad .material-icons { color: var(--danger); }

/* Freshness. Stale enough to mislead gets said out loud rather than left to be
   inferred from a timestamp nobody does arithmetic on. */
.freshness { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 8px 0 0;
             font-size: .74rem; color: var(--muted); }
.freshness b { font-weight: 700; color: var(--text); }
.freshness .is-stale { color: var(--warn); }

/* ==========================================================================
   DESK LAYOUT
   ==========================================================================

   The second axis. Everything above this block describes the floor layout — a
   handheld held in a gloved hand — and none of it changes here. This block only
   adds what a manager at a real computer gets: a standing left rail instead of
   a back arrow, a dashboard instead of a tile hub, and denser tables.

   Gated on [data-layout="desk"] on <html>, set before the first paint by the
   inline script in Shell::open(). NOT a media query: the point is that a
   manager can choose the floor view on a wide screen and keep it, which a
   breakpoint alone cannot express. The viewport is only the default, applied by
   that script and followed by layout.js until someone decides otherwise.

   Operator-shell pages are deliberately NOT redesigned here. Pick on a 27"
   monitor is still one bin, one SKU, one quantity, in a 720px column — a
   manager opening it sees what the floor sees, which is the whole reason they
   opened it. The rail wraps it; the screen inside is untouched. */

/* --top-h — where the header stops, and so where this rail starts — is defined
   once at the top of this file, beside the min-height that pins the header to
   it. It used to be redeclared here as a flat 68px, which is how the bar and the
   rail came to disagree by ten visible pixels. */

/* In the floor layout this element is not there as far as CSS is concerned, so
   <main> stays a direct flex child of <body> and every rule written for the
   handheld keeps working without knowing about the wrapper. */
.wms-body { display: contents; }
.wms-nav  { display: none; }

/* The mode button. Same compact shell shape as the dispatch and printer chips,
   because it sits in the same row and a third distinct shape there is noise.
   Hidden below the breakpoint: on a handheld there is no desk view worth
   offering, and the top bar has no room to say so. */
.wms-layout {
    display: flex; align-items: center; justify-content: center;
    min-width: 40px; height: 38px; padding: 0 10px;
    background: rgba(255,255,255,.08); border: 0; border-radius: 10px;
    color: #cbd5e1; cursor: pointer;
}
.wms-layout:hover { background: rgba(255,255,255,.16); color: #fff; }
.wms-layout .material-icons { font-size: 1.2rem; }
@media (max-width: 1023px) { .wms-layout { display: none; } }

/* The dashboard is built by home.js only when the page is actually in desk
   mode, but the container ships on every request — so it must not open a gap
   under the hub on a handheld. */
#homeDash { display: none; }

[data-layout="desk"] .wms-body {
    display: flex; align-items: flex-start;
    flex: 1; min-height: 0; width: 100%;
}

/* `align-items: flex-start` above is right for a page of cards — it stops a
   short one from stretching <main> into a tall empty panel — but it means
   <main> is only ever as tall as its content. The map editor is the one page
   that needs the opposite: it is a viewport, and its whole fill-the-space chain
   (.wms-map -> .wms-map__body -> .wms-canvas -> svg, each flex:1 or height:100%)
   resolves to nothing when the element they all sit inside has no height of its
   own to divide. Stretch it, and the chain has something to fill. */
[data-layout="desk"] body[data-route="map"] .wms-body { align-items: stretch; }

/* ---- The rail ---- */

[data-layout="desk"] .wms-nav {
    display: flex; flex-direction: column; gap: 2px;
    width: 232px; flex-shrink: 0;
    background: var(--ink); color: #cbd5e1;
    padding: 10px 10px 20px;
    /* Sticks under the header and scrolls independently, so a long rail cannot
       push the page down and a short page cannot leave the rail floating in the
       middle of the window. */
    position: sticky; top: var(--top-h);
    height: calc(100dvh - var(--top-h));
    overflow-y: auto; overscroll-behavior: contain;

    /* HEIGHT, NOT MAX-HEIGHT. With max-height the rail was only as tall as its
       own links, so on a short menu the dark panel stopped partway down and the
       page background showed through beneath it — read as a gap in the sidebar.
       A fixed height keeps the ink running the full window either way. */

    /* The default scrollbar is a light-mode widget sitting on a near-black
       panel, which is the single most obvious thing on the rail. Thin and
       tinted, and it only takes colour on hover. */
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,.16) transparent;
}
[data-layout="desk"] .wms-nav::-webkit-scrollbar { width: 8px; }
[data-layout="desk"] .wms-nav::-webkit-scrollbar-track { background: transparent; }
[data-layout="desk"] .wms-nav::-webkit-scrollbar-thumb {
    background: rgba(255,255,255,.14); border-radius: 999px;
    border: 2px solid transparent; background-clip: content-box;
}
[data-layout="desk"] .wms-nav:hover::-webkit-scrollbar-thumb { background-color: rgba(255,255,255,.28); }
[data-layout="desk"] .wms-nav__brand {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; margin-bottom: 8px;
    color: #fff; text-decoration: none; border-radius: 10px;
    font-size: .95rem; letter-spacing: -.01em;
}
[data-layout="desk"] .wms-nav__brand .material-icons { font-size: 1.3rem; color: #60a5fa; }
[data-layout="desk"] .wms-nav__brand:hover { background: rgba(255,255,255,.06); }
[data-layout="desk"] .wms-nav__brand.is-current { background: rgba(255,255,255,.1); }

/* Groups are <details>, so collapsing costs no JavaScript and keeps working if
   a script fails. The lead group (Exceptions) is a plain div — a single link
   with nothing to fold. */
[data-layout="desk"] .wms-nav__group { display: flex; flex-direction: column; gap: 2px; }
[data-layout="desk"] details.wms-nav__group { display: block; }
[data-layout="desk"] details.wms-nav__group[open] { display: flex; }
[data-layout="desk"] .wms-nav__group + .wms-nav__group { margin-top: 10px; }

[data-layout="desk"] .wms-nav__group > summary {
    display: flex; align-items: center; gap: 6px;
    list-style: none; cursor: pointer; user-select: none;
    font-size: .64rem; font-weight: 700; text-transform: uppercase; letter-spacing: .09em;
    color: #64748b; margin: 0 0 4px; padding: 5px 12px; border-radius: 6px;
}
/* Safari draws its own triangle through ::-webkit-details-marker. */
[data-layout="desk"] .wms-nav__group > summary::-webkit-details-marker { display: none; }
[data-layout="desk"] .wms-nav__group > summary:hover { color: #cbd5e1; background: rgba(255,255,255,.04); }
[data-layout="desk"] .wms-nav__group > summary .material-icons {
    font-size: 1rem; margin-left: auto; transition: transform .15s ease;
}
[data-layout="desk"] .wms-nav__group[open] > summary .material-icons { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
    [data-layout="desk"] .wms-nav__group > summary .material-icons { transition: none; }
}
/* A collapsed section still says whether the page you are on is inside it. */
[data-layout="desk"] .wms-nav__group:not([open]) > summary.has-current { color: #93c5fd; }

[data-layout="desk"] .wms-nav__item {
    display: flex; align-items: center; gap: 10px;
    padding: 8px 12px; border-radius: 8px;
    color: #cbd5e1; text-decoration: none; font-size: .85rem; font-weight: 500;
    /* Nothing like the 48px floor target — this is a mouse pointer on a desk,
       and a rail of 48px rows would not fit the pages we actually have. */
}
/* FIXED WIDTH, or the label starts wherever that particular glyph happens to
   end. Material Icons are not all the same advance width — `speed`, on
   Productivity, is visibly narrower than `local_shipping` — so a plain flex gap
   left one row's text pushed out of line with the rest of the column. */
[data-layout="desk"] .wms-nav__item .material-icons {
    font-size: 1.15rem; color: #64748b;
    width: 20px; flex-shrink: 0; text-align: center;
}
[data-layout="desk"] .wms-nav__item:hover { background: rgba(255,255,255,.06); color: #fff; }
[data-layout="desk"] .wms-nav__item:hover .material-icons { color: #94a3b8; }
[data-layout="desk"] .wms-nav__item.is-current {
    background: var(--primary); color: #fff; font-weight: 600;
}
[data-layout="desk"] .wms-nav__item.is-current .material-icons { color: #fff; }
/* Exceptions carries no count — see the note in layout.js — so the tint is all
   it has to say that this is where the wrong things go. */
[data-layout="desk"] .wms-nav__item.is-alert { color: #fca5a5; }
[data-layout="desk"] .wms-nav__item.is-alert .material-icons { color: #ef4444; }
[data-layout="desk"] .wms-nav__item.is-alert:hover { background: rgba(239,68,68,.14); color: #fecaca; }
[data-layout="desk"] .wms-nav__item.is-alert.is-current {
    background: var(--danger); color: #fff;
}
[data-layout="desk"] .wms-nav__item.is-alert.is-current .material-icons { color: #fff; }

/* The rail is the way around now, and two competing ones is how you end up
   pressing the wrong one. The hub is still reachable from the brand. */
[data-layout="desk"] .wms-top__back { display: none; }

/* ---- The page beside it ---- */

[data-layout="desk"] .wms-main { min-width: 0; }
/* Admin screens are tables and charts and have always wanted the width; 1240px
   was a compromise with a shell that had no rail to subtract. */
[data-layout="desk"] .wms--admin .wms-main { max-width: 1440px; padding: 22px 24px; }
/* The map editor is the one admin page that is not a column of cards — it is a
   viewport, and every pixel clipped off it is warehouse the manager cannot see.
   It already opts out of the 1240px clamp near the top of this file, but that
   rule is `body[data-route="map"] .wms-main` (0-2-1) and the desk rule above is
   (0-3-0), so on any screen wide enough to trigger desk mode the clamp came
   back and the floor plan sat in a 1440px box surrounded by grey. Repeat the
   opt-out here, scoped to desk, where it can win. */
[data-layout="desk"] body[data-route="map"] .wms-main { max-width: none; padding: 0; }
/* Tighter rows: a mouse and a 1440px table do not need a 48px hit area, and the
   extra height is what stops a manager seeing a whole wave without scrolling. */
[data-layout="desk"] .wms--admin .table { font-size: .82rem; }
[data-layout="desk"] .wms--admin .table td { padding: 8px 10px; }
[data-layout="desk"] .wms--admin .table th { padding: 7px 10px; }

/* The hub's tiles are what the rail replaces, so on a desk they always go.
   Hiding them rather than not rendering them is what lets the toggle work
   without a round trip.

   This USED TO BE conditional — `:has(#homeDash:not(:empty))`, so the tiles
   only went once the dashboard had drawn something, on the theory that a user
   without reporting rights should keep something to click. It was wrong twice
   over. `:has()` is unsupported often enough that the whole rule got dropped
   and the tiles stayed up on a full desktop; and tying the tiles to a fetch
   succeeding meant any failure at all produced the exact screen this layout
   exists to replace — a wall of big touch targets on a 27" monitor.

   home.js now guarantees #homeDash is never empty in desk mode: it holds the
   dashboard, or it says in plain words why it does not. */
[data-layout="desk"] body[data-route="home"] .wms-group { display: none; }
[data-layout="desk"] #homeDash { display: flex; flex-direction: column; gap: var(--stack); }

/* And so does the strip of work counters above them, for the same reason and by
   the same mechanism. On a handheld it is the only answer to "where is the
   work"; on a desk it was eleven tiles restating the panels directly beneath
   them — "Orders waiting 17" over a funnel whose first row is "Waiting to be
   waved 17", "Exceptions 67" under a red banner reading 67.

   CSS and not JavaScript, so the layout toggle needs no round trip and neither
   direction of it can leave the page with nothing on it. */
[data-layout="desk"] body[data-route="home"] #homeStats { display: none; }

/* ---- Dashboard ---- */

/* Wider than .stats' 90px floor tiles: these carry a label like "Dock to bin"
   and a projected figure under the number, not a single word. */
.dash-kpis { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.dash-kpi {
    background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
    padding: 20px; display: flex; flex-direction: column; gap: 8px;
    text-decoration: none; color: inherit;
}
a.dash-kpi:hover { border-color: var(--primary); }
.dash-kpi b { font-size: 1.6rem; font-weight: 800; line-height: 1.1; letter-spacing: -.02em; }
.dash-kpi span { font-size: .95rem; font-weight: 600; color: var(--text); order: -1; }
.dash-kpi small { font-size: .82rem; color: var(--muted); }
.dash-kpi.is-alert { border-color: #fecaca; background: #fef2f2; }
.dash-kpi.is-alert b { color: var(--danger); }

.dash-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr)); gap: var(--stack); }
.dash-grid > .card { margin: 0; }

/* A row of proportions — order ageing, activity split — where a full chart is
   more machinery than the number deserves. */
.dash-bars { display: flex; flex-direction: column; gap: 8px; }
.dash-bar { display: grid; grid-template-columns: 92px 1fr 44px; align-items: center; gap: 10px;
            font-size: .78rem; }
.dash-bar__track { height: 8px; border-radius: 999px; background: #eef2f7; overflow: hidden; }
.dash-bar__fill { height: 100%; border-radius: 999px; background: var(--primary); }
.dash-bar__fill.is-warn { background: var(--warn); }
.dash-bar__fill.is-bad  { background: var(--danger); }
.dash-bar b { text-align: right; font-weight: 700; }

/* Below this the rail costs more than it gives — but a manager who has pinned
   desk mode on a narrow window still gets the dashboard, just stacked. */
@media (max-width: 900px) {
    [data-layout="desk"] .wms-nav { display: none; }
    [data-layout="desk"] .wms-top__back { display: flex; }
}

/* Period switch. Today leads, because "how are we doing" is a question about
   now — the week is there for when it turns into "is this normal". */
.dash-range { display: flex; gap: 4px; align-self: flex-start;
              background: var(--surface); border: 1px solid var(--border);
              border-radius: 10px; padding: 3px; }
.dash-range__btn {
    border: 0; background: transparent; cursor: pointer;
    padding: 6px 14px; border-radius: 8px;
    font: inherit; font-size: .8rem; font-weight: 600; color: var(--muted);
}
.dash-range__btn:hover { color: var(--text); background: #f1f5f9; }
.dash-range__btn.is-current { background: var(--primary); color: #fff; }

/* A stage of the outbound funnel: icon, name, how full it is, and the count.
   The whole row is the link — "37 waiting to be waved" has exactly one useful
   response, and it is to go and wave them. */
.dash-stage {
    display: grid; grid-template-columns: 20px 150px 1fr 40px;
    align-items: center; gap: 10px;
    padding: 5px 6px; margin: 0 -6px; border-radius: 8px;
    color: inherit; text-decoration: none; font-size: .8rem;
}
.dash-stage:hover { background: #f1f5f9; }
.dash-stage .material-icons { font-size: 1.05rem; color: var(--muted); }
.dash-stage__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-stage b { text-align: right; font-weight: 700; }

/* A card's heading, and where the card goes.
   The title doubles as the link when there is a page behind the panel — a
   manager reading "Exceptions by type" is already most of the way to wanting
   the queue, and a separate "view all" button beside it is a second thing to
   aim at for the same destination. The chevron only appears on hover-capable
   pointers; it is a hint, not a control. */
.dash-head { display: flex; flex-direction: column; gap: 2px; }
.dash-head > small { color: var(--muted); font-size: .72rem; }
.dash-head__link { display: inline-flex; align-items: center; gap: 3px;
                   align-self: flex-start; color: inherit; text-decoration: none; }
.dash-head__link .material-icons { font-size: 1.05rem; color: var(--muted);
                                   opacity: 0; transition: opacity .12s ease; }
.dash-head__link:hover { color: var(--primary); }
.dash-head__link:hover .material-icons { opacity: 1; color: var(--primary); }
@media (prefers-reduced-motion: reduce) {
    .dash-head__link .material-icons { transition: none; }
}

/* A statement of the form "this much, of this thing" — a queue depth, a stage
   duration, an inventory figure. ONE row style for all three: they are the same
   shape of sentence, and three near-identical row treatments stacked down a
   dashboard read as an accident rather than a distinction.

   Deliberately NOT .dash-stage, which carries a proportion bar. A bar is only
   honest when the rows share a scale, and 472 count variances beside 18 open
   returns would flatten every other row to a sliver while comparing two things
   that have nothing to do with each other. */
.dash-rows { display: flex; flex-direction: column; gap: 1px; }
.dash-row {
    display: grid; grid-template-columns: 20px minmax(0, 1fr) auto;
    align-items: center; gap: 10px;
    padding: 7px 6px; margin: 0 -6px; border-radius: 8px;
    color: inherit; text-decoration: none; font-size: .8rem;
}
a.dash-row:hover { background: #f1f5f9; }
.dash-row > .material-icons { font-size: 1.05rem; color: var(--muted); text-align: center; }
.dash-row > div { min-width: 0; }
.dash-row small { display: block; color: var(--muted); font-size: .7rem; line-height: 1.35; }
.dash-row > b { font-weight: 700; font-size: .95rem; font-variant-numeric: tabular-nums; }
/* Reserved for a figure that wants doing something about, and used sparingly —
   a dashboard where four rows are red is a dashboard nobody reads. */
.dash-row.is-alert > b { color: var(--danger); }
.dash-row.is-alert > .material-icons { color: var(--danger); }

/* A KPI that is fine is not worth a colour — only the number that wants doing
   something about it gets one, or the row becomes a traffic light nobody
   reads. Green is reserved for "nothing stuck", which is genuinely good news. */
.dash-kpi.is-ok b { color: var(--ok); }

/* The hub's "these are desk pages" notice. Pointless beside a rail that is
   already offering every one of them. */
[data-layout="desk"] .home-desk-only { display: none; }

/* Manager overview: live workload, team, output, then supporting detail. */
.dash-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; }
.dash-toolbar h2 { margin: 0; font-size: 1.35rem; }
.dash-toolbar .hint { margin: 6px 0 0; }
.dash-toolbar .dash-range { align-self: center; }
#homeDash > .card { margin: 0; }
#homeDash .card { min-width: 0; }
.dash-flow-link { display: grid; grid-template-columns: 10px minmax(0, 1fr) auto 34px; gap: 10px; align-items: center; padding: 9px 6px; color: inherit; text-decoration: none; border-radius: 6px; }
.dash-flow-link:hover { background: #f1f5f9; }
.dash-flow-link i { width: 10px; height: 10px; border-radius: 50%; }
.dash-team-wrap { min-width: 0; }
.dash-team { width: 100%; table-layout: fixed; }
.dash-team th, .dash-team td { text-align: right; min-width: 0; white-space: nowrap; }
.dash-team th:first-child, .dash-team td:first-child { text-align: left; width: 36%; min-width: 0; }
.dash-team a { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: inherit; font-weight: 600; text-decoration: none; }
.dash-team a:hover { color: var(--primary); text-decoration: underline; }
#homeDash .dash-bars { margin-top: 16px; }
.dash-output .dash-head { margin-bottom: 12px; }
@media (max-width: 650px) {
    .dash-kpis { grid-template-columns: 1fr; }
    #homeDash .donut-layout { grid-template-columns: 1fr; }
}

/* Keep the team title, period caption and report link visually separate. */
.dash-team-head { display: flex; align-items: center; justify-content: space-between; gap: 12px 24px; flex-wrap: wrap; margin-bottom: 16px; }
.dash-team-head h3 { margin: 0; }
.dash-team-head p { margin: 5px 0 0; color: var(--muted); font-size: .82rem; line-height: 1.4; }
.dash-team-report { flex-shrink: 0; color: var(--primary); font-size: .82rem; font-weight: 600; text-decoration: none; padding: 8px 0; }
.dash-team-report:hover { text-decoration: underline; }
.dash-team-card > .hint { margin: 12px 0 0; }

.dash-team-card .dash-team { border-collapse: collapse; font-variant-numeric: tabular-nums; }
.dash-team-card .dash-team th { background: #f8fafc; padding: 10px 12px; white-space: nowrap; }
.dash-team-card .dash-team td { padding: 12px; vertical-align: middle; }
.dash-team-card .dash-team td:first-child { white-space: nowrap; }
.dash-team-card .dash-team tbody tr:hover { background: #f8fafc; }

/* ---- Integration modes ----
   A segmented control rather than a checkbox per mode: the three are mutually
   exclusive and the current one has to be legible at a glance, because the
   question this screen answers is "what is this system allowed to write right
   now". All three segments act. Live is tinted rather than merely selected —
   it is the only one that writes to somebody else's system, and the segment a
   person is looking for in a hurry is usually the way back off it. */
.mode-switch { display: flex; flex-wrap: wrap; gap: 6px; padding: 5px;
  background: #f1f5f9; border: 1px solid var(--border); border-radius: 12px; }
.mode-switch__option { flex: 1 1 96px; display: flex; align-items: center; justify-content: center;
  gap: 6px; min-height: 42px; padding: 8px 12px; border: 1px solid transparent; border-radius: 9px;
  background: transparent; color: var(--muted); font: inherit; font-weight: 600; font-size: .84rem;
  cursor: pointer; }
.mode-switch__option .material-icons { font-size: 18px; }
.mode-switch__option:hover:not(:disabled) { background: #fff; color: var(--text); }
.mode-switch__option--on { background: #fff; color: var(--text);
  border-color: var(--border); box-shadow: 0 1px 2px rgba(15, 23, 42, .08); }
.mode-switch__option:disabled { cursor: not-allowed; opacity: .5; }
.mode-switch__option--live.mode-switch__option--on { background: #dcfce7; color: #15803d;
  border-color: #86efac; }

.integration-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.integration-card__head h3 { text-transform: capitalize; }
.integration-card__head p { margin: 4px 0 0; }
.integration-card__facts { display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center; }
.integration-card__facts span { display: inline-flex; align-items: center; gap: 5px;
  color: var(--muted); font-size: .82rem; }
.integration-card__facts .material-icons { font-size: 17px; }
.integration-card__facts b { color: var(--text); font-weight: 700; }
.integration-card__facts .is-bad, .integration-card__facts .is-bad b { color: #b91c1c; }

/* A single outbound write, and whether it is allowed out. Collapsed unless
   something is held, because on a healthy integration the list is reference
   material and the mode switch above it is the control. */
.op-list { margin-top: 4px; border: 1px solid var(--border); border-radius: 11px; background: #f8fafc; }
.op-list > summary { list-style: none; display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 11px 13px; cursor: pointer; font-size: .8rem; font-weight: 600; color: var(--text); }
.op-list > summary::-webkit-details-marker { display: none; }
.op-list > summary:hover { color: var(--primary); }
.op-list__body { display: grid; gap: 8px; padding: 0 11px 11px; }
.op-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 10px; align-items: center;
  padding: 10px 11px; background: #fff; border: 1px solid var(--border); border-radius: 9px; }
.op-row--held { border-color: #fcd34d; background: #fffbeb; }
.op-row__what { min-width: 0; }
.op-row__what strong { display: block; font-size: .84rem; }
.op-row__what code { font-size: .7rem; color: var(--muted); overflow-wrap: anywhere; }
.op-row__what p { margin: 4px 0 0; }
.op-row__state { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; white-space: nowrap; }
.op-row__state small { font-size: .68rem; }
@media (max-width: 620px) {
    .op-row { grid-template-columns: minmax(0, 1fr) auto; }
    .op-row__state { grid-column: 2; }
    .op-row .btn { grid-column: 1 / -1; justify-content: center; }
}
