/* ============================================================================
   theme.css — the only stylesheet this application writes.

   It loads AFTER vendor/bootstrap/bootstrap.min.css and does exactly three
   jobs, in this order:

     1. Re-points Bootstrap's own CSS variables at the Jewellery Store palette and
        type scale. Bootstrap components are not re-implemented here — they
        are re-coloured. A rule that restyles a Bootstrap component must set
        that component's `--bs-*` variables, never override its declarations.
     2. Draws the application shell (sidebar, topbar, scrim). Bootstrap has no
        sidebar layout, so this is genuinely ours.
     3. Adds the five components Bootstrap does not have and this business
        needs: .stat, .thumb, .dropzone, .scan-bar, .action-bar — plus the
        money/SKU typography helpers.

   Rules:
     - Hex values live in the `:root` block at the top and nowhere else.
     - No `!important`, no ID selectors.
     - If Bootstrap already ships a class for it, use Bootstrap's class in the
       view instead of writing a rule here.
   ========================================================================= */

/* ── Public Sans, self-hosted. One variable file per subset, ~53 KB total,
      nothing fetched from a CDN. Chosen over the alternatives because it has
      a real `tnum` table — money columns actually align. ─────────────────── */

@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/public-sans-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
    U+1EA0-1EF9, U+20AB;
}

@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/public-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/public-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================================================
   1. Palette and scale — the single source of colour truth
   ========================================================================= */

:root {
  color-scheme: light;

  /* Sky: primary and action. White on #0284C7 measures 4.10:1, under the
     4.5:1 floor for a 13px button label, so a solid fill starts at sky-600. */
  --tv-sky-700: #075985;
  --tv-sky-600: #0369a1;
  --tv-sky-500: #0284c7;
  --tv-sky-100: #e0f2fe;
  --tv-sky-50: #f0f9ff;

  /* Ink: cool neutrals tuned to the sky hue. */
  --tv-ink-900: #0f172a;
  --tv-ink-700: #334155;
  --tv-ink-500: #64748b;
  --tv-ink-300: #cbd5e1;
  --tv-line: #e2e8f0;
  --tv-surface: #fff;
  --tv-canvas: #f6f9fc;
  --tv-on-sky: #fff;

  /* Semantic. Deliberately not blue: an alert must never read as an action.
     The -strong variants are the text weight for a label on its own wash;
     the -subtle variants are that wash. Both halves of a pair live here so a
     chip and an inline banner can never drift to two different greens. */
  --tv-success: #059669;
  --tv-success-strong: #047857;
  --tv-success-subtle: #ecfdf5;
  --tv-warning: #d97706;
  --tv-warning-strong: #b45309;
  --tv-warning-subtle: #fffbeb;
  --tv-danger: #dc2626;
  --tv-danger-strong: #b91c1c;
  --tv-danger-subtle: #fef2f2;

  /* One non-semantic accent, used only for the "mới" stock class so that new
     stock is never mistaken for healthy stock. */
  --tv-new: #7c3aed;
  --tv-new-subtle: #f5f3ff;

  /* Layout metrics. */
  --tv-sidebar-width: 248px;
  --tv-sidebar-collapsed: 64px;
  --tv-topbar-height: 56px;
  --tv-page-max: 1180px;
  --tv-thumb-ratio: 1;

  /* ── Density ────────────────────────────────────────────────────────────
     These four tokens are the whole responsive system. Every control, row and
     cell sizes itself from them, so the desktop and the phone are one design
     at two densities rather than two designs.

     This block is the desktop default: a mouse lands within a pixel or two, so
     36px rows are comfortable and a stock list shows ~40 lines instead of ~25.
     On a screen the owner reads for an hour, that is the difference between
     scanning and scrolling. Still clear of the 24px WCAG 2.5.8 floor. */
  --tv-tap-target: 36px;
  --tv-control-pad-y: 0.375rem;
  --tv-cell-pad-y: 0.4375rem;
  --tv-gap: 0.75rem;

  --tv-motion-fast: 120ms;
  --tv-motion-slide: 200ms;

  /* ── Bootstrap variable overrides ─────────────────────────────────────
     Everything below re-points a Bootstrap variable. Nothing invents a new
     colour; each one resolves to a --tv-* value above. */

  --bs-primary: var(--tv-sky-600);
  --bs-primary-rgb: 3, 105, 161;
  --bs-success: var(--tv-success);
  --bs-warning: var(--tv-warning);
  --bs-danger: var(--tv-danger);

  --bs-body-font-family: "Public Sans", system-ui, -apple-system, "Segoe UI", roboto, sans-serif;
  --bs-body-font-size: 0.9375rem;
  --bs-body-color: var(--tv-ink-700);
  --bs-body-bg: var(--tv-canvas);
  --bs-emphasis-color: var(--tv-ink-900);
  --bs-secondary-color: var(--tv-ink-500);
  --bs-heading-color: var(--tv-ink-900);

  --bs-border-color: var(--tv-line);
  --bs-border-radius: 0.5rem;
  --bs-border-radius-sm: 0.375rem;
  --bs-border-radius-lg: 0.875rem;

  --bs-link-color: var(--tv-sky-600);
  --bs-link-hover-color: var(--tv-sky-700);
  --bs-link-color-rgb: 3, 105, 161;

  --bs-font-monospace: ui-monospace, "SF Mono", "Cascadia Mono", consolas, monospace;
}

/* Vietnamese stacked diacritics need vertical room — nothing may go tighter
   than this, which is why the line-height is set once here and not per rule. */
body {
  line-height: 1.5;
}

h1,
h2,
h3,
h4 {
  line-height: 1.35;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --tv-motion-fast: 0ms;
    --tv-motion-slide: 0ms;
  }
}

/* ── Touch density ────────────────────────────────────────────────────────
   A fingertip contact patch is about 9 mm; below 44px it starts landing on
   the neighbouring row. The owner packs orders one-handed on a phone while
   holding the parcel, so on touch the whole scale grows — not just buttons.

   Keyed on `pointer: coarse` rather than width alone, because a 1280px tablet
   is still a finger and a 900px desktop window is still a mouse. The width
   clause is the fallback for browsers that do not report pointer type.

   ⚠ Redefining the four tokens here is the entire implementation. Nothing
   below this line re-states a size per breakpoint, and nothing should: a
   component that hardcodes its own height stops responding to density and
   only shows up as a mis-tap on somebody's phone. */
@media (pointer: coarse), (max-width: 991.98px) {
  :root {
    --tv-tap-target: 44px;
    --tv-control-pad-y: 0.5625rem;
    --tv-cell-pad-y: 0.6875rem;
    --tv-gap: 1rem;
  }
}

/* A management screen is scanned, not filled in — forty rows of reference data
   read at a glance. `.dense` lowers the same four tokens for one subtree, so
   everything inside stays one design at a tighter setting rather than becoming
   a second set of classes.

   ⚠ Gated on `pointer: fine`, not layered under the coarse block above: a
   class beats `:root`, so an ungated `.dense` would keep 30px rows under a
   thumb — the mis-tap this whole system exists to prevent. A browser that does
   not report pointer type simply stays roomy, which is the safe direction. */
@media (pointer: fine) {
  .dense {
    --tv-tap-target: 30px;
    --tv-control-pad-y: 0.1875rem;
    --tv-cell-pad-y: 0.25rem;
    --tv-gap: 0.5rem;
  }
}


/* ============================================================================
   2. Bootstrap component re-colouring

   Each rule sets that component's own --bs-* variables. None of them override
   a Bootstrap declaration, which is why none of them need !important.
   ========================================================================= */

.btn-primary {
  --bs-btn-bg: var(--tv-sky-600);
  --bs-btn-border-color: var(--tv-sky-600);
  --bs-btn-hover-bg: var(--tv-sky-700);
  --bs-btn-hover-border-color: var(--tv-sky-700);
  --bs-btn-active-bg: var(--tv-sky-700);
  --bs-btn-active-border-color: var(--tv-sky-700);
  --bs-btn-disabled-bg: var(--tv-sky-600);
  --bs-btn-disabled-border-color: var(--tv-sky-600);
  --bs-btn-focus-shadow-rgb: 2, 132, 199;
}

.btn-outline-primary {
  --bs-btn-color: var(--tv-sky-700);
  --bs-btn-border-color: var(--tv-sky-600);
  --bs-btn-hover-bg: var(--tv-sky-600);
  --bs-btn-hover-border-color: var(--tv-sky-600);
  --bs-btn-active-bg: var(--tv-sky-700);
  --bs-btn-active-border-color: var(--tv-sky-700);
  --bs-btn-focus-shadow-rgb: 2, 132, 199;
}

.btn-outline-secondary {
  --bs-btn-color: var(--tv-ink-700);
  --bs-btn-border-color: var(--tv-ink-300);
  --bs-btn-hover-color: var(--tv-ink-900);
  --bs-btn-hover-bg: var(--tv-sky-50);
  --bs-btn-hover-border-color: var(--tv-ink-500);
  --bs-btn-active-color: var(--tv-ink-900);
  --bs-btn-active-bg: var(--tv-sky-100);
  --bs-btn-active-border-color: var(--tv-ink-500);
}

/* The quiet button: used for row actions and back navigation, where a bordered
   control would out-shout the data it sits next to. */
.btn-light {
  --bs-btn-color: var(--tv-ink-700);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: var(--tv-ink-900);
  --bs-btn-hover-bg: var(--tv-sky-50);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-bg: var(--tv-sky-100);
  --bs-btn-active-border-color: transparent;
}

/* Every interactive control sizes from the density tokens, so it is compact
   under a mouse and clears the 44px touch floor under a finger. */
.btn {
  --bs-btn-padding-y: var(--tv-control-pad-y);
  --bs-btn-padding-x: 0.875rem;

  min-height: var(--tv-tap-target);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

/* .btn-sm is the density-exempt escape hatch — a row action inside a table
   cell, where a full-height button would double the row. It still grows on
   touch, just from a lower floor. */
.btn-sm {
  --bs-btn-padding-y: 0.25rem;
  --bs-btn-padding-x: 0.625rem;

  min-height: calc(var(--tv-tap-target) - 8px);
}

/* A square, icon-only control: the ↑/↓ reorder buttons on management tables,
   and anything else where the glyph is the label.

   ⚠ These were nine copies of `style="width:28px; height:28px"` in the views.
   28px is under even the 24px AA floor once you account for the gap between
   two of them stacked, and they are the primary interaction on those tables —
   on a phone the owner was hitting "move down" trying to hit "move up". */
.btn-icon {
  --bs-btn-padding-x: 0;
  --bs-btn-padding-y: 0;

  width: var(--tv-tap-target);
  min-height: var(--tv-tap-target);
  flex-shrink: 0;
}

/* ── Pick row: the packing screen's one-handed tick ────────────────────────
   The owner works down this list holding a parcel in the other hand, so the
   whole row is the target, not just the circle. The circle is only the state
   readout. Both size from the density token. ──────────────────────────────*/

.pick-row {
  display: flex;
  align-items: center;
  gap: var(--tv-gap);
  padding: var(--tv-cell-pad-y) 1rem;
  border-bottom: 1px solid var(--tv-line);
  cursor: pointer;
  user-select: none;
  transition: background-color var(--tv-motion-fast) ease-out;
}

.pick-row__check {
  display: flex;
  width: var(--tv-tap-target);
  min-height: var(--tv-tap-target);
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--tv-line);
  border-radius: 50%;
  background: var(--tv-surface);
  color: transparent;
  transition: all var(--tv-motion-fast) ease-out;
}

.pick-row__body {
  min-width: 0;
  flex: 1;
}

.card {
  --bs-card-border-color: var(--tv-line);
  --bs-card-bg: var(--tv-surface);
  --bs-card-cap-bg: var(--tv-surface);
  --bs-card-cap-padding-y: var(--tv-gap);
  --bs-card-spacer-y: var(--tv-gap);
}

.card-header {
  color: var(--tv-ink-900);
  font-weight: 500;
}

/* Tables carry the density difference more than anything else: this is where
   a compact desktop row turns 25 visible lines into 40, and where a touch row
   stops the owner opening the wrong order. */
.table {
  --bs-table-border-color: var(--tv-line);
  --bs-table-hover-bg: var(--tv-sky-50);
  --bs-table-color: var(--tv-ink-700);
  --bs-table-cell-padding-y: var(--tv-cell-pad-y);
}

/* Table head labels at --tv-ink-500 measure 4.46:1 — just under the floor —
   so they sit one step darker. */
.table > thead {
  color: var(--tv-ink-700);
  font-size: 0.8125rem;
  font-weight: 500;
}

.form-control,
.form-select {
  --bs-border-color: var(--tv-ink-500);

  min-height: var(--tv-tap-target);
  padding-top: var(--tv-control-pad-y);
  padding-bottom: var(--tv-control-pad-y);
}

/* A checkbox or radio is the smallest thing on the screen and the easiest to
   miss. It grows with density rather than staying at Bootstrap's 1em, because
   an inline stocktake row is ticked with a thumb. */
.form-check-input {
  width: calc(var(--tv-tap-target) * 0.45);
  height: calc(var(--tv-tap-target) * 0.45);
  margin-top: 0.15em;
}

/* Bootstrap's default input border (#dee2e6) is 1.48:1 against white, far
   under the 3:1 WCAG asks of a control boundary. --tv-ink-500 is 4.76:1. */
.form-control:focus,
.form-select:focus {
  border-color: var(--tv-sky-500);
  box-shadow: 0 0 0 0.25rem var(--tv-sky-100);
}

.form-control-sm {
  min-height: calc(var(--tv-tap-target) - 8px);
}

.form-label {
  color: var(--tv-ink-700);
  font-size: 0.8125rem;
  font-weight: 500;
  margin-bottom: 0.25rem;
}

.form-text {
  color: var(--tv-ink-500);
  font-size: 0.8125rem;
}

.nav-tabs {
  --bs-nav-tabs-border-color: var(--tv-line);
  --bs-nav-tabs-link-active-color: var(--tv-sky-700);
  --bs-nav-tabs-link-active-border-color: var(--tv-line) var(--tv-line) var(--tv-surface);
  --bs-nav-link-color: var(--tv-ink-500);
  --bs-nav-link-hover-color: var(--tv-ink-900);
}

.page-link {
  --bs-pagination-color: var(--tv-ink-700);
  --bs-pagination-hover-color: var(--tv-ink-900);
  --bs-pagination-hover-bg: var(--tv-sky-50);
  --bs-pagination-active-bg: var(--tv-sky-600);
  --bs-pagination-active-border-color: var(--tv-sky-600);
  --bs-pagination-focus-box-shadow: 0 0 0 0.25rem var(--tv-sky-100);
}

.alert-success {
  --bs-alert-color: var(--tv-success-strong);
}

.alert-warning {
  --bs-alert-color: var(--tv-warning-strong);
}

.alert-danger {
  --bs-alert-color: var(--tv-danger-strong);
}

/* ============================================================================
   3. The application shell — Bootstrap ships no sidebar layout
   ========================================================================= */

.shell {
  min-height: 100vh;
}

.sidebar {
  position: fixed;
  z-index: 40;
  inset: 0 auto 0 0;
  display: flex;
  width: var(--tv-sidebar-width);
  flex-direction: column;
  border-right: 1px solid var(--tv-line);
  background: var(--tv-surface);
}

.sidebar__brand {
  display: flex;
  height: var(--tv-topbar-height);
  flex-shrink: 0;
  align-items: center;
  gap: 0.5rem;
  padding: 0 1rem;
  border-bottom: 1px solid var(--tv-line);
  color: var(--tv-ink-900);
  font-weight: 500;
  text-decoration: none;
}

.sidebar__brand-mark {
  display: grid;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: var(--bs-border-radius-sm);
  background: var(--tv-sky-500);
  color: var(--tv-on-sky);
  font-size: 0.8125rem;
  place-items: center;
}

.sidebar__nav {
  overflow-y: auto;
  flex: 1;
  padding: 0.75rem 0.75rem 1.5rem;
}

.sidebar__section {
  margin-top: 1rem;
}

.sidebar__section:first-child {
  margin-top: 0;
}

/* Letter-spaced rather than uppercased: text-transform on Vietnamese collapses
   the stacked diacritics. */
.sidebar__section-label {
  padding: 0 0.75rem 0.5rem;
  color: var(--tv-ink-500);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.06em;
}

.sidebar__item {
  position: relative;
  display: flex;
  min-height: var(--tv-tap-target);
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--bs-border-radius-sm);
  color: var(--tv-ink-700);
  font-size: 0.8125rem;
  text-decoration: none;
  transition: background-color var(--tv-motion-fast) ease-out;
}

.sidebar__item:hover {
  background: var(--tv-sky-50);
  color: var(--tv-ink-900);
}

/* Active state is derived from the request path by the layout partial, never
   declared per page. */
.sidebar__item--active {
  background: var(--tv-sky-100);
  color: var(--tv-sky-700);
  font-weight: 500;
}

.sidebar__item--active::before {
  position: absolute;
  top: 50%;
  left: 0;
  width: 3px;
  height: 20px;
  border-radius: 0 var(--bs-border-radius-sm) var(--bs-border-radius-sm) 0;
  background: var(--tv-sky-500);
  content: "";
  transform: translateY(-50%);
}

.sidebar__label {
  overflow: hidden;
  flex: 1;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar__footer {
  flex-shrink: 0;
  padding: 0.75rem;
  border-top: 1px solid var(--tv-line);
}

.sidebar__collapse {
  display: flex;
  width: 100%;
  min-height: var(--tv-tap-target);
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border: 0;
  border-radius: var(--bs-border-radius-sm);
  background: none;
  color: var(--tv-ink-500);
  font-size: 0.8125rem;
}

.sidebar__collapse:hover {
  background: var(--tv-sky-50);
  color: var(--tv-ink-900);
}

/* Collapsed (icons only). The state rides on .shell and is remembered in a
   cookie, so the server renders the right width and nothing snaps on load. */
.shell--collapsed .sidebar {
  width: var(--tv-sidebar-collapsed);
}

.shell--collapsed .sidebar__label,
.shell--collapsed .sidebar__section-label,
.shell--collapsed .sidebar__brand-text,
.shell--collapsed .sidebar__collapse-text {
  display: none;
}

.shell--collapsed .sidebar__item,
.shell--collapsed .sidebar__collapse,
.shell--collapsed .sidebar__brand {
  justify-content: center;
  padding-right: 0.5rem;
  padding-left: 0.5rem;
}

.shell--collapsed .sidebar__section {
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--tv-line);
}

.shell--collapsed .sidebar__section:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.shell--collapsed .main {
  margin-left: var(--tv-sidebar-collapsed);
}

.topbar {
  position: sticky;
  z-index: 30;
  top: 0;
  display: flex;
  height: var(--tv-topbar-height);
  align-items: center;
  gap: 0.75rem;
  padding: 0 1rem;
  border-bottom: 1px solid var(--tv-line);
  background: var(--tv-surface);
}

.topbar__toggle {
  display: none; /* mobile only */
  width: var(--tv-tap-target);
  height: var(--tv-tap-target);
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--bs-border-radius-sm);
  background: none;
  color: var(--tv-ink-700);
}

.topbar__title {
  overflow: hidden;
  flex: 1;
  color: var(--tv-ink-900);
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.main {
  min-height: 100vh;
  margin-left: var(--tv-sidebar-width);
  background: var(--tv-canvas);
}

.main__inner {
  max-width: var(--tv-page-max);
  padding: 1.5rem 1rem 3rem;
  margin: 0 auto;
}

/* The vertical rhythm every screen composes into. One gap, set once. */
.page {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.scrim {
  position: fixed;
  z-index: 35;
  inset: 0;
  border: 0;
  background: var(--tv-ink-900);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--tv-motion-slide) ease-out;
}

@media (max-width: 767px) {
  .sidebar {
    box-shadow: 0 8px 24px rgb(15 23 42 / 12%);
    transform: translateX(-100%);
    transition: transform var(--tv-motion-slide) ease-out;
  }

  .shell--nav-open .sidebar {
    transform: translateX(0);
  }

  .shell--nav-open .scrim {
    opacity: 0.4;
    pointer-events: auto;
  }

  .main,
  .shell--collapsed .main {
    margin-left: 0;
  }

  .topbar__toggle {
    display: flex;
  }

  .main__inner {
    padding: 1rem 0.75rem 2rem;
  }

  /* Collapsing is a desktop affordance; on mobile the drawer is open or shut. */
  .sidebar__footer {
    display: none;
  }
}

/* Below 380px nothing may overflow horizontally. */
@media (max-width: 380px) {
  .sidebar {
    width: calc(100vw - 3rem);
  }

  .main__inner {
    padding-right: 0.5rem;
    padding-left: 0.5rem;
  }
}

/* ============================================================================
   4. Components Bootstrap does not ship
   ========================================================================= */

/* ── Icons: one SVG sprite, stroked, sized in two steps ────────────────── */

.icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  fill: none;
  stroke: currentcolor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.5;
}

.icon-sm {
  width: 16px;
  height: 16px;
}

/* ── Money and codes: tabular figures so columns align ─────────────────── */

.money {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.money-sm {
  font-size: 0.8125rem;
}

.money-lg {
  font-size: 1.125rem;
  font-weight: 600;
}

.money-hero {
  font-size: 1.75rem;
  font-weight: 600;
  color: var(--tv-ink-900);
}

/* The đ / ¥ demoted into its own span so the unit never competes with the
   figure. Emitted by the $money / $cny view helpers, never hand-written. */
.money__unit {
  margin-left: 0.125rem;
  color: var(--tv-ink-500);
  font-size: 0.8125rem;
}

/* A bare quantity. Tabular so a column of them lines up. */
.num {
  font-variant-numeric: tabular-nums;
}

/* A SKU or batch code. Monospace so a mistyped character is visible. */
.code {
  font-family: var(--bs-font-monospace);
  font-size: 0.8125rem;
  color: var(--tv-ink-900);
}

/* ── Stat tile: the number-first card used on the dashboard and headers ── */

.stat {
  display: flex;
  height: 100%;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1rem;
  border: 1px solid var(--tv-line);
  border-radius: var(--bs-border-radius);
  background: var(--tv-surface);
  color: inherit;
  text-decoration: none;
}

.stat__label {
  color: var(--tv-ink-500);
  font-size: 0.8125rem;
}

.stat__value {
  color: var(--tv-ink-900);
  font-size: 1.375rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.stat__meta {
  color: var(--tv-ink-500);
  font-size: 0.8125rem;
}

/* ── Thumb: an aspect-locked image well that never reflows on load ─────── */

.thumb {
  position: relative;
  overflow: hidden;
  display: block;
  width: 100%;
  border: 1px solid var(--tv-line);
  border-radius: var(--bs-border-radius-sm);
  aspect-ratio: var(--tv-thumb-ratio);
  background: var(--tv-sky-50);
}

.thumb__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.thumb--empty {
  display: grid;
  color: var(--tv-ink-300);
  place-items: center;
}

/* ── Dropzone: the biggest tap target on any screen, because on a phone it is
      hit while holding the parcel the photo is of. ──────────────────────── */

.dropzone {
  display: flex;
  width: 100%;
  min-height: calc(var(--tv-tap-target) * 1.75);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1rem;
  border: 1px dashed var(--tv-ink-500);
  border-radius: var(--bs-border-radius);
  background: var(--tv-sky-50);
  color: var(--tv-ink-700);
}

.dropzone:hover,
.dropzone--active {
  border-color: var(--tv-sky-500);
  background: var(--tv-sky-100);
}

/* ── Scan bar: the sticky barcode field on the packing screen ──────────── */

.scan-bar {
  position: sticky;
  z-index: 20;
  top: var(--tv-topbar-height);
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem;
  border: 1px solid var(--tv-line);
  border-radius: var(--bs-border-radius);
  background: var(--tv-surface);
}

/* ── Action bar: the fixed bottom bar for one-handed phone work ────────── */

.action-bar {
  position: fixed;
  z-index: 25;
  inset: auto 0 0;
  padding: var(--tv-gap) 1rem calc(var(--tv-gap) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--tv-line);
  background: var(--tv-surface);
}

.action-bar__inner {
  max-width: var(--tv-page-max);
  margin: 0 auto;
}

/* ── Stock chip: on-hand at a glance, coloured by stock class.
      The word is always shown alongside the colour — a helper may be red-green
      colourblind, and the owner hands them the phone. ─────────────────────── */

.chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.25rem;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

.chip-healthy {
  background: var(--tv-success-subtle);
  color: var(--tv-success-strong);
}

.chip-hot,
.chip-risk,
.chip-stockout_risk {
  background: var(--tv-danger-subtle);
  color: var(--tv-danger-strong);
}

.chip-overstock,
.chip-dead {
  background: var(--tv-warning-subtle);
  color: var(--tv-warning-strong);
}

.chip-new {
  background: var(--tv-new-subtle);
  color: var(--tv-new);
}

.chip-unit {
  color: var(--tv-ink-500);
  font-size: 0.6875rem;
}

.chip-label {
  font-size: 0.6875rem;
}

/* ── Grid-of-cards helper. Bootstrap's .row-cols-* needs every child wrapped
      in a .col; this is the same idea for a list of equal cards, without the
      wrapper element. ─────────────────────────────────────────────────────── */

.tile-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.tile-grid-wide {
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.tile-grid-narrow {
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}

/* ── Focus: one visible ring, everywhere, never removed ────────────────── */

:focus-visible {
  outline: 2px solid var(--tv-sky-500);
  outline-offset: 2px;
}

/* Skip link, visible only once focused. */
.skip-link {
  position: absolute;
  z-index: 60;
  top: 0.5rem;
  left: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--bs-border-radius-sm);
  background: var(--tv-surface);
  transform: translateY(-200%);
}

.skip-link:focus {
  transform: translateY(0);
}
