/* Stria — stock control.
 *
 * The look, and why it is this quiet.
 *
 * This is software somebody uses all day, so it tries to disappear. The
 * page is white, the type is dark grey, and almost nothing is drawn: no
 * panels, no boxes round things, no fills, no rules except where two things
 * genuinely need separating. Space does the work that lines used to.
 *
 * Colour is used only where it means something — a total that went down, a
 * status that needs a second look, the one action a page is for. Everything
 * else is greyscale, because an interface where five things are coloured has
 * nothing left to say when the sixth actually matters.
 *
 * The one piece of identity kept from the domain: every quantity, code,
 * location and time is set in IBM Plex Mono. It is the register printed under
 * a barcode, and it is why the figures line up down a column.
 */

/* --- Type --------------------------------------------------------------- */

@font-face {
  font-family: "Fira";
  src: url("../fonts/fira-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Fira";
  src: url("../fonts/fira-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Fira";
  src: url("../fonts/fira-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("../fonts/plex-mono-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("../fonts/plex-mono-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}

:root {
  color-scheme: light dark;

  --bg: #ffffff;
  --bg-soft: #f7f8f8;   /* the rail, and the odd resting surface */
  --text: #22262b;
  --text-2: #5a626c;    /* secondary text and labels */
  --text-3: #6b737d;    /* hints, placeholders, things you rarely read */
  --line: #e6e8ea;      /* hairlines; there are not many */
  --line-2: #d3d7db;    /* a slightly firmer one, for inputs */

  /* One accent. It marks where you are and the single action a page is for,
     and appears nowhere else. */
  --accent: #2f5d7c;
  --accent-soft: #eaf1f6;
  --accent-text: #ffffff;

  /* Meaning, kept quiet. Stock leaving is the only thing coloured by default,
     because it is the only figure somebody needs to catch at a glance. */
  --down: #a24b3c;
  --up: #3f7256;
  --warn: #8a6d2f;
  --warn-soft: #fbf6ea;

  --ui: "Fira", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --data: "Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --r: 6px;
  --control: 2.3rem; /* the height every input, select and button shares */
  --gutter: 1.5rem;
  --rail: 200px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16191d;
    --bg-soft: #1b1f24;
    --text: #e6e8ea;
    --text-2: #a7aeb6;
    --text-3: #858d96;
    --line: #272c32;
    --line-2: #343b43;

    --accent: #7fb3d5;
    --accent-soft: #1d2a33;
    --accent-text: #10161b;

    --down: #d98b7c;
    --up: #7fb595;
    --warn: #cfae6b;
    --warn-soft: #2a2418;
  }
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--ui);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
@media (max-width: 899px) { body { font-size: 16px; } }

h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.25; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
a { color: inherit; }
/* Links inside running prose need to look like links; links that are
   already a row, a tab or a button do not. */
.hint a, .section-note a, .panel-note a, .page-sub a, p.muted a { color: var(--accent); }
.hint a:hover, .section-note a:hover, .panel-note a:hover, .page-sub a:hover,
p.muted a:hover { text-decoration: underline; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

.mono { font-family: var(--data); font-variant-ligatures: none; }
.muted { color: var(--text-3); }
p.muted { font-size: .92rem; }
.nowrap { white-space: nowrap; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: -9999px; top: .5rem; z-index: 100;
  background: var(--accent); color: var(--accent-text);
  padding: .6rem 1rem; border-radius: var(--r);
}
.skip:focus { left: .5rem; }

/* Small labels are quiet grey text at a readable size — not capitals with the
   letters pushed apart, which turns every caption into something to decipher. */
.eyebrow, .label, .nav-label, .figure-label, .chart-scale,
.suggestion-label, .empty-title, .section-title, .panel-title {
  font-size: .82rem; font-weight: 500; letter-spacing: 0; text-transform: none;
}
.eyebrow, .label, .nav-label, .figure-label, .chart-scale { color: var(--text-2); }

/* --- The mark ----------------------------------------------------------- */

.mark { display: block; height: 18px; color: var(--text); }
.mark svg { display: block; height: 18px; width: auto; }
.brand { display: flex; align-items: center; gap: .5rem; color: var(--text); text-decoration: none; }
.brand-name { font-weight: 600; font-size: 1rem; letter-spacing: .01em; }
.symbol { display: block; width: 100%; color: var(--text); }
.symbol svg { display: block; width: 100%; height: auto; }

/* --- Shell -------------------------------------------------------------- */

.app { min-height: 100dvh; }

.topbar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: .9rem;
  padding: .6rem var(--gutter);
  padding-top: max(.6rem, env(safe-area-inset-top));
  background: var(--bg); color: var(--text);
  border-bottom: 1px solid var(--line);
}
.topbar-menu {
  display: grid; place-items: center;
  width: 36px; height: 36px; padding: 0;
  background: transparent; border: 1px solid var(--line-2); border-radius: var(--r);
  color: inherit; cursor: pointer;
}
.bars, .bars::before, .bars::after {
  display: block; width: 15px; height: 1.5px; background: currentColor;
  content: ""; border-radius: 1px;
}
.bars { position: relative; }
.bars::before { position: absolute; top: -5px; }
.bars::after { position: absolute; top: 5px; }

.topbar-id { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.topbar-workspace {
  font-weight: 500; font-size: .95rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar-slug { font-family: var(--data); font-size: .7rem; color: var(--text-3); }
.topbar-scan {
  margin-left: auto; padding: .4rem .9rem; border-radius: var(--r);
  background: var(--accent); color: var(--accent-text);
  font-weight: 500; font-size: .88rem; text-decoration: none;
}

/* The rail: a list of places, on a surface barely distinguishable from the
   page. Where you are is shown by weight and colour, not by a block of paint. */
.sidenav {
  position: fixed; inset: 0 auto 0 0; z-index: 40;
  width: var(--rail); padding: 1.1rem 0 1rem;
  background: var(--bg-soft); color: var(--text-2);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  transform: translateX(-100%); transition: transform .16s ease;
  overflow-y: auto;
}
.sidenav.is-open { transform: none; }
.sidenav .brand { padding: 0 var(--gutter) .9rem; }

.workspace { display: grid; padding: 0 var(--gutter) 1rem; }
.workspace-name { font-weight: 500; font-size: .9rem; line-height: 1.3; color: var(--text); }
.workspace-slug { font-family: var(--data); font-size: .7rem; color: var(--text-3); }

.nav { margin-bottom: 1rem; }
.nav a {
  display: flex; align-items: center; gap: .5rem;
  margin: 0 .5rem; padding: .34rem .75rem; border-radius: var(--r);
  text-decoration: none; color: var(--text-2);
  font-size: .95rem;
}
.nav a:hover { background: var(--line); color: var(--text); }
.nav a[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent); font-weight: 600;
}
.nav-label { padding: .2rem var(--gutter) .3rem; color: var(--text-3); font-size: .78rem; }
.nav-count {
  margin-left: auto; font-family: var(--data); font-size: .72rem;
  color: var(--text-3);
}
.nav a[aria-current="page"] .nav-count { color: inherit; }
.nav-form { padding: 0 var(--gutter); }
.nav-foot { margin-top: auto; padding: .9rem var(--gutter) 0; }

.me { display: flex; align-items: center; gap: .5rem; text-decoration: none; padding: 0 0 .7rem; }
.me-text { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.me-name { font-weight: 500; font-size: .9rem; color: var(--text); }
.me-role { font-size: .78rem; color: var(--text-3); }

.scrim { position: fixed; inset: 0; z-index: 35; background: rgba(20, 24, 28, .4); border: 0; }

.main { background: var(--bg); padding: 1.2rem var(--gutter) 5rem; min-height: 100dvh; }

@media (min-width: 900px) {
  .topbar { display: none; }
  .app { display: grid; grid-template-columns: var(--rail) 1fr; }
  .sidenav { transform: none; }
  .scrim { display: none; }
  .main { grid-column: 2; padding: 2rem 2.5rem 4rem; max-width: 1400px; }
}

.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  display: grid; grid-template-columns: repeat(4, 1fr);
  background: var(--bg); border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
}
.tabbar a {
  padding: .65rem .2rem; text-align: center; text-decoration: none;
  font-size: .8rem; color: var(--text-2);
}
.tabbar a[aria-current="page"] { color: var(--accent); font-weight: 500; }
@media (min-width: 900px) { .tabbar { display: none; } }

/* --- Bare pages --------------------------------------------------------- */

.shell-bare { display: grid; place-items: center; min-height: 100dvh; padding: 1.5rem 1rem; background: var(--bg-soft); }
.bare { width: 100%; max-width: 24rem; }
.bare-mark { margin-bottom: 1.5rem; }
.mark-name {
  display: block; margin-top: .4rem;
  font-family: var(--data); font-size: .74rem; font-weight: 500;
  letter-spacing: .5em; text-indent: .5em; text-transform: uppercase;
  color: var(--text-2); text-align: center;
}
.auth-panel {
  background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
  padding: 1.5rem 1.6rem 1.7rem;
}
.auth-title { font-size: 1.4rem; }
.auth-sub { color: var(--text-2); font-size: .95rem; margin-top: .2rem; }
.error-code { font-family: var(--data); font-size: 2.5rem; font-weight: 500; line-height: 1; color: var(--text-3); }

/* --- Page furniture ----------------------------------------------------- */

/* A page says what it is and then gets on with it. No rule above the title,
   no rule below it: the space is the separation. */
.page-head { margin-bottom: 1.8rem; display: grid; gap: .15rem; }
.eyebrow { display: flex; align-items: baseline; gap: .5rem; text-decoration: none; }
.eyebrow a { color: inherit; text-decoration: none; }
.eyebrow a:hover { color: var(--text); text-decoration: underline; }
.page-title { font-size: 1.6rem; letter-spacing: -.01em; grid-row: 3; margin-top: .05rem; }
.page-sub { grid-row: 4; color: var(--text-2); font-size: .95rem; max-width: 58ch; }
.page-head .row-actions { grid-row: 2; justify-self: start; margin-top: .5rem; }
@media (min-width: 700px) {
  .page-head { grid-template-columns: 1fr auto; align-items: start; }
  .eyebrow { grid-column: 1; }
  .page-title { grid-column: 1; grid-row: 2; }
  .page-sub { grid-column: 1; grid-row: 3; }
  .page-head .row-actions { grid-column: 2; grid-row: 1 / span 2; justify-self: end; align-self: start; margin-top: 0; }
}
.back::before { content: "← "; }

.section, .panel { margin-bottom: 2.2rem; }
.section-head, .panel-head {
  display: flex; align-items: baseline; gap: .8rem; flex-wrap: wrap;
  margin-bottom: .8rem;
}
/* A heading that follows a list starts a new group — space says so, since
   there are no boxes to draw the boundary. */
.lines + .section-head, .table-wrap + .section-head,
.lines + .panel-head, .hint + .section-head { margin-top: 1.6rem; }
.section-title, .panel-title { font-size: 1rem; font-weight: 600; color: var(--text); }
.section-note, .panel-note { color: var(--text-3); font-size: .88rem; }
.section-link, .panel-link {
  margin-left: auto; font-size: .88rem; color: var(--accent); text-decoration: none;
}
.section-link:hover, .panel-link:hover { text-decoration: underline; }
.panel-danger { border-top: 1px solid var(--line); padding-top: 1.2rem; }
.panel-danger .panel-title { color: var(--down); }

.split { display: grid; gap: 2rem; }
@media (min-width: 900px) {
  .split { grid-template-columns: 1fr 1fr; gap: 3rem; }
  .split-wide { grid-template-columns: 3fr 2fr; }
}
.prose { max-width: 62ch; color: var(--text-2); display: grid; gap: .6rem; }

/* --- Figures ------------------------------------------------------------ */

/* Four numbers with room around them. No box, no dividers — the gap is enough
   to tell one from the next. */
.figures {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: 1.2rem 2rem; margin-bottom: 2.2rem;
}
.figure { display: grid; gap: .1rem; }
/* How a thing is filed, sitting under the figures: a line of labels rather
   than a section of its own. */
.page-meta { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center;
  margin: -1.2rem 0 2rem; color: var(--text-2); font-size: .88rem; }
@media (min-width: 700px) { .figures { grid-template-columns: repeat(4, 1fr); } }
.figure-label { display: block; color: var(--text-2); }
.figure-value {
  display: block; font-family: var(--data); font-weight: 400;
  font-size: 1.65rem; line-height: 1.15; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; color: var(--text);
}
.figure-note { display: block; font-size: .84rem; color: var(--text-3); }
/* Stock leaving is not a problem — it is the job. A minus sign says which way a
   figure went; colour is saved for the two cases that actually want somebody's
   attention, which are a shortage and a mistake. */
.value-in, .value-out { color: inherit; }
.figure-alert .figure-value, .value-warn { color: var(--warn); }

/* --- Chart -------------------------------------------------------------- */

/* The chart is an SVG, so its bars take a fill rather than a background — they
   have been quietly drawing in the browser's default black until now. */
.chart { display: block; width: 100%; height: 104px; }
.bar-in { fill: var(--accent); opacity: .8; }
.bar-out { fill: var(--down); opacity: .8; }
.chart-base { stroke: var(--line-2); stroke-width: 1; }
.chart-scale { color: var(--text-3); padding-top: .5rem; display: block; font-size: .8rem; }
.key { display: inline-flex; align-items: center; font-size: .8rem; color: var(--text-2); }
.key + .key { margin-left: .7rem; }
.key::before {
  content: ""; display: inline-block; width: .5rem; height: .5rem;
  margin-right: .35rem; border-radius: 2px; opacity: .8;
}
.key-in::before { background: var(--accent); }
.key-out::before { background: var(--down); }

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

.lines { display: grid; }
.line {
  display: flex; align-items: baseline; gap: 1rem;
  padding: .6rem 0; border-top: 1px solid var(--line);
}
.line:first-child { border-top: 0; }
.line-main { min-width: 0; flex: 1; }
.line-title { font-weight: 500; }
.line-meta { display: block; font-size: .84rem; color: var(--text-3); }
.line-value { font-family: var(--data); font-variant-numeric: tabular-nums; }
.line-actions { display: flex; gap: .4rem; }
.line-empty { padding: .7rem 0; color: var(--text-3); }
.line-review .line-title { color: var(--warn); }

/* --- Tables ------------------------------------------------------------- */

/* Rows separated by one hairline each and nothing else: no outer border, no
   column rules, no stripes. Figures are right-aligned and set in mono, which
   is what actually makes a column readable. */
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: .92rem; }
.table th {
  position: sticky; top: 0; z-index: 1;
  text-align: left; font-weight: 500; font-size: .82rem; color: var(--text-2);
  padding: .5rem .9rem; background: var(--bg);
  border-bottom: 1px solid var(--line-2); white-space: nowrap;
}
.table td { padding: .65rem .9rem; border-bottom: 1px solid var(--line); vertical-align: top; }
.table th:first-child, .table td:first-child { padding-left: 0; }
.table th:last-child, .table td:last-child { padding-right: 0; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover td { background: var(--bg-soft); }
.table .num { text-align: right; font-family: var(--data); font-variant-numeric: tabular-nums; }
.row-muted td { color: var(--text-3); }
/* A location tree, indented by how deep it sits. Four levels is the cap.
   Qualified by the cell, because the table's own padding would otherwise
   win the shorthand. */
.table td.depth-1 { padding-left: 1.6rem; }
.table td.depth-2 { padding-left: 3rem; }
.table td.depth-3 { padding-left: 4.4rem; }
.cell-link { font-weight: 500; text-decoration: none; color: var(--text); }
.cell-link:hover { color: var(--accent); text-decoration: underline; }
.cell-sub { display: block; font-family: var(--data); font-size: .76rem; color: var(--text-3); }
.cell-why { max-width: 24rem; }
/* Where somebody works: a list of every place would fill the row, so it is
   folded away behind its own summary and opens in place. */
/* Free-form facts about a product: a label and a value, in two columns that
   collapse to one on a phone. Nothing is drawn around them — the pairing is
   the structure. */
.facts { display: grid; gap: .6rem 2rem; }
@media (min-width: 40rem) { .facts { grid-template-columns: repeat(2, 1fr); } }
.fact { display: grid; grid-template-columns: 10rem 1fr; gap: .8rem; align-items: baseline; }
.fact dt { color: var(--text-3); font-size: .86rem; }
.fact dd { margin: 0; }
/* The row you are already looking at, in a list of its siblings. */
.row-current td { background: var(--bg-soft); }

.cell-places { min-width: 9rem; }
/* One named permission and what has been said about it. The rank it comes with
   is set small underneath, because that is the answer most of the time. */
.perm { display: grid; gap: .2rem; margin-bottom: .5rem; }
.perm-name { font-size: .86rem; }
.perm-rank { display: block; color: var(--text-3); font-size: .76rem; }
.places > summary { cursor: pointer; color: var(--text-2); font-size: .86rem; }
.places[open] > summary { margin-bottom: .4rem; }
.places .check { font-size: .86rem; }
.places .hint { margin-top: .4rem; }

.cell-actions { white-space: nowrap; }
.cell-actions form { display: contents; }
.cell-actions .btn { margin: .1rem .3rem .1rem 0; }
.table-forms td { vertical-align: middle; }
.danger-form { display: flex; gap: .3rem; align-items: center; }

@media (max-width: 599px) {
  .table { min-width: 34rem; }
  .table th, .table td { padding-left: .5rem; padding-right: .5rem; white-space: nowrap; }
  .cell-why, .cell-actions { white-space: normal; }
}

/* --- Tags --------------------------------------------------------------- */

/* A soft grey pill. Only the two that mean something get a colour. */
.tag {
  display: inline-block; padding: .1rem .45rem; border-radius: 999px;
  background: var(--bg-soft); color: var(--text-2);
  border: 1px solid var(--line);
  font-size: .76rem; white-space: nowrap;
}
.tag-ok { color: var(--up); border-color: color-mix(in srgb, var(--up) 25%, var(--line)); }
.tag-warn { color: var(--warn); background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 25%, var(--line)); }
.tag-in { color: var(--text-2); }
.tag-out { color: var(--down); border-color: color-mix(in srgb, var(--down) 25%, var(--line)); }
/* ABC. A is the line that pays for the shelf, so it is the one that reads as
   solid; C is deliberately quiet rather than alarming — a slow line is not a
   fault, it is a fact. */
.tag-class-A { color: var(--up); border-color: color-mix(in srgb, var(--up) 30%, var(--line)); font-variant-numeric: tabular-nums; }
.tag-class-B { color: var(--text-2); }
.tag-class-C { color: var(--text-3, var(--text-2)); opacity: .8; }

/* --- Forms -------------------------------------------------------------- */

.form, .stack { display: grid; gap: 1.1rem; }
.field { display: grid; gap: .3rem; min-width: 0; }
.field-grow { flex: 1; min-width: 12rem; }
.field-qty { width: 7.5rem; }
.grid-2, .grid-3 { display: grid; gap: 1.1rem; }
@media (min-width: 700px) {
  .grid-2 { grid-template-columns: 1fr 1fr; }
  .grid-3 { grid-template-columns: repeat(3, 1fr); }
}
.label { color: var(--text-2); }
.label-note { font-size: .8rem; color: var(--text-3); font-weight: 400; }
.hint { font-size: .84rem; color: var(--text-3); max-width: 62ch; }

.input, .textarea, .select {
  /* Inputs, selects and buttons stand next to each other in filter rows, so
     they are all the same height even though their type is not. */
  width: 100%; min-height: var(--control); padding: .45rem .6rem;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line-2); border-radius: var(--r);
  font: inherit; font-size: .95rem;
}
.input::placeholder, .textarea::placeholder { color: var(--text-3); }
.input:hover, .textarea:hover, .select:hover { border-color: var(--text-3); }
.input:focus, .textarea:focus, .select:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.textarea { min-height: 5rem; resize: vertical; }
.select {
  appearance: none; padding-right: 1.8rem;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right .85rem center, right .6rem center;
  background-size: .25rem .25rem, .25rem .25rem;
  background-repeat: no-repeat;
}
.input-inline { width: auto; min-width: 6rem; max-width: 100%; }

.check { display: flex; align-items: flex-start; gap: .5rem; font-size: .95rem; }
.check input { width: 1rem; height: 1rem; margin-top: .22rem; accent-color: var(--accent); flex: none; }
.field-check { display: grid; gap: .35rem; }
.fieldset { border: 0; padding: 0; margin: 0; display: grid; gap: .5rem; }

.choices { display: grid; gap: .4rem; }
.choice {
  display: flex; gap: .6rem; padding: .6rem .7rem; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r);
}
.choice:hover { border-color: var(--line-2); }
.choice-body { display: grid; }
.choice-name { font-weight: 500; }
.choice-meta { font-family: var(--data); font-size: .78rem; color: var(--text-3); }
.choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }

.filters { display: flex; flex-wrap: wrap; gap: .9rem; align-items: flex-end; margin-bottom: 1.6rem; }
.filters .field-grow { min-width: 14rem; }
.inline-form { display: flex; gap: .7rem; align-items: flex-end; flex-wrap: wrap; }
/* A field in a row keeps a workable width instead of shrinking to whatever
   is in it, so a select never squashes its own text under the caret. One
   marked to grow is exempt — it is the field the row is for, and capping it
   was making a search box narrower than the hint underneath it. */
.filters > .field:not(.field-grow),
.inline-form > .field:not(.field-grow) { flex: 0 1 12rem; }
.filters > .field-checks, .inline-form > .field-checks { flex: 0 0 auto; }
@media (max-width: 560px) {
  .filters > .field:not(.field-grow),
  .inline-form > .field:not(.field-grow) { flex: 1 1 9rem; }
  .filters > .field-grow, .inline-form > .field-grow { flex: 1 1 100%; }
  /* A stack of tickboxes is as wide as its longest sentence; it gets its own
     row rather than squeezing the control beside it. */
  .filters > .field-checks, .inline-form > .field-checks { flex: 1 1 100%; }
}
.form-error { color: var(--down); font-size: .92rem; }
/* A heading inside a long form, so the fields arrive in groups a person can
   hold in their head rather than as one wall. */
.form-heading {
  font-size: .95rem; font-weight: 600; color: var(--text-2);
  margin: 1.4rem 0 -.2rem; padding-top: 1.2rem; border-top: 1px solid var(--line);
}
.form > .form-heading:first-of-type { margin-top: .6rem; }

/* --- Buttons ------------------------------------------------------------ */

/* One filled button per page — the thing that page is for. Everything else is
   an outline, and destructive things are outlined in red rather than filled. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  min-height: var(--control); padding: .45rem .9rem; cursor: pointer;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line-2); border-radius: var(--r);
  font-family: var(--ui); font-size: .9rem; font-weight: 500;
  text-decoration: none; white-space: nowrap;
}
.btn:hover { background: var(--bg-soft); border-color: var(--text-3); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
.btn-primary:hover { background: color-mix(in srgb, var(--accent) 85%, #000); border-color: transparent; }
.btn-quiet { border-color: transparent; color: var(--text-2); }
.btn-quiet:hover { border-color: var(--line-2); }
.btn-danger { color: var(--down); border-color: color-mix(in srgb, var(--down) 35%, var(--line-2)); }
.btn-danger:hover { background: color-mix(in srgb, var(--down) 8%, var(--bg)); border-color: var(--down); }
.btn-block { width: 100%; }
.btn-tiny { min-height: 0; padding: .22rem .55rem; font-size: .8rem; }
.is-disabled { opacity: .45; pointer-events: none; }
.row-actions { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }

/* --- Flash -------------------------------------------------------------- */

.flash {
  display: flex; align-items: baseline; gap: .8rem;
  padding: .7rem .9rem; margin-bottom: 1.4rem;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--bg-soft); font-size: .95rem;
}
.flash-success { border-color: color-mix(in srgb, var(--up) 30%, var(--line)); }
.flash-error { border-color: color-mix(in srgb, var(--down) 35%, var(--line)); color: var(--down); }
.flash-info { border-color: color-mix(in srgb, var(--accent) 30%, var(--line)); }
/* Something read, but not everything: worth a second look rather than an alarm. */
.flash-warn { border-color: color-mix(in srgb, var(--warn) 35%, var(--line)); background: var(--warn-soft); }
.flash-close {
  margin-left: auto; background: none; border: 0; cursor: pointer;
  color: var(--text-3); font: inherit; padding: 0 .2rem;
}

/* --- Nothing here yet --------------------------------------------------- */

.empty { padding: 2rem 0; display: grid; gap: .35rem; justify-items: start; }
.empty-title { color: var(--text); font-weight: 600; font-size: 1rem; }
.empty-hint { color: var(--text-2); font-size: .95rem; max-width: 52ch; }
.empty .btn { margin-top: .8rem; }

/* --- Pagination --------------------------------------------------------- */

.pager { display: flex; gap: .6rem; align-items: center; margin-top: 1.4rem; }
.pager-state { font-size: .84rem; color: var(--text-3); }
@media (max-width: 520px) { .pager { justify-content: center; } .pager-total { display: none; } }

.code { font-family: var(--data); font-size: .88rem; word-break: break-all; color: var(--text-2); }

/* --- Scan console ------------------------------------------------------- */

.scanner { display: grid; gap: 1.6rem; }
.scan-form { display: grid; gap: 1.2rem; }
.scan-row { display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-end; }

/* What has been scanned so far in a move, so nobody has to remember it. */
.move-slots { margin-bottom: 1.2rem; }
.slots { display: grid; gap: .4rem; margin-bottom: .7rem; }
.slot {
  display: flex; align-items: baseline; gap: .7rem;
  padding: .5rem .7rem; border: 1px solid var(--line); border-radius: var(--r);
}
.slot-label { color: var(--text-2); font-size: .82rem; min-width: 3.2rem; }
.slot-value { color: var(--text-3); min-width: 0; }
.slot-filled { border-color: var(--accent); background: var(--accent-soft); }
.slot-filled .slot-value { color: var(--text); font-weight: 500; }
@media (min-width: 700px) { .slots { grid-template-columns: repeat(3, 1fr); } }

/* Which way stock is going is the first decision and the biggest target. */
.modes {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: .7rem;
  /* It is a fieldset, which the browser draws a box around by default. */
  border: 0; padding: 0; margin: 0;
}
.mode { position: relative; cursor: pointer; }
.mode input { position: absolute; opacity: 0; width: 0; height: 0; }
.mode-body {
  display: grid; gap: .1rem; padding: .8rem .9rem;
  border: 1px solid var(--line-2); border-radius: var(--r);
}
.mode:hover .mode-body { border-color: var(--text-3); }
.mode-name { font-weight: 500; font-size: 1rem; }
.mode-hint { font-size: .84rem; color: var(--text-3); }
.mode input:focus-visible + .mode-body { outline: 2px solid var(--accent); outline-offset: 2px; }
.mode-in input:checked + .mode-body {
  border-color: var(--accent); background: var(--accent-soft);
}
.mode-out input:checked + .mode-body {
  border-color: var(--down); background: color-mix(in srgb, var(--down) 7%, var(--bg));
}
.mode-move input:checked + .mode-body {
  border-color: var(--warn); background: var(--warn-soft);
}
.mode input:checked + .mode-body .mode-name { color: inherit; }

/* The code goes in the same face it is printed in under the symbol itself. */
.scan-input { display: flex; gap: .8rem; align-items: flex-end; }
.input-code { font-family: var(--data); font-size: 1.2rem; letter-spacing: .02em; }
@media (max-width: 599px) { .input-code { font-size: 1.05rem; } }
.btn-camera { flex: none; }
.btn-camera-blocked { border-color: var(--line); color: var(--text-3); }
.btn-record { font-size: .95rem; padding: .6rem 1.2rem; }
.camera-icon { position: relative; display: inline-block; width: 15px; height: 11px; border: 1.5px solid currentColor; border-radius: 2px; }
.camera-icon::before { content: ""; position: absolute; inset: 2px 4px; border: 1.5px solid currentColor; border-radius: 50%; }
.camera-icon::after { content: ""; position: absolute; top: -4px; left: 3px; width: 5px; height: 2px; background: currentColor; }

.feed { display: grid; }
.queue-badge { font-size: .78rem; color: var(--warn); }
.line-pending { opacity: .6; }
.line-failed .line-value { color: var(--down); }
.net-state { display: inline-flex; align-items: center; gap: .4rem; font-size: .8rem; color: var(--text-3); }
.net-dot { width: .45rem; height: .45rem; border-radius: 50%; background: currentColor; }

/* The one piece of motion in the product: what you just recorded is marked for
   a moment so your eye finds it, and then it settles. */
@keyframes just-recorded {
  from { background: var(--accent-soft); }
  to { background: transparent; }
}
.feed .line:first-child { animation: just-recorded 2.4s ease-out 1; }

.details summary { cursor: pointer; font-size: .92rem; color: var(--text-2); }
.details summary:hover { color: var(--text); }
.details[open] summary { margin-bottom: .7rem; }

/* --- The camera, full screen -------------------------------------------- */

.camera-pane { position: fixed; inset: 0; z-index: 60; background: #000; display: grid; overflow: hidden; }
.camera-video { width: 100%; height: 100%; object-fit: cover; grid-area: 1 / 1; }
body.camera-live { overflow: hidden; }
/* The aim box is not decoration: scanarea.js sizes it to the exact rectangle
   being decoded, so what is inside it is what the camera can read and what is
   outside it is not. The darkening is one enormous box-shadow spilling out of
   the box in every direction — everything except the live rectangle, without
   four separate panels to keep in step with each other.

   The width and height here are only what is shown for the moment between the
   pane opening and the camera saying how big its picture is; the real ones
   arrive in pixels. */
.camera-frame {
  grid-area: 1 / 1; place-self: center; position: relative;
  width: min(78vw, 460px); height: min(30vh, 200px);
  border-radius: calc(var(--r) + 2px);
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .55);
  outline: 1.5px solid rgba(255, 255, 255, .55);
  transition: width .12s ease, height .12s ease;
}
/* Until it has been placed there is no honest rectangle to draw, so the mask
   stays off rather than darkening the wrong part of the picture. */
.camera-frame[data-unplaced] { box-shadow: none; outline: none; }
.camera-line { position: absolute; left: 0; right: 0; top: 50%; height: 1.5px; background: #fff; opacity: .7; }
@media (prefers-reduced-motion: reduce) { .camera-frame { transition: none; } }
.camera-top {
  grid-area: 1 / 1; align-self: start;
  display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
  padding: max(.8rem, env(safe-area-inset-top)) 1rem .8rem;
}
/* The lightweight viewfinder any barcode field can open: a video, a line of
   text and a way out. The scan console's own camera has more on it because it
   is doing more. */
.camera-bar {
  grid-area: 1 / 1; align-self: end;
  display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
  padding: 1rem 1rem max(1rem, env(safe-area-inset-bottom));
  background: linear-gradient(transparent, rgba(0, 0, 0, .65));
}
.camera-status { color: #fff; flex: 1; min-width: 0; }
.camera-bar .btn { background: #fff; color: #16191d; border-color: #fff; }

/* A field and its camera button, read as one control. */
.input-with-camera { display: flex; gap: .5rem; align-items: stretch; }
.input-with-camera .input { flex: 1; min-width: 0; }
.btn-camera-field { flex: none; }
.camera-top-actions { margin-left: auto; display: flex; gap: .5rem; }
.camera-btn {
  padding: .45rem .9rem; cursor: pointer; border-radius: var(--r);
  background: rgba(0, 0, 0, .5); color: #fff; border: 1px solid rgba(255, 255, 255, .35);
  font-family: var(--ui); font-size: .88rem; font-weight: 500;
}
.camera-btn:hover { background: rgba(0, 0, 0, .7); }
.camera-btn[aria-pressed="true"] { background: #fff; color: #16191d; border-color: #fff; }
.camera-btn-done { background: #fff; color: #16191d; border-color: #fff; }
.camera-btn-primary { background: var(--accent); color: #fff; border-color: transparent; }
.camera-btn:disabled { opacity: .5; cursor: default; }

.camera-toast {
  position: absolute; left: 50%; top: 9%;
  transform: translate(-50%, -8px);
  width: min(24rem, calc(100% - 2.5rem));
  background: rgba(12, 14, 16, .86); color: #fff;
  padding: .85rem 1rem .95rem; border-radius: 10px;
  opacity: 0; pointer-events: none;
  transition: opacity .22s ease, transform .22s ease;
}
.camera-toast.is-visible { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.camera-toast.is-bad { background: rgba(70, 26, 20, .9); }
.camera-toast-prompt { top: auto; bottom: 12%; }
.camera-toast-eyebrow { display: block; font-size: .74rem; color: rgba(255,255,255,.7); }
.camera-toast-title { display: block; font-weight: 600; font-size: 1.05rem; margin-top: .1rem; }
.camera-toast-text { display: block; font-size: .9rem; color: rgba(255, 255, 255, .8); margin-top: .15rem; }
.camera-toast-actions { display: flex; gap: .5rem; margin-top: .8rem; flex-wrap: wrap; }
.camera-hint {
  color: rgba(255, 255, 255, .75); font-size: .85rem; text-align: center;
  margin: 0 auto;
}

/* The dock: what is happening, how far through it is, and what to do next —
   all in the bottom third, because the other hand is holding a box. Nothing
   here opens a menu or navigates away: every control changes the next scan and
   leaves the picture where it is. */
.camera-dock {
  grid-area: 1 / 1; align-self: end;
  display: grid; gap: .7rem;
  padding: 1.1rem 1rem max(1rem, env(safe-area-inset-bottom));
  background: linear-gradient(transparent, rgba(0, 0, 0, .55) 22%, rgba(0, 0, 0, .8));
}

/* Moving: the three slots, in the order the move reads. A filled one is solid;
   an empty one is an outline waiting to be filled, which is the whole
   instruction. */
.dock-slots { display: flex; gap: .4rem; list-style: none; margin: 0; padding: 0; }
.dock-slot {
  flex: 1 1 0; min-width: 0;
  display: grid; gap: .1rem; padding: .45rem .6rem;
  border: 1px dashed rgba(255, 255, 255, .35); border-radius: var(--r);
  background: rgba(0, 0, 0, .35);
}
.dock-slot.is-filled { border-style: solid; border-color: rgba(255, 255, 255, .8); background: rgba(0, 0, 0, .6); }
.dock-slot-label {
  font-size: .68rem; letter-spacing: .04em; color: rgba(255, 255, 255, .6);
}
.dock-slot-value {
  font-size: .88rem; color: rgba(255, 255, 255, .55);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dock-slot.is-filled .dock-slot-value { color: #fff; font-weight: 500; }

/* In and out: where the work is, and how much of it has been done since the
   viewfinder was opened. */
.dock-status {
  display: flex; gap: .5rem; align-items: baseline; justify-content: space-between;
  color: #fff; font-size: .9rem; min-width: 0;
}
.dock-place { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dock-tally { color: rgba(255, 255, 255, .65); font-size: .8rem; flex: none; }

/* Two rows rather than one. Squeezing the modes, the quantity and undo onto a
   single line makes every one of them smaller than a thumb, which is the one
   thing this control cannot be. */
.dock-bar { display: grid; gap: .55rem; }

/* One tap to change what the next scan does. Thumb-sized, because it is used
   at arm's length by somebody who cannot look at the screen for long. */
.dock-modes {
  display: flex; gap: .25rem; padding: .25rem;
  background: rgba(0, 0, 0, .5); border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 999px;
}
.dock-mode {
  flex: 1 1 0; min-height: 44px; padding: .5rem .4rem; cursor: pointer;
  border: 0; border-radius: 999px; background: transparent; color: rgba(255, 255, 255, .85);
  font-family: var(--ui); font-size: .95rem; font-weight: 500;
}
.dock-mode[aria-pressed="true"] { background: #fff; color: #16191d; }

.dock-aside { display: flex; gap: .5rem; align-items: center; justify-content: space-between; }
.dock-qty {
  display: flex; align-items: center; gap: .2rem; padding: .25rem;
  background: rgba(0, 0, 0, .5); border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 999px;
}
.dock-step {
  width: 44px; height: 44px; cursor: pointer;
  border: 0; border-radius: 999px; background: transparent; color: #fff;
  font-family: var(--ui); font-size: 1.25rem; line-height: 1;
}
.dock-step:hover { background: rgba(255, 255, 255, .16); }
.dock-qty-value { min-width: 2ch; text-align: center; color: #fff; font-size: 1rem; }
.dock-undo { min-height: 44px; }

/* While the prompt is asking for a photograph it is the only question on the
   screen, so the controls step out of the way and leave the line of text. */
.camera-dock.is-prompting .dock-slots,
.camera-dock.is-prompting .dock-status,
.camera-dock.is-prompting .dock-bar { display: none; }

/* Wide screens — a tablet on a trolley, or the desktop this is being set up
   on: the dock stops stretching to the full width and stays a hand's width. */
@media (min-width: 40rem) {
  .camera-dock { width: min(34rem, 100%); margin: 0 auto; }
}

/* --- Items waiting to be identified ------------------------------------- */

.review-list { display: grid; gap: 1.5rem; }
.review-item { border-top: 1px solid var(--line); padding-top: 1.2rem; }
.review-item:first-child { border-top: 0; padding-top: 0; }
.review-head { display: flex; align-items: baseline; gap: .8rem; flex-wrap: wrap; }
.review-code { font-family: var(--data); font-size: 1.1rem; font-weight: 500; }
.review-meta { font-size: .84rem; color: var(--text-3); }
.review-note { font-size: .92rem; color: var(--text-2); margin-top: .25rem; }
.review-action { margin-top: .9rem; display: grid; gap: .9rem; }
.review-foot { display: flex; gap: .6rem; flex-wrap: wrap; align-items: flex-end; margin-top: .9rem; }
.review-foot .inline-form { flex: 1 1 22rem; }
.photos { display: flex; gap: .8rem; flex-wrap: wrap; margin-top: .9rem; }
.photo { display: grid; gap: .3rem; }
.photo img {
  display: block; width: 168px; height: 168px; object-fit: cover;
  border: 1px solid var(--line); border-radius: var(--r);
}
.photo figcaption { font-size: .78rem; color: var(--text-3); }

.suggestion { border-top: 1px solid var(--line); padding: .9rem 0 .2rem; margin-top: 1rem; }
.suggestion-ready { border-top-color: color-mix(in srgb, var(--accent) 30%, var(--line)); }
.suggestion-failed { border-top-color: color-mix(in srgb, var(--down) 30%, var(--line)); }
.suggestion-pending { border-top-style: dashed; }
.suggestion-head { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.suggestion-label { color: var(--text-2); }
.suggestion-body { margin-top: .35rem; display: grid; gap: .2rem; }
/* The filing is one line, not one row per part, so a brand and its "new"
   label stay together when it wraps. */
.suggestion-filing { display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .6rem; }
.filing { white-space: nowrap; }
/* Brand then category, told apart by a separator rather than by a label each:
   two words with nothing between them read as one phrase. */
.filing + .filing::before { content: "· "; color: var(--text-3); }
.suggestion-warning { color: var(--down); font-size: .9rem; }
.suggestion-confidence { font-family: var(--data); font-size: .8rem; color: var(--text-3); }
.suggestion-actions { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: .7rem; }
.suggestion-model { font-size: .78rem; color: var(--text-3); }
.ai-status { font-size: .84rem; color: var(--down); }

/* --- The audit trail's before and after --------------------------------- */

.changes { margin: .35rem 0 0; display: grid; gap: .1rem; }
.change { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap; font-size: .82rem; }
.change-field { color: var(--text-3); }
.change-field::after { content: ":"; }
.change-from { margin: 0; color: var(--text-3); text-decoration: line-through; }
.change-from:empty { display: none; }
.change-to { margin: 0; color: var(--text); }
.change-to::before { content: "→ "; color: var(--text-3); text-decoration: none; }

/* --- Second factor ------------------------------------------------------ */

.setup-key {
  font-family: var(--data); font-size: 1.05rem; letter-spacing: .1em;
  padding: .6rem .8rem; background: var(--bg-soft);
  border: 1px solid var(--line); border-radius: var(--r); word-break: break-all;
}
.recovery-codes {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: .4rem .8rem; margin: .7rem 0 1.2rem;
  padding: .9rem 1rem; background: var(--bg-soft);
  border: 1px solid var(--line); border-radius: var(--r);
}
.recovery-code { font-size: .95rem; letter-spacing: .04em; }

/* --- Labels ------------------------------------------------------------- */

.label-sheet {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(62mm, 1fr));
  gap: 4mm; margin-top: 1.2rem;
}
/* Named "sticker" rather than "label": .label is a form field's caption, and a
   printed barcode sticker sharing that class put a box round every label on
   every form in the product. */
.sticker {
  margin: 0; padding: 2mm; border: 1px solid var(--line); border-radius: 4px;
  display: grid; gap: 1mm; break-inside: avoid;
}
/* A carton label is wider than a shelf one: its symbol carries four fields. */
.sticker-carton { grid-column: 1 / -1; max-width: 110mm; }
/* A Data Matrix is square and small, and its identifiers are printed beside it
   rather than under it — there is no room underneath. */
.sticker-square { max-width: 62mm; }
/* A bay label goes on a rack and is read from further away, so the place's
   name is the biggest thing on it. */
.sticker-bay { max-width: 80mm; }
.bay-name { font-size: 1.1rem; font-weight: 600; line-height: 1.2; }
.sticker-square .label-code { display: flex; justify-content: center; }
.sticker-square .label-code svg { width: 20mm; height: 20mm; }
.sticker-square .label-foot { word-break: break-all; font-size: .68rem; }
.label-name {
  font-size: .78rem; font-weight: 500; line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.label-code svg { display: block; width: 100%; height: auto; }
.label-foot { display: flex; justify-content: space-between; font-size: .7rem; color: var(--text-2); }
.label-price { font-weight: 500; color: var(--text); }

@media print {
  .sidenav, .topbar, .tabbar, .no-print, .flash { display: none !important; }
  .main { padding: 0; }
  .app { display: block; }
  .label-sheet { gap: 0; }
  .sticker { border-color: #ddd; }
  body { font-size: 11px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}
