/* VarnaGold -- sales receipts for Noor Gold.
 *
 * Deliberately a fixed light theme rather than following the OS: this runs on a
 * shared counter machine and prints tax documents, and both of those want one
 * predictable appearance.
 */

:root {
  --gold: #b8860b;
  --gold-soft: #f5eddc;
  --gold-line: #e3d3ad;

  --ink: #1c1a17;
  --ink-soft: #5c574e;
  --ink-faint: #8b857a;

  --bg: #f7f6f3;
  --surface: #ffffff;
  --line: #e2ded6;

  --ok: #1c7c4a;
  --ok-bg: #e8f6ee;
  --danger: #b02a2a;
  --danger-bg: #fdecec;

  --radius: 8px;
  --shadow: 0 1px 2px rgba(28, 26, 23, 0.06), 0 4px 12px rgba(28, 26, 23, 0.04);
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0 0 0.5rem; line-height: 1.25; font-weight: 600; }
h1 { font-size: 1.6rem; }
h2 { font-size: 1.1rem; }
p { margin: 0 0 0.75rem; }
a { color: #8a6508; }
a:hover { color: var(--gold); }

.block { display: block; }
.muted { color: var(--ink-soft); }
.small { font-size: 0.86rem; }
.strong { font-weight: 600; }
/* A margin below zero is the whole point of the P&L, so it is coloured rather
   than left to be picked out of a column of similar-looking numbers. */
.is-negative { color: var(--danger); }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.req { color: var(--danger); }
.empty { color: var(--ink-faint); padding: 1rem 0; }

/* ---- App shell ---- */

.shell { display: flex; align-items: flex-start; min-height: 100vh; }
.shell-main { flex: 1 1 auto; min-width: 0; }

.brand { font-size: 1.15rem; font-weight: 700; text-decoration: none; letter-spacing: -0.01em; }
.brand-mark { color: var(--ink); }
.brand-gold { color: var(--gold); }

/* ---- Side menu ----
 * Sticky full-height rail. Sections are grouped so the list stays scannable as
 * the app grows (register check, more stock tools) instead of a widening row.
 */

.sidebar {
  flex: 0 0 226px;
  width: 226px;
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.9rem 0.75rem 0.75rem;
  background: var(--surface);
  border-right: 1px solid var(--line);
  overflow-y: auto;
}

.sidebar-head { display: flex; align-items: center; justify-content: space-between; padding: 0 0.35rem 0.9rem; }

/* Who is signed in, directly under the brand. Quiet enough not to compete
   with New receipt, present enough that nobody rings a sale up as the person
   who unlocked the iPad this morning. */
.sidebar-greeting {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem; flex-wrap: wrap;
  padding: 0 0.35rem 0.75rem;
}
.greeting-text { font-size: 0.95rem; color: var(--ink-soft); }
/* Specific enough to beat .btn, which is declared further down and sets
   display:inline-block -- under which `gap` does nothing and the icon sits
   flush against the word. */
.sidebar-greeting .greeting-switch {
  flex: 0 0 auto; padding: 0.25rem 0.5rem; font-size: 0.75rem;
  display: inline-flex; align-items: center; gap: 0.35rem;
}

/* The one thing the counter reaches for all day, kept above the nav. */
.sidebar-action {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.55rem 0.7rem;
  margin-bottom: 0.5rem;
  border: 1px solid var(--gold-line);
  border-radius: var(--radius);
  background: var(--gold-soft);
  color: #7a5906;
  font-weight: 600;
  text-decoration: none;
}
.sidebar-action:hover { background: #f0e4c9; color: #7a5906; }
.sidebar-action.active { background: var(--gold); border-color: var(--gold); color: #fff; }
.sidebar-action-plus { font-size: 1.15rem; line-height: 1; font-weight: 400; }

.sidenav { display: flex; flex-direction: column; gap: 0.9rem; }
.sidenav-group { display: flex; flex-direction: column; gap: 1px; }
.sidenav-label {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--ink-faint); font-weight: 600; padding: 0 0.7rem 0.3rem;
}
.sidenav a {
  padding: 0.42rem 0.7rem;
  border-radius: 6px;
  text-decoration: none;
  color: var(--ink-soft);
  font-weight: 500;
}
.sidenav a:hover { background: var(--bg); color: var(--ink); }
.sidenav a.active { background: var(--gold-soft); color: #7a5906; font-weight: 600; }

.sidebar-foot {
  margin-top: auto;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem;
  font-size: 0.86rem;
  padding-left: 0.7rem;
  padding-right: 0.35rem;
}
.user-name { text-decoration: none; color: var(--ink-soft); font-weight: 500; }

/* Its own line under the name and the sign-out controls, rather than a third
   thing competing for the same row -- at this width the row is already tight,
   and the version is reference material, not something anybody acts on. */
.sidebar-version {
  flex: 1 0 100%;
  padding-top: 0.5rem;
  font-size: 0.7rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* Off-canvas plumbing: no JS, so the menu works before anything else loads. */
.nav-toggle { position: absolute; opacity: 0; pointer-events: none; }
.nav-scrim, .nav-close, .mobilebar { display: none; }

.mobilebar {
  align-items: center;
  gap: 0.75rem;
  height: 52px;
  padding: 0 0.9rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
}
.mobilebar-action { margin-left: auto; }
.nav-burger { display: flex; flex-direction: column; justify-content: center; gap: 4px; width: 26px; height: 26px; cursor: pointer; }
.nav-burger span { display: block; height: 2px; background: var(--ink-soft); border-radius: 2px; }
.nav-close { font-size: 1.5rem; line-height: 1; color: var(--ink-faint); cursor: pointer; padding: 0 0.25rem; }

/* Collapse control. Only meaningful on wide screens: narrow ones already hide
   the rail behind the burger. */
.nav-collapse {
  display: none;
  border: 0; background: none; cursor: pointer;
  font-size: 1.1rem; line-height: 1; color: var(--ink-faint); padding: 0.2rem 0.35rem;
}
.nav-collapse:hover { color: var(--ink); }
.nav-expand { display: none; }

@media (min-width: 861px) {
  .nav-collapse { display: block; }

  /* Collapsed: the rail goes, and the slim bar becomes the way back. The page
     keeps the full width, which is the point on a tablet. */
  .nav-collapsed .sidebar { display: none; }
  .nav-collapsed .mobilebar { display: flex; }
  .nav-collapsed .nav-burger:not(.nav-expand) { display: none; }
  .nav-collapsed .nav-expand { display: flex; }
}

@media (max-width: 860px) {
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 40;
    height: 100%;
    transform: translateX(-100%);
    transition: transform 0.18s ease;
    box-shadow: var(--shadow);
  }
  .nav-toggle:checked ~ .shell .sidebar { transform: none; }
  .nav-toggle:checked ~ .shell .nav-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 30;
    background: rgba(28, 26, 23, 0.35);
  }
  .nav-close { display: block; }
  .mobilebar { display: flex; }
}

.subnav {
  display: flex;
  gap: 0.25rem;
  margin: 0 0 1rem;
  border-bottom: 1px solid var(--line);
}
.subnav a {
  padding: 0.5rem 0.9rem;
  text-decoration: none;
  color: var(--ink-soft);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-weight: 500;
}
.subnav a.active { color: var(--gold); border-bottom-color: var(--gold); }

/* ---- Page shell ---- */

/* Full width, deliberately.
   This was capped at 1180px, which is the right measure for PROSE and the wrong
   one for this app: nearly every screen here is a wide table of figures --
   receipts, counts, reports, the in-and-out lists -- and the cap only squeezed
   the columns while leaving a stripe of empty page down each side.
   Readable line length is preserved where it actually matters, by capping the
   descriptive paragraphs at 68ch rather than the whole page. Forms and small
   cards keep their own widths (.card.narrow, .auth-card, .error-card,
   .switch-card), so nothing that should be narrow becomes a wide field. */
.page { max-width: none; margin: 0 auto; padding: 1.5rem 1.25rem 4rem; }

/* Hiding the rail gives its space to the content. The width is already
   unconstrained, so this is only about reclaiming the side padding. */
.nav-collapsed .page { padding-left: 1rem; padding-right: 1rem; }
.page-head { margin-bottom: 1.25rem; }
.page-head .muted { max-width: 68ch; }
.head-actions { display: flex; gap: 0.5rem; margin-top: 0.75rem; flex-wrap: wrap; }

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem;
  margin-bottom: 1.25rem;
  box-shadow: var(--shadow);
}
.card.narrow { max-width: 620px; }
.card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }

.two-col { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 1.25rem; align-items: start; }
.grid { display: grid; gap: 0.9rem; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

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

label { display: block; font-weight: 500; font-size: 0.9rem; margin-bottom: 0.9rem; }
label > small { display: block; font-weight: 400; margin-top: 0.25rem; }

input[type="text"], input[type="email"], input[type="tel"], input[type="password"],
input[type="date"], input[type="search"], input[type="number"], select, textarea {
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.5rem 0.6rem;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid #cfc9be;
  border-radius: 6px;
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--gold);
  outline-offset: -1px;
  border-color: var(--gold);
}
input.compact { width: 8rem; }
textarea { resize: vertical; }

label.checkbox { display: flex; align-items: center; gap: 0.5rem; font-weight: 500; }
label.checkbox input { width: auto; margin: 0; }

.input-prefix, .input-suffix { display: flex; align-items: stretch; margin-top: 0.3rem; }
.input-prefix span, .input-suffix span {
  display: flex;
  align-items: center;
  padding: 0 0.6rem;
  background: var(--bg);
  border: 1px solid #cfc9be;
  color: var(--ink-soft);
}
.input-prefix span { border-radius: 6px 0 0 6px; border-right: 0; }
.input-prefix input { margin-top: 0; border-radius: 0 6px 6px 0; }
.input-suffix span { border-radius: 0 6px 6px 0; border-left: 0; }
.input-suffix input { margin-top: 0; border-radius: 6px 0 0 6px; }

.btn {
  display: inline-block;
  padding: 0.5rem 1rem;
  font: inherit;
  font-weight: 600;
  border-radius: 6px;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  text-align: center;
}
.btn-primary { background: var(--gold); border-color: #a2760a; color: #fff; }
.btn-primary:hover { background: #a2760a; color: #fff; }
.btn-secondary { background: var(--surface); border-color: #cfc9be; color: var(--ink); }
.btn-secondary:hover { background: var(--bg); color: var(--ink); }
.btn-danger { background: var(--danger); border-color: #8f2020; color: #fff; }
.btn-danger:hover { background: #8f2020; color: #fff; }
.btn-large { padding: 0.7rem 1.6rem; font-size: 1.05rem; }
.btn-sm { padding: 0.3rem 0.7rem; font-size: 0.86rem; }
.btn-block { width: 100%; }

.link-button {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: #8a6508;
  cursor: pointer;
  text-decoration: underline;
}
.inline-form { display: inline-flex; gap: 0.4rem; align-items: center; margin: 0; }

.form-actions { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 2rem; }

.filter-bar { display: flex; gap: 0.9rem; align-items: flex-end; flex-wrap: wrap; }
.filter-bar label { margin-bottom: 0; }
.filter-bar .grow { flex: 1 1 240px; }

/* ---- Alerts ---- */

.alert { padding: 0.8rem 1rem; border-radius: var(--radius); margin-bottom: 1.25rem; border: 1px solid; }
.alert ul { margin: 0.4rem 0 0; padding-left: 1.2rem; }
.alert-error { background: var(--danger-bg); border-color: #f0c5c5; color: #7c1d1d; }
.alert-success { background: var(--ok-bg); border-color: #bfe3ce; color: #14603a; }

.tag {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  font-size: 0.72rem;
  font-weight: 600;
  border-radius: 4px;
  background: var(--gold-soft);
  color: #7a5906;
  vertical-align: middle;
}
.tag-quiet { background: #eeece7; color: var(--ink-soft); }
.tag-ok { background: var(--ok-bg); color: var(--ok); }
.tag-danger { background: var(--danger-bg); color: var(--danger); }
/* A person walked over and counted. Deliberately heavier than tag-ok: it
   outranks the model's own 'high', and rendering the two identically hid the
   distinction exactly where it mattered. */
.tag-confirmed { background: var(--ok); color: #fff; }

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

.table-scroll { overflow-x: auto; }

.data-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.data-table th, .data-table td { padding: 0.5rem 0.6rem; text-align: left; }
.data-table thead th {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.data-table tbody tr { border-bottom: 1px solid #f0ede7; }
.data-table tbody tr:hover { background: #fbfaf7; }
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.row-void { opacity: 0.55; }
.row-void a { text-decoration: line-through; }
.day-break td { border-top: 2px solid var(--line); }

/* A shift with no check-out. Tinted rather than dimmed: it is the row that
   needs attention, not the row to be skipped over. */
.row-open-shift { background: var(--danger-bg); }
.row-open-shift:hover { background: var(--danger-bg); }
.shift-actions { white-space: nowrap; }
.shift-actions .link-button { margin-left: 0.5rem; }

/* ---- Counting screen: case cards ---- */
/*
   Replaces the six-column count table, which scrolled sideways on a phone and
   showed every category for every case whether or not anything was in it.
   Shares the daily check's card vocabulary on purpose: same job, same shape.
*/

/* The overview: one tile per case, the same grid the daily check uses. Tapping
   one opens that case's own page, where the counting happens. */
.count-tiles {
  display: grid; gap: 0.75rem; margin: 0.75rem 0 1rem;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}

.count-tile {
  display: flex; flex-direction: column;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line); border-left: 3px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
  color: inherit; text-decoration: none;
}
.count-tile:hover { border-color: var(--gold); }
.count-tile:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* Dashed until something is in it, so an untouched case reads as an outline
   rather than as a finished one holding zero. */
.count-tile.is-empty { border-style: dashed; border-left-style: solid; }
.count-tile.is-counted { border-left-color: var(--ok); }

.count-tile-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem; margin-bottom: 0.5rem;
}

.count-tile-photo {
  display: block; margin-bottom: 0.5rem; border-radius: 6px;
  aspect-ratio: 4 / 3; background: var(--bg);
}
.count-tile-photo img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  border-radius: 6px; border: 1px solid var(--line);
}

.count-tile-body { margin-top: auto; }

/* Three columns -- category, pieces, grams -- so the figures line up down the
   tile and the total below lands in the same places. */
.count-tile-counts {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: baseline; gap: 0.1rem 0.5rem; margin: 0;
  font-size: 0.85rem;
}
.count-tile-counts dt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.count-tile-counts dd { margin: 0; }
.count-tile-karat { color: var(--ink-faint); font-size: 0.78rem; }
.count-tile-pcs, .count-tile-g {
  font-variant-numeric: tabular-nums; text-align: right; min-width: 2.5rem;
}
.count-tile-g { min-width: 4rem; }

.count-tile-total {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: baseline; gap: 0.5rem;
  margin-top: 0.35rem; padding-top: 0.35rem;
  border-top: 1px solid var(--line);
  font-size: 0.85rem; font-weight: 600;
}

.count-tile-empty { margin: 0; font-size: 0.85rem; color: var(--ink-faint); }

/* ---- Counting one case ---- */

.count-unit { display: block; }
.count-unit-body { padding-top: 0.25rem; }

.crumb { margin: 0 0 0.25rem; font-size: 0.9rem; }
.crumb a { color: var(--ink-muted); text-decoration: none; }
.crumb a:hover { color: var(--gold); }

/* ---- A category within a case ---- */

.count-cats { display: grid; gap: 0.6rem; }

.count-cat {
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line); border-radius: 6px; background: var(--bg);
}
/* A typed weight is a deliberate departure from the trays, so it is marked --
   the tray list is still visible underneath and the two disagree on purpose. */
.count-cat.is-override { border-left: 3px solid var(--gold); }

.count-cat-head {
  display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;
}
.count-cat-name { font-weight: 600; flex: 1 1 auto; }
.count-cat-remove { color: var(--ink-faint); font-size: 1.1rem; line-height: 1; }
.count-cat-remove:hover { color: var(--danger); }

.count-cat-figures {
  display: flex; align-items: flex-end; gap: 0.5rem; flex-wrap: wrap;
}
.count-field { display: flex; flex-direction: column; gap: 0.15rem; }
.count-field-label { font-size: 0.8rem; color: var(--ink-muted); }
.count-field-note { color: var(--ink-faint); }
/* Wide enough for a four-figure gram weight without the sideways scroll the
   old table needed to fit six columns. */
.count-cat-figures .row-pieces { width: 5rem; }
.count-cat-figures .row-weight { width: 7rem; }
.count-cat-figures input { margin-top: 0; font-variant-numeric: tabular-nums; }
/* Read-only means the trays own it. Distinct from disabled, which would drop
   it out of the tab order and read as broken. */
.count-cat-figures .row-weight[readonly] {
  background: var(--surface); color: var(--ink-muted); border-style: dashed;
}
.count-cat-override { margin: 0.4rem 0 0; font-size: 0.85rem; color: var(--ink-muted); }

/* ---- Trays ---- */

.tray-list { list-style: none; margin: 0.5rem 0 0; padding: 0; display: grid; gap: 0.3rem; }

.tray {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.25rem 0.35rem; border-radius: 5px; background: var(--surface);
}
.tray.is-unread { background: var(--danger-bg); }

.tray-thumb { display: block; flex: 0 0 2.75rem; aspect-ratio: 1; }
.tray-thumb img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  border-radius: 4px; border: 1px solid var(--line);
}

.tray-body { display: flex; align-items: baseline; gap: 0.4rem; flex: 1 1 auto; flex-wrap: wrap; }
.tray-label { font-size: 0.8rem; color: var(--ink-faint); }
.tray-weight { font-variant-numeric: tabular-nums; font-weight: 600; }
/* The scale's own figure, kept beside a correction. Same rule as the daily
   check's piece counts: what the model read is never hidden. */
.tray-read { font-size: 0.8rem; color: var(--ink-faint); text-decoration: line-through; }
.tray-error { color: var(--danger); font-size: 0.85rem; }
.tray-pending { color: var(--ink-muted); font-size: 0.85rem; }

.tray-actions { display: flex; align-items: center; gap: 0.25rem; }
.tray-weight-input { width: 5rem; margin-top: 0; font-variant-numeric: tabular-nums; }
.tray-unit { font-size: 0.8rem; color: var(--ink-faint); }
.tray-remove { color: var(--ink-faint); font-size: 1.1rem; line-height: 1; }
.tray-remove:hover { color: var(--danger); }

.tray-total {
  margin: 0.35rem 0 0; font-size: 0.85rem; color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

.count-add-cat { margin-top: 0.7rem; }
.count-add-cat select { max-width: 18rem; }
.count-cats-empty { margin: 0.75rem 0; }

@media (max-width: 30rem) {
  /* On a phone the figures stack rather than wrapping mid-pair, so the label
     never ends up on a different line from its box. */
  .count-cat-figures { gap: 0.4rem 0.6rem; }
  .count-unit-photo { flex-basis: 5.5rem; }
}

/* ---- Receipt entry line table ---- */

.quick-add { display: flex; gap: 0.5rem; align-items: center; }
.quick-label { margin: 0; white-space: nowrap; }
.quick-add input { margin-top: 0; min-width: 22rem; font-family: var(--mono); font-size: 0.9rem; }
.quick-hint { font-size: 0.86rem; margin-top: 0.35rem; }
.quick-hint code { background: var(--bg); padding: 0.1rem 0.3rem; border-radius: 4px; }

.line-table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
.line-table th {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  text-align: left;
  padding: 0 0.3rem 0.35rem;
  white-space: nowrap;
}
.line-table td { padding: 0.2rem 0.3rem; vertical-align: top; }
.line-table input, .line-table select { margin-top: 0; }

.col-type { width: 6.5rem; }
.col-cat { width: 9.5rem; }
.col-karat { width: 5rem; }
.col-weight { width: 7rem; }
.col-amount { width: 8rem; }
.col-remove { width: 2rem; text-align: center; }
.col-weight input, .col-amount input { text-align: right; font-variant-numeric: tabular-nums; }
.rate-hint { display: block; margin-top: 0.2rem; font-size: 0.75rem; text-align: right; }
.remove-row { font-size: 1.1rem; text-decoration: none; color: var(--ink-faint); }
.remove-row:hover { color: var(--danger); }

.matches {
  margin-top: 0.35rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
  background: var(--surface);
}
.match {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  background: none;
  border: 0;
  border-bottom: 1px solid #f0ede7;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.match:last-child { border-bottom: 0; }
.match:hover { background: var(--gold-soft); }
.match span { color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* ---- Totals ---- */

.totals-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.5rem; margin-bottom: 1.25rem; }
.totals { margin: 0; }
.totals > div { display: flex; justify-content: space-between; gap: 1rem; padding: 0.3rem 0; }
/* A `display` rule beats the hidden attribute's UA style, so restate it. */
.totals > div[hidden] { display: none; }
.totals dt { color: var(--ink-soft); }
.totals dd { margin: 0; font-variant-numeric: tabular-nums; font-weight: 500; }
.totals .muted-row dt, .totals .muted-row dd { color: var(--ink-faint); font-size: 0.86rem; }
.totals .grand { border-top: 1px solid var(--line); margin-top: 0.3rem; padding-top: 0.5rem; }
.totals .grand dt { font-weight: 600; color: var(--ink); }
.totals .grand dd { font-weight: 700; font-size: 1.15rem; }

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 1rem; margin-bottom: 1.25rem; }
.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  box-shadow: var(--shadow);
}
.stat-primary { background: var(--gold-soft); border-color: var(--gold-line); }
.stat-label { display: block; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-faint); }
.stat-value { display: block; font-size: 1.45rem; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 0.15rem; }
.stat-value .unit { font-size: 0.9rem; font-weight: 500; color: var(--ink-soft); }
.stat-sub { display: block; font-size: 0.78rem; color: var(--ink-soft); margin-top: 0.15rem; }

/* A stat row nested inside a card. The card already carries the shadow, and a
   second one inside it reads as two stacked panels rather than one. */
.stat-row-tight { gap: 0.6rem; margin: 0.5rem 0 0.9rem; }
.stat-row-tight .stat { box-shadow: none; padding: 0.6rem 0.75rem; }
.stat-row-tight .stat-value { font-size: 1.25rem; }

/* ---- Bars: pieces and weight per category ---- */
/*
   Horizontal, because category names are words. Vertical bars would need the
   labels rotated or truncated, and "Bracelets" truncated to "Brace" costs more
   than the chart gives. Rendered server-side as a div with a width -- there is
   no charting library in this app and six categories do not justify one.
*/

.bar-charts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: 1rem 1.75rem; margin: 0 0 1.1rem;
}
@media (max-width: 44rem) { .bar-charts { grid-template-columns: 1fr; } }

.bar-chart-title {
  font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink-faint); font-weight: 600; margin: 0 0 0.5rem;
}
.bar-chart-unit { text-transform: none; letter-spacing: 0; font-weight: 400; }

/* Label column fixed so the bars of both charts start at the same offset and
   can be read against each other; the value column fixed so the numbers align
   down the right regardless of how long the bar is. */
.bar-row {
  display: grid; grid-template-columns: 7.5rem 1fr 4.5rem;
  align-items: center; gap: 0.5rem; padding: 0.15rem 0;
}
.bar-label {
  font-size: 0.85rem; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.bar-track {
  display: block; height: 0.85rem; border-radius: 3px;
  background: var(--bg); border: 1px solid var(--line); overflow: hidden;
}
.bar-fill { display: block; height: 100%; border-radius: 2px 0 0 2px; }
.bar-fill-pieces { background: var(--gold); }
/* Distinct from the piece bars at a glance, so a reader who has scrolled past
   the headings still knows which chart they are in. */
.bar-fill-weight { background: #7d8a6b; }
/* Not a zero bar -- a category nobody put on a scale. Hatched so it reads as
   absent data rather than a measurement that came out empty. */
.bar-fill-none {
  width: 100%; opacity: 0.5;
  background: repeating-linear-gradient(
    -45deg, var(--line) 0 3px, transparent 3px 7px);
}
.bar-value {
  font-size: 0.85rem; text-align: right; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.bar-value-none { font-size: 0.72rem; color: var(--ink-faint); }
.bar-chart-note { margin-top: 0.5rem; }

@media (max-width: 26rem) {
  /* The label gets its own line before it starts truncating category names. */
  .bar-row { grid-template-columns: 1fr 4.5rem; }
  .bar-label { grid-column: 1 / -1; }
}

/* A reference line inside a bar track -- the monthly target on the history
   chart. Absolute against the track, so it lands at the same place whether the
   bar reaches it or not. */
.bar-track { position: relative; }
.bar-target {
  position: absolute; top: -1px; bottom: -1px; width: 2px;
  background: var(--ink-faint); opacity: 0.75;
}
/* The month in progress, sitting beside completed ones. */
.bar-row.is-current .bar-label { font-weight: 700; }
.bar-row.is-current .bar-value { font-weight: 700; }
tr.is-current td { background: var(--gold-soft); }

@media print {
  /* Browsers drop backgrounds on paper, and a bar chart whose bars are the
     background prints as a row of empty outlines. */
  .bar-fill, .bar-target { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .bar-row { page-break-inside: avoid; }
}

/* ---- Dashboard: the month against its target ---- */

.gauge { margin: 0.4rem 0 0.9rem; }
.gauge-track {
  position: relative; height: 1.6rem; border-radius: 4px;
  background: var(--bg); border: 1px solid var(--line); overflow: visible;
}
.gauge-fill { height: 100%; border-radius: 3px 0 0 3px; transition: width 0.2s; }
.gauge-fill.is-met { background: var(--ok); }
.gauge-fill.is-ahead { background: var(--gold); }
/* Behind pace is not a failure -- it is the 14th and there is time. Amber, not
   the red the app uses for a variance that needs explaining. */
.gauge-fill.is-behind { background: #d9a441; }

/* Where an even month would stand by tonight. A line across the bar rather
   than a second fill: it marks a position, it does not measure a quantity. */
.gauge-pace {
  position: absolute; top: -3px; bottom: -3px; width: 2px;
  background: var(--ink); opacity: 0.8;
}
.gauge-pace::after {
  content: 'pace'; position: absolute; top: -1.05rem; left: 50%;
  transform: translateX(-50%);
  font-size: 0.65rem; color: var(--ink-soft); white-space: nowrap;
}
/* Only drawn when the bar has run past 100% and the axis has stretched, so the
   target stops being the right-hand edge. */
.gauge-target {
  position: absolute; top: -3px; bottom: -3px; width: 2px;
  background: var(--ok); opacity: 0.9;
}
.gauge-scale {
  display: flex; justify-content: space-between;
  font-size: 0.72rem; color: var(--ink-faint); margin-top: 0.2rem;
}

.gauge-readout {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.75rem; margin: 0.9rem 0 0.6rem;
}
.gauge-figure-value {
  display: block; font-size: 1.1rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.gauge-figure-value .unit { font-size: 0.8rem; font-weight: 500; color: var(--ink-soft); }
.gauge-figure-label { display: block; font-size: 0.75rem; color: var(--ink-soft); }

/* A stat that drills into the rows behind it. Still a card -- the affordance
   is the arrow and the hover, not a change of shape, because turning four
   summary figures into things that look like buttons would shout. */
.stat-link { display: block; text-decoration: none; color: inherit; }
.stat-link:hover { border-color: var(--gold); color: inherit; }
.stat-link:hover .stat-drill { opacity: 1; }
.stat-link:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.stat-drill {
  float: right; text-transform: none; letter-spacing: 0;
  color: var(--gold); opacity: 0; transition: opacity 0.15s;
}
@media (hover: none) {
  /* No hover on a tablet, so the arrow has to be permanently visible or the
     drill-down is undiscoverable on the device the counter actually uses. */
  .stat-drill { opacity: 0.7; }
}

.rate-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 0.9rem; margin-bottom: 1rem; }
.rate-field { margin-bottom: 0; }
.from-feed { color: var(--gold); font-weight: 600; }

/* Spot: the three numbers the website quotes, in the same order. */
.spot-card { margin-bottom: 1.25rem; border-color: var(--gold-line); }
.spot-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.spot-head h2 { margin-bottom: 0; }

.spot-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.75rem;
  margin: 1rem 0 0.75rem;
}
.spot-figure {
  padding: 0.8rem 0.9rem;
  background: var(--gold-soft);
  border: 1px solid var(--gold-line);
  border-radius: var(--radius);
}
.spot-label { display: block; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: #7a5906; font-weight: 600; }
.spot-value { display: block; margin-top: 0.2rem; font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.spot-stamp { margin: 0; }

.kv { margin: 0; }
.kv > div { display: flex; gap: 1rem; padding: 0.3rem 0; }
.kv dt { min-width: 6rem; color: var(--ink-soft); }
.kv dd { margin: 0; font-weight: 500; }

.category-editor { font-size: 0.9rem; }
.category-head, .category-row {
  display: grid;
  grid-template-columns: 1fr 6rem 5rem 5rem 4rem;
  gap: 0.75rem;
  align-items: center;
}
.category-head {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--line);
}
.category-row { padding: 0.35rem 0; border-bottom: 1px solid #f0ede7; margin: 0; }
.category-row input[type="text"], .category-row input[type="number"] { margin-top: 0; }
.category-row.is-inactive { opacity: 0.55; }

.void-card { border-color: #f0c5c5; }
.void-form { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.void-form input { margin-top: 0; flex: 1 1 260px; }

/* ---- Auth pages ---- */

.auth-wrap { display: flex; justify-content: center; padding-top: 3rem; }
.auth-card { width: 100%; max-width: 400px; }
.auth-title { text-align: center; font-size: 1.6rem; margin-bottom: 0.25rem; }
.auth-sub { text-align: center; margin-bottom: 1.5rem; }
.error-card { max-width: 560px; }

/* ---- The printable receipt ---- */

.receipt-doc {
  position: relative;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 2rem 2.25rem;
  max-width: 8.5in;
  margin: 0 auto 1.5rem;
  box-shadow: var(--shadow);
}
/* A statement is a working document, not a receipt: it is read across, and the
   8.5in cap that suits a printed receipt just wastes a third of a tablet screen.
   Print is unaffected -- @page still governs there. */
.receipt-doc.is-wide {
  max-width: none;
  padding: 1.1rem 1.25rem;
}
.receipt-doc.is-wide .receipt-header { margin-bottom: 0.75rem; padding-bottom: 0.6rem; }
.receipt-doc.is-wide .receipt-footer { margin-top: 0.75rem; }

/* The statement's two balance cards are a summary, not a price board: shorter,
   so more rows of the actual statement are above the fold. */
.statement-page .price-board { gap: 0.6rem; margin-bottom: 0.75rem; }
.statement-page .price-card { padding: 0.7rem 0.9rem; }
.statement-page .price-figure { font-size: clamp(1.3rem, 3vw, 1.7rem); }
.statement-page .price-note { margin-top: 0.2rem; font-size: 0.78rem; }
.statement-page .page-head { margin-bottom: 0.6rem; }

.receipt-doc.is-void { opacity: 0.85; }
.void-stamp {
  position: absolute;
  top: 40%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-18deg);
  font-size: 5rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  color: rgba(176, 42, 42, 0.16);
  pointer-events: none;
}

.receipt-header {
  display: flex;
  justify-content: space-between;
  gap: 2rem;
  padding-bottom: 1rem;
  border-bottom: 2px solid var(--gold);
  margin-bottom: 1.25rem;
  flex-wrap: wrap;
}
.receipt-header p { margin: 0; font-size: 0.88rem; color: var(--ink-soft); }
.store-name { font-size: 1.5rem; color: var(--gold); margin-bottom: 0.25rem; }
.receipt-meta h3 { font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-faint); }
.receipt-meta table { border-collapse: collapse; font-size: 0.88rem; }
.receipt-meta th { text-align: left; padding: 0.1rem 0.75rem 0.1rem 0; color: var(--ink-soft); font-weight: 500; }
.receipt-meta td { font-variant-numeric: tabular-nums; font-weight: 500; }

.receipt-customer { margin-bottom: 1.25rem; }
.receipt-customer h4, .receipt-notes h4 {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin-bottom: 0.3rem;
}
.receipt-customer p { margin: 0; font-size: 0.92rem; }

.receipt-items { width: 100%; border-collapse: collapse; font-size: 0.9rem; margin-bottom: 1.25rem; }
.receipt-items thead th {
  text-align: left;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
  border-bottom: 1px solid var(--line);
  padding: 0 0.5rem 0.35rem;
}
.receipt-items thead th.num, .receipt-items td.num { text-align: right; }
.receipt-items td { padding: 0.45rem 0.5rem; border-bottom: 1px solid #f4f2ed; vertical-align: top; }
.receipt-items td.num { font-variant-numeric: tabular-nums; }
.receipt-items .is-trade-in { color: var(--ink-soft); }

.receipt-totals { display: flex; justify-content: space-between; gap: 2rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.receipt-totals table { border-collapse: collapse; font-size: 0.92rem; }
.receipt-totals th { text-align: left; font-weight: 500; color: var(--ink-soft); padding: 0.22rem 1.5rem 0.22rem 0; }
.receipt-totals td { text-align: right; font-variant-numeric: tabular-nums; min-width: 6.5rem; }
.receipt-totals .grand th { color: var(--ink); font-weight: 700; border-top: 2px solid var(--gold); padding-top: 0.45rem; }
.receipt-totals .grand td { font-weight: 700; font-size: 1.15rem; border-top: 2px solid var(--gold); padding-top: 0.45rem; }
.weight-summary { align-self: flex-start; }
.receipt-totals.weight-summary th { color: var(--ink-faint); }

.receipt-notes p { font-size: 0.9rem; }
.receipt-footer {
  border-top: 1px solid var(--line);
  padding-top: 0.9rem;
  font-size: 0.85rem;
  color: var(--ink-soft);
}
.receipt-footer p { margin: 0 0 0.2rem; }

@media print {
  /* Print exactly the receipt: no chrome, no shadows, no wasted margin.
     The whitespace is split deliberately between @page and the document's own
     padding. @page alone is not enough: the print dialog's margin setting
     overrides it, and "None" -- or a Save-as-PDF destination remembering its
     own setting -- drops it to zero, printing the receipt flush to the paper
     edge. The padding below is the part no dialog can take away. */
  @page { size: letter; margin: 0.35in; }

  body { background: #fff; font-size: 11pt; }
  .no-print, .sidebar, .mobilebar, .void-card { display: none !important; }
  .shell { display: block; }
  .page { max-width: none; padding: 0; margin: 0; }

  .receipt-doc {
    border: 0;
    box-shadow: none;
    padding: 0.3in 0.35in;
    max-width: none;
    margin: 0;
  }
  .receipt-items thead { display: table-header-group; }
  .receipt-items tr, .receipt-totals { page-break-inside: avoid; }
  a { color: inherit; text-decoration: none; }
}

/* ---- Inventory count ---- */

.alert-info { background: #eef4fb; border-color: #c9dcf0; color: #244c74; }

.unit-picker .unit-chips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 0.6rem;
  margin-bottom: 0.75rem;
}
.unit-chip {
  display: block;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--ink);
  background: var(--surface);
}
.unit-chip:hover { border-color: var(--gold-line); background: #fdfbf6; color: var(--ink); }
.unit-chip.is-current { border-color: var(--gold); background: var(--gold-soft); box-shadow: inset 0 0 0 1px var(--gold); }
.unit-chip.is-done .unit-chip-name::after { content: ' ✓'; color: var(--ok); }
.unit-chip-name { display: block; font-weight: 600; }
.unit-chip-sub { display: block; font-size: 0.78rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

.count-table { width: 100%; border-collapse: collapse; margin: 0.75rem 0 1rem; }
.count-table th {
  font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink-faint); text-align: left; padding: 0 0.4rem 0.4rem; white-space: nowrap;
}
.count-table td { padding: 0.25rem 0.4rem; vertical-align: middle; }
.count-table input, .count-table select { margin-top: 0; }
.count-cat { font-weight: 600; }
.count-table .col-pieces { width: 7rem; }
.count-table .col-weight { width: 9rem; }
.count-table .col-karat { width: 6rem; }
.count-table .col-saved { width: 7rem; }
/* Big targets: this is typed on a tablet, one hand, beside a scale. */
.count-table .row-pieces, .count-table .row-weight {
  font-size: 1.05rem; padding: 0.55rem 0.6rem; text-align: right;
  font-variant-numeric: tabular-nums;
}
.count-row.is-extra { background: #fdfbf6; }

/* Photo slots: display shot and scale shot, one pair per line.
   No thumbnails here on purpose -- a filled slot is a link, so walking the
   store with twenty rows on screen never pulls image data. */
.count-table .col-photos { width: 13rem; }
.photo-slots { display: flex; gap: 0.35rem; align-items: center; flex-wrap: wrap; }
.photo-slot { display: flex; align-items: baseline; gap: 0.2rem; }
.photo-btn {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.4rem 0.55rem;
  font: inherit; font-size: 0.78rem;
  background: var(--surface); color: var(--ink-soft);
  border: 1px dashed var(--line); border-radius: var(--radius);
  cursor: pointer; white-space: nowrap;
}
.photo-btn:hover { border-color: var(--gold); color: var(--ink); }
.photo-btn.has-photo {
  border-style: solid; border-color: #bfe3ce;
  background: var(--ok-bg); color: #14603a; font-weight: 600;
}
.photo-btn-mark { font-size: 0.9rem; line-height: 1; }
.photo-slot.is-uploading .photo-btn { opacity: 0.55; cursor: progress; }
.photo-view { font-size: 0.72rem; color: var(--ink-faint); text-decoration: underline; }
.photo-view[hidden] { display: none; }
/* Quick switch: big targets, because this is tapped mid-sale on a tablet. */
.switch-card { max-width: 420px; }
.switch-people { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 0.6rem; margin: 1rem 0; }
.switch-person {
  display: flex; flex-direction: column; align-items: center; gap: 0.4rem;
  padding: 0.9rem 0.5rem; font: inherit; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
}
.switch-person:hover { border-color: var(--gold); background: var(--gold-soft); }
.switch-initials {
  display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border-radius: 50%;
  background: var(--gold-soft); color: #7a5906; font-weight: 700; font-size: 1.05rem;
}
.switch-name { font-weight: 600; font-size: 0.9rem; }
.switch-who { margin: 0.5rem 0; }
.switch-foot { margin-top: 1rem; text-align: center; }
#pin-input { font-size: 1.4rem; letter-spacing: 0.3em; text-align: center; }
.sidebar-foot-actions { display: flex; gap: 0.6rem; align-items: center; }

/* .link-button has no display of its own, so the icon and the word would sit
   on the text baseline with no space between them. Same reason the switch
   button needed a flex box of its own. The underline is dropped because it
   would run under the icon too, which reads as a drawing mistake. */
.sign-out {
  display: inline-flex; align-items: center; gap: 0.3rem;
  text-decoration: none;
}
.sign-out:hover { text-decoration: underline; }

.photo-note { margin-top: 0.25rem; font-size: 0.72rem; color: var(--ink-soft); }
.photo-note.is-error { color: var(--danger); font-weight: 600; }
.photo-note.is-ok { color: var(--ok); }
.line-table .col-photos, .receipt-items .col-photos { width: 12rem; }

/* Review page: one chip per shot, showing at a glance which lines are
   documented. Chips become thumbnails only when "Show photos" is pressed. */
.photo-cell { white-space: nowrap; }
.photo-chip {
  display: inline-block; margin-right: 0.3rem;
  padding: 0.1rem 0.45rem;
  font-size: 0.72rem; border-radius: 999px; border: 1px solid;
}
.photo-chip.has-photo {
  background: var(--ok-bg); border-color: #bfe3ce; color: #14603a;
  font-weight: 600; text-decoration: none;
}
.photo-chip.is-missing {
  background: transparent; border-color: var(--line); border-style: dashed;
  color: var(--ink-faint);
}
.photo-chip .photo-thumb {
  display: block; width: 88px; height: 88px;
  object-fit: cover; border-radius: var(--radius);
}
.photo-chip:has(.photo-thumb) { padding: 0; border: 0; background: none; }

/* Bar price board. Sized to be read across a counter, not studied. */
.price-board {
  display: grid; gap: 1rem; margin-bottom: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.price-card {
  padding: 1.4rem 1.5rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.price-card.price-sell { border-color: var(--gold-line); background: var(--gold-soft); }
.price-card.price-buy { border-color: #bfe3ce; background: var(--ok-bg); }
.price-label {
  font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-soft); margin-bottom: 0.35rem;
}
.price-figure {
  font-size: clamp(2rem, 5vw, 2.9rem); font-weight: 700; line-height: 1.1;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.price-dash { color: var(--ink-faint); margin: 0 0.15rem; font-weight: 400; }
.price-note { margin-top: 0.5rem; font-size: 0.84rem; color: var(--ink-soft); }

/* Daily check: one card per display case, sized for a phone held one-handed.
 *
 * The card's job is to show the counts, permanently. Everything else -- the
 * photo, the model's caveat, the controls -- is arranged so it can never push
 * the numbers out of the way. */
.check-units {
  display: grid; gap: 0.75rem; margin: 0.75rem 0 1rem;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}

/* State is carried by ONE custom property rather than a rule per state, so a
   new state is one line and the four can never drift apart in styling. */
.check-unit {
  --unit-accent: var(--line);
  display: flex; flex-direction: column;
  padding: 0.75rem 0.85rem;
  border: 1px solid var(--line); border-left: 3px solid var(--unit-accent);
  border-radius: var(--radius);
  background: var(--surface);
}
.check-unit.is-empty  { border-style: dashed; border-left-style: solid; }
.check-unit.is-read   { --unit-accent: var(--ok); }
/* Only the failed card is tinted. A fully-read grid should read as a clean
   sheet of numbers, not a wall of green -- green used to mean merely "bytes
   arrived", so a photo that could not be read still looked like success. */
.check-unit.is-failed { --unit-accent: var(--danger); background: var(--danger-bg); }
.check-unit.is-busy   { --unit-accent: var(--gold); }
/* Dim what is going stale, NOT the whole card -- the progress line has to stay
   the most readable thing on it while the read runs. */
.check-unit.is-busy .check-unit-body,
.check-unit.is-busy .check-unit-photo,
.check-unit.is-busy .check-unit-caveat { opacity: 0.45; }
.check-unit.is-busy .check-unit-actions { opacity: 0.5; }
.check-unit.is-offline { --unit-accent: var(--ink-faint); }

.check-unit-head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  margin-bottom: 0.5rem;
}

/* The case, shown. Click opens the full-size original in a new tab.
   Fixed aspect ratio so a portrait shot and a landscape one give the same
   card height -- otherwise one sideways photo stretches its whole grid row. */
.check-unit-photo {
  display: block; margin-bottom: 0.6rem; border-radius: 6px;
  /* Reserves the box before the lazy image arrives, so the card does not
     jump as you scroll. */
  aspect-ratio: 4 / 3; background: var(--bg);
}
.check-unit-photo img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  border-radius: 6px; border: 1px solid var(--line);
}
.check-unit-photo:hover img { border-color: var(--gold); }
.check-unit-photo:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.check-unit-body { margin-bottom: 0.5rem; }

.check-unit-counts {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: baseline; gap: 0.15rem 0.5rem; margin: 0;
}
.check-unit-counts dt { font-size: 0.86rem; color: var(--ink); }
.check-unit-counts dd { margin: 0; }
.check-unit-figure {
  font-weight: 700; font-size: 1rem; font-variant-numeric: tabular-nums;
  text-align: right;
}
/* The model's figure stays visible wherever a person overrode it. */
.check-unit-model {
  font-weight: 400; font-size: 0.78rem; color: var(--ink-faint);
  text-decoration: line-through;
}
.check-unit-conf { text-align: right; }
.check-unit-total {
  margin: 0.4rem 0 0; padding-top: 0.35rem; border-top: 1px solid var(--line);
  font-size: 0.78rem; color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.check-unit-note { margin: 0; font-size: 0.86rem; color: var(--ink-soft); }

/* Clamped: an unclamped error is the other way a card inflates its whole grid
   row. Full text is in the title attribute. */
.check-unit-error {
  margin: 0; font-size: 0.82rem; color: var(--danger); font-weight: 600;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Collapsed by default, and the summary is a fixed string -- the note itself
   is prose of any length, and in a summary it would stretch every card beside
   this one. */
.check-unit-caveat { font-size: 0.78rem; color: var(--ink-soft); margin-bottom: 0.5rem; }
.check-unit-caveat summary { cursor: pointer; }
.check-unit-caveat summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.check-unit-caveat p { margin: 0.3rem 0 0; }

/* Visible progress. A photo is read by a model and takes several seconds; a
   card that only dims is indistinguishable from a frozen one, and the natural
   response to that is to press the button again. */
.check-unit-progress {
  display: flex; align-items: center; gap: 0.45rem;
  margin: 0 0 0.5rem; font-size: 0.82rem; font-weight: 600; color: var(--gold);
}
.check-unit-spinner {
  flex: none; width: 0.85rem; height: 0.85rem; border-radius: 50%;
  border: 2px solid var(--gold-line); border-top-color: var(--gold);
  animation: check-unit-spin 0.7s linear infinite;
}
@keyframes check-unit-spin { to { transform: rotate(360deg); } }

/* Motion is the signal here, so removing it needs a replacement rather than
   nothing at all -- otherwise the reduced-motion reading is a static dot that
   says as little as the frozen card did. */
@media (prefers-reduced-motion: reduce) {
  .check-unit-spinner {
    animation: check-unit-pulse 1.4s ease-in-out infinite;
    border-top-color: var(--gold-line); background: var(--gold);
  }
  @keyframes check-unit-pulse { 50% { opacity: 0.25; } }
}

/* margin-top:auto pins the actions to the bottom, so rows of cards line up
   however tall their contents are. */
.check-unit-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
  margin-top: auto;
}
/* Scoped to the card rather than changing .btn globally: .btn is inline-block
   everywhere else in the app and flipping that for every button in every view
   to serve one icon is not a trade worth making. */
.check-unit .btn { display: inline-flex; align-items: center; gap: 0.35rem; }
.check-unit .btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* Sized in em so the glyph tracks the label rather than needing its own scale
   at every button size. */
.btn-icon { flex: none; width: 1.05em; height: 1.05em; }

/* Used one-handed at a display case. */
@media (pointer: coarse) {
  .check-unit .btn { min-height: 2.5rem; }
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Vendor statement: two running balances read side by side against theirs.
   Dense on purpose -- it is compared line by line against the vendor's own
   sheet, so fitting a month on one page matters more than breathing room. */
/* Dense by design: this is compared line by line against the vendor's own
   sheet, so how many rows fit at once matters more than breathing room. */
.statement-table { font-size: 0.84rem; }
.statement-table th, .statement-table td {
  vertical-align: top; padding: 0.18rem 0.4rem; line-height: 1.3;
}
.statement-table thead th { padding-bottom: 0.25rem; }
/* The sub-line under an entry (price x weight, ratio) is supporting detail --
   it should not cost a full row's height. */
.statement-table .small { font-size: 0.74rem; line-height: 1.2; }
.statement-table tbody tr { border-bottom: 1px solid #f2efe9; }
.statement-carried td { background: var(--gold-soft); font-style: italic; }

/* RTL, for a statement going to an Arabic-speaking vendor.
   Numeric columns keep their own alignment: a balance column reads right-aligned
   in both directions, and flipping it would break the decimal alignment that
   makes a column of money scannable. */
[dir='rtl'] .data-table th,
[dir='rtl'] .data-table td { text-align: right; }
[dir='rtl'] .data-table .num { text-align: left; }
[dir='rtl'] .receipt-header,
[dir='rtl'] .card-head { flex-direction: row-reverse; }
[dir='rtl'] .statement-table td:first-child { direction: ltr; text-align: right; }

/* Alternating rows. Applied to the whole row rather than cells so the tint sits
   under the balance columns too, which is where the eye tracks across. */
.statement-table tbody tr:nth-child(even),
.zebra tbody tr:nth-child(even) { background: #f6f4f0; }
.statement-table tbody tr:hover,
.zebra tbody tr:hover { background: var(--gold-soft); }
.report-filter {
  display: flex; align-items: flex-end; gap: 0.75rem; flex-wrap: wrap; margin: 0;
}
.report-filter label { margin-bottom: 0; }

.purchase-row .p-ratio, .line-table .col-ratio { width: 6.5rem; }

.saved-flag { font-size: 0.78rem; color: var(--ink-faint); }
.saved-flag.is-ok { color: var(--ok); }
.saved-flag.is-pending { color: var(--ink-faint); }
.saved-flag.is-error { color: var(--danger); font-weight: 600; }

.unit-nav { display: flex; gap: 0.5rem; justify-content: space-between; margin-top: 1.25rem; flex-wrap: wrap; }
.unit-total { font-variant-numeric: tabular-nums; }

/* Sticky running total: always visible while walking the store. */
.count-footer {
  position: sticky; bottom: 0;
  background: var(--surface);
  border-top: 2px solid var(--gold);
  margin: 0 -1.25rem -4rem;
  padding: 0.75rem 1.25rem;
  box-shadow: 0 -2px 10px rgba(28, 26, 23, 0.06);
}
.count-footer-inner {
  /* Tracks the page rather than the old 1180px measure, so the sticky footer
     stays aligned with the table it belongs to now the page runs full width. */
  margin: 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}
.count-footer-actions { display: flex; gap: 0.5rem; }

.active-count-card { border-color: var(--gold-line); background: var(--gold-soft); }

.stat-ok { background: var(--ok-bg); border-color: #bfe3ce; }
.stat-alert { background: var(--danger-bg); border-color: #f0c5c5; }

.is-short { color: var(--danger); font-weight: 600; }
.is-over { color: var(--ok); font-weight: 600; }
.is-alert-row th, .is-alert-row td { color: var(--danger); font-weight: 600; }
.total-row td, .total-row th { border-top: 2px solid var(--line); font-weight: 600; }

.recon-table th.group {
  text-align: center; border-bottom: 1px solid var(--line);
  font-size: 0.7rem; letter-spacing: 0.03em;
}
.recon-table th.group-strong { background: var(--gold-soft); color: #7a5906; }
.recon-table th.sub { font-size: 0.68rem; padding-bottom: 0.3rem; }
.recon-table td, .recon-table th { padding: 0.4rem 0.45rem; }

.write-in { border-bottom: 1px solid var(--ink-faint) !important; min-width: 4rem; }

/* ---- Purchases ---- */

.subhead { font-size: 0.95rem; margin-top: 1.25rem; color: var(--ink-soft); }
.purchase-line-table .col-qty { width: 5rem; }
.purchase-line-table .col-fine { width: 7rem; }
.purchase-line-table .col-qty input, .purchase-line-table .col-weight input { text-align: right; }
.p-fine { font-variant-numeric: tabular-nums; font-weight: 600; }

.purchase-summary { display: flex; gap: 2rem; justify-content: space-between; flex-wrap: wrap; margin-bottom: 1.25rem; }
.karat-summary { border-collapse: collapse; font-size: 0.88rem; }
.karat-summary th, .karat-summary td { padding: 0.3rem 0.7rem 0.3rem 0; text-align: left; }
.karat-summary thead th {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink-faint); border-bottom: 1px solid var(--line);
}
.karat-summary .num, .purchase-totals .num { text-align: right; font-variant-numeric: tabular-nums; }

.purchase-totals { border-collapse: collapse; font-size: 0.92rem; align-self: flex-start; }
.purchase-totals th { text-align: left; font-weight: 500; color: var(--ink-soft); padding: 0.25rem 1.5rem 0.25rem 0; }
.purchase-totals td { text-align: right; min-width: 7rem; }
.purchase-totals .grand th { color: var(--ink); font-weight: 700; border-top: 2px solid var(--gold); padding-top: 0.45rem; }
.purchase-totals .grand td { font-weight: 700; font-size: 1.1rem; border-top: 2px solid var(--gold); padding-top: 0.45rem; }

/* Item thumbnails in the in/out table. Small enough not to turn a ledger into
   a gallery, big enough to tell a chain from a bangle at a glance. */
.col-thumb { width: 56px; padding-right: 0.4rem !important; }
.tx-thumb {
  display: block; width: 48px; height: 48px; object-fit: cover;
  border-radius: 4px; border: 1px solid var(--line); background: var(--bg);
}
.tx-thumb.is-empty { border-style: dashed; }
/* A scale shot standing in for a missing item photo. Dimmed so the eye reads it
   as the fallback it is, and comes up to full on hover when you want to look. */
.tx-thumb.is-scale { opacity: 0.72; border-style: dotted; }
.tx-thumb.is-scale:hover { opacity: 1; }

/* ---- Photographic evidence: the same case, then and now ---- */

/* Two photos side by side at a size worth looking at. The whole point is that
   a person can see the case rather than read a number about it, so these are
   not thumbnails tucked into a table cell. */
.evidence-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 1rem; margin-top: 0.75rem;
}
.evidence { border: 1px solid var(--line); border-radius: var(--radius); padding: 0.6rem; background: var(--surface); }
.evidence.is-flagged { border-color: #e6b8b8; background: var(--danger-bg); }
.evidence-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; margin-bottom: 0.4rem; }
.evidence-change { font-variant-numeric: tabular-nums; font-size: 0.9rem; }
.evidence-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 0.4rem; }
.evidence-pair img {
  width: 100%; height: 150px; object-fit: cover;
  border-radius: 4px; border: 1px solid var(--line); display: block;
}
.evidence-missing {
  display: flex; align-items: center; justify-content: center; text-align: center;
  height: 150px; border: 1px dashed var(--line); border-radius: 4px;
  font-size: 0.78rem; color: var(--ink-faint); padding: 0.4rem;
}
.evidence-foot { margin-top: 0.4rem; display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; }

@media print {
  /* Paper is where these get argued over, so the pictures stay. */
  .evidence-pair img { height: 110px; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .evidence { page-break-inside: avoid; }
}

/* ---- The lead narrative on the variance trace ---- */

.lead-narrative .chain { list-style: none; margin: 0 0 1rem; padding: 0; font-size: 0.95rem; }
.lead-narrative .chain li { padding: 0.2rem 0; }
.lead-narrative .chain .chain-result {
  border-top: 1px solid var(--line); margin-top: 0.35rem; padding-top: 0.45rem;
}
.dir-out { color: var(--danger); font-variant-numeric: tabular-nums; }
.dir-in  { color: var(--ok); font-variant-numeric: tabular-nums; }

/* The leads are the point of the page, so they read as sentences rather than
   as another table. Amber, not red: a lead is somewhere to look, not a verdict. */
.lead-narrative h3 {
  font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-faint); margin: 0.6rem 0 0.3rem;
}
.leads { margin: 0; padding-left: 1.1rem; }
.leads li { padding: 0.25rem 0; line-height: 1.45; }

/* ---- Tracing a variance to a display unit ---- */

/* The variance figure is the way through to the breakdown, so it has to look
   like a way through -- but on a page that is mostly numbers, an underline on
   every one would be noise. Dotted, and only where there is a variance. */
.variance-link { color: inherit; text-decoration: none; border-bottom: 1px dotted currentColor; }
.variance-link:hover { border-bottom-style: solid; }

.data-table tr.row-uncounted > td { background: var(--danger-bg); }
.stat .small-value { font-size: 1.1rem; line-height: 1.4; }
.stat-short .stat-value { color: var(--danger); }

@media print {
  /* On paper the link is just a number again. */
  .variance-link { border-bottom: 0; }
}

/* ---- Reclassify: two sides and the balance between them ---- */

.reclassify-date { margin-bottom: 0; min-width: 11rem; }
.reclassify-sides {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 1.25rem;
  margin: 0.75rem 0;
}
.reclassify-side h3 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-faint); margin-bottom: 0.35rem; }
.reclassify-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.reclassify-table th { text-align: left; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-faint); padding-bottom: 0.2rem; }
.reclassify-table th.num, .reclassify-table td.num { text-align: right; }
.reclassify-table td { padding: 0.15rem 0.2rem 0.15rem 0; }
.reclassify-table input, .reclassify-table select { margin-top: 0; width: 100%; }
.reclassify-table .reclassify-pieces, .reclassify-table .reclassify-weight { text-align: right; }
.btn-small { padding: 0.25rem 0.6rem; font-size: 0.82rem; }

/* The running balance. Amber while it does not add up rather than red: an
   unbalanced form mid-typing is the normal state, not an error. */
.reclassify-balance { margin: 0.5rem 0 0.9rem; font-size: 0.88rem; }
.reclassify-balance:empty { display: none; }
.reclassify-balance.is-balanced { color: var(--ok); font-weight: 500; }
.reclassify-balance.is-off { color: #8a5a00; }
.reclassify-balance ul { margin: 0; padding-left: 1.1rem; }

/* Rows of one reclassification read as a block: they only make sense together,
   because what they say is that these figures cancel out. */
.data-table tr.is-grouped > td { background: #faf7f1; }
.data-table tr.starts-group > td { border-top: 2px solid var(--line); }

.tax-mode-field { margin-bottom: 0; min-width: 16rem; }
.tax-mode-hint { margin-top: -0.25rem; }

/* Sits in the Customer card head, opposite the heading. Narrow on purpose --
   it is a field you pass over on almost every sale, not one you fill in. */
.sale-date-field { margin-bottom: 0; min-width: 11rem; }
/* Amber rather than --danger: a backdated receipt is a normal thing to be
   doing here, and it should read as confirmation, not as an error. */
.sale-date-hint { margin: -0.25rem 0 0.75rem; color: #8a5a00; }
.mark-received { align-self: end; }

.vendor-row-cell { padding: 0.3rem 0.4rem !important; }
.vendor-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; margin: 0; }
.vendor-row input[type="text"], .vendor-row input[type="email"] { margin-top: 0; flex: 1 1 9rem; min-width: 8rem; }
.inline-check { margin-bottom: 0; white-space: nowrap; }

@media print {
  .count-footer, .unit-picker, .stat-row { display: none !important; }
  .purchase-summary { page-break-inside: avoid; }

  /* Statements are printed to be laid beside the vendor's own sheet, so the
     page is squeezed: less chrome, tighter rows, more lines per sheet. */
  .statement-table, .zebra { font-size: 9pt; }
  .statement-table th, .statement-table td,
  .zebra th, .zebra td { padding: 0.12rem 0.3rem; }

  .receipt-doc .receipt-header { margin-bottom: 0.4rem; padding-bottom: 0.3rem; }
  .receipt-doc .receipt-footer { margin-top: 0.4rem; }
  .card { padding: 0.5rem 0; margin-bottom: 0.5rem; border: 0; box-shadow: none; }
  .page-head { margin-bottom: 0.4rem; }
  .page-head p.muted { display: none; }
  h1 { font-size: 14pt; margin-bottom: 0.2rem; }
  h2 { font-size: 11pt; }

  /* Browsers drop backgrounds when printing unless told otherwise, which would
     lose the striping exactly where it helps most -- on paper. */
  .statement-table tbody tr:nth-child(even),
  .zebra tbody tr:nth-child(even),
  .statement-carried td,
  .data-table tr.grand td, .data-table tr.grand th {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
  /* Hover tint is a screen affordance; on paper it would just be a stray band. */
  .statement-table tbody tr:hover, .zebra tbody tr:hover { background: transparent; }

  /* A statement that splits mid-row is unreadable against the vendor's copy. */
  .statement-table tr, .zebra tr { page-break-inside: avoid; }
  .statement-table thead, .zebra thead { display: table-header-group; }
}

/* ---- Daily check: reviewing what the photo was read as ---- */

.reading-modal {
  width: min(46rem, calc(100vw - 2rem));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem 1.4rem 1.1rem;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow);
}
.reading-modal::backdrop { background: rgba(28, 26, 23, 0.45); }
.reading-modal h2 { margin-bottom: 0.15rem; }
.reading-modal h3 { font-size: 0.95rem; margin-bottom: 0.2rem; }

.reading-modal-close { position: absolute; top: 0.5rem; right: 0.75rem; }
.reading-modal-close button { font-size: 1.4rem; line-height: 1; color: var(--ink-faint); }

/* Capped so a tall phone photo cannot push the numbers off screen -- the
   picture is there to be glanced at, the figures are the work. */
.reading-photo img {
  display: block;
  max-width: 100%;
  max-height: 14rem;
  margin: 0 auto 0.9rem;
  border-radius: 6px;
  border: 1px solid var(--line);
}

.reading-table td { vertical-align: top; }
.reading-input { width: 5rem; text-align: right; margin: 0; }

.reading-add { margin-top: 0.4rem; }
.reading-add-row { display: flex; gap: 0.5rem; align-items: center; }
.reading-add-row select { flex: 0 1 16rem; margin: 0; }

.reading-hints { margin-top: 1rem; border-top: 1px solid var(--line); padding-top: 0.8rem; }
.hint-list { list-style: none; margin: 0 0 0.6rem; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.hint-list li {
  display: flex; justify-content: space-between; gap: 0.75rem; align-items: baseline;
  font-size: 0.9rem; padding: 0.3rem 0.5rem;
  background: var(--gold-soft); border: 1px solid var(--gold-line); border-radius: 5px;
}

.reading-modal-actions {
  display: flex; justify-content: flex-end; align-items: center; gap: 0.75rem;
  margin-top: 1rem; border-top: 1px solid var(--line); padding-top: 0.8rem;
}

/* Which cases made up a store-wide figure. Clickable, because the row you are
   staring at when you spot a wrong number is this one, not the photo card.
   Named apart from .unit-chip, which is the inventory count's picker tile --
   that rule's display:block used to leak in here. */
.row-units { display: inline-flex; flex-wrap: wrap; gap: 0.25rem; margin-left: 0.5rem; }
.review-chip {
  font-size: 0.72rem; padding: 0.1rem 0.4rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink-soft);
  white-space: nowrap; font-family: inherit;
}
button.review-chip { cursor: pointer; }
button.review-chip:hover { border-color: var(--gold); color: var(--gold); }
button.review-chip:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
.review-chip.is-corrected { background: var(--ok-bg); border-color: transparent; color: var(--ok); }

/* A closed check's reading is readable, not editable. The controls that write
   are removed rather than disabled: a Save button that cannot save leaves the
   reader working out why for themselves. */
.reading-modal.is-readonly .reading-add,
.reading-modal.is-readonly #reading-hint,
.reading-modal.is-readonly #reading-save,
.reading-modal.is-readonly .reading-empty-note { display: none; }
.reading-modal.is-readonly .reading-hints h3::after { content: ' (read-only)'; color: var(--ink-faint); font-weight: 400; }

/* ---- Reading an inventory count off its photos ---- */

.unit-photo-card.is-busy { opacity: 0.75; }
.unit-photo-body { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start; }
.unit-photo-shot { flex: 1 1 14rem; max-width: 20rem; display: flex; flex-direction: column; gap: 0.6rem; }
.unit-photo-shot .check-unit-photo { margin-bottom: 0; }
.unit-photo-shot .btn { align-self: flex-start; display: inline-flex; align-items: center; gap: 0.35rem; }
.unit-photo-read { flex: 1 1 18rem; min-width: 0; }

.unit-photo-counts {
  display: grid; grid-template-columns: 1fr auto auto auto;
  align-items: center; gap: 0.3rem 0.6rem; margin: 0.5rem 0 0;
}
.unit-photo-counts dt { font-size: 0.9rem; }
.unit-photo-counts dd { margin: 0; }

/* What the scale was read as, offered beside the weight box. Never typed into
   it -- see showWeightProposal for which readings are offerable at all. */
.weight-proposal {
  margin-top: 0.35rem; padding: 0.4rem 0.5rem;
  border: 1px solid var(--gold-line); border-radius: 5px;
  background: var(--gold-soft); font-size: 0.78rem;
  display: flex; flex-direction: column; gap: 0.3rem; align-items: flex-start;
  text-align: left; white-space: normal;
}
.weight-proposal.is-error { background: var(--danger-bg); border-color: transparent; color: var(--danger); }
/* A reading that needs looking at does not get to look reassuring. */
.weight-proposal.needs-eyes { background: var(--danger-bg); border-color: var(--danger); }
.weight-proposal-figure { font-weight: 600; font-variant-numeric: tabular-nums; }
.weight-proposal-warning { margin: 0; color: var(--danger); font-weight: 600; }

/* The scale shot, previewed on its row. Small on purpose: it is a glance
   check that the right tray was photographed and often that the number is
   legible, not a viewer -- clicking opens the original. */
.photo-thumb { display: block; margin-top: 0.3rem; line-height: 0; }
.photo-thumb img {
  width: 3.5rem; height: 2.6rem; object-fit: cover;
  border: 1px solid var(--line); border-radius: 4px;
}
.photo-thumb:hover img { border-color: var(--gold); }
.photo-thumb:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* ---- Count mode ---- */
/*
   A radio pair rather than a checkbox: "full" and "baseline" are two kinds of
   count, not one with a feature switched off. A checkbox would make the faster
   one read as a degraded version of the other, when in practice it is the one
   that gets run.
*/
.count-mode { border: 1px solid var(--line); border-radius: var(--radius); padding: 0.7rem 0.85rem; }
.count-mode legend { padding: 0 0.35rem; font-size: 0.85rem; color: var(--ink-muted); }
.count-mode .radio { display: flex; gap: 0.5rem; align-items: flex-start; margin-bottom: 0.6rem; }
.count-mode .radio:last-child { margin-bottom: 0; }
.count-mode .radio input { margin-top: 0.25rem; width: auto; }
.count-mode .radio small { display: block; margin-top: 0.15rem; }

/* ---- Count photos: line thumbnails and the gallery ---- */
/*
   The review page used to show word-chips that had to be clicked to become
   images, because photos were full-size camera shots. They are resized in the
   browser now, so a lazily-loaded thumbnail costs little and the evidence is
   simply visible.
*/

.photo-cell { display: flex; gap: 0.3rem; flex-wrap: wrap; align-items: center; }

.line-thumb {
  position: relative; display: block; width: 3rem; aspect-ratio: 1;
  border-radius: 4px; overflow: hidden; border: 1px solid var(--line);
}
.line-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.line-thumb:hover { border-color: var(--gold); }
.line-thumb:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* The label sits ON the image: at this size a caption beneath would be taller
   than the picture it describes. */
.line-thumb-tag {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 0.05rem 0.15rem;
  font-size: 0.6rem; line-height: 1.3; text-align: center;
  color: #fff; background: rgba(0, 0, 0, 0.55);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.photo-gallery {
  display: grid; gap: 0.75rem; margin-top: 0.75rem;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
.gallery-item { margin: 0; }
.gallery-item a { display: block; aspect-ratio: 4 / 3; background: var(--bg); border-radius: 6px; }
.gallery-item img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  border-radius: 6px; border: 1px solid var(--line);
}
.gallery-item a:hover img { border-color: var(--gold); }
.gallery-item figcaption { padding-top: 0.3rem; font-size: 0.85rem; }
.gallery-item figcaption span { display: block; }
/* The case shot is the anchor of its group, so it reads first at a glance. */
.gallery-item.is-case img { border-color: var(--gold); }

/* ---- Review: what was counted, per case ---- */
/*
   One card per case, replacing a flat table of every (case, category, karat)
   line. Same vocabulary as the daily check's cards and the counting tiles.
*/

/* Three across on a laptop, two on a tablet, one on a phone -- auto-fill on a
   ~22rem minimum lands there without hard breakpoints. The section is full
   width for this reason: it used to sit in a half-width column beside the
   valuation, which squeezed nineteen cases into a thin strip. */
.review-cards {
  display: grid; gap: 0.6rem; margin: 0.75rem 0 1rem;
  grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
  align-items: start;
}
@media (max-width: 48rem) {
  /* One per row well before the cards themselves get cramped: each holds a
     photo beside a table of categories, and two of those side by side on a
     tablet leaves neither readable. */
  .review-cards { grid-template-columns: 1fr; }
}

.review-card {
  border: 1px solid var(--line); border-left: 3px solid var(--gold);
  border-radius: var(--radius); background: var(--surface);
}

.review-card-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.5rem; flex-wrap: wrap;
  padding: 0.55rem 0.75rem; border-bottom: 1px solid var(--line);
}
.review-card-name { flex: 1 1 auto; }
.review-card-totals { display: flex; gap: 0.75rem; font-variant-numeric: tabular-nums; }

.review-card-body { display: flex; gap: 0.75rem; padding: 0.6rem 0.75rem; align-items: flex-start; }

/* Fixed box so a portrait shot and a landscape one give the same card height. */
.review-card-photo { display: block; flex: 0 0 5.5rem; aspect-ratio: 4 / 3; background: var(--bg); }
.review-card-photo img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  border-radius: 6px; border: 1px solid var(--line);
}
.review-card-photo:hover img { border-color: var(--gold); }

.review-card-lines { flex: 1 1 auto; min-width: 0; }
.review-line-table { width: 100%; border-collapse: collapse; }
.review-line-table td { padding: 0.15rem 0.3rem; vertical-align: middle; }
.review-line-table tr + tr td { border-top: 1px solid var(--line); }
.review-cat { width: 45%; }
.review-karat { color: var(--ink-faint); font-size: 0.8rem; }
.review-pcs, .review-g { font-variant-numeric: tabular-nums; white-space: nowrap; }
.review-g { color: var(--ink-muted); }
.review-trays { width: 1%; white-space: nowrap; text-align: right; }

/* The library alternative next to Weigh a tray: icon only, so it reads as a
   second way to do the same thing rather than a second thing to do. */
.pick-tray { margin-left: 0.35rem; padding-left: 0.5rem; padding-right: 0.5rem; }
.pick-tray .btn-icon { width: 1.1em; height: 1.1em; }

/* Small enough to sit inside a table row without stretching it. */
.tray-dot { display: inline-block; width: 1.6rem; aspect-ratio: 1; margin-left: 0.15rem; }
.tray-dot img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  border-radius: 3px; border: 1px solid var(--line);
}
.tray-dot:hover img { border-color: var(--gold); }

@media (max-width: 30rem) {
  /* On a phone the photo goes above the figures rather than squeezing them. */
  .review-card-body { flex-direction: column; }
  .review-card-photo { flex-basis: auto; width: 100%; max-width: 12rem; }
}

/* The karat on a source chip, shown only for a mixed-karat category. */
.chip-karat { color: var(--ink-faint); font-size: 0.75rem; margin-left: 0.15rem; }

/* ---- Printing a completed count ---- */
/*
   Three sheets, in the order the questions get asked: does it balance, where
   did the figures come from, and what exactly was in each case. Each starts a
   fresh page, because the three get read by different people at different
   times -- the reconciliation goes to the owner, the per-case detail goes back
   to the floor with whoever is recounting.

   Photographs are deliberately absent. A 1.6mm tray thumbnail prints as a grey
   square; anyone who needs to see one has the screen, where it is a link to the
   full-size original.
*/

/* Only appears on paper -- on screen the page head above says all of it. */
.print-head { display: none; }

@media print {
  .count-review-page .print-head {
    display: block;
    margin: 0 0 0.35rem;
    padding-bottom: 0.2rem;
    border-bottom: 1pt solid #000;
  }
  .count-review-page .print-head-title { font-weight: 700; font-size: 11pt; }
  .count-review-page .print-head-meta { font-size: 8.5pt; margin-left: 0.5em; }
  .count-review-page .print-head-section {
    display: block; font-size: 9.5pt; font-weight: 600; margin-top: 0.1rem;
  }

  /* The vendor-statement print block hides the stat row outright. Here the four
     headline figures ARE the reconciliation, so they stay. */
  .count-review-page .stat-row { display: grid !important; grid-template-columns: repeat(4, 1fr); gap: 0.4rem; margin-bottom: 0.4rem; }
  .count-review-page .stat { padding: 0.3rem 0.4rem; }
  .count-review-page .stat-value { font-size: 12pt; }
  .count-review-page .stat-label, .count-review-page .stat-sub { font-size: 8pt; }

  /* Screen chrome that means nothing on paper. The whole page head goes: its
     title and date are what the banner above each section already carries, and
     leaving it in put a stray sentence above the first banner with nothing to
     attach itself to. */
  .count-review-page .page-head,
  .count-review-page .review-card-photo,
  .count-review-page .review-trays { display: none !important; }

  /* Seventeen columns across a landscape sheet. Tabular figures keep the
     columns aligned once the type gets this small. */
  .count-review-page .recon-table { font-size: 7pt; }
  .count-review-page .recon-table th,
  .count-review-page .recon-table td { padding: 0.08rem 0.18rem; }
  .count-review-page .data-table { font-size: 8.5pt; }
  .count-review-page .data-table thead { display: table-header-group; }
  .count-review-page .data-table tr { page-break-inside: avoid; }

  /* The tint over Expected / Counted / Variance is what tells you which of the
     seventeen columns you are in. Browsers drop backgrounds on paper unless
     told not to, and dropping this one is dropping the only grouping the table
     has. Same for the total rule at the foot. */
  .count-review-page .recon-table th.group-strong,
  .count-review-page .data-table tr.total-row td {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* Variance is read by sign, and the sign is carried by colour on screen.
     Print in grey-safe terms: the +/- is already in the text, so all this has
     to survive is the emphasis. */
  .count-review-page .is-short, .count-review-page .is-over { font-weight: 700; }

  /* The two new sheets. */
  .count-review-page .count-section { page-break-before: always; }

  /* Their own heading is now the banner's section line, one row above it. The
     badge beside it ("6 categories") is not a repeat, so that stays. */
  .count-review-page .count-section > .card-head > h2 { display: none; }

  /* Three cases across a landscape sheet, and none of them split across the
     fold -- a case whose last two categories are on the next page is a case
     somebody will recount. */
  .count-review-page .review-cards {
    grid-template-columns: repeat(3, 1fr);
    gap: 0.35rem;
    margin: 0.4rem 0 0;
  }
  .count-review-page .review-card {
    page-break-inside: avoid;
    break-inside: avoid;
    border-left-width: 2pt;
  }
  .count-review-page .review-card-head { padding: 0.25rem 0.4rem; }
  .count-review-page .review-card-name { font-size: 9pt; }
  .count-review-page .review-card-totals { font-size: 8.5pt; }
  .count-review-page .review-card-body { padding: 0.25rem 0.4rem; }
  .count-review-page .review-line-table { font-size: 8pt; }
  .count-review-page .review-line-table td { padding: 0.06rem 0.2rem; }

  /* Source chips wrap inside a table cell; letting one break across pages
     leaves a category's cases orphaned from its total. */
  .count-review-page .review-chip { font-size: 7.5pt; padding: 0 0.2em; }
  .count-review-page .row-units { margin-left: 0.3em; }

  /* Explanatory prose earns its space on screen. On paper it pushes the
     figures down the sheet, and the reader already knows what a variance is. */
  .count-review-page .card > p.muted.small,
  .count-review-page .alert-info { font-size: 7.5pt; margin-bottom: 0.25rem; }
  .count-review-page .card-head { margin-bottom: 0.2rem; }

  .count-review-page a { color: inherit; text-decoration: none; }
}

/* ---- Photographs of what a vendor delivered ---- */

/* One row per purchase line, each with however many shots were taken of it.
   Not a grid of equal cells: the lines are the structure here, and a line with
   six photographs should look different from one with none. */
.purchase-photo-lines { list-style: none; margin: 0.75rem 0 0; padding: 0; }
.purchase-photo-line {
  padding: 0.7rem 0; border-top: 1px solid var(--line);
}
.purchase-photo-line:first-child { border-top: none; }
.purchase-photo-head {
  display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
}
/* Pushes the button to the right edge however long the description is. */
.purchase-photo-head .add-photo { margin-left: auto; }

.purchase-photo-strip {
  display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem;
}
.purchase-photo-strip:empty { margin-top: 0; }
.purchase-photo { position: relative; margin: 0; }
.purchase-photo img {
  display: block; width: 96px; height: 96px; object-fit: cover;
  border-radius: 6px; border: 1px solid var(--line); background: var(--bg);
}
/* Sits on the photo rather than beside it, so a row of six does not become a
   row of six alternating pictures and crosses. */
.purchase-photo .remove-photo {
  position: absolute; top: 2px; right: 2px;
  width: 20px; height: 20px; line-height: 18px; text-align: center;
  border-radius: 50%; background: rgba(0, 0, 0, 0.55); color: #fff;
  font-size: 0.9rem; opacity: 0; transition: opacity 0.12s ease;
}
.purchase-photo:hover .remove-photo,
.purchase-photo .remove-photo:focus { opacity: 1; }

/* A card that opens: the contact form on a customer page, and anywhere else a
   thing done once a year should not be the loudest item on screen. */
details.card > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: 0.5rem;
}
details.card > summary::-webkit-details-marker { display: none; }
details.card > summary::before {
  content: '▸'; color: var(--muted); font-size: 0.9rem; transition: transform 0.15s ease;
}
details.card[open] > summary::before { transform: rotate(90deg); }
.inline-head { display: inline; margin: 0; font-size: 1.05rem; }
/* A field that should run the full width of a two-column grid -- notes, mostly. */
.grid-wide { grid-column: 1 / -1; }

/* ---- A photo slot showing its own photograph ---- */

/* A filled slot IS the picture: "Item ✓" told you a photograph existed but not
   what of, and the whole reason for taking one is that "Chain" does not
   identify a piece. The slot goes square and a little larger than the count
   preview, because here it is the piece being identified rather than a number
   on a scale display being glanced at. */
.photo-slot.has-thumb { position: relative; }
.photo-slot.has-thumb .photo-thumb { margin-top: 0; }
.photo-slot.has-thumb .photo-thumb img {
  width: 3.25rem; height: 3.25rem; border-radius: 5px;
}

/* Re-shooting stays available now that the + button is gone, but stays out of
   the way: the common act is looking, not replacing. Always visible on touch,
   where there is no hover to reveal it. */
.photo-replace {
  position: absolute; top: -6px; right: -6px;
  width: 18px; height: 18px; line-height: 16px; text-align: center;
  border-radius: 50%; border: 1px solid var(--line);
  background: var(--surface); color: var(--muted);
  font-size: 0.72rem; padding: 0; cursor: pointer;
  opacity: 0; transition: opacity 0.12s ease;
}
.photo-slot.has-thumb:hover .photo-replace,
.photo-replace:focus-visible { opacity: 1; }
.photo-replace:hover { color: var(--ink); border-color: var(--gold); }
@media (hover: none) {
  .photo-replace { opacity: 1; }
}

/* ---- Sortable column headings ---- */

/* The heading IS the control, so the whole cell is the target rather than the
   few pixels of text -- these get clicked on a shop screen, often in a hurry. */
.col-sort { padding: 0 !important; }
.col-sort > a {
  display: block; padding: 0.5rem 0.6rem;
  color: inherit; text-decoration: none; white-space: nowrap;
}
.col-sort.num > a { text-align: right; }
.col-sort > a:hover { color: var(--ink); background: var(--bg); }
/* The column in force reads darker and heavier, so which order you are looking
   at is answered by the table itself rather than by remembering the click. */
.col-sort.is-sorted > a { color: var(--ink); font-weight: 700; }

/* The holding bucket, standing in for a case on the daily check.
   Marked apart from the display-unit chips because it is a different KIND of
   number: a record of what was set aside, not a reading of a photograph, and
   nobody is expected to go and count it. */
.review-chip.is-layaway {
  border-style: dashed; text-decoration: none;
  color: var(--muted); cursor: pointer;
}
.review-chip.is-layaway:hover { color: var(--ink); border-color: var(--gold); }

/* Layaway pieces on the daily check. Not an alert -- stock in the bucket is
   where it should be -- so it reads as quiet supporting information rather
   than borrowing the colour that means "short". */
.is-layaway-count { color: var(--muted); font-variant-numeric: tabular-nums; }

/* A zero that is still worth opening.
   The dash stays a dash to read -- nothing moved, and the eye should skip it
   when scanning the column -- but it is a link, because "nothing was bought" is
   the moment you most want to see what DID happen in that category. Underline
   only on hover, so a table of dashes does not turn into a wall of links. */
.zero-link { color: var(--muted); text-decoration: none; }
.zero-link:hover { color: var(--gold); text-decoration: underline; }

/* The kind filter on the drill-down. Counts live on the chip because a chip
   reading "Bought 0" IS the answer -- clicking through to an empty page to
   learn it is the dead end this page exists to remove. */
.kind-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.kind-chips .review-chip.is-empty { opacity: 0.55; }

/* Missing-item candidates.
   Styled as a list of leads to walk, not a table of findings: each one is a
   thing somebody goes and looks for, and the answer they come back with is
   entered on the same row. Resolved rows stay visible and go quiet rather than
   disappearing -- "somebody looked and it is fine" is worth more than silence. */
.missing-actions { margin: 0.75rem 0; }
.missing-status:empty { display: none; }
.missing-run { border-top: 1px solid var(--line); padding: 0.75rem 0; }
.missing-run:last-child { padding-bottom: 0; }
.missing-run.is-failed { opacity: 0.75; }
.missing-run-head { display: flex; justify-content: space-between; gap: 1rem; align-items: baseline; flex-wrap: wrap; }
.missing-error { color: var(--danger); margin: 0.35rem 0 0; }

.missing-list { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.missing-candidate { border-left: 3px solid var(--gold); padding: 0.5rem 0 0.5rem 0.75rem; margin-bottom: 0.6rem; }
.missing-candidate.is-resolved { border-left-color: var(--line); }
.missing-candidate-head { display: flex; gap: 0.5rem; align-items: baseline; flex-wrap: wrap; }
.missing-desc { font-weight: 600; }
/* The model's argument for why nothing explains this piece. Set apart because
   it is the part a reader should push back on, not skim past. */
.missing-reasoning { font-style: italic; }

.missing-answer { display: flex; gap: 0.4rem; margin-top: 0.4rem; flex-wrap: wrap; }
.missing-answer .missing-note { flex: 1 1 16rem; min-width: 0; }
.missing-resolved { margin: 0.4rem 0 0; display: flex; gap: 0.4rem; align-items: baseline; flex-wrap: wrap; }
.missing-resolved.is-confirmed { color: var(--danger); }

/* Entries keyed in after a check but dated before it -- what the frozen
   expected column could not have known. Set apart and below the comparison
   rather than woven into it: these explain a difference, they do not correct
   one, and a reader who takes them for part of the check has misread the whole
   point of freezing the figures. */
.late-entries { margin-top: 1.25rem; border-top: 1px dashed var(--line); padding-top: 0.75rem; }
.late-entries .data-table { font-size: 0.9em; }

/* A reclassification whose karat has been restated. Not an error -- the weight
   balances and it saves -- but it changes recorded pure gold, so it reads as
   something to look at rather than something to ignore. */
.reclassify-balance.is-restated {
  border-color: var(--gold); background: rgba(201, 162, 39, 0.07); color: var(--ink);
}
.reclassify-balance.is-restated p { margin: 0 0 0.35rem; font-weight: 600; }
.reclassify-balance.is-restated ul { margin: 0; padding-left: 1.1rem; }

/* An empty thumbnail that will take a photo. Same footprint as a real one, so
   a row does not shift when one is added. */
.tx-thumb.is-addable {
  cursor: pointer; color: var(--muted); font-size: 1rem; line-height: 1;
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.tx-thumb.is-addable:hover { color: var(--ink); border-color: var(--gold); border-style: solid; }
/* Edit and Delete sit together without colliding. */
.row-actions { white-space: nowrap; }
.row-actions .link-button { margin-right: 0.5rem; }

/* The store-wide verdict on a variance trace. Stated before any per-case
   reasoning, because "is anything missing" and "which case" are different
   questions and the second used to answer for both. */
.verdict {
  margin: 0.75rem 0 0; padding: 0.6rem 0.8rem;
  border-left: 3px solid var(--line); border-radius: 4px; background: var(--bg);
}
.verdict.is-short { border-left-color: var(--danger, #b3261e); }
.verdict.is-over { border-left-color: var(--gold); }
.verdict.is-ok { border-left-color: #2e7d32; }

/* Date-range presets on the flow drill-down. Same chip shape as the kind
   filter directly below them, because they are the same kind of control --
   one narrows WHEN, the other narrows WHAT. */
.range-presets { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.6rem; }

/* The reports landing page. Cards rather than a list, because each report needs
   a sentence explaining what question it answers -- the names alone do not
   separate "Daily sales" from "Item sales" for somebody who has not used both. */
.report-list { display: grid; gap: 0.75rem; }
@media (min-width: 60rem) { .report-list { grid-template-columns: 1fr 1fr; } }
.report-card h2 { margin: 0 0 0.25rem; font-size: 1.05rem; }
.report-card p { margin: 0 0 0.6rem; }
.filter-card { display: block; }
.filter-card .range-presets { margin: 0 0 0.6rem; }

/* Condensed reading mode for the report tables.
   (Full width used to live here too; it is now what every page does -- see the
   page shell above.) */

/* Condensed: same information, less air. Aimed at the case where a range
   covers hundreds of lines and the job is to find one -- every row that fits
   on screen is a row you do not have to scroll to. Padding and thumbnails
   shrink; nothing is hidden, because a mode that drops columns is a different
   report wearing the same name. */
.page-dense .card { padding: 0.7rem 0.8rem; }
.page-dense .data-table { font-size: 0.82rem; }
.page-dense .data-table th,
.page-dense .data-table td { padding: 0.2rem 0.4rem; }
.page-dense .tx-thumb { width: 28px; height: 28px; }
.page-dense .page-head { margin-bottom: 0.6rem; }
.page-dense .tag { padding: 0 0.3em; font-size: 0.7rem; }
.page-dense .filter-card { padding: 0.5rem 0.8rem; }

/* A figure in the "what the records say" chain, expanding into its own rows.
   A <details>, so no JavaScript is involved and the rows are simply already
   there. The marker is the only affordance -- the chain is read as a sentence,
   and underlining every number in it turns a narrative into a menu. */
.chain-detail > summary {
  cursor: pointer; list-style-position: outside;
}
.chain-detail > summary:hover { color: var(--gold); }
.chain-detail > summary::-webkit-details-marker { color: var(--muted); }
.chain-count { margin-left: 0.35rem; }
/* Indented and boxed off, so an open row table reads as belonging to the line
   above it rather than as the next item in the chain. */
.chain-detail[open] > .table-scroll {
  margin: 0.4rem 0 0.8rem; border-left: 2px solid var(--line); padding-left: 0.6rem;
}
.chain-detail .data-table { font-size: 0.82rem; }
.chain-detail .tx-thumb { width: 32px; height: 32px; }
