/* ==========================================================================
   Shopboard design system — Boro (spec §9)
   Plain CSS, no build step. Classes are the API between templates and CSS.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Fonts — vendored variable woff2 (spec §9 adaptation 2, no CDN)
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 100%;
  font-display: swap;
  src: url('fonts/bricolage-grotesque-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;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 100%;
  font-display: swap;
  src: url('fonts/bricolage-grotesque-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: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url('fonts/hanken-grotesk-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;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url('fonts/hanken-grotesk-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: 'Hanken Grotesk';
  font-style: italic;
  font-weight: 400 600;
  font-display: swap;
  src: url('fonts/hanken-grotesk-italic-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;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: italic;
  font-weight: 400 600;
  font-display: swap;
  src: url('fonts/hanken-grotesk-italic-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: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/jetbrains-mono-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;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/jetbrains-mono-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;
}

/* --------------------------------------------------------------------------
   Tokens (spec §9.1, verbatim)
   -------------------------------------------------------------------------- */
:root {
  /* Indigo (aizome) — brand, dark to light */
  --indigo-900:#11192e; --indigo-800:#18233f; --indigo-700:#1f2d50;
  --indigo-600:#2a3d6a; --indigo-500:#38528c; --indigo-400:#5876a8;
  --indigo-300:#8aa4c6; --indigo-200:#b9cbe1; --indigo-100:#dde7f1;
  --indigo-50:#eef3f8;

  /* Thread — pale blues (text/lines on dark indigo) */
  --thread-500:#7fa6cf; --thread-400:#a3c2e0; --thread-300:#c2d8ec;
  --thread-200:#dbe8f4;

  /* Canvas — warm cotton/paper neutrals (the light-mode world) */
  --canvas-900:#b6ac96; --canvas-700:#d8cfba; --canvas-500:#e7dfcd;
  --canvas-300:#f1ead9; --canvas-100:#f7f2e7; --canvas-50:#faf7ef;

  /* Persimmon — the one warm accent (~5% of any screen, max) */
  --persimmon-700:#9c4a22; --persimmon-500:#c2632f;
  --persimmon-300:#dd9468; --persimmon-100:#f0d9c8;

  /* Status hues */
  --madder-600:#9b3b2f; --madder-100:#efd3cd;   /* danger / late */
  --sage-600:#4f6f5e;   --sage-100:#d6e0d8;     /* success / done */
  --ochre-600:#b07d2c;  --ochre-100:#f0e2c2;    /* warning / at-risk */

  /* Semantic surfaces (office = warm paper, never pure white) */
  --surface-page:var(--canvas-300);
  --surface-raised:var(--canvas-100);
  --surface-card:var(--canvas-50);
  --surface-sunken:var(--canvas-500);
  --surface-inverse:var(--indigo-800);
  --surface-overlay:color-mix(in srgb, var(--indigo-900) 55%, transparent);

  /* Text */
  --text-strong:var(--indigo-900);
  --text-body:var(--indigo-800);
  --text-muted:color-mix(in srgb, var(--indigo-800) 62%, var(--canvas-500));
  --text-subtle:color-mix(in srgb, var(--indigo-800) 42%, var(--canvas-500));
  --text-on-indigo:var(--canvas-100);
  --text-on-indigo-muted:var(--indigo-200);
  --text-accent:var(--persimmon-700);
  --text-link:var(--indigo-600);

  /* Borders */
  --border-soft:color-mix(in srgb, var(--canvas-900) 38%, transparent);
  --border-strong:color-mix(in srgb, var(--indigo-800) 28%, transparent);

  /* Interactive */
  --brand:var(--indigo-700); --brand-hover:var(--indigo-800);
  --brand-active:var(--indigo-900);
  --accent:var(--persimmon-500); --accent-hover:var(--persimmon-700);
  --focus-ring:color-mix(in srgb, var(--indigo-500) 55%, transparent);
  --ring:0 0 0 3px var(--focus-ring);

  /* Status aliases */
  --success:var(--sage-600);  --success-soft:var(--sage-100);
  --warning:var(--ochre-600); --warning-soft:var(--ochre-100);
  --danger:var(--madder-600); --danger-soft:var(--madder-100);
  --info:var(--indigo-500);   --info-soft:var(--indigo-100);

  /* Type */
  --font-display:'Bricolage Grotesque','Hanken Grotesk',system-ui,sans-serif;
  --font-body:'Hanken Grotesk',system-ui,sans-serif;
  --font-mono:'JetBrains Mono',ui-monospace,monospace;
  --fs-h1:clamp(2.1rem,1.5rem + 2.6vw,3.25rem);
  --fs-h2:clamp(1.65rem,1.3rem + 1.6vw,2.4rem);
  --fs-h3:1.5rem; --fs-h4:1.25rem; --fs-body:1rem;
  --fs-small:.875rem; --fs-caption:.75rem; --fs-eyebrow:.8125rem;
  --tracking-eyebrow:.16em;

  /* Spacing — 4px grid */
  --space-1:.25rem; --space-2:.5rem; --space-3:.75rem; --space-4:1rem;
  --space-5:1.5rem; --space-6:2rem; --space-7:2.5rem; --space-8:3rem;
  --container:1200px;

  /* Radii — generous, soft, hand-cut; never sharp */
  --radius-xs:4px; --radius-sm:8px; --radius-md:12px;
  --radius-lg:18px;  /* default card */
  --radius-xl:26px; --radius-2xl:36px; --radius-pill:999px;

  /* Shadows — low, warm, indigo-tinted; never pure black, never glassy */
  --shadow-sm:0 1px 2px color-mix(in srgb,var(--indigo-900) 7%,transparent),
              0 2px 6px color-mix(in srgb,var(--indigo-900) 6%,transparent);
  --shadow-md:0 2px 4px color-mix(in srgb,var(--indigo-900) 6%,transparent),
              0 8px 20px color-mix(in srgb,var(--indigo-900) 10%,transparent);
  --shadow-lg:0 4px 8px color-mix(in srgb,var(--indigo-900) 7%,transparent),
              0 18px 40px color-mix(in srgb,var(--indigo-900) 14%,transparent);

  /* Motion — calm, settled; no bounce, no spring, no overshoot */
  --ease-out:cubic-bezier(.22,.61,.36,1);
  --dur-fast:120ms; --dur-base:200ms; --dur-slow:320ms;
}

/* --------------------------------------------------------------------------
   Element defaults (spec §9.2)
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* The hidden attribute must always win (SB-26): any component class that
   sets `display:` otherwise silently defeats it — this shipped a permanently
   visible "Working offline" banner and queued chip on the floor. !important
   is deliberate: hidden-ness is state, not styling; toggle visibility by
   toggling the attribute, never by out-specifying this rule. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  /* SB-79: reserve the vertical scrollbar's gutter permanently so centered
     content doesn't shift horizontally when navigating between pages that
     scroll and pages that don't. No-op on overlay-scrollbar platforms. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--surface-page);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  color: var(--text-body);
  line-height: 1.5;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-strong);
  line-height: 1.15;
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-h4); }

p { margin: 0 0 var(--space-4); }

code, kbd, samp, pre, .mono {
  font-family: var(--font-mono);
}

/* Eyebrow/kicker — section overlines (reports, traveler) */
.eyebrow {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: var(--text-accent);
  margin-bottom: var(--space-2);
}

a {
  color: var(--text-link);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

::selection {
  background: var(--indigo-900);
  color: var(--thread-400);
}

:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}

img, svg { max-width: 100%; }

hr {
  border: 0;
  border-top: 1px solid var(--border-soft);
  margin: var(--space-5) 0;
}

/* Icon helper — sprite usage: <svg class="icon"><use href="/static/icons.svg#play"/></svg> */
.icon {
  width: 1.25em;
  height: 1.25em;
  flex: none;
  vertical-align: -0.25em;
}

/* Layout helper */
.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-6);
}

/* --------------------------------------------------------------------------
   Card (spec §9.3) — the canonical surface
   -------------------------------------------------------------------------- */
.card {
  background: var(--surface-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.card--interactive:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--border-strong);
}

/* --------------------------------------------------------------------------
   Buttons (spec §9.3)
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 10px 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 500;
  line-height: 1.2;
  color: var(--text-body);
  background: transparent;
  cursor: pointer;
  text-decoration: none;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: .5;
  box-shadow: none;
  cursor: not-allowed;
  transform: none;
}

.btn--primary {
  background: var(--brand);
  color: var(--text-on-indigo);
}
.btn--primary:hover { background: var(--brand-hover); }
.btn--primary:active { background: var(--brand-active); }

.btn--secondary {
  background: var(--surface-card);
  border-color: var(--border-strong);
  color: var(--text-body);
}
.btn--secondary:hover { background: var(--surface-raised); }

.btn--ghost {
  background: transparent;
  color: var(--text-body);
}
.btn--ghost:hover { background: var(--canvas-500); }

/* Persimmon is scarce by design: at most one .btn--accent per screen */
.btn--accent {
  background: var(--accent);
  color: #fff;
}
.btn--accent:hover { background: var(--accent-hover); }

.btn--danger {
  background: var(--danger);
  color: #fff;
}
.btn--danger:hover { background: color-mix(in srgb, var(--danger) 85%, var(--indigo-900)); }

.btn--sm { padding: 7px 12px; font-size: var(--fs-small); }
.btn--lg { padding: 14px 24px; }
.btn--block { display: flex; width: 100%; }

/* --------------------------------------------------------------------------
   Badges (spec §9.3) — status pills
   -------------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-caption);
  font-weight: 600;
  line-height: 1.5;
  background: var(--surface-sunken);
  color: var(--text-muted);
}
.badge__dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: currentColor;
  flex: none;
}
.badge--neutral { background: var(--surface-sunken); color: var(--text-muted); }
.badge--info    { background: var(--indigo-100);    color: var(--indigo-600); }
.badge--success { background: var(--sage-100);      color: var(--sage-600); }
.badge--warning { background: var(--ochre-100);     color: var(--ochre-600); }
.badge--danger  { background: var(--madder-100);    color: var(--madder-600); }
.badge--accent  { background: var(--persimmon-100); color: var(--persimmon-700); }

/* --------------------------------------------------------------------------
   Forms (spec §9.3)
   -------------------------------------------------------------------------- */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-4);
}
.field > label {
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--text-strong);
}
.field__help {
  font-size: var(--fs-caption);
  color: var(--text-muted);
}

input, select, textarea {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  color: var(--text-body);
  background: var(--surface-raised);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 10px 12px;
}
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: none;
  border-color: var(--indigo-500);
  box-shadow: var(--ring);
}
textarea { resize: vertical; }

/* Machine-ish values render mono: qty, hours, PINs, timestamps */
input[type="number"], input[inputmode="numeric"], input[inputmode="decimal"],
.input--mono {
  font-family: var(--font-mono);
}

.field--error input, .field--error select, .field--error textarea {
  border-color: var(--danger);
}
.field__error {
  font-size: var(--fs-caption);
  color: var(--madder-600);
}

/* --------------------------------------------------------------------------
   Tabs (spec §9.3) — underline style
   -------------------------------------------------------------------------- */
.tabs {
  display: flex;
  gap: var(--space-2);
  border-bottom: 1px solid var(--border-soft);
}
.tabs .tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 10px 14px;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 500;
  color: var(--text-muted);
  cursor: pointer;
  margin-bottom: -1px;
  text-decoration: none;
}
.tabs .tab:hover { background: var(--canvas-500); color: var(--text-body); }
.tabs .tab.is-active {
  color: var(--text-strong);
  border-bottom-color: var(--brand);
}

/* --------------------------------------------------------------------------
   Toasts (spec §9.3) — htmx OOB target #toasts
   -------------------------------------------------------------------------- */
#toasts {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  z-index: 100;
  max-width: 360px;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.toast .icon { width: 18px; height: 18px; margin-top: 2px; }
.toast__title { font-weight: 600; color: var(--text-strong); }
.toast__body { font-size: var(--fs-small); color: var(--text-muted); }
.toast--success .icon { color: var(--success); }
.toast--warning .icon { color: var(--warning); }
.toast--danger  .icon { color: var(--danger); }
.toast--info    .icon { color: var(--info); }

/* --------------------------------------------------------------------------
   Tables (spec §9.3) — reports, admin lists, event history
   -------------------------------------------------------------------------- */
.table-card {
  overflow: hidden; /* clip corners */
  padding: 0;
}
table {
  width: 100%;
  border-collapse: collapse;
}
th, td {
  padding: 10px 14px;
  text-align: left;
}
thead th {
  background: var(--surface-sunken);
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--text-muted);
}
tbody tr + tr td { border-top: 1px solid var(--border-soft); }
tbody tr:hover td { background: var(--surface-raised); }
th.num, td.num {
  text-align: right;
  font-family: var(--font-mono);
}
td .delta-over  { color: var(--madder-600); font-family: var(--font-mono); }
td .delta-under { color: var(--sage-600);   font-family: var(--font-mono); }

/* --------------------------------------------------------------------------
   Modal (spec §9.3)
   -------------------------------------------------------------------------- */
.modal {
  position: fixed;
  inset: 0;
  background: var(--surface-overlay);
  display: grid;
  place-items: center;
  padding: var(--space-4);
  z-index: 90;
}
/* hidden-vs-display is handled globally by the `[hidden]` rule in the
   element-defaults layer (SB-26). */
.modal__dialog {
  background: var(--surface-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 520px;
  padding: var(--space-5);
}

/* --------------------------------------------------------------------------
   Auth (spec §1, §9.3 form classes) — login card
   -------------------------------------------------------------------------- */
.auth-card {
  max-width: 360px;
  margin: var(--space-8) auto;
  padding: var(--space-6);
}

/* --------------------------------------------------------------------------
   Shell bits (spec §9.4 / §9.6)
   -------------------------------------------------------------------------- */
.topbar {
  background: var(--surface-raised);
  border-bottom: 1px solid var(--border-soft);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-4);
}
.topbar .nav-link {
  border-bottom: 2px solid transparent;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}
.topbar .nav-link.is-active {
  color: var(--text-strong);
  border-bottom-color: var(--brand);
}

/* Quiet SSE reconnect chip (no red alarms unless down > 30s) */
.sse-status {
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  color: var(--text-subtle);
}

/* --------------------------------------------------------------------------
   Floor theme (spec §9.5) — the one dark surface world
   -------------------------------------------------------------------------- */
body.floor {
  --surface-page:var(--indigo-900);   --surface-raised:var(--indigo-800);
  --surface-card:var(--indigo-800);   --surface-sunken:var(--indigo-900);
  --text-strong:var(--canvas-100);    --text-body:var(--thread-200);
  --text-muted:var(--thread-500);     --text-subtle:var(--indigo-300);
  --border-soft:color-mix(in srgb, var(--thread-300) 22%, transparent);
  --border-strong:color-mix(in srgb, var(--thread-300) 40%, transparent);
  --text-link:var(--thread-300);
  --focus-ring:color-mix(in srgb, var(--thread-300) 55%, transparent);
}

/* Touch targets: every actionable element at least 64px tall */
body.floor .btn,
body.floor input,
body.floor select {
  min-height: 64px;
}
body.floor .btn {
  border-radius: var(--radius-xl);
  font-size: var(--fs-h4);
  font-weight: 600;
}
body.floor .btn--ghost:hover { background: var(--indigo-800); }

/* Floor action buttons — darken one step on press, 1px settle, no ripples */
.btn--start { background: var(--sage-600);   color: var(--canvas-100); }
.btn--stop  { background: var(--ochre-600);  color: var(--canvas-100); }
.btn--done  { background: var(--indigo-600); color: var(--canvas-100); }
.btn--scrap { background: var(--madder-600); color: var(--canvas-100); }
.btn--start:hover, .btn--start:active { background: color-mix(in srgb, var(--sage-600) 82%, var(--indigo-900)); }
.btn--stop:hover,  .btn--stop:active  { background: color-mix(in srgb, var(--ochre-600) 82%, var(--indigo-900)); }
.btn--done:hover,  .btn--done:active  { background: var(--indigo-700); }
.btn--scrap:hover, .btn--scrap:active { background: color-mix(in srgb, var(--madder-600) 82%, var(--indigo-900)); }

/* PIN pad — 3x4 grid of 84px keys */
.pinpad {
  display: grid;
  grid-template-columns: repeat(3, 84px);
  gap: var(--space-3);
  justify-content: center;
  border: 2px solid transparent;
  border-radius: var(--radius-lg);
  padding: var(--space-3);
}
.pinpad__key {
  width: 84px;
  height: 84px;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--indigo-800);
  color: var(--canvas-100);
  font-family: var(--font-mono);
  font-size: var(--fs-h3);
  cursor: pointer;
}
.pinpad__key:active {
  background: var(--indigo-900);
  transform: translateY(1px);
}
.pinpad.is-error { border-color: var(--madder-600); }

.pinpad-dots {
  display: flex;
  gap: var(--space-3);
  justify-content: center;
  padding: var(--space-4) 0;
}
.pinpad-dots span {
  width: 12px;
  height: 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--thread-500);
}
.pinpad-dots span.is-filled {
  background: var(--thread-300);
  border-color: var(--thread-300);
}

/* Qty entry tints */
.pinpad--good .pinpad__key { background: color-mix(in srgb, var(--sage-600) 30%, var(--indigo-800)); }
.pinpad--scrap .pinpad__key { background: color-mix(in srgb, var(--madder-600) 30%, var(--indigo-800)); }

/* Offline indicator — queued-count chip, top-right */
.queued-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  background: var(--indigo-800);
  color: var(--thread-300);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
}

/* Offline > 2 min banner: "Working offline" strip */
.offline-banner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--ochre-600);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--ochre-600) 14%, var(--indigo-900));
  color: var(--ochre-100);
  font-weight: 500;
}

.badge--rejected { background: var(--madder-600); color: var(--canvas-100); }

/* SB-89: floor-queue.js's wireRejectedChip() adds role="button" to
   #rejected-chip once it's tappable — bump it to the spec §9.5 64px
   touch-target floor without changing the plain badge look used elsewhere. */
#rejected-chip[role="button"] {
  cursor: pointer;
  min-height: 64px;
  padding-inline: var(--space-4);
}
#rejected-chip[role="button"]:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Locked overlay (SB-18): floor-queue.js injects this over an in-progress
   job_list/job_step screen when a drain discovers the station's token was
   revoked mid-session — same full-bleed-scrim treatment as .modal, but
   always on top and with nothing to dismiss back to (there's no cancel). */
.locked-overlay {
  position: fixed;
  inset: 0;
  background: var(--surface-overlay);
  display: grid;
  place-items: center;
  padding: var(--space-4);
  z-index: 100;
}
.locked-overlay__panel {
  background: var(--surface-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  max-width: 420px;
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  text-align: center;
}

/* Rejected-entries overlay (SB-89): same injected full-bleed-scrim pattern
   as .locked-overlay above, but wider/taller (a scrollable list of entries,
   not a single fixed message) and dismissable. Built entirely in JS —
   floor-queue.js's openRejectedOverlay() — no template changes. */
.rejected-overlay {
  position: fixed;
  inset: 0;
  background: var(--surface-overlay);
  display: grid;
  place-items: center;
  padding: var(--space-4);
  z-index: 100;
}
.rejected-overlay__panel {
  background: var(--surface-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 640px;
  max-height: 90vh;
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.rejected-overlay__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex: none;
}
.rejected-overlay__header h2 {
  margin: 0;
  font-size: var(--fs-h4);
  color: var(--text-strong);
}
.rejected-overlay__list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.rejected-overlay__dismiss-all { flex: none; }

.rejected-entry {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
}
.rejected-entry__main {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.rejected-entry__kind {
  font-family: var(--font-mono);
  font-size: var(--fs-h4);
  font-weight: 600;
  color: var(--text-strong);
}
.rejected-entry__time { color: var(--text-muted); font-size: var(--fs-small); }
.rejected-entry__qty  { color: var(--text-body); font-size: var(--fs-small); }
.rejected-entry__reason {
  color: var(--madder-600);
  font-weight: 600;
}
.rejected-entry__detail { color: var(--text-subtle); font-size: var(--fs-small); }
.rejected-entry__dismiss { flex: none; }

/* Floor job list / step card type scale */
.floor-job-number {
  font-family: var(--font-mono);
  font-size: var(--fs-h2);
  font-weight: 600;
  color: var(--canvas-100);
}
.floor-job-desc {
  font-size: var(--fs-h4);
  color: var(--thread-200);
}
.floor-job-step {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-small);
  color: var(--thread-500);
}
.floor-step-name {
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  color: var(--canvas-100);
}
.floor-timer {
  font-family: var(--font-mono);
  /* SB-90 round 2 (review blocker): the old `clamp(3rem, 2rem + 6vw, 6rem)`
     scaled only with viewport WIDTH, so on a wide-but-short viewport
     (1024x600) it rendered at ~93px regardless of how little vertical room
     was actually available — measured directly, that single line ate ~140px
     of a ~306px content box, and remains the single biggest contributor to
     the round-2 clipping regression at that viewport (see
     templates/floor/timer_card.html.j2's SB-90 comment). `min()` against a
     `vh`-based term makes the font shrink on a short viewport regardless of
     width: at 1024x600 (9vh=54px) it comes in well under the old 93px,
     freeing enough height that Start/Stop/Done stay inside the card even
     with the timer still visible (panel closed). At height 800 the vh term
     still wins (9vh=72px vs the width clamp's 96px ceiling — a deliberate
     25% trim, still "huge" per §9.5); only at heights above ~1067px
     (e.g. portrait 1280) does the width clamp's 96px take over. */
  font-size: min(clamp(3rem, 2rem + 6vw, 6rem), 9vh);
  color: var(--thread-300);
  font-variant-numeric: tabular-nums;
}
.floor-qty-summary {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}
/* SB-90 round 2 (review blocker): with the complete panel open, the card's
   full content (name/badge + huge timer + qty summary + Start/Stop/Done +
   the panel's own qty fields/Confirm button) genuinely does not fit the
   available box on shorter viewports (1280x800, 1024x600) -- no amount of
   flex/gap tuning changes that, the content is just too tall. The timer's
   huge clamp()'d font is the single biggest line item, so while the panel
   is open, hide the timer and the qty-summary badges (both are redundant
   with the panel: it already shows/edits the good/scrap counts and the
   clock isn't the operator's focus mid-entry) so Start/Stop/Done and the
   panel's Confirm button + inputs stay fully reachable instead of getting
   clipped by the card's/root's overflow:hidden. `:has()` is fine here --
   floor tablets run current Chrome (spec §1). */
.card:has(#complete-panel:not([hidden])) .floor-timer,
.card:has(#complete-panel:not([hidden])) .floor-qty-summary {
  display: none;
}
.floor-qty-fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* SB-90 round 2 (review blocker), continued: hiding the timer/qty-summary
   above is enough at 1280x800 and 800x1280, but measured directly, 1024x600
   still overflows by ~164px with the complete panel open even with those
   hidden -- name/badge + Start/Stop/Done + two STACKED 64px touch-target
   qty fields + Confirm add up to more than that viewport has, and neither
   the header nor the qty fields can shrink below their content (spec §9.5's
   64px floor applies to the inputs; the step name isn't a button, but it
   isn't optional content until it collides with a control that needs to be
   tappable). Only on a viewport this short: drop the header (the operator
   already navigated here on purpose, so the step name is the least
   essential line once they're mid-entry) and lay the two qty fields out as
   a row instead of stacked (recovers one field's worth of height). The
   780px breakpoint (review should-fix, lead-applied) covers the whole
   measured overflow band: at 700px the panel-open card overflowed up
   through ~768px-tall viewports (720p tablets included — Confirm sat
   off-viewport at 1366x701/1280x720), so the query must fire everywhere
   below the ticket's 800px floor, not just at 1024x600. Both AC heights
   (800, 1280) sit above it, so nothing in scope changes. */
@media (max-height: 780px) {
  .card:has(#complete-panel:not([hidden])) .floor-card-header {
    display: none;
  }
  .card:has(#complete-panel:not([hidden])) .floor-qty-fields {
    flex-direction: row;
  }
  .card:has(#complete-panel:not([hidden])) .floor-qty-fields .field {
    flex: 1;
    min-width: 0;
  }
}
.floor-outside-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--fs-body);
  color: var(--thread-200);
}

/* --------------------------------------------------------------------------
   Motion (spec §9.6) — cloth settles, nothing bounces.
   All animation and transitions live behind prefers-reduced-motion.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .card--interactive {
    transition: box-shadow var(--dur-base) var(--ease-out),
                border-color var(--dur-base) var(--ease-out);
  }
  .btn {
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                opacity var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
  }
  .tabs .tab {
    transition: color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
  }
  input, select, textarea {
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
  }
  tbody td { transition: background-color var(--dur-fast) var(--ease-out); }
  .pinpad { transition: border-color var(--dur-fast) var(--ease-out); }
  .pinpad__key { transition: background-color var(--dur-fast) var(--ease-out); }

  /* htmx settle: swapped cards flash --info-soft, easing back over --dur-slow */
  .card, tbody tr {
    transition-property: background-color, box-shadow, border-color;
    transition-duration: var(--dur-slow);
    transition-timing-function: var(--ease-out);
  }
  .htmx-settling { background-color: var(--info-soft); }

  /* Loading: dim the submit, spin its icon (the only continuous animation) */
  form.htmx-request button[type="submit"],
  form.htmx-request .btn {
    opacity: .7;
    pointer-events: none;
  }
  form.htmx-request button[type="submit"] .icon,
  form.htmx-request .btn .icon {
    animation: spin 1s linear infinite;
  }
  @keyframes spin { to { transform: rotate(360deg); } }

  /* Toast: slide-up + fade */
  .toast { animation: rise-in var(--dur-slow) var(--ease-out); }

  /* Modal: fade + 4px rise */
  .modal { animation: fade-in var(--dur-base) var(--ease-out); }
  .modal__dialog { animation: rise-in-4 var(--dur-base) var(--ease-out); }

  @keyframes fade-in { from { opacity: 0; } }
  @keyframes rise-in { from { opacity: 0; transform: translateY(8px); } }
  @keyframes rise-in-4 { from { opacity: 0; transform: translateY(4px); } }
}

/* Drag states (SortableJS) — usable without motion, so outside the media query */
.sortable-ghost { opacity: .55; }
.drop-target { background: var(--surface-sunken); }

/* --------------------------------------------------------------------------
   Printable machine-QR sheet (SB-52, admin_machine_qr.html.j2). One QR +
   machine name per section; grid on screen, one-per-line stack on paper.
   -------------------------------------------------------------------------- */
.qr-sheet {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-5);
}
.qr-sheet__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-5);
  text-align: center;
}
.qr-sheet__code svg {
  width: 100%;
  height: auto;
  max-width: 220px;
}
.qr-sheet__label {
  font-size: var(--fs-h4);
  font-weight: 700;
  color: var(--text-strong);
}

@media print {
  .no-print { display: none !important; }
  .topbar { display: none; }
  .qr-sheet {
    grid-template-columns: repeat(2, 1fr);
  }
  .qr-sheet__item {
    break-inside: avoid;
    page-break-inside: avoid;
    box-shadow: none;
    border: 1px solid #000;
  }
}

/* --------------------------------------------------------------------------
   TOTP setup QR + fallback values (SB-76/SB-77, totp_settings.html.j2).
   Same sizing approach as .qr-sheet__code above (single code, not a grid) —
   centered, capped so it never dominates the settings card at tablet width.
   -------------------------------------------------------------------------- */
.totp-setup__qr {
  display: flex;
  justify-content: center;
  margin: var(--space-4) 0;
}
.totp-setup__qr svg {
  width: 100%;
  height: auto;
  max-width: 220px;
}
/* The base32 secret and otpauth URI fallbacks: a sunken block so they read
   as data, not prose, with room to wrap in full rather than crop/scroll. */
.totp-setup__value {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-sunken);
  border-radius: var(--radius-md);
  overflow-wrap: anywhere;
}
.totp-setup__value:not(.totp-setup__value--uri) {
  letter-spacing: var(--tracking-eyebrow);
}

/* --------------------------------------------------------------------------
   Board (SB-78, board.html.j2) — full window width, bounded internal scroll.

   Problem this fixes: `.container` caps at --container (1200px), wasting
   width on wide displays; and the old board wrapper was overflow-x:auto
   with no height bound, so it grew to full content height and the
   DOCUMENT scrolled vertically — the board's own horizontal scrollbar
   ended up below the fold, under the fold of a page scroll the user had
   to trigger first.

   Fix is a flex-column height chain, scoped to `body.board-page` so every
   other page keeps its normal centered .container:
     body (100dvh, overflow:hidden — the page itself never scrolls)
       -> .topbar (flex:none, natural height)
       -> main.container (flex:1, min-height:0, max-width:none — full
          width minus the usual --space-6 gutters; still a flex column)
            -> eyebrow + h1 (flex:none, natural height — wrapping the
               heading just takes more of main's height, nothing hardcoded)
            -> .board-scroll (flex:1, min-width:0, min-height:0 — THE
               scroll area; a flex item defaults to min-{width,height}:auto,
               which refuses to shrink below its own content's intrinsic
               size — without the override here .board-scroll pushes its
               parent (and, on the width axis, the whole document) wider/
               taller instead of clipping and scrolling internally. Needed
               on BOTH axes here because .board-scroll is a column-flex
               item (vertical) that is itself a row-flex container whose
               children (fixed-basis columns) give it real intrinsic width)
   100dvh (not 100vh) so mobile browser chrome showing/hiding doesn't
   momentarily starve or overflow the scroll area.
   -------------------------------------------------------------------------- */
body.board-page {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
}
body.board-page .topbar {
  flex: none;
}
body.board-page main.container {
  max-width: none;
  /* `.container`'s own `margin-inline: auto` (needed elsewhere to center a
     max-width-capped box) has to go too, and not just because it's now
     meaningless without a max-width: an AUTO margin on a flex item's cross
     axis (here, horizontal — body's flex-direction is column) makes that
     item size to fit-content instead of stretching to fill the container,
     even with align-items' default `stretch` in play and min-width:0 set.
     Confirmed empirically: with margin-inline:auto still present, `main`
     rendered at its content's intrinsic (max-content) width — the same
     document-overflow bug this ticket exists to fix — even after max-width
     and min-width were already correctly overridden below. */
  margin-inline: 0;
  flex: 1 1 auto;
  /* Same min-{width,height}:auto flex trap as `.board-scroll` below, one
     level up the chain: `main` is itself a flex item of `body` (column
     direction), so it ALSO won't shrink narrower than its content's
     intrinsic width without this. */
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
body.board-page .board-scroll {
  flex: 1 1 auto;
  /* min-height:0 is the height half of this fix (see block comment above);
     min-width:0 is the width half and is just as required: .board-scroll
     is ALSO a flex item (of main's column layout), and its own children
     (the fixed-basis columns) give it a large intrinsic content width.
     Flex items default to min-width:auto, which refuses to shrink below
     that intrinsic width — without this override .board-scroll pushes
     `main` (and the whole document) wider instead of clipping and
     scrolling internally, reproducing the exact document-horizontal-
     overflow bug this ticket exists to fix. */
  min-width: 0;
  min-height: 0;
  overflow: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding-bottom: var(--space-4);
}
