/* ============================================================================
   VectraLabel design system — see DESIGN-SYSTEM.md at the repo root for the
   rationale behind every token and component recipe in this file.

   This is a full restart (2026-08-05, second pass). The previous dark
   "lab instrument" theme was rejected twice by the user; this file implements
   the warm-paper / tangerine concept that was reviewed and approved before
   implementation. Do not reintroduce the dark slate/teal palette as a toggle.

   Load order note: this file is the ONLY stylesheet the app screens and the
   marketing page share, so the token block below is the single source of truth
   for brand colour, type scale, spacing rhythm and elevation across
   app.html / recipes.html / integrations.html / index.html / home.html.
   Marketing-page classes (.vl-*) read these same tokens with fallbacks, so
   retuning a token here retunes the marketing page too. Do not rename tokens —
   the legacy alias block exists precisely so the marketing page keeps working.
   ========================================================================= */

:root {
  color-scheme: light;

  /* --- Surfaces: warm paper, not stark white ------------------------------
     A food tool used mid-morning in a bakery should default bright. The floor
     is an ivory paper stock; cards are true white so they read as sheets laid
     on top of it, and the nav rail / inputs are a half-step darker so they
     read as recessed. */
  --paper: #FAF5EA;
  --paper-raised: #FFFFFF;
  --paper-sunken: #F1E9D8;
  --line: #E8DFCB;
  --line-soft: #F0E8D6;

  /* --- Text ---------------------------------------------------------------
     Deep aubergine, never flat black — black on warm ivory reads as a printing
     error. --ink on --paper measures ~14.9:1, --ink-soft ~5.9:1,
     --ink-faint ~3.6:1 (faint is meta/placeholder only, never body copy). */
  --ink: #241B2E;
  --ink-soft: #6B5F73;
  --ink-faint: #8A7D91;

  /* --- Brand: one vibrant accent -----------------------------------------
     Tangerine is spent on primary actions, the brand mark, active nav and
     focus rings. Nowhere else. --citrus-deep is the text-safe variant
     (5.0:1 on --paper); --citrus itself is a fill colour, not a text colour. */
  --citrus: #FF6B35;
  --citrus-deep: #C7410F;
  --citrus-soft: #FFE3D1;
  --citrus-line: #FFC6A9;

  /* --- Semantic -----------------------------------------------------------
     Leaf green means exactly one thing: this label passes compliance / this
     action succeeded. If leaf shows up anywhere decorative, that is a bug. */
  --leaf: #2F7D5C;
  --leaf-deep: #1F5A41;
  --leaf-soft: #DCEFE2;
  --leaf-line: #B4DCC3;

  --sun: #D98C00;
  --sun-deep: #96610A;
  --sun-soft: #FBEACB;
  --sun-line: #EFD49B;

  --berry: #B23A6B;
  --berry-deep: #8E2B53;
  --berry-soft: #F6DEE8;
  --berry-line: #E7BCCE;

  --red: #D64545;
  --red-deep: #A62B2B;
  --red-soft: #F8DEDE;
  --red-line: #EFB9B9;
  --red-hover: #F3CFCF;

  /* Informational (neither success nor warning). Kept deliberately quiet and
     hue-distant from both citrus and leaf so it never competes with them. */
  --sky: #3A6EA5;
  --sky-deep: #2A5480;
  --sky-soft: #E1EAF5;
  --sky-line: #BCCFE6;

  /* --- Type ---------------------------------------------------------------
     Three roles, deliberately different families. No webfont downloads — all
     three are system stacks, so first paint is never blocked on the network.
     --nutrition is Helvetica for the regulated panel AND for every numeric or
     tabular figure in the app, which ties the app's data typography to the
     same authentic convention as the printed document. */
  --display: "Century Gothic", "Avenir Next", "Futura", Poppins, ui-rounded, ui-sans-serif, sans-serif;
  --body: Avenir, "Segoe UI", ui-sans-serif, -apple-system, "Helvetica Neue", sans-serif;
  --nutrition: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-sans: var(--body);           /* legacy alias */
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  --fs-0: 0.6875rem; /* 11px — micro labels, column heads */
  --fs-1: 0.75rem;   /* 12px — badges, helper text */
  --fs-2: 0.8125rem; /* 13px — secondary/meta */
  --fs-3: 0.9375rem; /* 15px — body, inputs, cells */
  --fs-4: 1.0625rem; /* 17px — card titles */
  --fs-5: 1.25rem;   /* 20px — section headings */
  --fs-6: 1.5rem;    /* 24px — page titles */
  --fs-7: 2rem;      /* 32px — metrics */
  --lh-tight: 1.2;
  --lh-snug: 1.4;
  --lh-body: 1.6;

  /* --- Spacing (4px rhythm) ---------------------------------------------- */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.25rem;
  --sp-6: 1.5rem;
  --sp-8: 2rem;
  --sp-10: 2.5rem;
  --sp-12: 3rem;

  /* --- Radii --------------------------------------------------------------
     Rounder than the retired system (8/14/20 rather than 4/6/10) to match the
     warmer direction. Status pills stay fully round because the shape IS the
     meaning; everything else uses the fixed scale. */
  --r-sm: 8px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-xl: 20px;
  --r-pill: 999px;

  /* --- Elevation ----------------------------------------------------------
     Tinted toward --ink rather than neutral black so shadows on warm paper
     read as warm shade, not as grey haze. */
  --sh-1: 0 1px 2px rgba(36, 27, 46, 0.06), 0 1px 3px rgba(36, 27, 46, 0.05);
  --sh-2: 0 2px 6px rgba(36, 27, 46, 0.07), 0 10px 24px -8px rgba(36, 27, 46, 0.13);
  --sh-3: 0 12px 32px -8px rgba(36, 27, 46, 0.18), 0 32px 64px -16px rgba(36, 27, 46, 0.20);
  --sh-focus: 0 0 0 3px rgba(255, 107, 53, 0.28);
  --shadow: var(--sh-2);       /* names used by DESIGN-SYSTEM.md §4 */
  --shadow-lg: var(--sh-3);

  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --dur: 140ms;

  /* --- Legacy aliases -----------------------------------------------------
     The marketing page (index.html / index.dev.html / home.html) reads these
     names inline with hardcoded dark fallbacks. Renaming or dropping any of
     them silently drops marketing back to the retired dark palette, so they
     stay defined, now pointing at the warm system above. */
  --bg: var(--paper);
  --surface: var(--paper-raised);
  --surface-raised: var(--paper-raised);
  --surface-sunken: var(--paper-sunken);
  --surface-hover: rgba(36, 27, 46, 0.035);
  --panel: var(--paper-raised);
  --input-bg: var(--paper-raised);
  --border: var(--line);
  --border-strong: #DBCFB4;
  --border-subtle: var(--line-soft);
  --text: var(--ink);
  --muted: var(--ink-soft);
  --faint: var(--ink-faint);
  --brand: var(--citrus);
  --brand-2: var(--berry);
  --brand-gradient: linear-gradient(135deg, #FF6B35, #B23A6B);
  --accent: var(--citrus-deep);
  --accent-dim: var(--citrus-soft);
  --accent-line: var(--citrus-line);
  --ok: var(--leaf);
  --ok-dim: var(--leaf-soft);
  --ok-line: var(--leaf-line);
  --warn: var(--sun);
  --warn-dim: var(--sun-soft);
  --warn-line: var(--sun-line);
  --danger: var(--red);
  --danger-dim: var(--red-soft);
  --danger-line: var(--red-line);
  --info: var(--sky);
  --info-dim: var(--sky-soft);
  --info-line: var(--sky-line);
  --premium: var(--berry);
  --premium-dim: var(--berry-soft);
  --premium-line: var(--berry-line);
  /* Warm neutral steps — a handful of component rules still reference these. */
  --n-950: #EDE3CE;
  --n-900: var(--paper);
  --n-850: #F6EFE1;
  --n-800: #F4EDDD;
  --n-750: #EFE6D2;
  --n-700: var(--line);
  --n-600: var(--border-strong);
  --n-500: #C9BB9C;
  --n-400: var(--ink-faint);
  --n-300: var(--ink-faint);
  --n-200: var(--ink-soft);
  --n-100: var(--ink);
  --n-050: var(--ink);
}

/* Dark is a separately-tuned second pass, not an inversion. Component CSS
   never branches on theme — only the tokens below change. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --paper: #211925;
    --paper-raised: #2C2233;
    --paper-sunken: #1A1420;
    --line: #3C2F42;
    --line-soft: #352A3C;
    --ink: #F7F1E8;
    --ink-soft: #C9BFC0;
    --ink-faint: #9C8FA1;
    --citrus: #FF8459;
    --citrus-deep: #FFA680;
    --citrus-soft: #4A2A22;
    --citrus-line: #6B3A2A;
    --leaf: #5BB98C;
    --leaf-deep: #8FD6B0;
    --leaf-soft: #1E3B2E;
    --leaf-line: #2F5A45;
    --sun: #EDB44B;
    --sun-deep: #F5CE84;
    --sun-soft: #3D2F16;
    --sun-line: #5C481F;
    --berry: #DE7FA6;
    --berry-deep: #EFA9C4;
    --berry-soft: #3D2130;
    --berry-line: #5A3145;
    --red: #F08A8A;
    --red-deep: #F6B4B4;
    --red-soft: #3E2222;
    --red-line: #5C3333;
    --red-hover: #4C2A2A;
    --sky: #7FA9D8;
    --sky-deep: #A9C6E7;
    --sky-soft: #1E2B3A;
    --sky-line: #33465C;
    --surface-hover: rgba(255, 255, 255, 0.045);
    --border-strong: #4C3D53;
    --sh-1: 0 1px 2px rgba(0, 0, 0, 0.35);
    --sh-2: 0 2px 6px rgba(0, 0, 0, 0.35), 0 10px 24px -8px rgba(0, 0, 0, 0.45);
    --sh-3: 0 12px 32px -8px rgba(0, 0, 0, 0.55), 0 32px 64px -16px rgba(0, 0, 0, 0.6);
    --n-950: #150F1A;
    --n-850: #2A2031;
    --n-800: #322639;
    --n-750: #3A2C42;
    --n-500: #574562;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --paper: #211925;
  --paper-raised: #2C2233;
  --paper-sunken: #1A1420;
  --line: #3C2F42;
  --line-soft: #352A3C;
  --ink: #F7F1E8;
  --ink-soft: #C9BFC0;
  --ink-faint: #9C8FA1;
  --citrus: #FF8459;
  --citrus-deep: #FFA680;
  --citrus-soft: #4A2A22;
  --citrus-line: #6B3A2A;
  --leaf: #5BB98C;
  --leaf-deep: #8FD6B0;
  --leaf-soft: #1E3B2E;
  --leaf-line: #2F5A45;
  --sun: #EDB44B;
  --sun-deep: #F5CE84;
  --sun-soft: #3D2F16;
  --sun-line: #5C481F;
  --berry: #DE7FA6;
  --berry-deep: #EFA9C4;
  --berry-soft: #3D2130;
  --berry-line: #5A3145;
  --red: #F08A8A;
  --red-deep: #F6B4B4;
  --red-soft: #3E2222;
  --red-line: #5C3333;
  --red-hover: #4C2A2A;
  --sky: #7FA9D8;
  --sky-deep: #A9C6E7;
  --sky-soft: #1E2B3A;
  --sky-line: #33465C;
  --surface-hover: rgba(255, 255, 255, 0.045);
  --border-strong: #4C3D53;
  --n-950: #150F1A;
  --n-850: #2A2031;
  --n-800: #322639;
  --n-750: #3A2C42;
  --n-500: #574562;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

* { box-sizing: border-box; }

/* Load-bearing. The nav rail and full-bleed elements are sized against the
   viewport; without clipping, screens gain a stray horizontal scrollbar.
   Must be `clip`, not `hidden` — `hidden` would make <html> a scroll container
   and break every position:sticky in the app. */
html { overflow-x: clip; }

body {
  margin: 0;
  font-family: var(--body);
  font-size: var(--fs-3);
  line-height: var(--lh-body);
  background: var(--paper);
  color: var(--ink);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body.sheet-open { overflow: hidden; }

/* Numeric data uses the nutrition face and never jitters between rows — dates,
   GTINs, nutrient values and counts all align on the digit. */
td, .page-info, .label-count, input[type="number"],
.nf-value, .cost-table td, .stat-value, .card-meta-figure, code {
  font-family: var(--nutrition);
  font-variant-numeric: tabular-nums;
}

:focus-visible {
  outline: 2px solid var(--citrus);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -100px;
  z-index: 200;
  background: var(--citrus);
  color: #fff;
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-sm);
  font-weight: 600;
  text-decoration: none;
  transition: top var(--dur) var(--ease);
}
.skip-link:focus { top: var(--sp-4); }

h1 {
  margin: 0;
  font-family: var(--display);
  font-size: var(--fs-4);
  font-weight: 800;
  line-height: var(--lh-tight);
  letter-spacing: -0.01em;
}
h2 {
  font-family: var(--display);
  font-size: var(--fs-5);
  font-weight: 800;
  line-height: var(--lh-snug);
  letter-spacing: -0.01em;
  margin: 0 0 var(--sp-2);
}
h3 {
  font-family: var(--display);
  font-size: var(--fs-4);
  font-weight: 800;
  line-height: var(--lh-snug);
  margin: 0 0 var(--sp-2);
}

.sub { color: var(--ink-soft); margin: var(--sp-1) 0 0; font-size: var(--fs-2); line-height: var(--lh-snug); }
.hidden { display: none !important; }

/* ============================================================================
   Shell — left nav rail + content column
   ============================================================================
   Replaces the retired top app bar. A rail gives every screen a permanent
   sense of place (you can always see where you are and where else you can go)
   and frees the top of the content column for the screen's own identity.
   ========================================================================= */

.app-shell {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  min-height: 100vh;
  align-items: stretch;
}

.rail {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  padding: var(--sp-5) var(--sp-4);
  background: var(--paper-sunken);
  border-right: 1px solid var(--line);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.rail-brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  text-decoration: none;
  color: var(--ink);
  padding: var(--sp-1);
  min-width: 0;
}

.mark {
  width: 34px; height: 34px;
  flex: 0 0 auto;
  border-radius: var(--r-sm);
  background: var(--brand-gradient);
  display: grid; place-items: center;
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--fs-2);
  letter-spacing: 0.04em;
  color: #fff;
  box-shadow: var(--sh-1), inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

.rail-wordmark {
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--fs-4);
  letter-spacing: -0.01em;
  line-height: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rail-nav { display: flex; flex-direction: column; gap: 2px; }

.rail-nav a {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  color: var(--ink-soft);
  text-decoration: none;
  font-size: var(--fs-3);
  font-weight: 600;
  border: 1px solid transparent;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.rail-nav a:hover { background: var(--surface-hover); color: var(--ink); }
/* Active nav is the one place besides primary actions and the mark where the
   brand colour is spent. Its scarcity is what makes it read as "you are here". */
.rail-nav a[aria-current="page"] {
  background: var(--citrus-soft);
  border-color: var(--citrus-line);
  color: var(--citrus-deep);
}
.rail-ico { width: 18px; height: 18px; flex: 0 0 auto; display: grid; place-items: center; }
.rail-ico svg { width: 18px; height: 18px; display: block; }
/* The qualifying half of a nav label ("GS1 prefixes"). On the sidebar the full
   phrase is what makes the destination legible; on the phone bottom bar five
   cells share the viewport, so the qualifier is dropped rather than allowed to
   wrap that one cell to two lines while its neighbours stay on one. */
.rail-nav-more { white-space: pre; }

.rail-foot {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
  min-width: 0;
}

.wrap {
  max-width: 1120px;
  width: 100%;
  margin: 0 auto;
  padding: var(--sp-8) var(--sp-8) var(--sp-12);
  min-width: 0;
}

.session { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.session.loading .session-email { color: var(--ink-faint); }
.session-email { font-size: var(--fs-2); color: var(--ink-soft); line-height: var(--lh-snug); overflow-wrap: anywhere; }
.session-tenant { display: inline-flex; align-items: center; gap: var(--sp-1); font-size: var(--fs-1); color: var(--ink-faint); flex-wrap: wrap; }
.session-tenant code {
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  background: var(--paper-raised);
  border: 1px solid var(--line);
  padding: 1px 6px;
  border-radius: var(--r-sm);
  color: var(--ink-soft);
}
.session.impersonating { border-left: 2px solid var(--sun); padding-left: var(--sp-2); }
.session-badge {
  display: inline-flex;
  align-self: flex-start;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-0);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: var(--sun-soft);
  border: 1px solid var(--sun-line);
  color: var(--sun-deep);
}

.actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.rail-foot .actions { gap: var(--sp-1); }
.rail-foot .actions > * { flex: 1 1 auto; }

/* Any stray <a> nav links still rendered into an .actions row (older screens,
   dev-only links) keep a quiet tab treatment rather than looking like buttons. */
.actions > a:not(.btn) {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: var(--fs-2);
  font-weight: 600;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.actions > a:not(.btn):hover { color: var(--ink); background: var(--surface-hover); }
.actions > a[aria-current="page"] { color: var(--citrus-deep); background: var(--citrus-soft); border-color: var(--citrus-line); }

/* ============================================================================
   Buttons — four intents, one geometry
   ========================================================================= */

button, a.btn, .btn {
  border: 1px solid var(--border-strong);
  background: var(--paper-raised);
  color: var(--ink);
  padding: 0.5rem 0.85rem;
  border-radius: var(--r-md);
  text-decoration: none;
  cursor: pointer;
  font-family: var(--body);
  font-size: var(--fs-2);
  font-weight: 600;
  line-height: 1.3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  white-space: nowrap;
  box-shadow: var(--sh-1);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
/* The rule above sets display on buttons and .btn anchors. It is an author rule,
   so it outranks the UA stylesheet's [hidden] { display: none } no matter how
   specific that selector looks — a button toggled with the `hidden` attribute
   would otherwise stay on screen. Global, because this trap applies to every
   display-setting rule in this file, not just buttons. */
[hidden] { display: none !important; }
button:hover, a.btn:hover, .btn:hover { background: var(--paper-sunken); border-color: var(--n-500); }
button:active, a.btn:active, .btn:active { transform: translateY(1px); box-shadow: none; }

/* Exactly one primary per screen. Tangerine plus a tangerine-tinted shadow is
   its only home in the app UI, which is what makes it read as *the* action. */
a.btn.primary, button.primary, .btn.primary {
  background: linear-gradient(135deg, #FF7A45, #FF5A1F);
  border-color: transparent;
  color: #fff;
  font-weight: 700;
  box-shadow: 0 2px 4px rgba(198, 62, 15, 0.22), 0 8px 20px -6px rgba(255, 107, 53, 0.45);
}
a.btn.primary:hover, button.primary:hover, .btn.primary:hover {
  background: linear-gradient(135deg, #FF6B35, #E6541F);
  border-color: transparent;
}

button.ghost, .btn.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--ink-soft);
  box-shadow: none;
}
button.ghost:hover, .btn.ghost:hover { background: var(--surface-hover); color: var(--ink); border-color: transparent; }

/* Destructive: never the visually dominant control, always paired with an
   explicit cancel (see .confirm-panel). */
button.danger, .btn.danger {
  background: var(--red-soft);
  border-color: var(--red-line);
  color: var(--red-deep);
  box-shadow: none;
}
button.danger:hover, .btn.danger:hover { background: var(--red-hover); border-color: var(--red); }

button:disabled, .btn.disabled, a.btn.disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
  filter: none;
}
button:disabled:hover, .btn.disabled:hover { background: var(--paper-raised); border-color: var(--border-strong); }

/* ============================================================================
   Banners / inline messaging
   ========================================================================= */

.banner {
  border: 1px solid var(--line);
  border-left: 3px solid var(--n-500);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-4);
  background: var(--paper-raised);
  font-size: var(--fs-2);
  color: var(--ink-soft);
  box-shadow: var(--sh-1);
}
.banner strong { color: var(--ink); font-weight: 700; }
.banner.warn { border-left-color: var(--sun); background: var(--sun-soft); border-color: var(--sun-line); color: var(--sun-deep); }
.banner.limit { border-left-color: var(--red); background: var(--red-soft); border-color: var(--red-line); color: var(--red-deep); }
.banner.ok { border-left-color: var(--leaf); background: var(--leaf-soft); border-color: var(--leaf-line); color: var(--leaf-deep); }
.banner.signed-out { border-left-color: var(--citrus); background: var(--citrus-soft); border-color: var(--citrus-line); color: var(--citrus-deep); }

.error { color: var(--red-deep); font-size: var(--fs-2); }

/* Callout — a result the user asked for (a compliance verdict, a cost
   warning), as distinct from .banner, which is ambient page state nobody
   requested. Tinted fill + hairline + coloured rail, so the outcome is legible
   from the shape before a word is read. */
.callout {
  border: 1px solid var(--line);
  border-left: 3px solid var(--n-500);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  margin-top: var(--sp-4);
  max-width: 60rem;
  background: var(--paper-sunken);
}
.callout-title {
  margin: 0;
  font-size: var(--fs-3);
  font-weight: 700;
  line-height: var(--lh-snug);
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
/* The glyph is a second, non-colour channel for the outcome (WCAG 1.4.1). */
.callout-title::before { font-size: var(--fs-2); }
.callout-body { margin: var(--sp-2) 0 0; font-size: var(--fs-2); color: var(--ink-soft); line-height: var(--lh-body); }
.callout-list { margin: var(--sp-2) 0 0; padding-left: var(--sp-5); font-size: var(--fs-2); color: var(--ink-soft); line-height: var(--lh-body); }
.callout-list li + li { margin-top: var(--sp-1); }

.callout--ok { border-color: var(--leaf-line); border-left-color: var(--leaf); background: var(--leaf-soft); }
.callout--ok .callout-title { color: var(--leaf-deep); }
.callout--ok .callout-title::before { content: "\2713"; }

.callout--error { border-color: var(--red-line); border-left-color: var(--red); background: var(--red-soft); }
.callout--error .callout-title { color: var(--red-deep); }
.callout--error .callout-title::before { content: "\2717"; }

.callout--warn { border-color: var(--sun-line); border-left-color: var(--sun); background: var(--sun-soft); }
.callout--warn .callout-title { color: var(--sun-deep); }
.callout--warn .callout-title::before { content: "\26A0"; }

.callout--info { border-color: var(--sky-line); border-left-color: var(--sky); background: var(--sky-soft); }
.callout--info .callout-title { color: var(--sky-deep); }
.callout--info .callout-title::before { content: "\2139"; }

/* ============================================================================
   Cards & page structure
   ========================================================================= */

.card {
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-6);
  box-shadow: var(--sh-1);
  margin-bottom: var(--sp-5);
}
.card > h2:first-child, .card > h3:first-child { margin-top: 0; }

.card.card--table { padding: 0; overflow: hidden; }
.card.card--table > .table-scroll { margin: 0; padding: 0; }
.card.card--table > .label-count,
.card.card--table > .pagination-controls { padding-left: var(--sp-6); padding-right: var(--sp-6); }

.card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-bottom: var(--sp-4);
}
.card-head > div { min-width: 0; }
.card-head h2, .card-head h3 { margin: 0; }
.card-head .sub { margin-top: var(--sp-1); max-width: 68ch; }

.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-bottom: var(--sp-6);
}
/* The title block flexes; the action block never does — otherwise the long
   description claims the row and pushes the screen's primary action onto a
   line of its own, where it stops reading as primary. */
.page-head > div:first-child { flex: 1 1 22rem; min-width: 0; }
.page-head > .actions { flex: 0 0 auto; flex-wrap: nowrap; }
.page-head h2 {
  font-family: var(--display);
  font-size: var(--fs-6);
  font-weight: 800;
  letter-spacing: -0.02em;
  margin: 0;
}
.page-head .sub { margin-top: var(--sp-2); max-width: 70ch; font-size: var(--fs-2); }
.page-eyebrow {
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 var(--sp-1);
}

.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--sp-4); }

/* ============================================================================
   Stat tiles — dashboard headline numbers
   ============================================================================
   Rendered in the nutrition face, tabular, because they are data and should
   look like data even when they are large. The tile itself stays quiet: the
   number is the only loud thing in it.
   ========================================================================= */

.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp-4);
  margin-bottom: var(--sp-6);
}

.stat-tile {
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-5);
  box-shadow: var(--sh-1);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  position: relative;
  overflow: hidden;
}
/* A 3px rail keyed to what the number means — the only colour in the tile. */
.stat-tile::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--line);
}
.stat-tile--ok::before { background: var(--leaf); }
.stat-tile--warn::before { background: var(--sun); }
.stat-tile--brand::before { background: var(--citrus); }
/* Informational, not a pass and not a warning — the lifecycle counts on the
   barcode dashboard (drafts awaiting a GTIN). Reuses the same --sky the info
   callout already carries, so no new hue enters the system. */
.stat-tile--info::before { background: var(--sky); }

.stat-label {
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.stat-value {
  font-family: var(--nutrition);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-7);
  font-weight: 700;
  line-height: 1.1;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.stat-tile--ok .stat-value { color: var(--leaf-deep); }
.stat-hint { font-size: var(--fs-2); color: var(--ink-soft); line-height: var(--lh-snug); }

/* ============================================================================
   Photo-forward label cards
   ============================================================================
   Labels are food. A card leads with its photo swatch, carries its compliance
   status overlaid on that swatch (status is scanned before the name), then the
   name and its meta. Real photo upload does not exist yet, so the swatch is a
   deterministic gradient derived from the label's own name — same label, same
   colour, every visit, which makes the grid learnable.
   ========================================================================= */

.label-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--sp-5);
}

.label-card {
  display: flex;
  flex-direction: column;
  /* Load-bearing: .label-card is a <button>, and the base button rule sets
     align-items:center. Without resetting it to stretch, the photo swatch and
     body size to fit-content on the cross axis instead of filling the card
     (DESIGN-SYSTEM.md §9). */
  align-items: stretch;
  text-align: left;
  padding: 0;
  overflow: hidden;
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  cursor: pointer;
  white-space: normal;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.label-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--sh-2);
  border-color: var(--citrus-line);
  background: var(--paper-raised);
}
.label-card:active { transform: none; }

.card-photo {
  position: relative;
  aspect-ratio: 16 / 10;
  background: linear-gradient(135deg, var(--swatch-a, #FFB380), var(--swatch-b, #FF6B35));
  display: grid;
  place-items: center;
  border-bottom: 1px solid var(--line);
}
/* The initial is a stand-in for a product photo, not decoration — it keeps the
   swatch from reading as an image that failed to load. */
.card-photo-initial {
  font-family: var(--display);
  font-size: 2.25rem;
  font-weight: 800;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 1px 2px rgba(36, 27, 46, 0.25);
  line-height: 1;
  user-select: none;
}
.card-photo .pill {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  box-shadow: var(--sh-1);
}

.card-body { padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.card-title {
  font-family: var(--display);
  font-size: var(--fs-4);
  font-weight: 800;
  line-height: var(--lh-snug);
  color: var(--ink);
  margin: 0;
  overflow-wrap: anywhere;
  /* Clamp to 2 lines with an ellipsis rather than wrapping indefinitely — a label name that's
     long (or, in test/demo data, an ID-shaped string) would otherwise dominate the whole card.
     Full text is still available via the title="" attribute set alongside this class. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card-sub { font-size: var(--fs-2); color: var(--ink-soft); margin: 0; overflow-wrap: anywhere; }
.card-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--line-soft);
  font-size: var(--fs-1);
  color: var(--ink-faint);
  flex-wrap: wrap;
}
.card-meta-figure { font-family: var(--nutrition); font-variant-numeric: tabular-nums; }
.card-meta-sep { opacity: 0.5; }

.tag {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-0);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: var(--berry-soft);
  border: 1px solid var(--berry-line);
  color: var(--berry-deep);
  white-space: nowrap;
}

/* List controls: search + sort, above the grid. */
.list-controls {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: var(--sp-5);
}
.list-controls .search-bar { flex: 1 1 18rem; margin-bottom: 0; }

.sort-group {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 3px;
  background: var(--paper-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  flex: 0 0 auto;
}
.sort-group-label { font-size: var(--fs-1); color: var(--ink-faint); padding: 0 var(--sp-2); white-space: nowrap; }
.sort-group button {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  color: var(--ink-soft);
  padding: 0.3rem 0.6rem;
  font-size: var(--fs-2);
}
.sort-group button:hover { background: var(--surface-hover); border-color: transparent; }
.sort-group button[aria-pressed="true"] {
  background: var(--paper-raised);
  border-color: var(--line);
  color: var(--citrus-deep);
  box-shadow: var(--sh-1);
}
.sort-group button[aria-pressed="true"][data-dir="asc"]::after { content: "\2191"; font-size: var(--fs-1); }
.sort-group button[aria-pressed="true"][data-dir="desc"]::after { content: "\2193"; font-size: var(--fs-1); }

/* ============================================================================
   Data tables (templates, ingredient lines, integrations detail)
   ========================================================================= */

table { width: 100%; border-collapse: collapse; font-size: var(--fs-3); }
thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--paper-sunken);
  color: var(--ink-faint);
  font-family: var(--body);
  font-weight: 700;
  font-size: var(--fs-0);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: var(--sp-3) var(--sp-4);
  text-align: left;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
td {
  text-align: left;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
  color: var(--ink-soft);
}
td strong { color: var(--ink); font-weight: 700; font-family: var(--body); }
td .sub { margin: 2px 0 0; font-size: var(--fs-1); color: var(--ink-faint); }
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background var(--dur) var(--ease); }
tbody tr:hover td { background: var(--surface-hover); }

td:last-child:has(button):not(.empty), td:last-child:has(.btn):not(.empty) { text-align: right; white-space: nowrap; }
td .btn, td button { font-size: var(--fs-1); padding: 0.35rem 0.7rem; }

/* A per-row destructive action repeated down a table turns the column into a
   red wall that drowns out the status column. Row danger stays neutral until
   the pointer is on it, where the red is informative rather than ambient. */
td button.danger, td .btn.danger {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--ink-soft);
}
td button.danger:hover, td .btn.danger:hover {
  background: var(--red-soft);
  border-color: var(--red-line);
  color: var(--red-deep);
}

.sort-header {
  all: unset;
  cursor: pointer;
  font: inherit;
  font-weight: 700;
  color: inherit;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}
.sort-header:hover { color: var(--ink-soft); }
.sort-header:focus-visible { outline: 2px solid var(--citrus); outline-offset: 2px; }
th[aria-sort="ascending"] .sort-header,
th[aria-sort="descending"] .sort-header { color: var(--citrus-deep); }
th[aria-sort="ascending"] .sort-header::after { content: "\2191"; font-size: var(--fs-1); }
th[aria-sort="descending"] .sort-header::after { content: "\2193"; font-size: var(--fs-1); }

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-scroll table { min-width: 620px; }
/* An empty-state row (single td.empty spanning the table) has no real columns to keep
   readable side-by-side, so the min-width above just forces a pointless horizontal
   scrollbar under a short message. Drop it for that case. */
.table-scroll table:has(> tbody > tr > td.empty) { min-width: 0; }

/* ============================================================================
   Status chips
   ========================================================================= */

.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 2px 10px 2px 9px;
  border-radius: var(--r-pill);
  font-family: var(--body);
  font-size: var(--fs-1);
  font-weight: 700;
  line-height: 1.5;
  background: var(--paper-sunken);
  border: 1px solid var(--line);
  color: var(--ink-soft);
  white-space: nowrap;
}
/* A leading dot carries the state independently of hue — colour alone must
   never be the only signal (WCAG 1.4.1). */
.pill::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
/* Leaf green appears here and only here in the app chrome: a label that passes
   compliance. Nothing decorative may borrow it. */
.pill.valid, .pill.linked { background: var(--leaf-soft); border-color: var(--leaf-line); color: var(--leaf-deep); }
.pill.approved { background: var(--leaf-soft); border-color: var(--leaf-line); color: var(--leaf-deep); }
.pill.incomplete { background: var(--sun-soft); border-color: var(--sun-line); color: var(--sun-deep); }
.pill.changes-requested { background: var(--red-soft); border-color: var(--red-line); color: var(--red-deep); }
/* Barcode lifecycle: a minted-but-not-yet-downloaded GTIN. It is neither a
   compliance pass (leaf is reserved) nor a warning (nothing is wrong), so it
   takes the informational --sky the info callout already uses. */
.pill.generated { background: var(--sky-soft); border-color: var(--sky-line); color: var(--sky-deep); }
.pill.draft { background: var(--paper-sunken); border-color: var(--border-strong); color: var(--ink-soft); }
.pill.premium {
  background: var(--berry-soft);
  border-color: var(--berry-line);
  color: var(--berry-deep);
  font-size: var(--fs-0);
  vertical-align: middle;
  margin-left: var(--sp-2);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.pill.premium::before { display: none; }
/* On a photo swatch the chip needs its own opaque ground to stay legible over
   an arbitrary gradient. */
.card-photo .pill { background: rgba(255, 255, 255, 0.94); }
.card-photo .pill.valid { background: #EAF6EE; }
.card-photo .pill.incomplete { background: #FDF3DF; }

.link-small {
  background: none;
  border: none;
  color: var(--citrus-deep);
  cursor: pointer;
  font-size: var(--fs-1);
  font-weight: 600;
  padding: 0;
  box-shadow: none;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.link-small:hover { background: none; border-color: transparent; color: var(--citrus); }
.line-country-cell { margin-top: var(--sp-1); }
.country-warning {
  margin-top: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  background: var(--sun-soft);
  border: 1px solid var(--sun-line);
  border-left-width: 3px;
  color: var(--sun-deep);
  font-size: var(--fs-2);
}
.country-warning ul { margin: var(--sp-1) 0 0; padding-left: var(--sp-5); }

/* ============================================================================
   Forms
   ========================================================================= */

/* Capped to a readable column rather than the full content width — a very wide
   single-line input gives the eye no anchor for where a value starts or ends. */
.form-grid { display: grid; gap: var(--sp-4); max-width: 46rem; }
.confirm-panel .form-grid { max-width: none; }

label { display: grid; gap: var(--sp-2); font-size: var(--fs-2); }
label > span:first-child {
  color: var(--ink);
  font-weight: 600;
  font-size: var(--fs-2);
  line-height: var(--lh-snug);
}
label small, th small { font-size: var(--fs-1); color: var(--ink-faint); font-weight: 400; }

input, textarea, select {
  background: var(--paper-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  color: var(--ink);
  padding: 0.55rem 0.75rem;
  font-family: var(--body);
  font-size: var(--fs-3);
  line-height: 1.4;
  width: 100%;
  max-width: 100%;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
input::placeholder, textarea::placeholder { color: var(--ink-faint); }
input:hover, textarea:hover, select:hover { border-color: var(--n-500); }
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--citrus);
  box-shadow: var(--sh-focus);
}
input[aria-invalid='true'], textarea[aria-invalid='true'] { border-color: var(--red); }
input[aria-invalid='true']:focus, textarea[aria-invalid='true']:focus { box-shadow: 0 0 0 3px rgba(214, 69, 69, 0.22); }
input:disabled, select:disabled, textarea:disabled {
  background: var(--paper-sunken);
  color: var(--ink-faint);
  cursor: not-allowed;
  opacity: 0.8;
}
/* Suppress native spin buttons app-wide rather than leaving them to appear
   inconsistently (Chrome only shows them once a number input has focus, so
   two fields side by side can look mismatched mid-tab). None of our numeric
   fields (nutrition values, dimensions, costs) benefit from click-to-step —
   they're typed or pasted — so the plain custom-input look wins everywhere. */
input[type="number"] {
  font-family: var(--nutrition);
  appearance: textfield;
  -moz-appearance: textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-soft) 50%),
                    linear-gradient(135deg, var(--ink-soft) 50%, transparent 50%);
  background-position: calc(100% - 16px) 52%, calc(100% - 11px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 2rem;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  -webkit-box-shadow: 0 0 0 1000px var(--paper-raised) inset;
  transition: background-color 999999s ease-in-out 0s;
}
textarea { min-height: 84px; resize: vertical; line-height: var(--lh-body); }

.field-error { color: var(--red-deep); font-size: var(--fs-1); margin-top: 2px; }
.form-errors {
  border: 1px solid var(--red-line);
  border-left: 3px solid var(--red);
  background: var(--red-soft);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-4);
  font-size: var(--fs-2);
  color: var(--red-deep);
}
.form-errors strong { color: var(--red-deep); }
.form-errors ul { margin: var(--sp-2) 0 0; padding-left: var(--sp-5); }

/* Same panel, opposite outcome. Leaf green is spent here because this is
   literally the "this label passes compliance" message — the one meaning the
   colour is reserved for. */
.form-errors.is-ok {
  border-color: var(--leaf-line);
  border-left-color: var(--leaf);
  background: var(--leaf-soft);
  color: var(--leaf-deep);
}
.form-errors.is-ok strong { color: var(--leaf-deep); }

.toolbar { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; margin-bottom: var(--sp-4); }
.toolbar label { margin: 0; }

/* Shared toolbar input widths — replaces one-off inline style="min-width:...".
   Sizes correspond to the field's expected content (domain/API-key vs. a short label). */
.toolbar-input-lg { min-width: 280px; }
.toolbar-input-md { min-width: 220px; }
.toolbar-input-sm { min-width: 200px; }

/* ============================================================================
   Empty states — never a bare "no data"; always what happened + what to do
   ========================================================================= */

.empty {
  color: var(--ink-soft);
  padding: var(--sp-12) var(--sp-6);
  text-align: center;
  font-size: var(--fs-2);
  line-height: var(--lh-body);
}
.empty strong { color: var(--ink); }
td.empty { border-bottom: none; }

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  max-width: 34rem;
  margin: 0 auto;
  text-align: center;
}
.empty-state-icon {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: var(--r-md);
  background: var(--citrus-soft);
  border: 1px solid var(--citrus-line);
  color: var(--citrus-deep);
  font-size: var(--fs-5);
  line-height: 1;
  margin-bottom: var(--sp-1);
}
/* width:100% is load-bearing. As flex items under `align-items: center` these
   paragraphs size to fit-content on the cross axis, which does NOT clamp to the
   container — the copy would render as one long line overflowing the measure. */
.empty-state-title { font-family: var(--display); font-size: var(--fs-4); font-weight: 800; color: var(--ink); margin: 0; width: 100%; }
.empty-state-body { font-size: var(--fs-2); color: var(--ink-soft); margin: 0; line-height: var(--lh-body); width: 100%; }
.empty-state .btn, .empty-state button { margin-top: var(--sp-2); }

/* The grid's empty state spans the whole grid rather than sitting in column 1. */
.label-grid > .empty-state-wrap { grid-column: 1 / -1; padding: var(--sp-10) var(--sp-4); }

/* ============================================================================
   Modals
   ========================================================================= */

.confirm-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(36, 27, 46, 0.45);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 60;
  padding: var(--sp-4);
}
.confirm-panel {
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-6);
  max-width: 440px;
  width: 100%;
  max-height: min(85vh, 900px);
  overflow-y: auto;
  box-shadow: var(--sh-3);
  text-align: left;
}
.confirm-panel h3 { font-size: var(--fs-5); }
.confirm-panel .sub { font-size: var(--fs-2); line-height: var(--lh-body); margin-bottom: var(--sp-5); }
/* Cancel first, destructive second (LTR): the safe exit is where the eye lands
   first, and the irreversible action requires a deliberate reach. */
.confirm-panel .toolbar { margin-bottom: 0; justify-content: flex-end; flex-direction: row-reverse; }

/* The MFA enrollment step renders a long, unbroken otpauth:// URI inside a
   <code>. .confirm-panel sets overflow-y:auto, which per the overflow spec
   forces the other axis off "visible" too — without an explicit wrap the panel
   grows a horizontal scrollbar at every viewport width. */
#security-body code {
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  overflow-wrap: anywhere;
  background: var(--paper-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 2px 6px;
}

.backup-codes-panel {
  margin-bottom: var(--sp-6);
  padding: var(--sp-4);
  border: 1px solid var(--citrus-line);
  border-radius: var(--r-md);
  background: var(--citrus-soft);
}
.backup-codes-text {
  font-family: var(--font-mono);
  font-size: var(--fs-2);
  line-height: 1.7;
  white-space: pre-wrap;
  padding: var(--sp-3);
  border-radius: var(--r-md);
  background: var(--paper-raised);
  border: 1px solid var(--line);
  max-height: 12rem;
  overflow: auto;
}

#form-view .form-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

.approval-card {
  margin-top: var(--sp-4);
}
.approval-card h3 {
  margin: 0;
  font-size: var(--fs-5);
}
.approval-summary {
  color: var(--ink-soft);
  font-size: var(--fs-2);
  margin-bottom: var(--sp-3);
}

/* Destructive actions sit at the far end of the action row so a mis-aimed
   click on Save can never land on Delete. */
.form-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.form-actions .danger, .form-actions button.danger { margin-left: auto; }
#state { margin-top: var(--sp-4); color: var(--ink-faint); font-size: var(--fs-1); }

/* ============================================================================
   Nutrition Facts data-entry section — the EDITING UI, not the printed panel.
   The regulated panel (black-on-white Helvetica, statutory rule weights) lives
   in recipes.css / label-print.css and deliberately ignores this system.
   ========================================================================= */

#nutrition-section {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--paper-sunken);
}
.nutrition-header {
  background: var(--paper-raised);
  border-bottom: 1px solid var(--line);
  padding: var(--sp-3) var(--sp-4);
}
.nutrition-title {
  font-size: var(--fs-0);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}
.nutrition-fields { padding: var(--sp-4); display: grid; gap: var(--sp-4); }
.nutrition-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.nutrition-label { display: grid; gap: var(--sp-2); font-size: var(--fs-2); }
.nutrition-label > span:first-child { color: var(--ink); font-weight: 600; }
.nutrition-label small { font-size: var(--fs-1); color: var(--ink-faint); }

.nf-toggle-btn {
  margin-top: var(--sp-2);
  font-size: var(--fs-2);
  padding: 0.4rem 0.75rem;
  border-color: transparent;
  background: transparent;
  color: var(--citrus-deep);
  box-shadow: none;
  width: auto;
  justify-content: flex-start;
}
.nf-toggle-btn:hover { background: var(--citrus-soft); border-color: var(--citrus-line); }
/* A chevron gives the control an obvious expand/collapse affordance instead of
   reading as a plain text link — it rotates to point the way the disclosure
   is about to move, and its state is also carried in aria-expanded for
   assistive tech. */
.nf-toggle-chevron {
  display: inline-flex;
  flex: 0 0 auto;
  transition: transform var(--dur) var(--ease);
}
.nf-toggle-chevron svg { width: 14px; height: 14px; display: block; }
.nf-toggle-btn[aria-expanded="true"] .nf-toggle-chevron { transform: rotate(180deg); }
#nf-optional-fields { margin-top: var(--sp-3); display: grid; gap: var(--sp-4); }

/* ============================================================================
   Toasts / search / pagination
   ========================================================================= */

.vl-toast {
  position: fixed;
  bottom: var(--sp-6);
  right: var(--sp-6);
  z-index: 9999;
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-md);
  font-size: var(--fs-2);
  font-weight: 600;
  color: #fff;
  box-shadow: var(--sh-3);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease);
  pointer-events: none;
  max-width: min(28rem, calc(100vw - 2 * var(--sp-6)));
}
.vl-toast.vl-toast-visible { opacity: 1; transform: translateY(0); }
.vl-toast-success { background: var(--leaf-deep); }
.vl-toast-error   { background: var(--red-deep); }

#btn-validate:disabled, #btn-print:disabled { opacity: 0.45; cursor: not-allowed; }

.search-bar { margin-bottom: var(--sp-4); position: relative; }
.search-bar input { width: 100%; padding-left: 2.35rem; font-size: var(--fs-3); }
/* Magnifier drawn as a masked background — no network request, no layout
   shift, and it inherits the token colour. */
.search-bar::before {
  content: "";
  position: absolute;
  left: 0.8rem;
  top: 50%;
  width: 14px; height: 14px;
  margin-top: -7px;
  pointer-events: none;
  z-index: 1;
  background: var(--ink-faint);
  -webkit-mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M20 20l-4.5-4.5'/%3E%3C/svg%3E");
  mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M20 20l-4.5-4.5'/%3E%3C/svg%3E");
}

.label-count {
  font-size: var(--fs-1);
  color: var(--ink-faint);
  padding: var(--sp-3) 0 var(--sp-2);
  margin-bottom: 0;
}
.card--table > .label-count { border-bottom: 1px solid var(--line-soft); }

.pagination-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) 0 0;
  margin-top: var(--sp-5);
  border-top: 1px solid var(--line);
}
.card--table > .pagination-controls { margin-top: 0; padding-bottom: var(--sp-4); }
.pagination-controls .btn { font-size: var(--fs-2); }
.pagination-controls .btn:disabled { opacity: 0.4; cursor: not-allowed; }
.page-info { font-size: var(--fs-1); color: var(--ink-faint); white-space: nowrap; }

.app-banner {
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-2);
  border-radius: var(--r-md);
  border: 1px solid transparent;
  margin-bottom: var(--sp-3);
}
.app-banner--error { background: var(--red-soft); border-color: var(--red-line); color: var(--red-deep); }
.app-banner--warning { background: var(--sun-soft); border-color: var(--sun-line); color: var(--sun-deep); }
.app-banner button {
  margin-left: auto;
  background: none;
  border: none;
  box-shadow: none;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  padding: 0 var(--sp-1);
  color: inherit;
  opacity: 0.7;
}
.app-banner button:hover { opacity: 1; background: none; border-color: transparent; }
.app-banner a { color: inherit; font-weight: 700; }

/* ============================================================================
   Layout-template editor
   ============================================================================
   A layout template describes a physical piece of label stock and which fields
   print on it. It used to be edited as a raw JSON textarea, which asked a baker
   to hand-author a data structure. The controls below express the same data as
   what it actually is: a trim size, and an ordered list of fields.
   ========================================================================= */

.template-size-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }

.field-layout {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--paper-sunken);
  padding: var(--sp-4);
  margin: 0;
}
.field-layout > legend {
  font-family: var(--display);
  font-size: var(--fs-3);
  font-weight: 800;
  color: var(--ink);
  padding: 0 var(--sp-2);
}
.field-layout > .sub { margin: 0 0 var(--sp-3); max-width: 60ch; }

.field-layout-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.field-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.field-row-order {
  font-family: var(--nutrition);
  font-size: var(--fs-1);
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  min-width: 1.25rem;
  text-align: right;
}
.field-row-toggle {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 1 1 auto;
  min-width: 0;
  cursor: pointer;
}
.field-row-toggle input { flex: 0 0 auto; width: auto; accent-color: var(--citrus); }
/* Required label fields render as checked+disabled; Chromium desaturates the
   native accent-color checkmark for disabled controls to near-invisible
   contrast, which contradicts the adjacent "Required" pill (it looks
   unchecked even though the field always prints). Give required rows a
   fully custom checked mark instead of relying on native disabled styling. */
.field-row-toggle input:disabled {
  appearance: none;
  -webkit-appearance: none;
  width: 1rem;
  height: 1rem;
  border: 1.5px solid var(--citrus);
  border-radius: 3px;
  background: var(--citrus);
  position: relative;
  cursor: not-allowed;
  margin: 0;
}
.field-row-toggle input:disabled::after {
  content: "";
  position: absolute;
  left: 3px;
  top: 1px;
  width: 4px;
  height: 8px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.field-row-text { min-width: 0; }
.field-row-name { font-size: var(--fs-3); color: var(--ink); display: block; }
.field-row-hint { font-size: var(--fs-1); color: var(--ink-faint); display: block; }

/* A field switched off keeps its place in the order rather than disappearing —
   the customer needs to see what is *not* printing as much as what is. */
.field-row.is-hidden { opacity: 0.62; }
.field-row.is-hidden .field-row-name { text-decoration: line-through; }

.field-row-required {
  font-size: var(--fs-0);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  padding: 1px var(--sp-2);
  white-space: nowrap;
}

.field-row-move { display: flex; gap: var(--sp-1); margin-left: auto; flex: 0 0 auto; }
/* Reordering is low-frequency and low-stakes: it stays visually quiet so seven
   rows of arrows do not compete with the form's single primary action. */
.field-row-move button { min-width: 2rem; padding: var(--sp-1) var(--sp-2); font-size: var(--fs-2); line-height: 1; }

.advanced-json > summary {
  cursor: pointer;
  font-size: var(--fs-2);
  font-weight: 600;
  color: var(--ink-soft);
  padding: var(--sp-2) 0;
}
.advanced-json > summary:hover { color: var(--ink); }
.advanced-json > .sub { max-width: 60ch; }
.advanced-json textarea { font-family: var(--font-mono); font-size: var(--fs-1); width: 100%; }

/* ============================================================================
   Responsive
   ============================================================================
   Under 860px the rail is not squashed — it dissolves. `display: contents`
   promotes its three children to shell children so the brand and account block
   stack at the top of the page and the nav becomes a fixed bottom bar, which
   is where a thumb actually is on a phone.
   ========================================================================= */

@media (max-width: 1100px) {
  .app-shell { grid-template-columns: 200px minmax(0, 1fr); }
  .wrap { padding: var(--sp-6) var(--sp-6) var(--sp-10); }
}

@media (max-width: 860px) {
  .app-shell { display: flex; flex-direction: column; min-height: 100vh; }
  .rail { display: contents; }

  .rail-brand {
    order: 1;
    background: var(--paper-sunken);
    border-bottom: 1px solid var(--line);
    padding: var(--sp-3) var(--sp-4);
    position: sticky;
    top: 0;
    z-index: 30;
  }
  .rail-foot {
    order: 2;
    margin-top: 0;
    border-top: none;
    border-bottom: 1px solid var(--line);
    background: var(--paper-sunken);
    padding: var(--sp-3) var(--sp-4);
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    flex-wrap: wrap;
  }
  .rail-foot .actions { flex: 0 0 auto; }
  .rail-foot .actions > * { flex: 0 0 auto; }
  .wrap { order: 3; padding: var(--sp-5) var(--sp-4) var(--sp-12); }

  .rail-nav {
    order: 4;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 40;
    flex-direction: row;
    gap: 0;
    background: var(--paper-raised);
    border-top: 1px solid var(--line);
    padding: var(--sp-1) var(--sp-2);
    padding-bottom: max(var(--sp-1), env(safe-area-inset-bottom));
    box-shadow: 0 -2px 12px rgba(36, 27, 46, 0.08);
  }
  .rail-nav a {
    flex: 1 1 0;
    flex-direction: column;
    gap: 2px;
    padding: var(--sp-2) var(--sp-1);
    font-size: var(--fs-1);
    text-align: center;
    min-height: 48px;
    justify-content: center;
  }
  /* On the bottom bar the active pill would fill the whole cell, so the state
     is carried by colour plus weight instead of a filled block. */
  .rail-nav a[aria-current="page"] { background: transparent; border-color: transparent; color: var(--citrus-deep); }
  /* Five cells share ~375px. The qualifier is dropped so every label holds one
     line at the same height — see .rail-nav-more. */
  .rail-nav-more { display: none; }
  body { padding-bottom: 68px; }

  .page-head { align-items: flex-start; }
  .page-head > .actions { flex-wrap: wrap; width: 100%; }
  .page-head > .actions > .primary { flex: 1 1 auto; }
  .label-grid { grid-template-columns: 1fr; }
  .stat-row { grid-template-columns: 1fr; gap: var(--sp-3); }
  .stat-tile { flex-direction: row; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
  .stat-tile .stat-value { order: -1; font-size: var(--fs-6); }
  .stat-tile .stat-hint { width: 100%; }
  .list-controls { gap: var(--sp-2); }
  .list-controls .search-bar { flex: 1 1 100%; }
  .sort-group { width: 100%; overflow-x: auto; }
  .vl-toast { left: var(--sp-4); right: var(--sp-4); bottom: 80px; max-width: none; }
}

/* On a phone a centred dialog floats with its actions in the middle of the
   screen, out of thumb reach. Below 767px every confirm dialog becomes a bottom
   sheet: it rises from the edge the thumb already rests on, and its actions land
   in the same place every time. */
@media (max-width: 767px) {
  .confirm-backdrop { align-items: flex-end; padding: 0; }
  .confirm-panel {
    max-width: none;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-bottom: none;
    padding: var(--sp-5) var(--sp-4) calc(var(--sp-6) + env(safe-area-inset-bottom));
  }
  .confirm-panel .toolbar { flex-direction: column-reverse; align-items: stretch; }
  .confirm-panel .toolbar button { width: 100%; }
}

@media (max-width: 700px) {
  .nutrition-row { grid-template-columns: 1fr; }
  .template-size-row { grid-template-columns: 1fr; }

  /* At 375px a single row of [order][checkbox][name][badge][up][down] cannot
     hold its line. It wraps to two: field and state on top, move controls
     beneath, right-aligned at a full tap target. */
  .field-row { flex-wrap: wrap; row-gap: var(--sp-2); }
  .field-row-move { width: 100%; justify-content: flex-end; }
  .field-row-move button { min-width: 2.75rem; min-height: 2.25rem; }
  .field-row-required { order: 3; }
  .toolbar { gap: var(--sp-2); }
  .card { padding: var(--sp-4); }
}
