/* =============================================================================
   VaporCraft — shared design system
   =============================================================================
   One file, imported by every page. Two themes: LOUNGE (default, dark) and
   APOTHECARY (toggle, light). Set data-theme="apothecary" on <html>.

   TABLE OF CONTENTS
     1.  Tokens (both themes)
     2.  Reset + base
     3.  Vapor field (ambient background)
     4.  Layout shell — sidebar, topbar, content
     5.  Mobile bottom navigation
     6.  Topbar mobile controls
     7.  Typography helpers
     8.  Buttons
     9.  Forms
    10.  Cards, glass panels, stat tiles
    11.  Badges / tags / pills / role chip
    12.  Tables
    13.  Safety banners + safety panel
    14.  Modal / dialog
    15.  Toast notifications
    16.  Utility classes
    17.  Print styles
    18.  Accessibility (focus, motion, scrollbars)
    19.  Address lookup combobox
    20.  Ratio split control
    21.  PIN pad
    22.  Charts (SVG)
    23.  Invoice / receipt document
   ============================================================================= */

/* ---------- 1. Tokens -------------------------------------------------------- */

:root {
  --bg: #0a0c16;
  --bg-deep: #05060c;
  --surface: #12162a;
  --surface-2: #191f38;
  --surface-glass: rgba(18, 22, 42, .64);
  --line: rgba(167, 139, 250, .16);
  --line-strong: rgba(167, 139, 250, .34);
  --ink: #eef0f8;
  --muted: #8b93ac;
  --faint: #5c637c;

  --mist: #4dd8c4;
  --mist-ink: #06211d;
  --mist-wash: rgba(77, 216, 196, .14);
  --ember: #a78bfa;
  --ember-ink: #1c1330;
  --ember-wash: rgba(167, 139, 250, .14);

  --good: #34d399;
  --good-wash: rgba(52, 211, 153, .14);
  --warn: #f5b942;
  --warn-wash: rgba(245, 185, 66, .14);
  --bad: #f2607a;
  --bad-wash: rgba(242, 96, 122, .14);

  --shadow-1: 0 1px 2px rgba(0,0,0,.5);
  --shadow-2: 0 12px 32px rgba(3,4,10,.45);
  --shadow-glow: 0 0 40px rgba(77,216,196,.14);

  --radius-lg: 20px;
  --radius: 14px;
  --radius-sm: 10px;
  --radius-pill: 999px;

  --font-display: 'Space Grotesk','Inter',sans-serif;
  --font-body: 'Inter',system-ui,sans-serif;
  --font-mono: 'JetBrains Mono',ui-monospace,monospace;

  /* Mobile bottom nav height — used in .main padding-bottom. Includes
     safe-area-inset-bottom so notched iPhones get proper clearance. */
  --nav-h: 66px;

  color-scheme: dark;
}

[data-theme="apothecary"] {
  --bg: #f1efe7;
  --bg-deep: #e7e3d7;
  --surface: #ffffff;
  --surface-2: #f8f6f0;
  --surface-glass: rgba(255,255,255,.68);
  --line: rgba(14,128,116,.16);
  --line-strong: rgba(14,128,116,.34);
  --ink: #1b1e27;
  --muted: #5b6072;
  --faint: #8a8f9e;

  --mist: #0e8074;
  --mist-ink: #ffffff;
  --mist-wash: rgba(14,128,116,.10);
  --ember: #7c3aed;
  --ember-ink: #ffffff;
  --ember-wash: rgba(124,58,237,.10);

  --good: #0d8a5f;
  --good-wash: rgba(13,138,95,.10);
  --warn: #a6690f;
  --warn-wash: rgba(166,105,15,.12);
  --bad: #c22a4e;
  --bad-wash: rgba(194,42,78,.10);

  --shadow-1: 0 1px 2px rgba(30,25,10,.06);
  --shadow-2: 0 12px 28px rgba(30,25,10,.08);
  --shadow-glow: 0 0 40px rgba(14,128,116,.10);

  color-scheme: light;
}

/* ---------- 2. Reset + base -------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 8px;
  line-height: 1.2;
}
h1 { font-size: clamp(1.5rem, 3.5vw, 2.1rem); }
h2 { font-size: clamp(1.1rem, 2.2vw, 1.4rem); }
h3 { font-size: 1rem; }
p { margin: 0 0 12px; color: var(--muted); }
a { color: var(--mist); }
small { color: var(--faint); font-size: .8rem; }
code, .mono { font-family: var(--font-mono); }
hr { border: none; border-top: 1px solid var(--line); margin: 20px 0; }

/* ---------- 3. Vapor field (ambient background) ------------------------------ */

.vapor-field {
  position: fixed; inset: 0; z-index: 0;
  overflow: hidden; pointer-events: none;
  background: var(--bg);
}
.vapor-field::before, .vapor-field::after {
  content: '';
  position: absolute;
  width: 56vw; height: 56vw;
  min-width: 380px; min-height: 380px;
  border-radius: 50%;
  filter: blur(90px);
  opacity: .28;
  will-change: transform;
}
.vapor-field::before {
  background: var(--mist);
  top: -18%; left: -12%;
  animation: vc-drift-a 42s ease-in-out infinite alternate;
}
.vapor-field::after {
  background: var(--ember);
  bottom: -22%; right: -14%;
  animation: vc-drift-b 48s ease-in-out infinite alternate;
}
[data-theme="apothecary"] .vapor-field::before,
[data-theme="apothecary"] .vapor-field::after { opacity: .13; }

@keyframes vc-drift-a {
  from { transform: translate(0,0) scale(1); }
  to   { transform: translate(6vw,5vh) scale(1.14); }
}
@keyframes vc-drift-b {
  from { transform: translate(0,0) scale(1); }
  to   { transform: translate(-5vw,-6vh) scale(1.1); }
}

/* ---------- 4. Layout shell -------------------------------------------------- */

.app-shell { position: relative; z-index: 1; min-height: 100dvh; display: flex; }

/* DESKTOP sidebar */
.sidebar {
  width: 224px;
  flex-shrink: 0;
  padding: 20px 12px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  border-right: 1px solid var(--line);
  /* Sticky so it stays on screen while content scrolls */
  position: sticky;
  top: 0;
  height: 100dvh;
  overflow-y: auto;
}

.brand {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px 20px;
  text-decoration: none; color: var(--ink);
}
.brand-mark {
  width: 32px; height: 32px;
  border-radius: var(--radius-sm);
  background: linear-gradient(135deg, var(--mist), var(--ember));
  flex-shrink: 0;
  box-shadow: var(--shadow-glow);
}
.brand-name { font-family: var(--font-display); font-weight: 700; font-size: 1rem; letter-spacing: -0.02em; }

.nav-link {
  display: flex; align-items: center; gap: 11px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  color: var(--muted);
  text-decoration: none;
  font-size: .9rem;
  font-weight: 500;
  transition: background .15s, color .15s;
  position: relative;
  min-height: 44px; /* WCAG touch target */
}
.nav-link svg { width: 18px; height: 18px; flex-shrink: 0; transition: transform .15s ease; }
.nav-link:hover { background: var(--surface-2); color: var(--ink); }
.nav-link:hover svg { transform: translateX(1px); }
.nav-link.active {
  background: linear-gradient(135deg, var(--mist-wash), var(--ember-wash));
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.nav-link .badge { margin-left: auto; }

/* Small uppercase divider above a group of related sidebar links (e.g.
   Sales / Stock / Admin) — the sidebar was previously one flat list with
   no grouping at all. */
.nav-section-label {
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--faint);
  padding: 12px 12px 4px;
}
.nav-section-label:first-child { padding-top: 0; }

.nav-foot {
  margin-top: auto;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* Role chip in the sidebar footer */
.role-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px;
  border-radius: var(--radius-pill);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .02em;
  margin: 4px 2px 8px;
  border: 1px solid var(--line);
  color: var(--muted);
}
.role-chip.admin { border-color: var(--mist); color: var(--mist); background: var(--mist-wash); }
.role-chip.staff { border-color: var(--ember); color: var(--ember); background: var(--ember-wash); }
.role-chip-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* Main content area */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  min-height: 60px;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 12px;
  padding: 10px 20px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-glass);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  position: sticky; top: 0; z-index: 20;
}
.topbar-left { display: flex; align-items: center; gap: 10px; }
.topbar-title { font-family: var(--font-display); font-weight: 600; font-size: 1rem; }
.topbar-right { display: flex; align-items: center; gap: 8px; }
.content { flex: 1; padding: 20px; max-width: 1400px; width: 100%; margin: 0 auto; }

@media (min-width: 1600px) {
  .sidebar { width: 256px; padding: 28px 16px; }
  .content { padding: 30px; max-width: 1560px; }
}

/* ---------- 5. Mobile bottom navigation -------------------------------------- */

@media (max-width: 860px) {
  /* Hide the desktop sidebar entirely on mobile */
  .sidebar { display: none; }

  /* Bottom nav bar */
  .mobile-nav {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    background: var(--surface-glass);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-top: 1px solid var(--line);
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
  }
  .mobile-nav-item {
    flex: 1;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px;
    padding: 6px 2px;
    text-decoration: none;
    color: var(--muted);
    font-size: .6rem;
    font-weight: 600;
    letter-spacing: .01em;
    border-radius: var(--radius-sm);
    transition: color .15s, background .15s;
    min-height: 52px;
    position: relative;
  }
  .mobile-nav-item svg { width: 20px; height: 20px; }
  .mobile-nav-item.active { color: var(--mist); }
  .mobile-nav-item .badge {
    position: absolute;
    top: 2px; right: calc(50% - 18px);
    min-width: 16px; height: 16px;
    padding: 0 4px;
    font-size: .6rem;
    line-height: 16px;
  }
  .mobile-nav-item .badge-dot {
    position: absolute;
    top: 6px; right: calc(50% - 16px);
    width: 8px; height: 8px;
    min-width: 0; padding: 0;
    border-radius: 50%;
    background: var(--warn);
  }

  /* Offset content so it's not hidden behind the nav bar */
  .main { padding-bottom: calc(var(--nav-h) + env(safe-area-inset-bottom)); }
  .content { padding: 14px; }
  .topbar { padding: 10px 14px; min-height: 54px; }

  /* Toast stack clears the bottom nav */
  .toast-stack { bottom: calc(var(--nav-h) + 12px + env(safe-area-inset-bottom)); }
}

/* Desktop: hide mobile nav */
@media (min-width: 861px) {
  .mobile-nav { display: none; }
}

/* ---------- 6. More sheet (mobile secondary nav — Ingredients, Calculator,
   Reports, Reset PIN, theme, sign out; see nav.js's openMoreSheet) --------- */

.more-sheet-links { display: flex; flex-direction: column; gap: 2px; }
.more-sheet-link {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 10px;
  border-radius: var(--radius-sm);
  color: var(--ink);
  text-decoration: none;
  font-size: .92rem; font-weight: 500;
  background: none; border: none;
  width: 100%; text-align: left; cursor: pointer;
  min-height: 48px;
}
.more-sheet-link:hover { background: var(--surface-2); }
.more-sheet-link svg { width: 19px; height: 19px; flex-shrink: 0; color: var(--muted); }
.more-sheet-divider { height: 1px; background: var(--line); margin: 8px 4px; }

/* ---------- 7. Typography helpers ------------------------------------------- */

.eyebrow {
  font-family: var(--font-mono);
  font-size: .7rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--faint);
}
.figure { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.figure-lg { font-family: var(--font-mono); font-size: clamp(1.5rem, 4vw, 2.2rem); font-weight: 500; }

/* Receipt-style label/value row — a running total or spec sheet (the
   Calculator's recipe breakdown, Billing's cart summary). Promoted here
   from a copy that used to live only in calculator.html's own <style>
   block: billing.html used the same .result-row/.label classes but never
   defined them anywhere, so its Subtotal/Discount/Total rows were
   rendering with no layout at all (no left/right alignment, no divider
   lines) — one shared definition so every page's totals render the same
   way instead of quietly depending on whichever page happened to carry
   its own copy. .result-total is new: a stronger closing row for whatever
   number the section is building up to (Billing's grand Total), visually
   promoted above its own Subtotal/Discount rows rather than matching
   their weight. */
.result-row {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 10px 0;
  border-top: 1px solid var(--line);
  gap: 12px;
}
.result-row:first-child { border-top: none; }
.result-row .label { color: var(--muted); font-size: .86rem; }
.result-row.result-total {
  margin-top: 4px;
  padding-top: 14px;
  border-top: 1px solid var(--line-strong);
  font-weight: 700;
  font-size: 1.05rem;
}
.result-row.result-total .figure { color: var(--mist); }

/* ---------- 8. Buttons ------------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 10px 16px;
  font-family: var(--font-body);
  font-size: .88rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform .15s cubic-bezier(.2,.8,.2,1), box-shadow .2s, background .15s, opacity .15s, filter .15s;
  text-decoration: none;
  white-space: nowrap;
  min-height: 40px;
}
/* Small lift on hover, tactile press-down on click — the two states read
   as one continuous gesture (rises to meet the cursor, sinks back under
   it) rather than two unrelated effects bolted together. */
.btn:hover:not(:disabled) { transform: translateY(-1px); }
.btn:active:not(:disabled) { transform: translateY(0) scale(.97); }
.btn:disabled { opacity: .45; cursor: not-allowed; transform: none; }
.btn-primary {
  background-color: var(--mist);
  background-image: linear-gradient(100deg, transparent 30%, rgba(255,255,255,.35) 45%, transparent 60%);
  background-size: 250% 100%;
  background-position: -40% 0;
  background-repeat: no-repeat;
  color: var(--mist-ink);
  box-shadow: var(--shadow-glow);
  transition: background-position .5s ease, box-shadow .2s, transform .15s cubic-bezier(.2,.8,.2,1), filter .15s;
}
/* Shine sweep — a soft highlight travels across the button on hover via
   background-position rather than a separate pseudo-element, so it can't
   clip .btn-primary's own glow shadow the way overflow:hidden would.
   Reduces to an instant jump under prefers-reduced-motion (section 18). */
.btn-primary:hover:not(:disabled) { filter: brightness(1.07); background-position: 140% 0; box-shadow: var(--shadow-glow), var(--shadow-2); }
.btn-secondary { background: var(--surface-2); color: var(--ink); border-color: var(--line); }
.btn-secondary:hover:not(:disabled) { border-color: var(--line-strong); box-shadow: var(--shadow-1); }
.btn-ghost { background: transparent; color: var(--muted); }
.btn-ghost:hover:not(:disabled) { color: var(--ink); background: var(--surface-2); }
.btn-danger { background: var(--bad-wash); color: var(--bad); border-color: var(--bad); }
.btn-danger:hover:not(:disabled) { background: var(--bad); color: #fff; }
.btn-sm { padding: 7px 12px; font-size: .8rem; min-height: 34px; }
.btn-block { width: 100%; }
.btn-icon { padding: 9px; min-height: 40px; width: 40px; }

/* On mobile, a row of sibling buttons (typically a modal's Save/Cancel
   footer) stacks into a full-width column instead of squeezing side by
   side — each becomes an equally-prominent, easy-to-tap target instead of
   two cramped halves. .btn-sm-auto is the escape hatch for the day a
   *mixed* row shows up (e.g. a small "Skip" text link next to a full-size
   primary action) where that one button should keep its natural width
   instead of also stretching. */
@media (max-width: 480px) {
  .btn-stack-mobile { flex-direction: column !important; }
  .btn-stack-mobile .btn { width: 100%; }
  .btn-stack-mobile .btn-sm-auto { width: auto; align-self: flex-end; }
}

/* Segmented tab switcher — two (or more) mutually-exclusive options that
   belong together as one control, e.g. Billing's Add product / Custom mix
   switch (previously two plain .btn-sm buttons sitting directly on top of
   the search field below with no track/spacing of their own, which read
   as cramped rather than as one control). Each option gets its own
   visible pill inside a shared track, rather than one border around the
   whole group — reuse this for any future either-or control instead of
   rebuilding it from bare buttons. */
.tab-bar {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: 18px;
}
.tab-btn {
  flex: 1;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  padding: 9px 14px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font-family: var(--font-body);
  font-size: .86rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s, box-shadow .15s;
  min-height: 38px;
}
.tab-btn:hover:not(.active) { color: var(--ink); }
.tab-btn.active {
  background: var(--surface);
  border-color: var(--line-strong);
  color: var(--ink);
  box-shadow: var(--shadow-1);
}

/* ---------- 9. Forms --------------------------------------------------------- */

label { display: block; font-size: .82rem; font-weight: 600; color: var(--muted); margin-bottom: 6px; }
.field { margin-bottom: 16px; }
.input, select.input, textarea.input {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: .92rem;
  padding: 11px 12px;
  transition: border-color .15s, box-shadow .15s;
  min-height: 44px; /* WCAG 2.5.8 touch target */
}
.input::placeholder { color: var(--faint); }
.input:focus { outline: none; border-color: var(--mist); box-shadow: 0 0 0 3px var(--mist-wash); }
textarea.input { resize: vertical; min-height: 80px; }
.input-row { display: flex; gap: 10px; }
.input-row > * { flex: 1; min-width: 0; }
.hint { font-size: .76rem; color: var(--faint); margin-top: 5px; }
.error-text { font-size: .76rem; color: var(--bad); margin-top: 5px; }

/* Stack input rows on very narrow mobile */
@media (max-width: 480px) {
  .input-row { flex-direction: column; gap: 0; }
}

/* Range slider */
input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 6px; border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--mist), var(--ember));
  outline: none;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--ink); border: 3px solid var(--mist); cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--ink); border: 3px solid var(--mist); cursor: pointer;
}

/* Bottle-price calculator helper (inventory/ingredients edit modals) */
.calc-toggle {
  background: none; border: none; color: var(--mist); font-size: .78rem;
  cursor: pointer; padding: 4px 0; text-decoration: underline;
  text-underline-offset: 3px; display: block; margin-top: 4px;
}
.calc-toggle:hover { color: var(--ink); }
.calc-row {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 8px; padding: 10px;
  background: var(--surface-2); border-radius: var(--radius);
}
.calc-input { width: 90px !important; flex: 0 0 auto; }
.calc-label { font-size: .82rem; color: var(--muted); white-space: nowrap; }

/* ---------- 10. Cards, glass panels, stat tiles ------------------------------ */

.card {
  background: var(--surface-glass);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2);
  padding: 20px;
  margin-bottom: 16px;
}
.card:last-child { margin-bottom: 0; }
.card-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.card-header h2, .card-header h3 { margin: 0; }

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

@media (max-width: 640px) {
  .grid-stats { grid-template-columns: 1fr 1fr; gap: 10px; }
}

.stat-tile {
  background: var(--surface-glass);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  position: relative;
  overflow: hidden;
}
.stat-tile .eyebrow { margin-bottom: 6px; }
.stat-tile .figure-lg { display: block; }
.stat-tile .delta { font-size: .78rem; font-weight: 600; margin-top: 4px; }
.delta.up { color: var(--good); }
.delta.down { color: var(--bad); }

/* ---------- 11. Badges / tags / pills / role chip ---------------------------- */

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  font-size: .72rem;
  font-weight: 600;
  border: 1px solid transparent;
  white-space: nowrap;
}
.badge-mist   { background: var(--mist-wash);  color: var(--mist);  border-color: currentColor; }
.badge-ember  { background: var(--ember-wash); color: var(--ember); border-color: currentColor; }
.badge-good   { background: var(--good-wash);  color: var(--good);  }
.badge-warn   { background: var(--warn-wash);  color: var(--warn);  }
.badge-bad    { background: var(--bad-wash);   color: var(--bad);   }
.badge-neutral { background: var(--surface-2); color: var(--muted); }
.dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* Gentle attention pulse for the sidebar/topbar's alert-count badges
   (low stock, expiring ingredients) — an outward-fading ring in the
   badge's own color, quiet enough to sit in a sidebar all day without
   being annoying, unlike a blinking dot. Scoped to :not(.hidden) so it
   only ever runs while there's something real to flag (see nav.js, which
   removes .hidden exactly when count > 0). Skipped entirely under
   prefers-reduced-motion (section 18). */
@keyframes vc-badge-pulse {
  0%, 100% { box-shadow: 0 0 0 0 currentColor; }
  50%      { box-shadow: 0 0 0 4px transparent; }
}
.low-stock-badge:not(.hidden), .expiry-badge:not(.hidden) {
  animation: vc-badge-pulse 2.2s ease-in-out infinite;
}

/* ---------- 12. Tables ------------------------------------------------------- */

.table-wrap { overflow-x: auto; border-radius: var(--radius); border: 1px solid var(--line); }
table { width: 100%; border-collapse: collapse; min-width: 580px; }
thead th {
  text-align: left;
  font-size: .7rem; letter-spacing: .05em; text-transform: uppercase;
  color: var(--faint); font-weight: 700;
  padding: 11px 14px;
  background: var(--surface-2);
  position: sticky; top: 0;
  white-space: nowrap;
}
tbody td { padding: 13px 14px; border-top: 1px solid var(--line); font-size: .88rem; vertical-align: middle; }
tbody tr { transition: background .12s; }
tbody tr:hover { background: var(--surface-2); }
td.num, th.num { text-align: right; font-family: var(--font-mono); }
tbody tr.row-warn { background: var(--warn-wash); }
tbody tr.row-bad  { background: var(--bad-wash); }
td.actions { white-space: nowrap; }
/* Rows that open something on click (Invoices' search results) — the
   hover tint above already applies to every row generically, so this
   only needs to add the pointer cursor that actually signals "clickable"
   rather than implying every table row is interactive. */
tbody tr.row-clickable { cursor: pointer; }

/* ---------- 13. Safety banners ----------------------------------------------- */

.safety-banner {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--warn);
  background: var(--warn-wash);
  color: var(--ink);
  font-size: .85rem;
  margin-bottom: 16px;
}
.safety-banner svg { flex-shrink: 0; margin-top: 1px; color: var(--warn); }
.safety-banner.danger { border-color: var(--bad); background: var(--bad-wash); }
.safety-banner.danger svg { color: var(--bad); }
.safety-banner strong { display: block; margin-bottom: 2px; }

.safety-panel summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; justify-content: space-between;
  font-weight: 600; font-size: .92rem; padding: 4px 0;
}
.safety-panel summary::-webkit-details-marker { display: none; }
.safety-panel[open] summary { margin-bottom: 10px; }
.safety-list { display: grid; gap: 10px; margin-top: 10px; }
.safety-item { display: flex; gap: 10px; font-size: .84rem; }
.safety-item .tag { flex-shrink: 0; }

/* ---------- 14. Modal -------------------------------------------------------- */

.modal-backdrop {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(4,5,10,.6);
  backdrop-filter: blur(6px);
  display: flex; align-items: flex-end; justify-content: center;
  padding: 0;
}
/* On desktop, modals centre in the viewport like a conventional dialog. */
@media (min-width: 641px) {
  .modal-backdrop { align-items: center; padding: 16px; }
  .modal { border-radius: var(--radius-lg); }
}

.modal {
  width: 100%;
  max-width: 500px;
  /* On mobile, slide up from the bottom like a sheet */
  max-height: 92dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-2);
  padding: 20px 18px calc(20px + env(safe-area-inset-bottom));
  /* Sheet drag handle on mobile — purely visual */
  position: relative;
}
.modal::before {
  content: '';
  display: block;
  width: 36px; height: 4px;
  background: var(--line-strong);
  border-radius: var(--radius-pill);
  margin: 0 auto 16px;
}
@media (min-width: 641px) {
  .modal::before { display: none; }
  .modal { padding: 22px; border-radius: var(--radius-lg); }
}
.modal-wide { max-width: 700px; }
.modal h2 { margin-bottom: 16px; }

/* Entrance animation — the comment above ("slide up like a sheet") already
   described this behaviour but the modal previously just appeared with no
   motion. Plays automatically whenever .hidden is removed (that's how
   CSS animations work on a freshly-displayed element — no JS timing
   needed) so every open call site gets this for free. Deliberately no
   matching exit animation: .hidden goes back on instantly on close, and
   for a till/counter workflow a snappy close beats waiting out a fade
   when the next customer's already waiting. Skipped under
   prefers-reduced-motion (section 18). */
.modal-backdrop:not(.hidden) { animation: vc-modal-backdrop-in .18s ease-out; }
.modal-backdrop:not(.hidden) .modal { animation: vc-modal-in .22s cubic-bezier(.2,.8,.2,1); }
@keyframes vc-modal-backdrop-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes vc-modal-in {
  from { opacity: 0; transform: translateY(16px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

/* ---------- 15. Toast notifications ------------------------------------------ */

.toast-stack {
  position: fixed; right: 14px; bottom: 14px; z-index: 80;
  display: flex; flex-direction: column; gap: 8px;
  max-width: min(340px, calc(100vw - 28px));
}
.toast {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-2);
  padding: 12px 14px;
  font-size: .85rem;
  display: flex; gap: 10px; align-items: flex-start;
  animation: vc-toast-in .18s ease-out;
}
.toast.good { border-left: 3px solid var(--good); }
.toast.bad  { border-left: 3px solid var(--bad); }
.toast.warn { border-left: 3px solid var(--warn); }
@keyframes vc-toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
/* Exit — added alongside a small toast.js change so the element waits for
   this to finish before being removed, instead of vanishing mid-frame the
   way a bare el.remove() did before. */
.toast.toast-exit { animation: vc-toast-out .15s ease-in forwards; }
@keyframes vc-toast-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(6px) scale(.96); }
}

/* ---------- 16. Utility classes ---------------------------------------------- */

.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-8 { gap: 8px; } .gap-12 { gap: 12px; } .gap-16 { gap: 16px; }
.wrap { flex-wrap: wrap; }
.muted { color: var(--muted); }
.faint { color: var(--faint); }
.text-good { color: var(--good); }
.text-warn { color: var(--warn); }
.text-bad  { color: var(--bad); }
.mt-0 { margin-top: 0; }  .mb-0 { margin-bottom: 0; }
.mt-8 { margin-top: 8px; } .mt-16 { margin-top: 16px; } .mt-24 { margin-top: 24px; }
.w-full { width: 100%; }
.text-right { text-align: right; }
.hidden { display: none !important; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 640px)  { .hide-mobile  { display: none !important; } }
@media (min-width: 641px)  { .hide-desktop { display: none !important; } }
@media (max-width: 780px)  { .hide-tablet  { display: none !important; } }

/* ---------- 17. Print --------------------------------------------------------- */

@media print {
  .sidebar, .mobile-nav, .topbar, .toast-stack, .vapor-field,
  .no-print, [data-calc-toggle], .calc-row { display: none !important; }
  .app-shell { display: block; }
  .main { padding: 0; }
  .content { padding: 0; max-width: none; }
  body { background: #fff; color: #000; }
  .card { box-shadow: none; border: 1px solid #ccc; background: #fff; backdrop-filter: none; }

  /* The rules above only reset the CARD's own background/border and the
     BODY element's base text color — anything that reads a token
     directly (.muted, .figure, .hint, .label, every class in section 23
     below) still resolves against whichever theme is active on screen,
     because color-scheme isn't something print output otherwise touches.
     Printing the Lounge (dark) theme without this override means near-
     white ink on the now-white card — invisible. Forcing every token to
     a print-safe value here, regardless of the active theme, means a
     printed invoice always comes out legible instead of depending on
     which theme happened to be on screen at the time. */
  :root, [data-theme] {
    --ink: #17181d;
    --muted: #52565f;
    --faint: #82869a;
    --line: #d8d8dc;
    --line-strong: #b7b7c2;
    --mist: #0e8074;
    --good: #0d8a5f;
    --warn: #a6690f;
    --bad: #c22a4e;
  }
}

/* ---------- 18. Accessibility ------------------------------------------------- */

:focus-visible { outline: 2px solid var(--mist); outline-offset: 2px; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: var(--radius-pill); }

/* ---------- 19. Address lookup combobox -------------------------------------- */

.addr-combo { position: relative; }
.addr-dropdown {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 30;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-2);
  max-height: 240px; overflow-y: auto; padding: 6px;
}
.addr-option { padding: 9px 10px; border-radius: var(--radius-sm); cursor: pointer; }
.addr-option:hover, .addr-option.active { background: var(--mist-wash); }
.addr-option-main { font-size: .86rem; font-weight: 600; color: var(--ink); }
.addr-option-sub  { font-size: .74rem; color: var(--muted); margin-top: 1px; }
.addr-empty { padding: 10px; font-size: .8rem; color: var(--muted); text-align: center; }
input.addr-unmatched { border-style: dashed; border-color: var(--warn); }

/* ---------- 20. Ratio split control ------------------------------------------ */

.ratio-split { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; }
.ratio-numbers { display: flex; gap: 10px; flex: 1 1 200px; }
.ratio-numbers > div { flex: 1; min-width: 0; }
.ratio-sublabel { font-size: .74rem; margin-bottom: 4px; }
.ratio-slider-wrap { flex: 1 1 200px; }
.ratio-slider-wrap input[type="range"] { width: 100%; }
.ratio-slider-labels { display: flex; justify-content: space-between; font-size: .68rem; color: var(--faint); margin-top: 6px; }

/* ---------- 21. PIN pad ------------------------------------------------------- */

.pin-dots { display: flex; justify-content: center; gap: 14px; margin: 4px 0 26px; }
.pin-dot {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--line-strong); background: transparent;
  transition: background .15s, border-color .15s, transform .15s;
}
.pin-dot.filled { background: var(--mist); border-color: var(--mist); transform: scale(1.1); }

.pin-pad {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 12px; max-width: 280px; margin: 0 auto;
}
.pin-key {
  aspect-ratio: 1; border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface-glass);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  color: var(--ink); font-family: var(--font-display);
  font-size: 1.3rem; font-weight: 600; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background .12s, border-color .12s, transform .08s;
}
.pin-key:hover:not(:disabled) { border-color: var(--line-strong); background: var(--surface-2); }
.pin-key:active:not(:disabled) { transform: scale(.9); }
.pin-key-empty { background: transparent; border-color: transparent; cursor: default; }
.pin-key-back { color: var(--muted); }
.pin-key-back svg { width: 20px; height: 20px; }

@keyframes vc-pin-shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(3px); }
  30%, 50%, 70% { transform: translateX(-6px); }
  40%, 60% { transform: translateX(6px); }
}
.pin-shake { animation: vc-pin-shake .45s cubic-bezier(.36,.07,.19,.97) both; }
.pin-attempts-left { text-align: center; font-size: .8rem; color: var(--warn); margin-top: 12px; min-height: 1.2em; }
.login-toggle-row { text-align: center; margin-top: 18px; }

/* ---------- 22. Charts ------------------------------------------------------- */

.chart-wrap { width: 100%; }
.chart-wrap svg { width: 100%; height: auto; display: block; overflow: visible; }
.chart-empty { padding: 32px 0; text-align: center; color: var(--muted); font-size: .86rem; }

/* The donut still rendered oversized and bleeding out past its card even
   with explicit width/height attributes on the <svg> (see charts.js) and
   a max-width here — that combination depends on the browser correctly
   inferring the svg's rendered height from width:100% + height:auto
   against its intrinsic ratio, which is exactly the kind of inference
   this comment previously (and wrongly, it turns out) assumed was now
   reliable everywhere. Rather than continue chasing that inference,
   .chart-donut-frame now establishes a hard square box up front —
   width capped at 220px, aspect-ratio locked to 1/1, so the height never
   depends on the svg at all — and clips anything that would still try to
   escape it. The svg then just fills that already-square box (height:100%,
   not auto). This can't render oversized regardless of the exact
   mechanism that caused it before. */
.chart-donut-frame {
  width: 100%;
  max-width: 220px;
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  overflow: hidden;
}
.chart-donut-frame svg { width: 100%; height: 100%; display: block; }
.chart-bar-fill { transition: opacity .15s; }
.chart-bar-group:hover .chart-bar-fill { opacity: .8; }
.chart-axis-label  { font-family: var(--font-mono); font-size: 9px; fill: var(--faint); }
.chart-value-label { font-family: var(--font-mono); font-size: 10px; fill: var(--muted); font-weight: 600; }
.chart-line-path { fill: none; stroke: var(--mist); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.chart-line-area { fill: url(#vc-area-fill); opacity: .5; }
.chart-line-dot  { fill: var(--bg); stroke: var(--mist); stroke-width: 2; }
.chart-donut-slice { transition: opacity .15s, transform .15s; transform-origin: center; }
.chart-donut-slice:hover { opacity: .85; }
.chart-legend { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 14px; justify-content: center; }
.chart-legend-item { display: flex; align-items: center; gap: 6px; font-size: .78rem; color: var(--muted); }
.chart-legend-swatch { width: 9px; height: 9px; border-radius: 3px; flex-shrink: 0; }
.stat-tile-spark { position: absolute; left: 0; right: 0; bottom: 0; height: 36px; opacity: .55; pointer-events: none; }

/* ---------- 23. Invoice / receipt document ------------------------------------ */
/* shared/js/receipt.js's VC.renderReceiptHtml() — the sale-complete view in
   Billing and the detail view in Invoices both render into a plain .card,
   so this is what makes that card actually read as an invoice rather than
   another app panel: a proper masthead, a Bill To block, a real item
   table instead of a stacked list, and a totals block anchored to the
   trailing edge the way a paper invoice's would be. Deliberately its own
   small table styling rather than reusing section 12's — that one is
   tuned for wide multi-column data grids (a 580px min-width so nothing
   important gets squeezed) and would force horizontal scrolling on an
   invoice's much narrower 4-column table for no reason. The one intentional
   flourish is the mist-coloured rule under the masthead — a single quiet
   accent line, echoing the precise, measured feel "Apothecary" already
   evokes elsewhere, not a redesign of the whole document. */
.invoice-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 12px;
  padding-bottom: 14px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--line-strong);
  position: relative;
}
.invoice-head::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: -4px;
  height: 2px;
  background: var(--mist);
  opacity: .55;
}
.invoice-shop { font-family: var(--font-display); font-size: 1.3rem; font-weight: 700; letter-spacing: -.01em; }
.invoice-eyebrow {
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--mist);
  margin-top: 2px;
}
.invoice-meta { text-align: right; flex-shrink: 0; }
.invoice-number { font-family: var(--font-mono); font-weight: 600; font-size: .92rem; }
.invoice-billto { margin-bottom: 18px; }

.invoice-table { width: 100%; border-collapse: collapse; margin-bottom: 4px; }
.invoice-table th {
  text-align: left;
  font-size: .68rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--faint); font-weight: 700;
  padding: 0 10px 8px; border-bottom: 1px solid var(--line-strong);
}
.invoice-table td { padding: 9px 10px; border-bottom: 1px solid var(--line); font-size: .86rem; vertical-align: top; }
.invoice-table th:first-child, .invoice-table td:first-child { padding-left: 0; }
.invoice-table th:last-child, .invoice-table td:last-child { padding-right: 0; }
.invoice-table th.num, .invoice-table td.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.invoice-table tbody tr:last-child td { border-bottom: none; }

.invoice-totals { max-width: 280px; margin-left: auto; margin-top: 8px; }
.invoice-foot { margin-top: 22px; padding-top: 14px; border-top: 1px dashed var(--line); font-size: .8rem; color: var(--muted); }
.invoice-thanks { margin-top: 6px; font-style: italic; color: var(--faint); }

@media print {
  /* On paper the mist rule is the only colour left in the whole document
     (everything else already collapses to ink-on-white above) — kept
     deliberately, both as the one signature touch and because a totally
     colourless receipt reads as a screenshot of a form, not an invoice. */
  .invoice-eyebrow, .invoice-head::after { opacity: 1; }
}
