/*
 * Screen layouts — Ninja Vapes IMS.
 *
 * Everything here composes the Modernist components in modernist.css. No new
 * colours, type sizes or spacing values are invented; only layout.
 */

/* ══════════════════════════════════════════════════════════════════════════
   Sign in
   ══════════════════════════════════════════════════════════════════════ */

.signin {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  grid-template-columns: 1.1fr 1fr;
}

/* ---- Left: the brand panel ---- */

.signin-brand {
  background: var(--color-accent);
  color: #fff;
  padding: 56px 56px 40px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 40px;
}

.signin-mark {
  display: flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
}

.signin-mark span {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 17px;
  letter-spacing: -0.01em;
}

.signin-mark i {
  width: 22px;
  height: 22px;
  flex: none;
  background: #fff;
}

.signin-kicker {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.85;
  margin-bottom: 18px;
}

.signin-headline {
  font-size: clamp(34px, 4.4vw, 60px);
  line-height: 0.98;
  letter-spacing: -0.03em;
  margin: 0 0 22px;
  max-width: 11em;
}

.signin-lede {
  font-size: 15px;
  max-width: 34em;
  margin: 0;
  opacity: 0.92;
}

.signin-foot {
  display: flex;
  gap: 40px;
  flex-wrap: wrap;
  border-top: 2px solid rgba(255, 255, 255, 0.45);
  padding-top: 16px;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ---- Right: the form ---- */

.signin-panel {
  padding: 56px;
  display: flex;
  align-items: center;
}

.signin-form {
  width: 100%;
  max-width: 408px;
}

.signin-form h6 {
  color: var(--color-accent);
  margin-bottom: 10px;
}

.signin-form h2 {
  margin-bottom: 6px;
}

.signin-intro {
  font-size: 14px;
  margin-bottom: 24px;
}

.signin-form .field {
  margin-bottom: 14px;
}

/* The password field carries a "show" control inside its right edge. */
.password-wrap {
  position: relative;
}

.password-wrap .input {
  padding-right: 62px;
}

.password-toggle {
  position: absolute;
  top: 50%;
  right: 1px;
  transform: translateY(-50%);
  height: calc(100% - 2px);
  padding: 0 10px;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
}

.password-toggle:hover {
  color: var(--color-accent);
}

.signin-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: 20px;
}

.signin-submit {
  height: 46px;
  font-size: 15px;
  justify-content: flex-start;
  width: 100%;
}

.signin-note {
  font-size: 12px;
  margin: 0;
}

.signin-notice {
  margin-bottom: 20px;
}

/* ══════════════════════════════════════════════════════════════════════════
   Sign in — tablet and phone

   The canvas designed this screen at desktop only. Below 900px the two
   columns stack: the brand panel collapses to a short header that keeps the
   mark and the headline, and the form takes the remaining height.
   ══════════════════════════════════════════════════════════════════════ */

@media (max-width: 900px) {
  .signin {
    grid-template-columns: 1fr;
    /* Brand panel takes only the height its content needs; the form gets the
       rest, so there is no band of empty red between the two. */
    grid-template-rows: auto 1fr;
  }

  .signin-brand {
    /* space-between is right for a full-height column, but once the panel is
       only a few hundred pixels tall it strands the mark and the headline at
       opposite ends. Group them at the top instead. */
    justify-content: flex-start;
    padding: 28px 32px 34px;
    gap: 26px;
  }

  .signin-headline {
    font-size: clamp(28px, 5.4vw, 40px);
    margin-bottom: 0;
    max-width: 16em;
  }

  .signin-lede,
  .signin-foot {
    display: none;
  }

  .signin-panel {
    padding: 32px;
    align-items: flex-start;
  }

  .signin-form {
    max-width: none;
  }
}

@media (max-width: 480px) {
  .signin-brand {
    padding: 24px 20px 26px;
  }

  .signin-panel {
    padding: 26px 20px 40px;
  }

  .signin-kicker {
    margin-bottom: 12px;
  }
}

/* A short viewport in landscape should scroll, not squash. */
@media (max-height: 620px) and (min-width: 901px) {
  .signin-panel {
    align-items: flex-start;
    padding-block: 40px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   App shell — sidebar variant

   The frame every signed-in screen sits inside: a fixed sidebar, a page header
   carrying the screen's kicker and title, and a scrolling content area.
   ══════════════════════════════════════════════════════════════════════ */

:root {
  --head-h: 84px;
}

/*
 * Belt-and-braces alongside .app's own height/overflow: capping html/body
 * too, scoped to :has(.app) so the sign-in screen (.signin, no sidebar
 * shell) keeps its own natural scroll.
 */
html:has(.app), body:has(.app) {
  height: 100%;
  overflow: hidden;
}

/*
 * The actual fix for the sidebar/header scrolling away with the page: on a
 * long enough .page-body (needs its own scrollbar), the browser can still
 * register that overflow on the document root even though .page-body's own
 * overflow:auto visibly contains it — overflow:hidden here alone doesn't
 * stop it. `contain: layout` does: it's a stronger guarantee that nothing
 * inside .app can affect layout/scroll calculation outside it, closing the
 * gap that overflow:hidden alone left open.
 */
.app {
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  contain: layout;
  display: flex;
  background: var(--color-bg);
}

/* ---- Sidebar ---- */

.side {
  width: 236px;
  flex: none;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  border-right: 2px solid var(--color-divider);
}

.side-head {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 18px 18px 16px;
  border-bottom: 2px solid var(--color-divider);
}

.side-mark {
  display: flex;
  align-items: center;
  gap: 9px;
}

.side-mark i {
  width: 18px;
  height: 18px;
  flex: none;
  background: var(--color-accent);
}

.side-mark span {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 15px;
  letter-spacing: -0.01em;
}

.side-version {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-top: 6px;
  padding-left: 27px;
}

.side-nav {
  flex: 1;
  overflow: auto;
  padding-bottom: 12px;
}

/*
 * One optical left edge down the whole sidebar. A nav row carries a 3px
 * transparent border for the current-page marker, so its text sits at
 * 3 + 14 — and anything without that border has to be inset by the sum, not
 * by the 14 alone. Before this the column read 18px (section labels), 17px
 * (rows) and 14px (the footer), which shows up as a jog at "Sign out".
 */
:root {
  --side-inset: 17px;
}

.side-section {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* Tightened with the rows below it, so the group breaks stay in
     proportion rather than becoming the only air left in the column. */
  padding: 14px 18px 5px var(--side-inset);
}

/* One navigation row. Three states: current, available, locked. */
.side-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  /* Tight enough that eleven rows read as one list. The drawer under
     1023px keeps 12px: there the row is a touch target on a warehouse
     tablet, and this would be too small to hit. */
  padding: 6px 14px;
  border: 0;
  border-left: 3px solid transparent;
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-heading);
  /* Semi-bold, not the 800 the headings use. Nine items all shouting at
     once flattened the sidebar into one solid block — nothing led the eye,
     and the page heading it sits beside had to compete with it. Archivo is
     a variable font across 100-900, so this is a real weight rather than a
     browser-synthesised one. */
  font-weight: 600;
  font-size: 16px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.side-item span:first-child {
  flex: 1;
}

.side-item .badge {
  font-size: 11px;
  font-weight: 400;
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}

/* Square-and-arrow, same slot as the draft badges — a ↗ glyph
   next to the words read as punctuation, not as “opens elsewhere”. */
.side-item .side-ext {
  flex: none;
  display: block;
  width: 14px;
  height: 14px;
  color: inherit;
}

.side-item:hover {
  background: color-mix(in srgb, var(--color-text) 6%, transparent);
}

.side-item[aria-current="page"] {
  border-left-color: var(--color-accent);
  background: var(--color-accent-100);
  color: var(--color-accent-700);
  /* The colour, the rule and the tint already say which page this is; the
     weight is a fourth signal, one step up rather than the full 800 that
     turned the whole column into a block. */
  font-weight: 700;
}

.side-item[aria-current="page"]:hover {
  background: var(--color-accent-100);
}

.side-item[aria-current="page"] .badge {
  color: inherit;
}

/* A module this role cannot open. Not a link, not clickable. */
.side-item.is-locked {
  opacity: 0.3;
  cursor: not-allowed;
}

.side-item.is-locked:hover {
  background: transparent;
}

/* The barcode a search matched, under the product name it belongs to. */
.row-hit {
  margin-top: 3px;
  font-size: 11.5px;
  font-weight: 400;
}

.row-hit .code {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  color: var(--color-accent-700);
}

/* ---- Sidebar footer: who is signed in ---- */

.side-foot {
  border-top: 2px solid var(--color-divider);
  padding: 14px 14px 14px var(--side-inset);
}

.side-user {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 10px;
}

.avatar {
  display: grid;
  place-items: center;
  flex: none;
  width: 30px;
  height: 30px;
  background: var(--color-text);
  color: var(--color-bg);
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 11px;
}

.side-user-name {
  line-height: 1.15;
  min-width: 0;
}

.side-user-name strong {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
}

.side-user-name span {
  display: block;
  font-size: 10.5px;
}

.side-signout {
  font-size: 12px;
  padding: 0;
}

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

.main {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.page-head {
  display: flex;
  align-items: flex-end;
  gap: 20px;
  flex-wrap: wrap;
  padding: 20px 28px 14px;
  border-bottom: 2px solid var(--color-divider);
}

.page-head-title {
  margin-right: auto;
}

.page-kicker {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: 4px;
}

.page-head h3 {
  margin: 0;
  letter-spacing: -0.02em;
}

.page-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.page-body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 28px;
}

/*
 * The sidebar header and the page header sit either side of the sidebar's
 * border and read as one band, so their bottom rules have to meet. Left to
 * their own padding and content they land 3px apart, so both are pinned to a
 * single height. Only relevant while the sidebar is beside the content — in
 * the drawer they are never adjacent.
 */
@media (min-width: 1024px) {
  .side-head,
  .page-head {
    min-height: var(--head-h);
  }
}

/* ---- Mobile header: only exists below the sidebar breakpoint ---- */

.bar {
  display: none;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 2px solid var(--color-divider);
  background: var(--color-bg);
  position: sticky;
  top: 0;
  z-index: 20;
}

.bar-burger {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: none;
  padding: 0;
  border: 1px solid var(--color-divider);
  background: transparent;
  cursor: pointer;
}

.bar-burger span {
  display: block;
  width: 17px;
  height: 2px;
  background: var(--color-text);
  box-shadow:
    0 -5px 0 var(--color-text),
    0 5px 0 var(--color-text);
}

.side-close {
  display: none;
}

.scrim {
  display: none;
}

/* ══════════════════════════════════════════════════════════════════════════
   App shell — tablet and phone

   The canvas designed the shell at desktop only. Below 1024px the sidebar
   becomes a drawer: a compact bar appears with the mark and a menu button, and
   the sidebar slides in over the content behind a scrim.
   ══════════════════════════════════════════════════════════════════════ */

@media (max-width: 1023px) {
  .app {
    flex-direction: column;
  }

  .bar {
    display: flex;
  }

  .bar .side-mark {
    margin-right: auto;
  }

  .side {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 40;
    width: 272px;
    max-width: 84vw;
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    box-shadow: var(--shadow-lg);
  }

  .side.is-open {
    transform: translateX(0);
  }

  .side-close {
    display: grid;
    place-items: center;
    position: absolute;
    top: 14px;
    right: 12px;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid var(--color-divider);
    background: transparent;
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
  }

  .scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 30;
    background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
    opacity: 0;
    visibility: hidden;
    transition:
      opacity 0.22s ease,
      visibility 0.22s ease;
  }

  .scrim.is-open {
    opacity: 1;
    visibility: visible;
  }

  /* Touch targets on a warehouse tablet. */
  .side-item {
    padding: 12px 14px;
  }

  .page-head {
    padding: 16px 18px 12px;
  }
  .page-body {
    padding: 18px;
  }
}

@media (max-width: 560px) {
  .page-head {
    padding: 14px 16px 12px;
  }
  .page-body {
    padding: 16px;
  }
  .page-actions {
    width: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .side,
  .scrim {
    transition: none;
  }
}

/* ---- Placeholder, shown only in this shell mock ---- */

.slot {
  display: grid;
  place-items: center;
  min-height: 320px;
  padding: var(--space-8);
  border: 2px dashed var(--color-divider);
  text-align: center;
}

.slot p {
  max-width: 46ch;
}

/* ══════════════════════════════════════════════════════════════════════════
   Dashboard

   Full-bleed inside the shell: the headline band and the two columns carry
   their own padding and dividers, so .page-body drops its own.
   ══════════════════════════════════════════════════════════════════════ */

.page-body.is-flush {
  padding: 0;
}

/* ---- Headline band ---- */

.dash-head {
  display: flex;
  align-items: flex-end;
  gap: 32px;
  flex-wrap: wrap;
  padding: 26px 28px 20px;
  border-bottom: 2px solid var(--color-divider);
}

.dash-kicker {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 6px;
}

.dash-figure {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 76px;
  line-height: 0.92;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
}

.dash-aside {
  padding-bottom: 10px;
}

.dash-delta {
  font-size: 15px;
  font-weight: 600;
}

.dash-sub {
  font-size: 12.5px;
}

.dash-trend {
  margin-left: auto;
  padding-bottom: 10px;
}

.dash-trend svg {
  width: 220px;
  height: 48px;
  display: block;
}

/* ---- Two columns ---- */

.dash-cols {
  display: grid;
  grid-template-columns: 1.5fr 1fr;
}

.dash-queue {
  padding: 22px 28px;
  border-right: 1px solid var(--color-divider);
  min-width: 0;
}

.dash-side {
  padding: 22px 28px;
  display: flex;
  flex-direction: column;
  gap: 22px;
  min-width: 0;
}

.dash-h {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 6px;
}

.dash-h h6 {
  margin: 0;
}

.dash-h-note {
  font-size: 11.5px;
}

.dash-h:not(:has(.queue-ignored-toggle)) .dash-h-note {
  margin-left: auto;
}

.queue-ignored-toggle {
  margin-left: auto;
  flex: none;
  font-size: 12.5px;
  font-weight: 600;
}

/* ---- One row in the decision queue ---- */

.queue-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 0;
  border-bottom: 1px solid var(--color-divider);
}

.queue-row .tag {
  min-width: 94px;
  justify-content: center;
  white-space: nowrap;
}

.queue-text {
  flex: 1;
  min-width: 0;
}

.queue-title {
  font-size: 14px;
  font-weight: 600;
}

.queue-detail {
  font-size: 12px;
}

.queue-row .btn {
  font-size: 12.5px;
  min-width: 78px;
  flex: none;
}

.queue-row form {
  margin: 0;
  flex: none;
}

.queue-row.is-ignored {
  opacity: 0.55;
}

.queue-row .queue-hide {
  min-width: 0;
  font-size: 12px;
}

/* ---- Start a job ---- */

.dash-jobs {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.dash-jobs .btn {
  justify-content: flex-start;
  height: 46px;
}

/* ---- Today's ledger ---- */

.ledger-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--color-divider);
}

.ledger-time {
  font-size: 11.5px;
  min-width: 34px;
  flex: none;
}

.ledger-item {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
}

.ledger-qty {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.dash-link {
  font-size: 11.5px;
  margin-left: auto;
}

a.ledger-row.is-link {
  color: inherit;
  text-decoration: none;
}

a.ledger-row.is-link:hover .ledger-item {
  color: var(--color-accent);
}

/* Sits under the last queue row, where the list stops. .queue-row already
   draws the divider above it, so this only needs the breathing room. */
.queue-more {
  display: inline-block;
  margin-top: 14px;
  font-size: 12.5px;
  font-weight: 600;
}

/* ══════════════════════════════════════════════════════════════════════════
   Dashboard — tablet and phone

   Designed at desktop only in the canvas. The two columns stack, and the
   headline figure scales down rather than wrapping.
   ══════════════════════════════════════════════════════════════════════ */

@media (max-width: 1100px) {
  .dash-cols {
    grid-template-columns: 1fr;
  }

  .dash-queue {
    border-right: 0;
    border-bottom: 2px solid var(--color-divider);
  }
}

@media (max-width: 1023px) {
  .dash-head {
    padding: 20px 18px 16px;
    gap: 24px;
  }
  .dash-queue,
  .dash-side {
    padding: 18px;
  }
}

@media (max-width: 700px) {
  .dash-figure {
    font-size: clamp(52px, 15vw, 76px);
  }

  .dash-trend {
    margin-left: 0;
    width: 100%;
  }

  .dash-trend svg {
    width: 100%;
  }

  .dash-h-note {
    display: none;
  }

  /* The action moves under the text rather than squeezing it. */
  .queue-row {
    flex-wrap: wrap;
    gap: 10px 12px;
  }

  .queue-text {
    flex-basis: calc(100% - 106px);
  }

  .queue-row .queue-act {
    margin-left: 108px;
  }

  .queue-hide {
    margin-left: 0;
  }
}

@media (max-width: 560px) {
  .dash-head {
    padding: 18px 16px 14px;
  }
  .dash-queue,
  .dash-side {
    padding: 16px;
  }

  .queue-row .tag {
    min-width: 0;
  }
  .queue-text {
    flex-basis: 100%;
  }
  .queue-row .queue-act {
    margin-left: 0;
    width: 100%;
  }
  .queue-row .queue-act.btn,
  .queue-row .queue-act .btn {
    width: 100%;
  }
  .queue-hide {
    margin-left: auto;
    width: auto;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Scan screen — stock in / stock out

   The screen warehouse staff live in. Two columns inside one framed block:
   the scanning workspace on the left, a running total and the post actions on
   the right. The scan field is deliberately the largest control in the system.
   ══════════════════════════════════════════════════════════════════════ */

.scan {
  padding: 20px 28px 40px;
}

.scan-frame {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  border: 2px solid var(--color-divider);
}

/* The divider belongs to the two-column frame: it separates the work area
   from the panel beside it. Barcode capture has no frame — its side panel
   sits underneath — so an unscoped rule drew a line down the right of the
   page with nothing on the other side of it. */
.scan-frame > .scan-work {
  border-right: 2px solid var(--color-divider);
}

.scan-work {
  min-width: 0;
}

/* ---- Parked drafts, shown only when there is more than one ----
   Save as draft sets the current document aside and opens a fresh one, so
   a part-counted delivery can wait while another is received. These are the
   way back to it. */

.draft-switch {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 10px 20px;
  border-bottom: 2px solid var(--color-divider);
}

.draft-switch-label {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.draft-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--color-divider);
  font-size: 12.5px;
  color: var(--color-text);
  text-decoration: none;
}

.draft-chip strong {
  font-weight: 600;
}

.draft-chip:hover {
  background: color-mix(in srgb, var(--color-text) 6%, transparent);
}

.draft-chip.is-current {
  border-color: var(--color-accent);
  background: var(--color-accent-100);
}

/* ---- Document bar: date, type, party, reference ---- */

.doc-bar {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  border-bottom: 2px solid var(--color-divider);
}

.doc-cell {
  padding: 12px 14px;
  border-right: 1px solid var(--color-divider);
  min-width: 0;
}

.doc-cell:last-child {
  border-right: 0;
}

.doc-label {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 5px;
}

.doc-value {
  font-size: 14px;
  font-weight: 600;
}

.doc-cell .input {
  min-height: 28px;
  padding: 2px 6px;
  font-size: 13px;
}

.doc-cell .input.is-ref {
  font-family: ui-monospace, Menlo, monospace;
}

/* ---- The scan field ---- */

.scan-panel {
  padding: 18px 20px 16px;
  background: var(--color-surface);
  border-bottom: 2px solid var(--color-divider);
}

/* On Stock in and Stock out the scan panel is followed by the flash, the
   alert strip and the scanned lines, and the rule divides it from them. On
   Barcode capture it is the only thing in the column, where the same rule
   is just a line under a box. */
.scan-panel:last-child {
  border-bottom: 0;
}

.scan-row {
  display: flex;
  align-items: flex-end;
  gap: 14px;
}

.scan-field {
  flex: 1;
  min-width: 0;
}

.scan-label {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 7px;
  flex-wrap: wrap;
}

.scan-label strong {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 600;
}

.scan-field .input {
  min-height: 62px;
  font-size: 26px;
  font-family: ui-monospace, Menlo, monospace;
  letter-spacing: 0.04em;
  padding: 0 14px;
  background: var(--color-bg);
  border-width: 2px;
}

.scan-add {
  height: 62px;
  padding: 0 22px;
  font-size: 15px;
  flex: none;
}

/* ---- Flash: the scan that just landed ---- */

.scan-flash {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 16px 20px;
  border-bottom: 2px solid var(--color-divider);
  animation: scanflash 700ms ease-out forwards;
}

/* Nothing has been scanned yet, so there is no last scan to show. Needed
   for the same reason .dialog-backdrop needs it: display:flex above would
   otherwise beat [hidden]. */
.scan-flash[hidden] {
  display: none;
}

.scan-flash-qty {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 44px;
  line-height: 1;
  letter-spacing: -0.03em;
  min-width: 96px;
  font-variant-numeric: tabular-nums;
}

.scan-flash-name {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 19px;
  line-height: 1.1;
}

.scan-flash-variant {
  font-size: 13.5px;
  opacity: 0.9;
}

.scan-flash-meta {
  text-align: right;
  font-size: 12px;
  line-height: 1.5;
  flex: none;
}

.scan-flash-meta .code {
  font-family: ui-monospace, Menlo, monospace;
}

/* ---- Alert strip ---- */

.scan-alert {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 20px;
  border-bottom: 2px solid var(--color-divider);
  background: var(--color-accent-100);
}

/* Same reason .scan-flash and .dialog-backdrop need it: display:flex above
   would otherwise beat [hidden]. */
.scan-alert[hidden] {
  display: none;
}

.scan-alert .tag {
  background: var(--color-accent);
  color: #fff;
  flex: none;
}

.scan-alert p {
  margin: 0;
  font-size: 13.5px;
  color: var(--color-accent-800);
}

.scan-alert p strong {
  font-family: ui-monospace, Menlo, monospace;
}

.scan-alert .btn {
  white-space: nowrap;
}

.scan-alert .btn {
  margin-left: auto;
  font-size: 12px;
  flex: none;
}

/* ---- Scanned lines ---- */

.scan-lines {
  min-height: 220px;
}

.scan-empty {
  padding: 52px 20px;
  text-align: center;
}

.scan-empty p {
  margin: 0;
  font-size: 13.5px;
}

/*
 * position: relative matters. Absolutely positioned content inside the table —
 * an .sr-only label in a header cell — otherwise resolves against the initial
 * containing block, lands at the table's un-scrolled x position, and widens the
 * whole document. Containing it here keeps the scroll inside the wrapper.
 */
.table-wrap {
  position: relative;
  overflow-x: auto;
}

.scan-lines .table {
  min-width: 560px;
}

.scan-line-product .scan-line-title {
  font-weight: 600;
}

.scan-line-code {
  margin-top: 3px;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 11px;
  font-weight: 400;
  color: var(--color-neutral-600);
}

.scan-lines td.qty {
  text-align: right;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 16px;
  font-variant-numeric: tabular-nums;
}

.scan-qty {
  width: 4.75em;
  margin-left: auto;
  min-height: 36px;
  padding: 4px 8px;
  text-align: center;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 16px;
  font-variant-numeric: tabular-nums;
}

.line-actions {
  display: flex;
  gap: 4px;
  justify-content: flex-end;
}

/* ---- Right column: total, document, actions, rules ---- */

.scan-side {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.scan-total {
  padding: 18px 18px 16px;
  border-bottom: 2px solid var(--color-divider);
}

.scan-total-figure {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 54px;
  line-height: 1;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
}

.scan-total-sub {
  font-size: 12.5px;
  margin-top: 8px;
}

.scan-meta {
  padding: 16px 18px;
  border-bottom: 1px solid var(--color-divider);
  font-size: 12.5px;
  line-height: 1.7;
}

.scan-meta div {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
}

.scan-meta .code {
  font-family: ui-monospace, Menlo, monospace;
}

.scan-actions {
  padding: 16px 18px;
}

.scan-actions .btn {
  margin: 0 0 8px;
}
.scan-actions .btn:last-child {
  margin-bottom: 0;
}

.scan-actions .btn-primary {
  height: 48px;
  font-size: 15px;
}

.scan-rules {
  margin-top: auto;
  padding: 16px 18px;
  border-top: 1px solid var(--color-divider);
  background: var(--color-surface);
}

/* Inside the scan panel this block sits on the panel's own surface, so its
   background matches what is behind it and the rule is a hairline across the
   middle of a grey box. In the side column it is a different matter: there
   margin-top:auto pins it to the bottom against the page background, and the
   rule marks where it starts. */
.scan-panel > .scan-rules {
  border-top: 0;
}

/* ---- Barcode capture: the code being held, and finding its home ----
   Its own block rather than .scan-flash, which belongs to Stock in: there it
   announces a scan that just landed and animates once. Here the code sits
   and waits for someone to say where it goes. */

.scan-held {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
  margin: 14px 0 20px;
}

.scan-held-code {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 24px;
  letter-spacing: 0.04em;
}

.scan-held-note {
  font-size: 13.5px;
}

.scan-held-discard {
  margin-left: auto;
}

/* The search is the only thing to do on this panel, so it takes the width
   rather than the 260px a list screen's filter bar would give it — and a
   product name is long. */
.scan-find {
  display: flex;
  gap: 10px;
}

.scan-find .input {
  flex: 1;
  min-width: 0;
}

.scan-find .btn {
  flex: none;
}

.scan-rules ul {
  margin: 8px 0 0;
  padding-left: 16px;
  font-size: 11.5px;
  line-height: 1.65;
}

/* ══════════════════════════════════════════════════════════════════════════
   Scan screen — tablet and phone

   The canvas drew a separate fixed tablet layout. This is the desktop layout
   made to reflow instead, so there is one screen to build and maintain.
   ══════════════════════════════════════════════════════════════════════ */

@media (max-width: 1180px) {
  .scan-frame {
    grid-template-columns: 1fr;
  }

  .scan-work {
    border-right: 0;
    border-bottom: 2px solid var(--color-divider);
  }

  /* Total and actions read as a row of three across the full width. */
  .scan-side {
    display: grid;
    grid-template-columns: minmax(200px, 1fr) minmax(220px, 1fr) minmax(
        220px,
        1fr
      );
  }

  .scan-total,
  .scan-meta {
    border-bottom: 0;
    border-right: 1px solid var(--color-divider);
  }

  .scan-rules {
    grid-column: 1 / -1;
    margin-top: 0;
  }
}

@media (max-width: 1023px) {
  .scan {
    padding: 18px;
  }
}

@media (max-width: 860px) {
  .scan-side {
    grid-template-columns: 1fr;
  }

  .scan-total,
  .scan-meta {
    border-right: 0;
    border-bottom: 1px solid var(--color-divider);
  }
}

@media (max-width: 700px) {
  .scan {
    padding: 16px;
  }

  /* The scan field keeps full width; the button drops beneath it. */
  .scan-row {
    flex-wrap: wrap;
  }
  .scan-field {
    flex-basis: 100%;
  }
  .scan-add {
    width: 100%;
  }

  .scan-flash {
    flex-wrap: wrap;
    gap: 12px 20px;
  }
  .scan-flash-qty {
    font-size: 38px;
    min-width: 0;
  }
  .scan-flash-meta {
    text-align: left;
    width: 100%;
  }

  .scan-alert {
    flex-wrap: wrap;
  }
  .scan-alert .btn {
    margin-left: 0;
  }

  .doc-cell {
    border-right: 0;
    border-bottom: 1px solid var(--color-divider);
  }
  .doc-cell:last-child {
    border-bottom: 0;
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   Scan screen — touch sizing

   The canvas drew a separate tablet artboard whose stated rule was
   "Touch targets 56px · one thumb, no typing." That layout is gone, but the
   intent is not: once the frame stacks, every control a gloved hand has to
   hit grows to 56px and the numbers scale up with it.
   ────────────────────────────────────────────────────────────────────── */

@media (max-width: 1180px) {
  .scan .btn-icon {
    width: 56px;
    height: 56px;
    font-size: 20px;
  }

  .scan .line-actions {
    gap: 6px;
  }

  /*
   * The row number is dropped rather than letting the table scroll sideways
   * — a horizontal scroll would put the +/- controls out of reach, which is
   * the one thing this layout cannot afford. Barcode (and Box ×N when the
   * last scan was a case) sit under the product or variant they belong to.
   */
  .scan-lines .table th:nth-child(1),
  .scan-lines .table td:nth-child(1) {
    display: none;
  }

  .scan-lines .table {
    min-width: 600px;
  }
  .scan-lines .table th:last-child,
  .scan-lines .table td:last-child {
    width: 200px;
  }

  .scan-lines td {
    padding-top: 12px;
    padding-bottom: 12px;
  }

  .scan-lines td.qty {
    font-size: 20px;
  }

  .scan-qty {
    min-height: 56px;
    width: 5.25em;
    font-size: 20px;
  }

  .scan-actions .btn {
    height: 56px;
    font-size: 15px;
  }

  .scan-total-figure {
    font-size: 56px;
  }

  .scan-add {
    height: 70px;
    font-size: 17px;
  }

  .scan-field .input {
    min-height: 70px;
    font-size: 30px;
  }
}

/*
 * The running total is the number staff check while scanning, and once the
 * frame stacks it falls below the lines. On touch it rides at the bottom of
 * the viewport instead, carrying the count and the post action.
 */
@media (max-width: 860px) {
  .scan-total,
  .scan-meta,
  .scan-actions {
    padding-block: 12px;
  }

  /* On a phone the count and the post action carry the screen; the document
     recap and the rules panel are reference material and fold away. */
  .scan-meta,
  .scan-rules {
    display: none;
  }

  .scan-total {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    border-bottom: 1px solid var(--color-divider);
  }

  .scan-total .doc-label {
    margin-bottom: 0;
  }
  .scan-total-figure {
    font-size: 40px;
  }
  .scan-total-sub {
    margin-top: 0;
    margin-left: auto;
  }
}

/* A despatch line scanned beyond what is on hand. */
.scan-lines tr.is-short td {
  background: var(--color-accent-100);
}
.scan-lines tr.is-short td.qty {
  color: var(--color-accent-700);
}
.scan-lines tr.is-short .scan-qty {
  color: var(--color-accent-700);
  border-color: var(--color-accent-400);
}
.scan-lines tr.is-short:hover td {
  background: var(--color-accent-200);
}

/* ══════════════════════════════════════════════════════════════════════════
   Dialog contents

   Used by the unrecognised-barcode flow on the scan screens.
   ══════════════════════════════════════════════════════════════════════ */

/* The scanned code, shown back at the size it was read. */
.dialog-code {
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  padding: 12px 14px;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 20px;
  word-break: break-all;
}

/* A stack of full-width choices, where each is a route rather than a confirm. */
.dialog-choices {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.dialog-choices .btn {
  justify-content: flex-start;
  height: 44px;
  text-align: left;
}

.dialog-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

/* Pickable list of products to attach a barcode to. */
.attach-list {
  border: 1px solid var(--color-divider);
  max-height: 240px;
  overflow: auto;
}

.attach-option {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-bottom: 1px solid var(--color-divider);
  background: transparent;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-body);
  color: var(--color-text);
}

.attach-option:last-child {
  border-bottom: 0;
}

.attach-option:hover {
  background: color-mix(in srgb, var(--color-text) 6%, transparent);
}

/* The one that has been picked, before the attach is confirmed. Marked with
   the accent edge rather than a fill, so it stays legible under :hover. */
.attach-option.is-picked {
  background: var(--color-accent-100);
  box-shadow: inset 3px 0 0 var(--color-accent);
}

.attach-option-text {
  flex: 1;
  min-width: 0;
}

.attach-option-text strong {
  font-weight: 600;
  font-size: 13.5px;
}

.attach-option-text span {
  display: block;
  font-size: 12px;
}

.attach-option .tag {
  flex: none;
}

@media (max-width: 560px) {
  .dialog-grid {
    grid-template-columns: 1fr;
  }
  .dialog-actions {
    flex-wrap: wrap;
  }
  .dialog-actions .btn {
    flex: 1;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Products list
   ══════════════════════════════════════════════════════════════════════ */

.list {
  padding: 20px 28px 40px;
}

/* Filter bar: search and narrowing on the left, actions pushed right. */
.filters {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.filters .input {
  width: auto;
}
.filters .f-search {
  /* Gives way first when the bar runs out of room. A fifth select pushed
     the row past the content column at 1400px and bounced "New product"
     onto a line of its own; the search box shrinking a little is a better
     trade than a stranded button. */
  flex: 1 1 220px;
  width: auto;
  min-width: 170px;
  max-width: 260px;
}
.filters .f-brand {
  width: 150px;
}
.filters .f-cat {
  width: 150px;
}
.filters .f-status {
  width: 140px;
}
.filters .f-variants {
  width: 150px;
}

.filters .btn-primary {
  margin-left: auto;
}

/* Product row thumbnail, standing in for the photo. */
.thumb {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  background: var(--color-neutral-300);
  color: var(--color-neutral-700);
  font-size: 8px;
  letter-spacing: 0.06em;
}

/* Scoped to .products-table, not the bare .list wrapper, which the
   brands & categories screen also uses for its own, differently-shaped
   tables. */
.products-table {
  min-width: 1080px;
}

.list td.num {
  text-align: right;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-variant-numeric: tabular-nums;
}

.list td.bin {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12.5px;
}

.products-table .col-updated {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.list td.row-open {
  text-align: right;
}
.list td.row-open .btn {
  font-size: 12px;
}

/* ── Products list — tablet and phone ─────────────────────────────────
   Columns drop in order of least use to a person scanning the list, so the
   table stops scrolling sideways rather than being dragged at.
   ────────────────────────────────────────────────────────────────────── */

@media (max-width: 1180px) {
  .list {
    padding: 18px;
  }

  /* Category and Bin go first — both are detail, available on the product. */
  .products-table .col-cat,
  .products-table .col-bin {
    display: none;
  }

  .products-table {
    min-width: 820px;
  }
}

@media (max-width: 1023px) {
  /* Search takes its own row; the three narrowing selects share the next. */
  .filters .f-search {
    flex: 1 1 100%;
    width: auto;
  }
  .filters .f-brand,
  .filters .f-cat,
  .filters .f-status {
    flex: 1 1 140px;
    width: auto;
  }
}

@media (max-width: 700px) {
  /* Then brand and last updated — a phone is looking up a name, not a timestamp. */
  .products-table .col-brand,
  .products-table .col-updated {
    display: none;
  }

  .products-table {
    min-width: 460px;
  }
}

@media (max-width: 560px) {
  .list {
    padding: 16px;
  }
  .filters .btn-primary {
    margin-left: 0;
  }
  .filters .btn {
    flex: 1;
  }

  /*
   * Last to go is the variant count. What remains is the question a phone is
   * actually used to answer — what is this, how many are there, and is that a
   * problem — with no sideways drag to reach the status.
   */
  .products-table .col-variants {
    display: none;
  }

  .products-table {
    min-width: 0;
  }
  .products-table td,
  .products-table th {
    padding-inline: 6px;
  }
  .list td.row-open .btn,
  .list td.row-act .btn {
    padding-inline: 4px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Pagination

   Not in the canvas — built from the system's own parts. The joined-cell shape
   follows .seg: one outer hairline, dividers between cells, square corners,
   and the current page filled with the accent exactly as a checked .seg-opt is.
   ══════════════════════════════════════════════════════════════════════ */

.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-divider);
}

.pager-count {
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}

.pager-list {
  display: inline-flex;
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--color-divider);
}

.pager-list li + li {
  border-left: 1px solid var(--color-divider);
}

.pager-item {
  display: grid;
  place-items: center;
  min-width: 36px;
  height: 36px;
  padding: 0 8px;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
  text-decoration: none;
  cursor: pointer;
  background: transparent;
  border: 0;
}

.pager-item:hover {
  background: color-mix(in srgb, var(--color-text) 7%, transparent);
  color: var(--color-text);
}

.pager-item[aria-current="page"] {
  background: var(--color-accent);
  color: var(--color-bg);
}

.pager-item[aria-current="page"]:hover {
  background: var(--color-accent);
}

/* Previous and Next carry words, so they need room. */
.pager-edge {
  padding: 0 14px;
}

.pager-item[aria-disabled="true"] {
  opacity: 0.35;
  cursor: not-allowed;
}

.pager-item[aria-disabled="true"]:hover {
  background: transparent;
}

/* The gap in the run of numbers. Not a control. */
.pager-gap {
  display: grid;
  place-items: center;
  min-width: 30px;
  height: 36px;
  font-size: 13px;
  color: color-mix(in srgb, var(--color-text) 45%, transparent);
  user-select: none;
}

/* Shown only once the numbers are hidden. */
.pager-position {
  display: none;
}

@media (max-width: 700px) {
  .pager {
    justify-content: center;
    gap: var(--space-2);
  }

  .pager-count {
    order: 2;
    width: 100%;
    text-align: center;
  }

  /*
   * Numbers give way to Previous / position / Next. The list items are hidden
   * too, not just their contents — an emptied <li> still draws its 1px
   * divider, which would leave a row of stray hairlines.
   * (:has() is already part of the canvas system, see .seg-opt.)
   */
  .pager-list li:has(.pager-num),
  .pager-list li:has(.pager-gap) {
    display: none;
  }

  .pager-position {
    display: grid;
    place-items: center;
    min-width: 92px;
    height: 36px;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
  }

  .pager-list {
    flex: 1;
  }
  .pager-edge {
    flex: 1;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Product detail

   One product, everything the warehouse needs to know about it. The canvas
   did not draw this screen, so it is assembled from parts already designed
   for the ones that were: the framed two-column block from the scan screens,
   the bordered cell strip from the document bar, and the section headings
   from the dashboard.

   No prices appear anywhere — the system counts units only.
   ══════════════════════════════════════════════════════════════════════ */

.prod {
  padding: 20px 28px 40px;
}

.prod-frame {
  border: 2px solid var(--color-divider);
}

/* ---- Identity band: photo, what it is, and how it is stocked ---- */

.prod-head {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  padding: 20px 22px;
  border-bottom: 2px solid var(--color-divider);
}

/* Stands in for the product photo, same as .thumb does in the list. */
.prod-photo {
  width: 96px;
  height: 96px;
  flex: none;
  display: grid;
  place-items: center;
  background: var(--color-neutral-300);
  color: var(--color-neutral-700);
  font-size: 10px;
  letter-spacing: 0.08em;
}

.prod-id {
  min-width: 0;
  flex: 1;
}

.prod-kicker {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 6px;
}

.prod-name {
  margin: 0 0 8px;
  font-size: 26px;
  letter-spacing: -0.02em;
}

/* SKU, bin and status on one line — the three things read off a shelf. */
.prod-idline {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12.5px;
}

.prod-idline .code {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12px;
}

.prod-idline .sep {
  color: color-mix(in srgb, var(--color-text) 35%, transparent);
}

/* ---- Figure strip ---- */

.prod-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  border-bottom: 2px solid var(--color-divider);
}

.prod-stat {
  padding: 14px 18px 16px;
  border-right: 1px solid var(--color-divider);
  min-width: 0;
}

.prod-stat:last-child {
  border-right: 0;
}

.prod-stat-label {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 4px;
}

.prod-stat-figure {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 34px;
  line-height: 1.05;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.prod-stat-sub {
  font-size: 11.5px;
  margin-top: 3px;
}

/* Available is the figure a picker acts on, so it carries the accent. */
.prod-stat.is-key .prod-stat-figure {
  color: var(--color-accent);
}

/* ---- Two columns ---- */

.prod-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
}

.prod-main {
  border-right: 2px solid var(--color-divider);
  min-width: 0;
}

/* One block of the left column. Each is separated by a rule, not a card, so
   the whole column reads as one document rather than a stack of panels. */
.prod-sec {
  padding: 18px 22px 20px;
  border-bottom: 1px solid var(--color-divider);
}

.prod-sec:last-child {
  border-bottom: 0;
}

/* Mirrors .dash-h — same shape, kept local so the dashboard stays untouched. */
.prod-h {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.prod-h h6 {
  margin: 0;
}

.prod-h-note {
  font-size: 11.5px;
  margin-left: auto;
}

.prod-h .btn {
  font-size: 12.5px;
}

.prod-sec .notice {
  margin-bottom: 12px;
}
.prod-sec .notice p {
  margin: 0;
}

.prod-sec .table {
  min-width: 720px;
}

.prod-sec td.num {
  text-align: right;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-variant-numeric: tabular-nums;
}

.prod-sec th.num {
  text-align: right;
}

.prod-sec td.code {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12.5px;
  white-space: nowrap; /* a hyphenated SKU otherwise breaks across two lines */
}

/* The direction tags stack in one column, so they are set to a common width
   rather than each shrinking to its own word. */
.prod-moves td .tag {
  min-width: 58px;
  justify-content: center;
}

/* A tag is a label, not a paragraph — it wraps to two lines in a narrow
   status column otherwise. Same rule the dashboard queue uses. */
.prod-sec .tag {
  white-space: nowrap;
}

.prod-sec td.row-act {
  text-align: right;
  white-space: nowrap;
}
.prod-sec td.row-act .btn {
  font-size: 12px;
}

.prod-sec .table td.muted {
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}

/* A variant sitting below its low-stock level. Only the two counts that are
   actually short turn accent — the reservation and the level itself are
   reference, and colouring them too would spread the alarm across the row. */
.prod-sec tr.is-low td.num:not(.muted) {
  color: var(--color-accent-700);
}

/* ---- Right column ---- */

.prod-side {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Label / value pairs. Same construction as .scan-meta. */
.prod-meta {
  padding: 16px 18px;
  border-bottom: 1px solid var(--color-divider);
  font-size: 12.5px;
  line-height: 1.7;
}

.prod-meta div {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
}

.prod-meta div > span:last-child {
  text-align: right;
  font-weight: 600;
}

.prod-meta .code {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12px;
}

.prod-actions {
  padding: 16px 18px;
  border-bottom: 1px solid var(--color-divider);
}

.prod-actions .btn {
  width: 100%;
  justify-content: flex-start;
  margin-bottom: 8px;
}

.prod-actions .btn:last-child {
  margin-bottom: 0;
}

/* Archiving is the one destructive control here, so it reads apart from the
   rest and is never the primary. A plain modifier class — .prod-actions adds
   its own top margin to separate it from the button stack above; the colour
   and hover apply wherever the class is used. */
.btn-archive {
  color: var(--color-danger);
}
.btn-archive:hover {
  background: var(--color-danger-100);
}
.prod-actions .btn-archive {
  margin-top: 14px;
}

/*
 * Sits directly under the actions rather than being pushed to the foot of the
 * column: the left column runs to three tables, and an auto margin would
 * strand this half a screen below anything else.
 */
.prod-notes {
  padding: 16px 18px;
  border-top: 1px solid var(--color-divider);
  background: var(--color-surface);
}

.prod-notes ul {
  margin: 8px 0 0;
  padding-left: 16px;
  font-size: 11.5px;
  line-height: 1.65;
}

/* ── Product detail — tablet and phone ────────────────────────────────
   The side column carries reference detail and actions, not the answer to
   "how many are there", so it goes under the tables rather than above them.
   ────────────────────────────────────────────────────────────────────── */

@media (max-width: 1180px) {
  .prod-cols {
    grid-template-columns: minmax(0, 1fr);
  }

  .prod-main {
    border-right: 0;
    border-bottom: 2px solid var(--color-divider);
  }
}

@media (max-width: 1023px) {
  .prod {
    padding: 18px;
  }

  .prod-head {
    padding: 18px;
  }
  .prod-sec {
    padding: 16px 18px 18px;
  }

  /*
   * Variants: the low-stock level is stated in the notice when it is
   * breached, and the barcode is one tap away in the Edit dialog, so both
   * columns can go rather than be dragged at.
   */
  .prod-variants .table th:nth-child(3),
  .prod-variants .table td:nth-child(3),
  .prod-variants .table th:nth-child(7),
  .prod-variants .table td:nth-child(7) {
    display: none;
  }

  .prod-sec .table {
    min-width: 640px;
  }
}

@media (max-width: 700px) {
  .prod-photo {
    width: 64px;
    height: 64px;
  }
  .prod-name {
    font-size: 21px;
  }
  .prod-stat-figure {
    font-size: 28px;
  }

  /* The bin and the SKU go: both are repeated in the Edit variant dialog,
     and available/status are what anyone opening this on a phone needs. */
  .prod-variants .table th:nth-child(2),
  .prod-variants .table td:nth-child(2),
  .prod-variants .table th:nth-child(4),
  .prod-variants .table td:nth-child(4) {
    display: none;
  }

  .prod-variants .table {
    min-width: 0;
  }

  /*
   * Row actions go on a phone. Edit is a deliberate, two-handed job, and
   * keeping it here costs the width that Available and Status — the reason
   * anyone opens this on a phone — need to stay on screen. It remains on
   * tablet and desktop.
   */
  .prod-variants .table th:nth-child(9),
  .prod-variants .table td:nth-child(9) {
    display: none;
  }

  /* Movements keeps time, quantity and reference — enough to recognise the
     entry — and drops who and which variant. */
  .prod-moves .table th:nth-child(3),
  .prod-moves .table td:nth-child(3),
  .prod-moves .table th:nth-child(6),
  .prod-moves .table td:nth-child(6) {
    display: none;
  }

  .prod-moves .table {
    min-width: 0;
  }
}

@media (max-width: 560px) {
  .prod {
    padding: 16px;
  }
  .prod-head {
    gap: 14px;
  }
  .prod-sec .table td,
  .prod-sec .table th {
    padding-inline: 6px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Brands & categories

   Two lists sharing one page-body, switched by a .seg radio pair. The switch
   is pure CSS: :has() on the wrapping .tabs element reads which radio is
   checked and hides the other panel — the same technique .seg itself and the
   pager already use, so the screen needs no script.
   ══════════════════════════════════════════════════════════════════════ */

.tabs-bar {
  padding: 18px 28px 0;
}

.tab-panel {
  display: block;
}

.tabs:has(#tab-cats:checked) .panel-brands {
  display: none;
}
.tabs:has(#tab-cats:checked) .panel-cats {
  display: block;
}
.tabs:not(:has(#tab-cats:checked)) .panel-cats {
  display: none;
}

/* Both panels reuse .list's own padding, so nothing here repeats it. */

.list td.row-act {
  text-align: right;
  white-space: nowrap;
}
.list td.row-act .btn {
  font-size: 12px;
}
.list td.row-act .btn + .btn {
  margin-left: 4px;
}

/* ---- Categories table ---- */

.cats-table tr.cat-parent td:first-child {
  font-weight: 600;
}

.cats-table tr.cat-parent td {
  border-top: 1px solid var(--color-divider);
}

.cats-table tr.cat-parent:first-child td {
  border-top: 0;
}

.cats-table tr.cat-child td:first-child {
  padding-left: calc(var(--space-2) + 22px);
  color: color-mix(in srgb, var(--color-text) 80%, transparent);
}

/* ── Brands & categories — tablet and phone ───────────────────────────
   ────────────────────────────────────────────────────────────────────── */

@media (max-width: 1023px) {
  .tabs-bar {
    padding: 16px 18px 0;
  }

  /* Categories only has three columns to begin with, so only the brands
     table's own extra column needs to give way here. */
  .panel-brands .table th:nth-child(3),
  .panel-brands .table td:nth-child(3) {
    display: none;
  }
  .panel-brands .table {
    min-width: 560px;
  }
}

@media (max-width: 700px) {
  .panel-brands .table th:nth-child(1),
  .panel-brands .table td:nth-child(1) {
    display: none;
  }
  .panel-brands .table {
    min-width: 420px;
  }
}

@media (max-width: 560px) {
  .tabs-bar {
    padding: 14px 16px 0;
  }
  .filters .btn-primary {
    margin-left: 0;
  }
  .filters .btn {
    flex: 1;
  }

  .list td.row-act {
    text-align: left;
  }
  .list td.row-act .btn {
    padding-inline: 4px;
  }

  /*
   * Row actions go, same call as the product screen: Edit and Archive are
   * deliberate two-handed jobs, and giving up the width lets Brand and
   * Status stay on screen with nothing to drag sideways for. Products
   * follows — Variants is the number that actually says how much is
   * stocked under this brand.
   */
  .panel-brands .table th:nth-child(4),
  .panel-brands .table td:nth-child(4),
  .panel-brands .table th:nth-child(7),
  .panel-brands .table td:nth-child(7) {
    display: none;
  }

  .panel-brands .table {
    min-width: 0;
  }

  .cats-table td.row-act .btn:last-child {
    display: none;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Suppliers

   Same list construction as the products and brands tables, scoped to its
   own .suppliers-table class rather than the bare .list wrapper — sharing
   that selector with another screen's table caused columns to vanish
   unexpectedly on the brands & categories screen, so every catalogue table
   now hides its own columns under its own class.
   ══════════════════════════════════════════════════════════════════════ */

.suppliers-table {
  min-width: 900px;
}

.suppliers-table td.code {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12.5px;
  white-space: nowrap;
}

/* ── Suppliers — tablet and phone ─────────────────────────────────────
   Email goes first — a phone number is dialled, an email is not read off
   a warehouse floor. Contact name and the product count follow.
   ────────────────────────────────────────────────────────────────────── */

@media (max-width: 1180px) {
  .suppliers-table th:nth-child(5),
  .suppliers-table td:nth-child(5) {
    display: none;
  }
  .suppliers-table {
    min-width: 720px;
  }
}

@media (max-width: 900px) {
  .suppliers-table th:nth-child(3),
  .suppliers-table td:nth-child(3),
  .suppliers-table th:nth-child(6),
  .suppliers-table td:nth-child(6) {
    display: none;
  }
  .suppliers-table {
    min-width: 520px;
  }
}

@media (max-width: 700px) {
  .suppliers-table th:nth-child(1),
  .suppliers-table td:nth-child(1) {
    display: none;
  }
  .suppliers-table {
    min-width: 420px;
  }
}

@media (max-width: 560px) {
  .suppliers-table th:nth-child(8),
  .suppliers-table td:nth-child(8) {
    display: none;
  }
  .suppliers-table {
    min-width: 0;
  }
  .suppliers-table td,
  .suppliers-table th {
    padding-inline: 6px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Movement history

   A straight read of the stock_movements ledger — every row it shows was
   written once, at the moment of a confirmed in, out or adjustment, and nothing
   here can be edited. Same table shape as the product screen's own "Recent
   movements", with a Product column added since this view spans all of them.
   ══════════════════════════════════════════════════════════════════════ */

.filters .f-type {
  width: 130px;
}
.filters .f-date {
  width: 152px;
}

.filters-to {
  font-size: 13px;
}

.moves-table {
  min-width: 860px;
}

.moves-table td.code {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12.5px;
  white-space: nowrap;
}

/* The direction tags stack in one column, so a common width reads as a list
   rather than each tag shrinking to its own word — same call as the product
   screen's own movements table. */
.moves-table td .tag {
  min-width: 58px;
  justify-content: center;
}

/* ── Movement history — tablet and phone ──────────────────────────────
   By goes first — who moved it matters least once the reference and
   quantity are on screen — then Variant, which the reference already
   narrows down, then Reference itself once the phone is down to When,
   Type, Product and Qty: the four that answer "what happened".
   ────────────────────────────────────────────────────────────────────── */

@media (max-width: 1023px) {
  .list {
    padding: 18px;
  }

  .filters .f-date {
    width: 138px;
  }
}

@media (max-width: 900px) {
  .moves-table th:nth-child(7),
  .moves-table td:nth-child(7) {
    display: none;
  }
  .moves-table {
    min-width: 680px;
  }
}

@media (max-width: 700px) {
  .moves-table th:nth-child(4),
  .moves-table td:nth-child(4) {
    display: none;
  }
  .moves-table {
    min-width: 520px;
  }
}

@media (max-width: 560px) {
  .list {
    padding: 16px;
  }

  .filters .f-search {
    flex: 1 1 100%;
    width: auto;
  }
  .filters .f-type {
    flex: 1 1 auto;
  }
  .filters .f-date {
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
  }

  .moves-table th:nth-child(6),
  .moves-table td:nth-child(6) {
    display: none;
  }
  .moves-table {
    min-width: 0;
  }
  .moves-table td,
  .moves-table th {
    padding-inline: 6px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Reports & analytics

   Eight fixed reports, picked from a vertical list rather than a dashboard —
   each is CSV/Excel-ready output, not a live overview. Unit counts
   throughout: the system holds no cost or sale price, so no report carries
   a value, margin or revenue column. Panel switching is the same CSS-only
   :has() technique as the other tab pairs in this system, scaled to eight
   panels sharing one radio group instead of two.
   ══════════════════════════════════════════════════════════════════════ */

.rpt {
  padding: 0 0 40px;
}

.rpt-shell {
  display: flex;
  align-items: stretch;
}

.rpt-nav {
  width: 260px;
  flex: none;
  padding: 20px 20px 24px;
  border-right: 1px solid var(--color-divider);
}

.rpt-nav-kicker {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 45%, transparent);
  margin-bottom: 14px;
}

.rpt-nav-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rpt-nav-item {
  display: block;
  padding: 9px 12px;
  margin: 0 -20px;
  font-size: 13px;
  /* Matches .side-item: this nav sits directly beside the sidebar, and
     letting the two drift apart would invert the hierarchy between them. */
  font-weight: 600;
  line-height: 1.35;
  cursor: pointer;
  border-left: 2px solid transparent;
}

.rpt-nav-item input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.rpt-nav-item:hover {
  background: var(--color-bg);
}

.rpt-nav-item:has(input:checked) {
  color: var(--color-accent);
  border-left-color: var(--color-accent);
  background: var(--color-bg);
}

/* The app renders one report per request, so its nav is links rather than a
   radio group and the panel it picks is already the only one on the page.
   Same look, driven by aria-current and an explicit class instead. */
a.rpt-nav-item {
  text-decoration: none;
  color: inherit;
}

.rpt-nav-item[aria-current="page"] {
  color: var(--color-accent);
  border-left-color: var(--color-accent);
  background: var(--color-bg);
  font-weight: 700;
}

.rpt-panel.is-shown {
  display: block;
}

.rpt-content {
  flex: 1;
  min-width: 0;
  padding: 24px 28px 28px;
}

/* Eight panels, one radio group — default to panel 1, then one show rule
   and one hide-panel-1 rule per remaining radio. */
.rpt-panel {
  display: none;
}
.rpt-panel-1 {
  display: block;
}

.rpt-shell:has(#rpt-2:checked) .rpt-panel-1 {
  display: none;
}
.rpt-shell:has(#rpt-3:checked) .rpt-panel-1 {
  display: none;
}
.rpt-shell:has(#rpt-4:checked) .rpt-panel-1 {
  display: none;
}
.rpt-shell:has(#rpt-5:checked) .rpt-panel-1 {
  display: none;
}
.rpt-shell:has(#rpt-6:checked) .rpt-panel-1 {
  display: none;
}
.rpt-shell:has(#rpt-7:checked) .rpt-panel-1 {
  display: none;
}
.rpt-shell:has(#rpt-8:checked) .rpt-panel-1 {
  display: none;
}

.rpt-shell:has(#rpt-2:checked) .rpt-panel-2 {
  display: block;
}
.rpt-shell:has(#rpt-3:checked) .rpt-panel-3 {
  display: block;
}
.rpt-shell:has(#rpt-4:checked) .rpt-panel-4 {
  display: block;
}
.rpt-shell:has(#rpt-5:checked) .rpt-panel-5 {
  display: block;
}
.rpt-shell:has(#rpt-6:checked) .rpt-panel-6 {
  display: block;
}
.rpt-shell:has(#rpt-7:checked) .rpt-panel-7 {
  display: block;
}
.rpt-shell:has(#rpt-8:checked) .rpt-panel-8 {
  display: block;
}

.rpt-toolbar {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-bottom: 18px;
}

.rpt-report-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px 20px;
  flex-wrap: wrap;
}

.rpt-report-head h4 {
  margin: 0 0 4px;
}
.rpt-report-head .text-muted {
  font-size: 12.5px;
}

.rpt-export {
  display: flex;
  gap: 8px;
  align-items: center;
  flex: none;
}

.rpt-filters {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-end;
  gap: 10px;
  min-width: 0;
}

.rpt-filter-fields {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-end;
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
}

.rpt-filters .btn {
  /* Sits on the input baseline; the fields above it carry a kicker label. */
  flex: none;
  height: 38px;
}

.rpt-filters .field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 0;
  min-width: 0;
}

.rpt-filters .field label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: color-mix(in srgb, var(--color-text) 45%, transparent);
}

.rpt-filters .input,
.rpt-filters .f-brand,
.rpt-filters .f-cat,
.rpt-filters .f-product {
  width: 100%;
  min-width: 0;
}

/* Native multiple selects are tall list boxes. Clamp them until combo-select
   hides them, so the filter row does not jump on first paint. */
.rpt-filters select[multiple] {
  height: 38px;
  min-height: 38px;
}

/* ---- Weekly trend: plain bars, no axis labels — matches the reference's
   own minimal chart rather than reusing the dashboard's line/area chart. ---- */

.rpt-chart-bars {
  display: flex;
  align-items: flex-end;
  /* The reference always drew eight bars. A real range can ask for two or
     for a year of them, so the gap has to give way rather than push the
     bars out of the box: at 52 weeks a fixed 16px is 832px of gap alone. */
  gap: clamp(2px, 1.5%, 16px);
  height: 140px;
  padding: 0 4px 12px;
  border-bottom: 1px solid var(--color-divider);
  margin-bottom: 22px;
}

.rpt-chart-bar {
  /* Capped at the width eight bars happen to take, so a two-week range
     reads as a chart rather than as two red slabs. */
  flex: 1 1 0;
  min-width: 2px;
  max-width: 72px;
  background: var(--color-accent);
}

.rpt-panel-note {
  font-size: 12px;
  margin-top: 12px;
}

/* ── Reports & analytics — tablet and phone ───────────────────────────
   ────────────────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .rpt-shell {
    flex-direction: column;
  }
  .rpt-nav {
    width: auto;
    border-right: 0;
    border-bottom: 1px solid var(--color-divider);
  }
}

@media (max-width: 1023px) {
  .rpt {
    padding: 0 0 18px;
  }
  .rpt-content {
    padding: 18px;
  }
  .page-actions {
    flex-wrap: wrap;
  }
}

@media (max-width: 560px) {
  .rpt {
    padding: 0 0 16px;
  }
  .rpt-report-head {
    flex-direction: column;
    align-items: stretch;
  }
  .rpt-export {
    align-self: flex-start;
  }
  .rpt-filters,
  .rpt-filter-fields {
    flex-wrap: wrap;
  }
  .rpt-filter-fields .field {
    flex: 1 1 calc(50% - 5px);
  }
  .rpt-filters .btn {
    margin-left: auto;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Staff & roles

   Same two-panel construction as brands & categories: a .seg radio pair,
   :has() to switch panels, no script. Its own table classes throughout —
   .staff-table and .roles-table — so hiding a column here can never reach
   into another screen's table the way the early products/brands collision
   did.
   ══════════════════════════════════════════════════════════════════════ */

.tabs:has(#tab-roles:checked) .panel-staff {
  display: none;
}
.tabs:has(#tab-roles:checked) .panel-roles {
  display: block;
}
.tabs:not(:has(#tab-roles:checked)) .panel-roles {
  display: none;
}

.staff-table {
  min-width: 860px;
}

/* A locked or disabled row reads as inactive at a glance, before the tag
   itself is even read. */
.staff-table tr.is-locked td,
.staff-table tr.is-disabled td {
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}

.staff-table tr.is-locked td:nth-child(2),
.staff-table tr.is-disabled td:nth-child(2) {
  color: var(--color-text);
}

.roles-table {
  min-width: 640px;
}
.roles-table td:nth-child(2) {
  max-width: 480px;
}

/* ── Staff & roles — tablet and phone ─────────────────────────────────
   Last active goes first, then email — a name and a role already say who
   this is and what they can do; when they were last signed in and their
   own address are the detail.
   ────────────────────────────────────────────────────────────────────── */

@media (max-width: 1023px) {
  .list {
    padding: 18px;
  }

  .staff-table th:nth-child(6),
  .staff-table td:nth-child(6) {
    display: none;
  }
  .staff-table {
    min-width: 680px;
  }

  .roles-table {
    min-width: 560px;
  }
}

@media (max-width: 900px) {
  .staff-table th:nth-child(3),
  .staff-table td:nth-child(3) {
    display: none;
  }
  .staff-table {
    min-width: 520px;
  }
}

@media (max-width: 700px) {
  .staff-table th:nth-child(1),
  .staff-table td:nth-child(1) {
    display: none;
  }
  .staff-table {
    min-width: 420px;
  }

  .roles-table td:nth-child(2) {
    display: none;
  }
  .roles-table {
    min-width: 0;
  }
}

@media (max-width: 560px) {
  .list {
    padding: 16px;
  }
  .filters .btn-primary {
    margin-left: 0;
  }
  .filters .btn {
    flex: 1;
  }

  .staff-table th:nth-child(7),
  .staff-table td:nth-child(7) {
    display: none;
  }
  .staff-table {
    min-width: 0;
  }
  .staff-table td,
  .staff-table th {
    padding-inline: 6px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   New/edit dialogs — brand, category, staff

   .check and .radio are both <label> elements. Placed as direct children of
   .field they collide with .field > label's own heading style (font-size
   12px, muted colour, block layout) — that selector's specificity beats
   .check/.radio's own, and the row collapses to plain stacked text with no
   visible box or dot. Wrapping the group in its own div keeps .field's
   direct-child selector matching only the field's heading label.
   ══════════════════════════════════════════════════════════════════════ */

.check-group,
.radio-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 2px;
}

/* ══════════════════════════════════════════════════════════════════════════
   Product form — new and edit

   One column, not two — a form is filled in top to bottom, unlike the
   product detail screen's read view, which has a reference rail worth
   keeping beside the content. Sections are separated by rules rather than
   cards, matching the product screen's own .prod-sec rhythm, under its own
   .pform-sec class so nothing here can reach into that screen's tables.
   ══════════════════════════════════════════════════════════════════════ */

.pform {
  padding: 20px 28px 40px;
}

.pform-frame {
  border: 2px solid var(--color-divider);
  max-width: 860px;
}

.pform-sec {
  padding: 20px 24px 22px;
  border-bottom: 1px solid var(--color-divider);
}

.pform-sec:last-child {
  border-bottom: 0;
}

.pform-h {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.pform-h h6 {
  margin: 0;
}

.pform-h-note {
  font-size: 11.5px;
  margin-left: auto;
}

/* ---- Identity: photo beside the fields ---- */

.pform-row {
  display: flex;
  gap: 20px;
  align-items: flex-start;
}

.pform-photo {
  width: 84px;
  height: 84px;
  flex: none;
  font-size: 9px;
}

.pform-fields {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.pform-field-wide {
  max-width: 480px;
}

.pform-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
}

.pform-grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-top: 14px;
}

.radio-group.is-row {
  flex-direction: row;
  gap: 20px;
}

/* ---- Variant rows ---- */

.pform-variant-rows {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-bottom: 14px;
}

.pform-variant-row {
  display: grid;
  grid-template-columns: 1.4fr 1fr 110px 110px auto;
  gap: 12px;
  align-items: end;
}

.pform-field-num .input {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.pform-row-remove {
  flex: none;
}

/* ---- Empty state (barcodes not yet added) ---- */

.pform-empty {
  padding: 14px;
  margin-bottom: 14px;
  background: var(--color-surface);
  font-size: 13px;
  text-align: center;
}

/* ---- Notes ---- */

.pform-sec textarea.input {
  min-height: 90px;
}

/* ── Product form — tablet and phone ──────────────────────────────────
   ────────────────────────────────────────────────────────────────────── */

@media (max-width: 1023px) {
  .pform {
    padding: 18px;
  }
  .pform-sec {
    padding: 16px 18px 18px;
  }
}

@media (max-width: 700px) {
  .pform-row {
    flex-direction: column;
  }
  .pform-photo {
    width: 64px;
    height: 64px;
  }

  .pform-variant-row {
    grid-template-columns: 1fr 1fr;
  }

  .pform-row-remove {
    grid-column: 1 / -1;
    justify-self: start;
  }
}

@media (max-width: 560px) {
  .pform {
    padding: 16px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Variant capture — used in the Add/Edit variant dialogs (product detail,
   new product, edit product) and the "no variants" product identity block.

   A variant carries exactly one primary item barcode and one box barcode;
   extra codes can be scanned in later as aliases, which is why the note
   under the dialog exists rather than a repeatable barcode list.
   ══════════════════════════════════════════════════════════════════════ */

.scan-inline {
  display: flex;
  gap: 8px;
}
.scan-inline .input {
  flex: 1;
  min-width: 0;
  font-family: ui-monospace, Menlo, monospace;
}
.scan-inline .btn {
  flex: none;
}

/* Track-by-variant toggle: pure-CSS show/hide via :has(), same technique as
   the brands/staff tab switching. Each product page defines its own pair of
   radio ids (pn-/pe-) so nothing here can reach into another screen's
   markup — only the shared :has() rule and default state live here. */
.pform-hasvariant {
  display: none;
}

.pform:has(#pn-track-yes:checked) .pform-novariant,
.pform:has(#pe-track-yes:checked) .pform-novariant {
  display: none;
}

.pform:has(#pn-track-yes:checked) .pform-hasvariant,
.pform:has(#pe-track-yes:checked) .pform-hasvariant {
  display: block;
}

.pform:has(#pn-track-no:checked) .pform-hasvariant,
.pform:has(#pe-track-no:checked) .pform-hasvariant {
  display: none;
}

/* Read-only summary rows on the Edit product page, when the product already
   carries variants — mirrors the product detail page's own variant table
   but compact, since this is Identity context, not the stock desk. */
.pform-variants-list {
  border: 1px solid var(--color-divider);
}

.pform-variant-item {
  border-bottom: 1px solid var(--color-divider);
}

.pform-variant-item:last-child {
  border-bottom: 0;
}

/* Filtered out by the search above the list. Stated explicitly because a row
   given any display value would otherwise beat [hidden]. */
.pform-variant-item[hidden] {
  display: none;
}

/* :last-child still matches a filtered-out row, so the last *visible* one is
   marked from script to keep the list's bottom edge a single line. */
.pform-variant-item.is-last-shown {
  border-bottom: 0;
}

/* ---- Search over the held variants ---- */

.pform-variant-search {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.pform-variant-search .input {
  flex: 1;
  min-width: 0;
}

.pform-variant-search-count {
  flex: none;
  font-size: 11.5px;
  white-space: nowrap;
}

.pform-variant-empty {
  padding: 14px;
  font-size: 12.5px;
}

.pform-variant-empty[hidden] {
  display: none;
}

.pform-variant-summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
}

/* Edit drill-down: expands in place, same pure-CSS :has() technique as the
   add-variant panel below, so editing a held line never opens a dialog. */
.pform-variant-editing {
  display: none;
  border-top: 2px solid var(--color-accent);
  padding: 16px 18px;
}

.pform-variant-item:has(input:checked) .pform-variant-summary {
  display: none;
}
.pform-variant-item:has(input:checked) .pform-variant-editing {
  display: block;
}

.pform-variant-item .prod-photo {
  width: 40px;
  height: 40px;
  font-size: 8px;
  flex: none;
}

.pform-variant-text {
  flex: 1;
  min-width: 0;
}

.pform-variant-text strong {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
}

.pform-variant-text span {
  display: block;
  font-size: 11.5px;
  font-family: ui-monospace, Menlo, monospace;
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pform-variant-qty {
  flex: none;
  font-size: 13px;
  font-weight: 600;
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  padding: 4px 10px;
  border-radius: var(--radius-md, 6px);
}

.pform-variant-item .btn {
  flex: none;
}

.pform-variant-delete-panel {
  margin-top: 12px;
}

@media (max-width: 560px) {
  .pform-variant-text span {
    white-space: normal;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Edit product — two-column layout with a reference/photo rail.

   Reuses .prod-cols/.prod-main/.prod-side wholesale from the product detail
   page rather than inventing a parallel layout system — it is the same
   left-main, right-reference split, just holding form fields instead of
   read-only data, and it inherits that page's own collapse-to-one-column
   breakpoint for free.
   ══════════════════════════════════════════════════════════════════════ */

.pform-frame.pform-wide {
  max-width: 1120px;
}

.pform-photo-box {
  aspect-ratio: 1;
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  display: flex;
  align-items: center;
  justify-content: center;
  color: color-mix(in srgb, var(--color-text) 45%, transparent);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 10px;
}

.pform-side-sec {
  padding: 16px 18px;
  border-bottom: 1px solid var(--color-divider);
}
.pform-side-sec:last-child {
  border-bottom: 0;
}
.pform-side-sec h6 {
  margin: 0 0 12px;
}
.pform-side-sec .btn {
  width: 100%;
  margin-bottom: 8px;
}
.pform-side-sec p.text-muted {
  font-size: 11.5px;
  margin: 0;
}

/* Add-variant panel — expands inline within Variants Held via a hidden
   checkbox, the same :has() technique as the Stock tracking toggle, so
   filling in a new line never leaves the page or opens a dialog on top of
   the list it is adding to. */
.pform-add-variant-btn {
  width: 100%;
  justify-content: flex-start;
  margin-top: 10px;
}

/* Bulk edit sits beside Add, same weight. The add label still hides when
   the inline panel opens; the bulk link stays, so a hundred-line job is
   never one extra click behind a form they did not mean to open. The
   pair sits above the list so neither is buried under a long flavour run. */
.pform-variant-actions {
  display: flex;
  gap: 8px;
  margin: 0 0 10px;
}

.pform-variant-actions .pform-add-variant-btn {
  width: auto;
  flex: 1;
  margin-top: 0;
  justify-content: center;
}

.pform-variant-actions > .btn {
  flex: 1;
  justify-content: center;
}

.pform-adding-variant {
  display: none;
  border: 2px solid var(--color-accent);
  padding: 16px 18px;
  margin: 0 0 10px;
}

.pform-hasvariant:has(#pe-var-adding:checked) .pform-adding-variant {
  display: block;
}
.pform-hasvariant:has(#pe-var-adding:checked) .pform-add-variant-btn {
  display: none;
}

.pform-adding-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}

.pform-adding-h span {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent-700);
}

.pform-adding-h label {
  cursor: pointer;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--color-accent);
}

.pform-adding-note {
  font-size: 11.5px;
  margin-top: 10px;
}

/* ---- Multi-barcode rows ---- */

[data-barcode-list] {
  margin-top: 14px;
}

.pform-barcodes-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.pform-barcode-row {
  display: flex;
  gap: 10px;
  align-items: flex-end;
  margin-bottom: 10px;
}

/* Barcode code field: 4 parts */
.pform-barcode-row > .field {
  flex: 4 1 0;
  min-width: 0;
  margin-bottom: 0;
}

/* Type and qty: 2 parts each — same width as other inputs in the 3-col grids above */
.pform-barcode-row > .pform-barcode-type { flex: 2 1 0; }
.pform-barcode-row > .pform-barcode-qty  { flex: 2 1 0; }

.pform-barcode-remove {
  flex: none;
  margin-bottom: 2px;
}

/* ══════════════════════════════════════════════════════════════════════════
   Searchable option picker

   Not in the canvas — the canvas predates a catalogue with 2,400 flavours in
   it. A native select is fine up to a dozen options and unusable past that,
   so any variable with more than ten is upgraded to this: the same field, a
   filtered list underneath, keyboard driven.

   The native <select> stays in the DOM and stays the thing that submits. It
   is only hidden once script has replaced it, so with JavaScript off the page
   falls back to the plain select rather than to nothing.
   ══════════════════════════════════════════════════════════════════════════ */

.combo {
  position: relative;
}

/* Applied by script, never in the markup — see the note above. */
.combo-native.is-upgraded {
  display: none;
}

/*
 * Width lives on the select's own class in a filter bar (.f-brand and
 * friends), and combo-select.js copies those classes onto the wrapper it
 * puts in the select's place. On a form field there is no such class, so
 * the wrapper and the input both have to fill the .field themselves.
 */
.filters .combo .combo-input,
.field .combo,
.field .combo .combo-input {
  width: 100%;
}

.combo-input[aria-expanded="true"] {
  border-color: var(--color-accent);
}

/* A native select draws its own chevron; this one has to be given one, or the
   field reads as a plain text box and nobody thinks to click it.
   Drawn as a mask over currentColor rather than a coloured SVG, so it follows
   the theme instead of pinning a hex value. */
.combo::after {
  content: "";
  position: absolute;
  top: 18px;
  right: 11px;
  width: 12px;
  height: 12px;
  transform: translateY(-50%);
  pointer-events: none;
  background-color: color-mix(in srgb, var(--color-text) 55%, transparent);
  -webkit-mask: var(--combo-chevron) center / contain no-repeat;
  mask: var(--combo-chevron) center / contain no-repeat;
}

.combo {
  --combo-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='square'%3E%3Cpath d='M4 6.5l4 4 4-4'/%3E%3C/svg%3E");
}

/* Points at the list while it is open. */
.combo:has(.combo-input[aria-expanded="true"])::after {
  transform: translateY(-50%) rotate(180deg);
  background-color: var(--color-accent);
}

/* Room for the chevron, so a long flavour never runs underneath it. */
.combo-input {
  padding-right: 30px;
}

.combo-list {
  position: absolute;
  z-index: 30;
  top: calc(100% - 1px);
  left: 0;
  right: 0;
  max-height: 264px;
  overflow-y: auto;
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--color-surface);
  border: 1px solid var(--color-accent);
  box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
}

.combo-list[hidden] {
  display: none;
}

.combo-option {
  display: block;
  width: 100%;
  padding: 8px 12px;
  border: 0;
  border-bottom: 1px solid var(--color-divider);
  background: transparent;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--color-text);
}

.combo-option:last-child {
  border-bottom: 0;
}

/* One highlight for both pointer and keyboard, so arrowing down and hovering
   never disagree about which row is active. */
.combo-option:hover,
.combo-option.is-active {
  background: var(--color-accent-100);
}

.combo-option[aria-selected="true"] {
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--color-accent);
}

.combo.is-multi .combo-option {
  padding-left: 32px;
  position: relative;
}

.combo.is-multi .combo-option::before {
  content: "";
  position: absolute;
  left: 10px;
  top: 50%;
  width: 12px;
  height: 12px;
  transform: translateY(-50%);
  border: 1px solid var(--color-neutral-500);
  background: var(--color-surface);
}

.combo.is-multi .combo-option[aria-selected="true"]::before {
  border-color: var(--color-accent);
  background: var(--color-accent);
}

.combo-clear {
  display: block;
  width: 100%;
  padding: 8px 12px;
  border: 0;
  border-bottom: 1px solid var(--color-divider);
  background: transparent;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  color: var(--color-accent);
}

.combo-clear:hover {
  background: var(--color-accent-100);
}

/* "Nothing matches" and "N more — keep typing" share a row shape but are not
   choices, so neither is focusable nor hoverable. */
.combo-note {
  padding: 8px 12px;
  font-size: 12px;
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
  border-top: 1px solid var(--color-divider);
}

.combo-note:only-child {
  border-top: 0;
}

/* ── How it works ─────────────────────────────────────────────────────────
   This is a FAQ, not a report. The reports screen next door uses a left nav,
   a page title, stat tiles and a two-column rule list. Reusing that chrome
   made these pages look like another analytics panel. Questions and answers
   are the shape the content already had; the layout now matches it.

   Type stays on the signed-off scale: questions at body 15px bold, answers
   at 15px, examples at 13px, nav at 13px. h4 is 20px in modernist.css, which
   would read as a report heading, so .faq-q overrides it. */

.faq {
  display: flex;
  align-items: stretch;
  min-height: 100%;
}

.faq-nav {
  width: 240px;
  flex: none;
  padding: 24px 0 32px 28px;
  border-right: 1px solid var(--color-divider);
}

.faq-nav-kicker {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 45%, transparent);
  margin: 0 16px 12px 0;
}

.faq-nav a {
  display: block;
  padding: 9px 16px 9px 0;
  margin-right: -1px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  color: inherit;
  text-decoration: none;
  border-right: 2px solid transparent;
}

.faq-nav a:hover {
  color: var(--color-accent);
}

.faq-nav a[aria-current="page"] {
  color: var(--color-accent);
  border-right-color: var(--color-accent);
  font-weight: 700;
}

.faq-main {
  flex: 1;
  min-width: 0;
  padding: 8px 36px 48px;
  max-width: 720px;
}

.faq-qa {
  padding: 22px 0;
  border-bottom: 1px solid var(--color-divider);
}

.faq-qa:last-child {
  border-bottom: 0;
}

.faq-q {
  position: relative;
  margin: 0 0 10px;
  padding-left: 32px;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1.4;
  text-transform: none;
}

.faq-q::before {
  content: "Q";
  position: absolute;
  left: 0;
  top: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.4;
  color: var(--color-accent);
}

.faq-a {
  position: relative;
  padding-left: 32px;
  font-size: 15px;
  line-height: 1.65;
}

.faq-a::before {
  content: "A";
  position: absolute;
  left: 0;
  top: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.65;
  color: color-mix(in srgb, var(--color-text) 45%, transparent);
}

.faq-a p {
  margin: 0 0 10px;
}

.faq-a p:last-child {
  margin-bottom: 0;
}

/* A worked example inside an answer. Same accent tint as before, so it still
   reads as the numbers walked through rather than as another rule. */
.faq-eg {
  margin: 12px 0 0;
  padding: 12px 14px;
  background: var(--color-accent-100);
  border-left: 3px solid var(--color-accent);
}

.faq-eg p {
  margin: 0 0 8px;
  font-size: 13px;
  line-height: 1.65;
}

.faq-eg p:last-child {
  margin-bottom: 0;
}

.faq-eg strong {
  font-weight: 700;
}

@media (max-width: 900px) {
  .faq {
    flex-direction: column;
  }
  .faq-nav {
    width: auto;
    padding: 16px 20px 8px;
    border-right: 0;
    border-bottom: 1px solid var(--color-divider);
  }
  .faq-nav a {
    margin-right: 0;
    padding: 8px 0;
    border-right: 0;
    border-left: 2px solid transparent;
    padding-left: 10px;
  }
  .faq-nav a[aria-current="page"] {
    border-right-color: transparent;
    border-left-color: var(--color-accent);
  }
  .faq-main {
    padding: 8px 20px 32px;
  }
}

/* ── Bulk variant sheet ────────────────────────────────────────────────
   A product can hold a few hundred flavours. The grid is meant to feel
   like a spreadsheet: tight cells, sticky headers, Tab/Enter, paste.
   Barcodes sit first; SKU and bin start hidden. */

.sheet-body {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}

.sheet-intro {
  margin: 0;
  padding: 12px 28px;
  font-size: 13px;
  line-height: 1.5;
  border-bottom: 1px solid var(--color-divider);
}

.sheet-intro a {
  color: inherit;
}

/* Click a name to hide that column; faded names are already hidden and
   click to come back. Unused category variables start hidden so the grid
   matches what this product actually holds. */
.sheet-cols {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  flex-wrap: wrap;
  padding: 8px 28px 10px;
  border-bottom: 1px solid var(--color-divider);
  background: var(--color-bg);
}

.sheet-cols-kicker {
  flex: none;
  margin-top: 5px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}

.sheet-cols-list {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  flex: 1;
  min-width: 0;
}

.sheet-col-toggle {
  appearance: none;
  margin: 0;
  padding: 4px 8px;
  border: 1px solid var(--color-divider);
  background: #fff;
  color: var(--color-text);
  font: inherit;
  font-size: 11px;
  letter-spacing: 0.02em;
  cursor: pointer;
}

.sheet-col-toggle:hover {
  border-color: var(--color-text);
}

.sheet-col-toggle[aria-pressed="false"] {
  background: transparent;
  color: color-mix(in srgb, var(--color-text) 40%, transparent);
  text-decoration: line-through;
}

.sheet-cols-all {
  appearance: none;
  flex: none;
  margin: 0;
  margin-left: auto;
  padding: 4px 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12px;
  color: var(--color-accent);
  cursor: pointer;
}

.sheet [data-col].is-col-hidden {
  display: none;
}

.sheet-form {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.sheet-wrap {
  flex: 1;
  min-height: 0;
  overflow: auto;
  background: var(--color-bg);
}

/* Size to content so each column takes its natural width rather than
   splitting the container width proportionally.  min-width: 100% keeps
   the table flush-left when there are few columns. */
table.sheet {
  width: max-content;
  min-width: 100%;
  border-collapse: collapse;
}

/* Rows are 22px on purpose: a hundred-line paste should show as many
   flavours as the viewport allows, closer to a spreadsheet than to a form. */

.sheet th {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  background: var(--color-surface);
  border: 1px solid var(--color-divider);
  padding: 4px 6px;
  white-space: nowrap;
}

.sheet th:has(.sheet-sort) {
  padding: 0;
}

.sheet-sort {
  appearance: none;
  display: block;
  width: 100%;
  margin: 0;
  padding: 4px 6px;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.sheet-sort:hover {
  color: var(--color-text);
}

.sheet-sort:focus-visible {
  outline: 1px solid var(--color-accent);
  outline-offset: -1px;
}

.sheet-sort::after {
  content: '';
  display: inline-block;
  width: 0;
  height: 0;
  margin-left: 5px;
  vertical-align: middle;
  border-left: 3px solid transparent;
  border-right: 3px solid transparent;
  opacity: 0;
}

.sheet-sort:hover::after {
  border-bottom: 4px solid currentColor;
  opacity: 0.35;
}

.sheet th[aria-sort="ascending"] .sheet-sort::after {
  border-bottom: 4px solid var(--color-text);
  border-top: 0;
  opacity: 1;
}

.sheet th[aria-sort="descending"] .sheet-sort::after {
  border-top: 4px solid var(--color-text);
  border-bottom: 0;
  opacity: 1;
}

.sheet th[aria-sort="ascending"] .sheet-sort,
.sheet th[aria-sort="descending"] .sheet-sort {
  color: var(--color-text);
}

.sheet td {
  padding: 0;
  border: 1px solid var(--color-divider);
  background: #fff;
  vertical-align: middle;
}

.sheet tbody tr:nth-child(even) td {
  background: color-mix(in srgb, var(--color-surface) 55%, #fff);
}

.sheet tbody tr.is-error td,
.sheet tbody tr:nth-child(even).is-error td {
  background: var(--color-warning-100);
}

.sheet tbody tr.is-error td:first-child {
  box-shadow: inset 3px 0 0 var(--color-warning);
}

.sheet td input {
  display: block;
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  height: 22px;
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0 6px;
  font: inherit;
  font-size: 12.5px;
  line-height: 22px;
  color: inherit;
}

.sheet td input:focus {
  outline: 1px solid var(--color-accent);
  outline-offset: -1px;
  background: #fff;
}

/* Disabled Opening qty — clearly locked so users know stock is set */
.sheet td input:disabled {
  color: var(--color-text-muted);
  cursor: default;
  opacity: 1;
}

.sheet td.is-selected {
  background: color-mix(in srgb, var(--color-accent) 14%, #fff);
}

.sheet tbody tr:nth-child(even) td.is-selected {
  background: color-mix(in srgb, var(--color-accent) 16%, #fff);
}

.sheet tbody tr.is-error td.is-selected,
.sheet tbody tr:nth-child(even).is-error td.is-selected {
  background: color-mix(in srgb, var(--color-accent) 10%, var(--color-warning-100));
}

.sheet.is-selecting,
.sheet.is-selecting input {
  user-select: none;
}

.sheet .sheet-num {
  width: 28px;
  min-width: 28px;
  text-align: right;
  padding: 0 6px;
  font-size: 10px;
  line-height: 22px;
  color: color-mix(in srgb, var(--color-text) 45%, transparent);
  user-select: none;
}

.sheet th.sheet-num,
.sheet td.sheet-num {
  background: var(--color-surface);
}

.sheet .sheet-opt { min-width: 80px; }
.sheet .sheet-code { min-width: 124px; }
.sheet .sheet-qty { width: 68px; min-width: 50px; }

/* Column resize handle — sits on the right edge of every resizable th */
.col-resize-handle {
  position: absolute;
  right: -2px;
  top: 0;
  bottom: 0;
  width: 6px;
  cursor: col-resize;
  z-index: 3;
}
.col-resize-handle:hover { background: var(--color-accent); opacity: 0.35; }
.col-resize-handle.is-dragging { background: var(--color-accent); opacity: 0.5; }

.sheet .sheet-code input {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12px;
}

.sheet th.sheet-del,
.sheet td.sheet-del {
  position: sticky;
  right: 0;
  z-index: 1;
  width: 48px;
  min-width: 48px;
  padding: 0;
  text-align: center;
  background: #fff;
  user-select: none;
}

.sheet th.sheet-del {
  z-index: 2;
  background: var(--color-surface);
}

.sheet tbody tr:nth-child(even) td.sheet-del {
  background: color-mix(in srgb, var(--color-surface) 55%, #fff);
}

.sheet tbody tr.is-error td.sheet-del {
  background: var(--color-warning-100);
}

.sheet-del-btn {
  appearance: none;
  display: block;
  width: 100%;
  height: 22px;
  margin: 0;
  padding: 0 4px;
  border: 0;
  border-radius: 0;
  background: transparent;
  font: inherit;
  font-size: 11px;
  color: var(--color-accent);
  cursor: pointer;
}

.sheet-del-btn:hover {
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
}

.sheet-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 10px 28px;
  border-top: 1px solid var(--color-divider);
  background: var(--color-bg);
}

.sheet-foot .text-muted {
  font-size: 12px;
}

/* ══════════════════════════════════════════════════════════════════════════
   Reports overview — chart pack on the first report. Tokens only; no new
   colours. Bars and the line are CSS/SVG so this screen has no chart library.
   ══════════════════════════════════════════════════════════════════════ */

.ov-kicker {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 18px 0 8px;
}

.ov-kicker:first-child {
  margin-top: 0;
}

.ov-tiles {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 1px;
  background: var(--color-divider);
  border: 1px solid var(--color-divider);
  margin-bottom: 8px;
}

.ov-tiles-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.ov-tile {
  display: block;
  padding: 12px 14px 14px;
  background: var(--color-surface);
  color: inherit;
  text-decoration: none;
}

a.ov-tile.is-link:hover {
  background: var(--color-bg);
}

.ov-tile-kicker {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.ov-tile-figure {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 26px;
  letter-spacing: -0.03em;
  line-height: 1.1;
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
}

.ov-tile-sub {
  font-size: 11px;
  margin-top: 4px;
}

.ov-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-top: 18px;
}

.ov-card {
  min-width: 0;
  padding: 14px 16px 16px;
  border: 1px solid var(--color-divider);
  background: var(--color-surface);
}

.ov-card-wide {
  grid-column: 1 / -1;
}

.ov-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.ov-card-head h6 {
  margin: 0;
}

.ov-empty {
  font-size: 12.5px;
  margin: 8px 0 0;
}

.ov-line {
  display: block;
  width: 100%;
  height: 140px;
}

.ov-line-axis {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 11px;
  color: var(--color-neutral-600);
  margin-top: 6px;
}

.ov-legend {
  display: flex;
  gap: 16px;
  font-size: 11px;
  margin-bottom: 10px;
  color: var(--color-neutral-700);
}

.ov-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  margin-right: 6px;
  vertical-align: -1px;
  background: var(--color-neutral-500);
}

.ov-swatch.is-in {
  background: var(--color-neutral-500);
}

.ov-swatch.is-out {
  background: var(--color-accent);
}

.ov-weeks {
  display: flex;
  align-items: stretch;
  gap: 8px;
  height: 140px;
}

.ov-week {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.ov-week-bars {
  flex: 1;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 3px;
  width: 100%;
  border-bottom: 1px solid var(--color-divider);
}

.ov-week-in,
.ov-week-out {
  width: 40%;
  max-width: 18px;
  min-height: 0;
  background: var(--color-neutral-500);
}

.ov-week-out {
  background: var(--color-accent);
}

.ov-week-label {
  font-size: 10px;
  margin-top: 6px;
  color: var(--color-neutral-600);
  white-space: nowrap;
}

.ov-hbars {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ov-hbar {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.6fr) auto;
  gap: 8px 10px;
  align-items: center;
  font-size: 12px;
  color: inherit;
  text-decoration: none;
}

a.ov-hbar.is-link:hover .ov-hbar-label {
  color: var(--color-accent);
}

.ov-hbar-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ov-hbar-track {
  display: block;
  height: 10px;
  background: var(--color-bg);
}

.ov-hbar-track span {
  display: block;
  height: 100%;
  background: var(--color-accent);
  min-width: 0;
}

.ov-hbar-value {
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--color-neutral-700);
}

/* ---- Sortable table headers -------------------------------------- */

table[data-sortable] thead th {
  padding: 0;
  white-space: nowrap;
}

.tbl-sort-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  width: 100%;
  padding: 8px 12px;
  background: none;
  border: none;
  font: inherit;
  font-size: inherit;
  font-weight: 600;
  color: inherit;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
}

table[data-sortable] thead th.num .tbl-sort-btn {
  justify-content: flex-end;
  text-align: right;
}

/* td.num right-aligns in .list (movement history etc.) but the report
   tables live in .rpt, which has no equivalent rule. */
.rpt table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.rpt table th.num {
  text-align: right;
}

.tbl-sort-btn:hover {
  color: var(--color-accent);
}

.tbl-sort-icon::before {
  content: '↕';
  font-size: 11px;
  color: var(--color-neutral-400);
}

[aria-sort="ascending"]  .tbl-sort-icon::before { content: '↑'; color: var(--color-accent); }
[aria-sort="descending"] .tbl-sort-icon::before { content: '↓'; color: var(--color-accent); }

/* ---- Interactive chart ------------------------------------------- */

.rpt-chart-interactive {
  position: relative;
  height: 240px;
  margin-bottom: 20px;
}

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

/* 4-column bar row: label | track | sold/day | days badge */
.dash-hbar-4 {
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1.6fr) 7ch auto;
}

/* Days-remaining status badge */
.dash-badge {
  display: inline-block;
  padding: 2px 7px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  background: var(--color-bg);
  color: var(--color-neutral-600);
}

.dash-badge.is-critical {
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  color: var(--color-accent);
  font-weight: 600;
}

.dash-badge.is-warn {
  background: #fef3c7;
  color: #92400e;
  font-weight: 600;
}

.dash-badge.is-ok {
  background: #dcfce7;
  color: #166534;
}

/* Alert tile (running-out count) */
.ov-tile.is-alert .ov-tile-figure {
  color: var(--color-accent);
}

/* Running-out list */
.dash-low-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.dash-low-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  gap: 2px 8px;
  align-items: center;
}

.dash-low-name {
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-low-meta {
  grid-column: 1;
  font-size: 11px;
}

.dash-low-row .dash-badge {
  grid-row: 1;
}

@media (max-width: 1180px) {
  .ov-tiles {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .ov-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 700px) {
  .ov-tiles,
  .ov-tiles-3 {
    grid-template-columns: 1fr 1fr;
  }

  .ov-hbar {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .ov-hbar-track {
    grid-column: 1 / -1;
    order: 3;
  }
}
