/*
  Veyra - the design system.
  ==========================================================================

  ONE FILE, LOADED LAST, ON EVERY PAGE - public and authenticated.

  WHAT THIS REPLACES

  The previous surface was built on translucency: backdrop blur, radial
  glows, orbital arcs, a particle field and colour-coded left stripes. It
  photographed well and wore badly. Blur costs a composited layer on every
  panel, glow competes with the data for attention, and a stripe down the
  edge of a card encodes meaning in a place people do not read.

  This replaces that vocabulary rather than layering over it:

      translucency  ->  matte surfaces separated by one hairline
      glow          ->  contrast and weight
      radial light  ->  a single flat ground
      decoration    ->  typography and spacing

  HOW IT WORKS WITHOUT A REWRITE

  veyra-app.css renders ~1,500 lines of working component rules through a
  small set of tokens: --glass*, --lift*, --glow*, --panel, --ring. Those
  tokens are redefined here, so every card, table, pill and drawer re-skins
  at once - and not one class name changes. That matters more than it
  sounds: the browser suites assert against .kpi, .inv-row, .lane-row,
  .order-card and .pill, so a redesign that renamed them would be a redesign
  that broke 97 passing tests.

  THE IDENTITY

    Type      A serif for language, a grotesque for interface, a mono for
              figures. Veyra is a system of record, so numbers are always
              tabular and never reflow as they tick.
    Colour    One ground, one accent, two status hues. Nothing else has a
              colour, so when something does, it means something.
    Surface   Matte. A panel is a slightly lighter plane with a hairline.
              No blur, no gradient standing in for depth.
    Space     The 4px scale already in veyra-core.css, used generously.
              Hierarchy comes from space before it comes from size.
    Motion    120-180ms, opacity and small translation only, on intent.

  PAPER MODE is honoured throughout: veyra-theme.js sets
  [data-ground='paper'], and every value below has a light counterpart.
*/

/* ===================================================== 1. type resources */

/*
  Newsreader for display.

  Not Inter, Geist or Space Grotesk: those are the default voice of every
  developer-tools landing page, and Veyra is sold to restaurant operators
  rather than to engineers. A text serif with real optical sizing reads as
  considered and durable - closer to a trade journal than to a startup - and
  it separates Veyra's language from its interface at a glance.

  IBM Plex Sans and Mono are already loaded by every page and stay: Plex is
  engineered rather than fashionable, and its mono has the tabular figures an
  inventory system lives on.
*/
@import url('https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;1,6..72,400&display=swap');

/* ========================================================== 2. the tokens */

:root {
  /* ---------- ground and surface ---------- */

  /*
    A single near-black navy. Cool, but nowhere near the blue-violet of the
    startup palette this is deliberately not. Every surface above it is a
    step of lightness, never a translucency.
  */
  --v-ground: #0b1017;
  --v-ground-2: #0e141d;
  --v-surface: #121a25;
  --v-surface-2: #16202d;
  --v-surface-3: #1b2735;

  /* Hairlines do the work shadows used to. */
  --v-line: rgba(233, 240, 248, 0.09);
  --v-line-strong: rgba(233, 240, 248, 0.16);
  --v-line-quiet: rgba(233, 240, 248, 0.055);

  /* ---------- ink ---------- */
  --v-ink: #e9f0f8;
  --v-ink-2: #a3b1c4;
  --v-ink-3: #6f7f95;
  --v-ink-faint: #4b5a6e;

  /* ---------- the one accent ----------
     A deepened cyan. The mark's colour, pulled back from the luminous end so
     it can sit on a control without glowing. Neon is explicitly not the
     brief; this reads as instrument lighting, not signage. */
  --v-accent: #4d9fd0;
  --v-accent-hi: #7bbde4;
  --v-accent-lo: #2f6f97;
  --v-accent-wash: rgba(77, 159, 208, 0.1);
  --v-accent-line: rgba(77, 159, 208, 0.34);

  /* ---------- status, and only status ---------- */
  --v-watch: #d8a13f;
  --v-watch-wash: rgba(216, 161, 63, 0.11);
  --v-critical: #d9736b;
  --v-critical-wash: rgba(217, 115, 107, 0.11);

  /* ---------- geometry ----------
     Restrained. A 4px control and a 6px panel read as machined; the 14px
     everything of the previous surface read as soft. */
  --v-r-control: 4px;
  --v-r-panel: 6px;
  --v-r-pill: 3px;

  /* ---------- elevation ----------
     One shadow, used sparingly, and never as a glow. Most separation is a
     hairline plus a lightness step. */
  --v-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  --v-shadow-raised: 0 8px 24px rgba(0, 0, 0, 0.36);
  --v-shadow-overlay: 0 24px 60px rgba(0, 0, 0, 0.5);

  /* ---------- motion ---------- */
  --v-fast: 120ms;
  --v-base: 180ms;
  --v-ease: cubic-bezier(0.2, 0, 0.2, 1);

  /* ---------- type ---------- */
  --v-display: 'Newsreader', 'Iowan Old Style', Georgia, 'Times New Roman', serif;

  /* =========================================================================
     RE-SKIN: the tokens veyra-app.css and app.css already render through.
     Redefining them here re-skins every existing component at once.
     ========================================================================= */

  --glass: var(--v-surface);
  --glass-strong: var(--v-surface-2);
  --glass-quiet: var(--v-ground-2);
  --glass-line: var(--v-line);
  --glass-line-lit: var(--v-line-strong);
  --glass-blur: none;                    /* no translucency anywhere */

  --glow-cyan: transparent;              /* glow is not part of this system */
  --glow-soft: var(--v-accent-wash);
  --ring: 0 0 0 1px var(--v-accent-line);

  --lift-1: var(--v-shadow);
  --lift-2: var(--v-shadow);
  --lift-3: var(--v-shadow-raised);

  --panel: var(--v-r-panel);
  --panel-sm: var(--v-r-control);

  --swift: var(--v-fast);
  --calm: var(--v-base);
  --curve: var(--v-ease);

  /* core aliases */
  --bg: var(--v-ground);
  --surface: var(--v-surface);
  --surface-raised: var(--v-surface-2);
  --surface-sunken: var(--v-ground-2);
  --border: var(--v-line);
  --border-strong: var(--v-line-strong);
  --text: var(--v-ink);
  --text-secondary: var(--v-ink-2);
  --text-tertiary: var(--v-ink-3);
  --accent: var(--v-accent);
  --accent-hover: var(--v-accent-hi);
  --accent-contrast: #06121c;

  --ok: var(--v-accent);
  --ok-soft: var(--v-accent-wash);
  --low: var(--v-watch);
  --low-soft: var(--v-watch-wash);
  --out: var(--v-critical);
  --out-soft: var(--v-critical-wash);

  --radius: var(--v-r-panel);
  --radius-lg: var(--v-r-panel);
  --radius-panel: var(--v-r-panel);
  --radius-control: var(--v-r-control);
  --app-shadow: var(--v-shadow);
}

/*
  Paper mode. The same system with the ground inverted: warm off-white
  rather than pure white, which is both easier to read under kitchen
  lighting and explicitly not the blank-white default.
*/
[data-ground='paper'] {
  --v-ground: #f4f2ee;
  --v-ground-2: #eeece7;
  --v-surface: #ffffff;
  --v-surface-2: #faf9f6;
  --v-surface-3: #f2f0ec;

  --v-line: rgba(20, 32, 46, 0.12);
  --v-line-strong: rgba(20, 32, 46, 0.2);
  --v-line-quiet: rgba(20, 32, 46, 0.07);

  --v-ink: #16202c;
  --v-ink-2: #4a5768;
  --v-ink-3: #6b788a;
  --v-ink-faint: #8b96a6;

  --v-accent: #1d6f9c;
  --v-accent-hi: #17587c;
  --v-accent-lo: #7fb4d4;
  --v-accent-wash: rgba(29, 111, 156, 0.08);
  --v-accent-line: rgba(29, 111, 156, 0.28);

  --v-watch: #9a6512;
  --v-watch-wash: rgba(154, 101, 18, 0.1);
  --v-critical: #a8423a;
  --v-critical-wash: rgba(168, 66, 58, 0.09);

  --v-shadow: 0 1px 2px rgba(20, 32, 46, 0.08);
  --v-shadow-raised: 0 8px 24px rgba(20, 32, 46, 0.1);
  --v-shadow-overlay: 0 24px 60px rgba(20, 32, 46, 0.16);

  --accent-contrast: #ffffff;
}

/* =================================================== 3. retiring the old */

/*
  The atmosphere layer is removed rather than restyled. Its vocabulary -
  radial light, a technical grid, orbital arcs, drifting particles - is the
  specific thing this system rejects, and leaving it present but invisible
  would mean every page still paying for four fixed layers it never shows.

  veyra-app.js still mounts the element; this makes it inert and weightless.
  The element is aria-hidden, so removing it visually removes it entirely.
*/
.v-atmos {
  display: none !important;
}

/* No translucency anywhere, whatever an older rule asked for. */
.app-bar-inner,
.card,
.kpi,
.modal-panel,
.drawer-panel,
.order-card {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* ========================================================= 4. foundation */

html {
  background: var(--v-ground);
}

body {
  background: var(--v-ground);
  color: var(--v-ink);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/*
  Figures never reflow.

  A stock count that jitters as it updates reads as unreliable, and this is a
  system of record. Every number in the product is tabular.
*/
.kpi-value,
.health-score,
.table .num,
.order-cost,
.num,
time,
[data-figure] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* SKUs, lot codes and quantities are mono: they are identifiers, not words. */
.cell-sub,
.field-label,
.kpi-note,
.lane-why,
code,
kbd {
  font-family: var(--font-mono);
}

/* ======================================================== 5. typography */

/*
  Display type is the serif, and it is used only where there is language to
  read: page titles, section headings, the marketing voice. Interface
  chrome - labels, buttons, table headers - stays in the grotesque, because
  a serif at 11px in a table header is costume rather than design.
*/
.page-title,
.hero-title,
.closer-title,
.section-title,
.card-title,
.drawer-title,
.modal-title,
.login-title,
.auth-title,
h1,
h2 {
  font-family: var(--v-display);
  font-weight: 500;
  letter-spacing: -0.012em;
  line-height: 1.14;
  color: var(--v-ink);
}

.page-title {
  font-size: clamp(1.9rem, 1.4rem + 1.6vw, 2.6rem);
  margin: 0 0 var(--space-2);
}

.page-sub,
.section-sub {
  max-width: 62ch;
  color: var(--v-ink-2);
  font-size: var(--text-md);
}

/* Small caps labels: the instrument voice. Used for every field label. */
.field-label,
.card-note,
.kpi .field-label,
.table thead th {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-3);
}

/* ====================================================== 6. the app shell */

/*
  The masthead is a fixed rule across the top, not a floating slab. It
  belongs to the window rather than to the page, which is how an operational
  tool behaves: the chrome holds still and the data moves underneath it.
*/
.app-bar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--v-ground-2);
  border-bottom: 1px solid var(--v-line);
  box-shadow: none;
}

.app-bar .page {
  padding-top: 0;
  padding-bottom: 0;
}

.app-bar-inner {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  min-height: 56px;
  gap: var(--space-6);
}

.app-bar-inner::before {
  display: none;
}

.app-brand {
  font-family: var(--v-display);
  font-size: 1.12rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--v-ink);
}

.app-brand img {
  filter: none;
}

/*
  Navigation. The active section is marked by weight, ink and a 2px rule
  sitting on the masthead's own border - the quietest signal that still
  reads instantly, and no coloured pill.
*/
.app-nav {
  gap: 2px;
}

/*
  The nav item fills the full height of the bar, so its underline lands
  exactly on the masthead's own bottom border rather than floating below it.
*/
.app-nav a {
  position: relative;
  display: inline-flex;
  align-items: center;
  align-self: stretch;
  min-height: 56px;
  padding: 0 var(--space-3);
  border-radius: 0;
  background: none;
  color: var(--v-ink-3);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.005em;
  transition: color var(--v-fast) var(--v-ease);
}

.app-bar-inner,
.app-nav,
.nav-collapse {
  align-items: stretch;
}

.app-nav a:hover {
  color: var(--v-ink);
  background: none;
  box-shadow: none;
}

.app-nav a[aria-current='page'] {
  color: var(--v-ink);
  background: none;
  box-shadow: none;
}

/*
  left/right span the item, so width and transform must both be cleared.
  veyra-app.css sets width: 16px and transform: translateX(-50%) for the dash
  it draws instead; inheriting those here left the box over-constrained, so
  right: 0 was dropped and a 16px bar sat on the item's left edge - measured
  41.69px left of the label at 1440px and 1100px. Resetting them lets left and
  right do the work, which centres the underline on the label by construction
  and keeps every item identical.
*/
.app-nav a[aria-current='page']::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  width: auto;
  bottom: -1px;
  height: 2px;
  border-radius: 0;
  background: var(--v-accent);
  box-shadow: none;
  transform: none;
}

/* ============================================================ 7. panels */

.card,
.kpi,
.order-card,
.empty-state,
.all-clear {
  background: var(--v-surface);
  border: 1px solid var(--v-line);
  border-radius: var(--v-r-panel);
  box-shadow: none;
  transition: border-color var(--v-fast) var(--v-ease);
}

.card::before,
.kpi::before,
.kpi::after,
.order-card::before {
  display: none !important;   /* no top-edge light, no left stripes */
}

.card:hover,
.kpi:hover,
.order-card:hover {
  transform: none;            /* panels do not move under the cursor */
  border-color: var(--v-line-strong);
  box-shadow: none;
}

.card-head {
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--v-line-quiet);
}

.card-title {
  font-size: var(--text-xl);
}

/* ------------------------------------------------------------ KPI ------ */

/*
  A figure, its label and one line of context. The label sits above in mono
  small-caps so the eye finds the number first, and the number is set large
  enough to be read across a kitchen pass.
*/
.kpi {
  padding: var(--space-5) var(--space-5) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.kpi-value {
  font-family: var(--font-sans);
  font-size: clamp(1.6rem, 1.3rem + 1vw, 2.1rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--v-ink);
}

.kpi-value.warn { color: var(--v-watch); }
.kpi-value.alert { color: var(--v-critical); }

.kpi-note {
  font-size: var(--text-2xs);
  letter-spacing: 0.04em;
  color: var(--v-ink-3);
  text-transform: none;
}

/* =========================================================== 8. tables */

/*
  Density is the point. An operator scanning ninety lines wants rows close
  enough to compare and a header that stays legible while they scroll.
*/
.table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--text-sm);
}

/*
  The header is NOT sticky.

  .table-wrap is an overflow:auto container, so a sticky <th> sticks to that
  container rather than to the viewport - and with a non-zero `top` it
  offsets itself downward, leaving an empty band above the header exactly as
  tall as the offset. The wrap scrolls horizontally, so vertical stickiness
  bought nothing and cost a visible gap.
*/
.table thead th {
  padding: var(--space-3) var(--space-4);
  background: var(--v-ground-2);
  border-bottom: 1px solid var(--v-line);
  text-align: left;
  white-space: nowrap;
}

.table thead th.num,
.table td.num {
  text-align: right;
}

.table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--v-line-quiet);
  color: var(--v-ink);
  vertical-align: middle;
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

.inv-row,
.table tbody tr {
  transition: background var(--v-fast) var(--v-ease);
}

.inv-row:hover,
.table tbody tr:hover {
  background: var(--v-surface-2);
  box-shadow: none;
}

/*
  NO COLOURED EDGE MARKERS, ANYWHERE.

  Both app.css and veyra-app.css draw a 2-3px border-left on .lane-low and
  .lane-out, and the inventory table drew a lit spine on hover. A stripe at
  the very edge of a row encodes meaning where nobody reads, duplicates the
  status the row already states in words, and turns any list with mixed
  states into a colour chart.

  The eyebrow on each lane row ("LOW STOCK", "OUT OF STOCK") keeps its
  colour and does the whole job.
*/
.inv-row > td:first-child::before,
.lane-out::before,
.lane-low::before {
  display: none !important;
}

.lane-row,
.lane-in,
.lane-low,
.lane-out {
  border-left: 0 !important;
  padding-left: var(--space-4);
}

/* The status word carries the state. */
.lane-kind {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.lane-kind-in { color: var(--v-ink-3); }
.lane-kind-low { color: var(--v-watch); }
.lane-kind-out { color: var(--v-critical); }

.lane-name {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--v-ink);
}

.lane-why {
  font-size: var(--text-xs);
  color: var(--v-ink-3);
}

.cell-sub {
  display: block;
  margin-top: 3px;
  font-size: var(--text-2xs);
  letter-spacing: 0.04em;
  color: var(--v-ink-3);
}

/* ============================================================ 9. status */

/*
  One shape for every state. The difference between them is ink and a
  hairline, not a fill - a table of forty filled pills is a rainbow, which
  is the thing to avoid.
*/
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px;
  border: 1px solid var(--v-line-strong);
  border-radius: var(--v-r-pill);
  background: none;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--v-ink-2);
}

.pill::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.pill-in {
  color: var(--v-ink-2);
  border-color: var(--v-line-strong);
  background: none;
}

.pill-low {
  color: var(--v-watch);
  border-color: color-mix(in srgb, var(--v-watch) 34%, transparent);
  background: var(--v-watch-wash);
}

.pill-out {
  color: var(--v-critical);
  border-color: color-mix(in srgb, var(--v-critical) 36%, transparent);
  background: var(--v-critical-wash);
}

/* Legend and activity dots follow the same restraint: no pulsing. */
.legend-dot,
.act-dot {
  animation: none !important;
  box-shadow: none !important;
}

.legend-in, .act-in { color: var(--v-ink-3); background: var(--v-ink-3); }
.legend-low, .act-low { color: var(--v-watch); background: var(--v-watch); }
.legend-out, .act-out { color: var(--v-critical); background: var(--v-critical); }

.delta-up { color: var(--v-ink-2); }
.delta-down { color: var(--v-critical); }
.trend-up { color: var(--v-ink-2); }
.trend-down { color: var(--v-critical); }

/* ========================================================== 10. controls */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 14px;
  border: 1px solid transparent;
  border-radius: var(--v-r-control);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.005em;
  cursor: pointer;
  transition:
    background var(--v-fast) var(--v-ease),
    border-color var(--v-fast) var(--v-ease),
    color var(--v-fast) var(--v-ease);
}

.btn:hover:not(:disabled) {
  transform: none;             /* buttons do not lift */
}

.btn-primary {
  background: var(--v-accent);
  border-color: var(--v-accent);
  color: var(--accent-contrast);
  box-shadow: none;
}

.btn-primary:hover:not(:disabled) {
  background: var(--v-accent-hi);
  border-color: var(--v-accent-hi);
  box-shadow: none;
}

.btn-secondary {
  background: var(--v-surface-2);
  border-color: var(--v-line-strong);
  color: var(--v-ink);
}

.btn-secondary:hover:not(:disabled) {
  background: var(--v-surface-3);
  border-color: var(--v-ink-faint);
}

.btn-ghost {
  background: none;
  border-color: transparent;
  color: var(--v-ink-2);
}

.btn-ghost:hover:not(:disabled) {
  background: var(--v-surface-2);
  color: var(--v-ink);
}

.btn-sm {
  padding: 5px 10px;
  font-size: var(--text-xs);
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ------------------------------------------------------------ inputs --- */

.input,
select.input,
textarea.input {
  width: 100%;
  padding: 8px 11px;
  background: var(--v-ground-2);
  border: 1px solid var(--v-line-strong);
  border-radius: var(--v-r-control);
  color: var(--v-ink);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  transition:
    border-color var(--v-fast) var(--v-ease),
    background var(--v-fast) var(--v-ease);
}

.input::placeholder { color: var(--v-ink-faint); }

.input:hover:not(:focus) { border-color: var(--v-ink-faint); }

.input:focus,
.input:focus-visible {
  outline: none;
  border-color: var(--v-accent);
  background: var(--v-ground);
  box-shadow: 0 0 0 3px var(--v-accent-wash);
}

.input-hint {
  margin: 5px 0 0;
  font-size: var(--text-xs);
  color: var(--v-ink-3);
}

.form-row label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-3);
}

/* Focus is always the accent, never the browser default. */
a:focus-visible,
button:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--v-accent);
  outline-offset: 2px;
  border-radius: var(--v-r-control);
}

/* ======================================================== 11. overlays */

.modal,
.drawer {
  background: rgba(6, 10, 16, 0.62);
}

.modal-panel,
.drawer-panel {
  background: var(--v-surface);
  border: 1px solid var(--v-line-strong);
  border-radius: var(--v-r-panel);
  box-shadow: var(--v-shadow-overlay);
}

.drawer-panel {
  border-radius: var(--v-r-panel) 0 0 var(--v-r-panel);
}

.modal-head,
.drawer-head {
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--v-line);
}

/* ==================================================== 12. product states */

/*
  Empty, loading and error are product states, not accidents, and they get
  the same care as a populated screen: a line of type that says what is
  true, one that says what to do, and the control that does it.
*/
.empty-state {
  padding: var(--space-12) var(--space-6);
  text-align: center;
  background: var(--v-ground-2);
  border: 1px dashed var(--v-line-strong);
}

.empty-state::before,
.empty-state::after {
  display: none !important;   /* no rings, no grid, no ornament */
}

.empty-title,
.empty-state strong {
  display: block;
  margin-bottom: var(--space-2);
  font-family: var(--v-display);
  font-size: var(--text-xl);
  font-weight: 500;
  color: var(--v-ink);
}

.empty-body {
  max-width: 46ch;
  margin: 0 auto;
  color: var(--v-ink-2);
  font-size: var(--text-sm);
}

.empty-state .btn {
  margin-top: var(--space-5);
}

/* Skeletons: the shape of the content that is coming, at rest. */
.v-skeleton {
  display: block;
  height: 1em;
  border-radius: var(--v-r-control);
  background: linear-gradient(
    90deg,
    var(--v-surface-2) 0%,
    var(--v-surface-3) 50%,
    var(--v-surface-2) 100%
  );
  background-size: 200% 100%;
  animation: v-shimmer 1.4s linear infinite;
}

.v-skeleton + .v-skeleton { margin-top: var(--space-2); }
.v-skeleton-sm { width: 40%; }
.v-skeleton-lg { height: 1.8em; width: 65%; }

@keyframes v-shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

/* Inline notices. Ink and a hairline; no icon, no fill. */
.v-notice {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--v-line-strong);
  border-radius: var(--v-r-control);
  background: var(--v-surface-2);
  font-size: var(--text-sm);
  color: var(--v-ink-2);
}

.v-notice-warn {
  border-color: color-mix(in srgb, var(--v-watch) 32%, transparent);
  background: var(--v-watch-wash);
  color: var(--v-watch);
}

.v-notice-error,
.form-error:not(:empty) {
  border: 1px solid color-mix(in srgb, var(--v-critical) 34%, transparent);
  border-radius: var(--v-r-control);
  background: var(--v-critical-wash);
  color: var(--v-critical);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
}

.v-notice-ok {
  border-color: var(--v-accent-line);
  background: var(--v-accent-wash);
  color: var(--v-accent-hi);
}

/* ========================================================= 13. the meter */

/*
  The health reading. A plain channel and a solid fill: the figure beside it
  is what carries the meaning, so the bar only has to be honest about
  proportion.
*/
.meter {
  height: 6px;
  border-radius: 2px;
  background: var(--v-surface-3);
  overflow: hidden;
  box-shadow: none;
}

.meter-fill {
  height: 100%;
  border-radius: 2px;
  background: var(--v-accent);
  box-shadow: none;
  animation: none;
}

.meter-fill::after { display: none; }

.health-card {
  background: var(--v-surface);
  overflow: visible;
}

.health-card::after { display: none !important; }   /* no sweep */

.health-score {
  font-family: var(--font-sans);
  font-size: clamp(2.6rem, 2rem + 2.4vw, 3.6rem);
  font-weight: 600;
  letter-spacing: -0.035em;
  color: var(--v-ink);
  text-shadow: none;
}

.health-of {
  color: var(--v-ink-3);
  font-size: var(--text-md);
}

/* ========================================================= 14. charts */

.bar-fill,
.admin-bar-fill,
.flow-fill,
.split-fill {
  background: var(--v-accent);
  box-shadow: none;
  animation: none;
  border-radius: 2px;
}

.bar-track,
.admin-bar-track,
.flow-track,
.split-track {
  background: var(--v-surface-3);
  box-shadow: none;
  border-radius: 2px;
}

/* ======================================================= 15. arrival */

/*
  One movement, once: content fades in over 180ms as it arrives. No
  staggering, no sliding sections, nothing that repeats on every scroll.
*/
@keyframes v-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

main.page > .card,
main.page > .kpi-row,
main.page > section {
  animation: v-fade var(--v-base) var(--v-ease) both;
  transform: none !important;
}

main.page > *:nth-child(n) { animation-delay: 0ms; }

.lane-row:hover {
  background: var(--v-surface-2);
  transform: none;
}

/* ====================================================== 16. responsive */

@media (max-width: 900px) {
  .app-bar-inner { min-height: 52px; }

  .app-nav a { padding: 14px var(--space-2); }

  /* Dense tables become stacked records rather than a sideways scroll. */
  .table.table-stack thead { display: none; }

  .table.table-stack tbody tr {
    display: block;
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--v-line);
  }

  .table.table-stack td {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    padding: 3px var(--space-1);
    border: 0;
  }

  .table.table-stack td::before {
    content: attr(data-label);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--v-ink-3);
  }
}

@media (max-width: 640px) {
  .page-title { font-size: 1.75rem; }
  .kpi { padding: var(--space-4); }
  .kpi-value { font-size: 1.5rem; }
  .empty-state { padding: var(--space-8) var(--space-4); }
}

/* =================================================== 17. reduced motion */

@media (prefers-reduced-motion: reduce) {
  .v-skeleton,
  main.page > .card,
  main.page > .kpi-row,
  main.page > section {
    animation: none !important;
  }

  * {
    transition-duration: 1ms !important;
  }
}

/* ====================================================== 18. the front door */

/*
  Sign-in and sign-up were built at different times against different
  stylesheets: login.html renders .login-*, signup.html renders .auth-*.
  One is a centred card, the other a bare column; one labels fields in
  sentence case, the other in mono small-caps. Arriving at Veyra therefore
  looked like arriving at two products.

  Both vocabularies are unified here rather than rewritten, because the two
  pages are the most heavily tested path in the browser suites and their
  markup is what those tests drive.
*/

/*
  The page container, and ONLY the page container.

  .auth-shell was in this list and must not be: it is not a page shell
  despite the name. signup.html uses it twice, as the max-width wrapper
  INSIDE the masthead and the footer -

      <header class="auth-bar"><div class="auth-shell"> logo </div></header>
      <footer class="auth-foot"><div class="auth-shell"> links </div></footer>

  - so `min-height: 100vh` made the masthead a full screen tall and centred
  the logo inside it, pushing the entire registration form one whole
  viewport below the fold. The page looked blank apart from a centred logo;
  the form was present, styled and working the whole time, just scrolled out
  of sight. The footer got the same treatment, which is why the document
  ended up roughly three viewports tall.

  .login (login.html) and .auth-main (signup.html) are the real containers
  and keep this treatment unchanged.
*/
.login,
.auth-main {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-8) var(--space-5);
  background: var(--v-ground);
}

/* One card, one width, one treatment. */
.login-card,
.auth-card {
  width: 100%;
  max-width: 27rem;
  margin: 0 auto;
  padding: var(--space-8);
  background: var(--v-surface);
  border: 1px solid var(--v-line);
  border-radius: var(--v-r-panel);
  box-shadow: none;
}

.login-brand,
.auth-brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-bottom: var(--space-6);
  font-family: var(--v-display);
  font-size: 1.12rem;
  font-weight: 600;
  color: var(--v-ink);
}

/*
  The sign-up brand sits in its own bar outside the card, where login keeps
  it inside. Positioning it absolutely resolved it against a centred
  ancestor and parked it halfway down the page, so it is kept in flow and
  centred above the card instead: the same place on both pages.
*/
.auth-bar {
  position: static;
  display: flex;
  justify-content: center;
  width: 100%;
  margin-bottom: var(--space-6);
  padding: 0;
  background: none;
  border: 0;
}

/* Air under the standfirst before the first label. */
.auth-form { margin-top: var(--space-6); }

.auth-form .field-row { display: flex; gap: var(--space-4); }
.auth-form .field-row > .field { flex: 1; }

.login-eyebrow,
.auth-eyebrow {
  display: block;
  margin-bottom: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--v-accent);
}

.login-title,
.auth-title {
  margin: 0 0 var(--space-3);
  font-family: var(--v-display);
  font-size: clamp(1.6rem, 1.3rem + 1vw, 2rem);
  font-weight: 500;
  letter-spacing: -0.015em;
  line-height: 1.16;
  color: var(--v-ink);
}

.login-title .dot,
.auth-title .stop {
  color: var(--v-accent);
}

.login-sub,
.auth-sub {
  margin: 0 0 var(--space-7);
  color: var(--v-ink-2);
  font-size: var(--text-sm);
}

/* Fields: one label style across both pages. */
.login-field,
.auth-form .field,
.auth-form .field-row > .field {
  margin-bottom: var(--space-5);
}

.login-field label,
.auth-form label {
  display: block;
  margin-bottom: 6px;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-3);
}

.login-field input,
.auth-form input {
  width: 100%;
  padding: 9px 11px;
  background: var(--v-ground-2);
  border: 1px solid var(--v-line-strong);
  border-radius: var(--v-r-control);
  color: var(--v-ink);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  transition: border-color var(--v-fast) var(--v-ease);
}

.login-field input::placeholder,
.auth-form input::placeholder {
  color: var(--v-ink-faint);
}

.login-field input:focus,
.auth-form input:focus {
  outline: none;
  border-color: var(--v-accent);
  background: var(--v-ground);
  box-shadow: 0 0 0 3px var(--v-accent-wash);
}

/* The reveal control is a control, not a link. */
.login-password button {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-3);
  background: none;
  border: 0;
  cursor: pointer;
}

.login-password button:hover { color: var(--v-ink); }

.login-submit,
.auth-submit {
  width: 100%;
  margin-top: var(--space-2);
  padding: 11px 16px;
  background: var(--v-accent);
  border: 1px solid var(--v-accent);
  border-radius: var(--v-r-control);
  color: var(--accent-contrast);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--v-fast) var(--v-ease);
}

.login-submit:hover:not(:disabled),
.auth-submit:hover:not(:disabled) {
  background: var(--v-accent-hi);
  border-color: var(--v-accent-hi);
}

/*
  The progress rail on sign-up. It tells someone how many steps are left,
  which is the single most useful thing an account form can say, so it is
  kept and given the instrument voice rather than removed for symmetry.
*/
.auth-steps {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-faint);
}

.auth-steps .is-current,
.auth-steps [aria-current] { color: var(--v-accent); }

/* Errors and hints. */
.login-field-error,
.field-error {
  margin: 5px 0 0;
  font-size: var(--text-xs);
  color: var(--v-critical);
}

.login-note,
.field-hint,
.auth-legal,
.auth-dev-note {
  font-size: var(--text-xs);
  color: var(--v-ink-3);
}

.login-error:not([hidden]) {
  margin-bottom: var(--space-5);
  padding: var(--space-3) var(--space-4);
  border: 1px solid color-mix(in srgb, var(--v-critical) 34%, transparent);
  border-radius: var(--v-r-control);
  background: var(--v-critical-wash);
  color: var(--v-critical);
  font-size: var(--text-sm);
}

/* Footers under the card. */
.login-switch,
.auth-switch,
.login-back,
.auth-foot {
  margin-top: var(--space-6);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--v-ink-2);
}

.login-switch a,
.auth-switch a,
.login-back a {
  color: var(--v-accent);
  font-weight: 500;
}

.login-switch a:hover,
.auth-switch a:hover,
.login-back a:hover { color: var(--v-accent-hi); }

.login-legal,
.auth-foot-legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-4);
  margin-top: var(--space-5);
  font-size: var(--text-xs);
}

.login-legal a,
.auth-foot-legal a { color: var(--v-ink-3); }

.login-legal a:hover,
.auth-foot-legal a:hover { color: var(--v-ink-2); }

@media (max-width: 560px) {
  .login-card,
  .auth-card { padding: var(--space-6); border: 0; background: none; }
  .auth-bar { position: static; margin-bottom: var(--space-5); }
}

/* ==================================================== 19. the operator console */

/*
  Admin was built on app.css alone and never received the later surface
  work, so it had drifted a visual generation behind the customer product.
  It is the same system here, with one deliberate difference: the masthead
  carries a marked badge.

  Knowing which console you are in is a safety feature when the two look
  alike and one of them can see every business on the platform.
*/

.admin-bar {
  background: var(--v-ground-2);
  border-bottom: 1px solid var(--v-line);
}

.admin-badge {
  margin-left: var(--space-2);
  padding: 2px 7px;
  border: 1px solid var(--v-accent-line);
  border-radius: var(--v-r-pill);
  background: var(--v-accent-wash);
  color: var(--v-accent);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.admin-section {
  background: var(--v-surface);
  border: 1px solid var(--v-line);
  border-radius: var(--v-r-panel);
}

.admin-section-head {
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--v-line-quiet);
}

.admin-section-title {
  font-family: var(--v-display);
  font-size: var(--text-xl);
  font-weight: 500;
  color: var(--v-ink);
}

.admin-count,
.admin-org {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-3);
}

/* The platform role. ADMIN is marked; BUSINESS is simply not marked. */
.role-tag {
  display: inline-flex;
  align-items: center;
  padding: 2px 7px;
  border: 1px solid var(--v-line-strong);
  border-radius: var(--v-r-pill);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--v-ink-3);
  background: none;
}

.role-tag.is-admin {
  border-color: var(--v-accent-line);
  background: var(--v-accent-wash);
  color: var(--v-accent);
}

/* Integration status. The same three states as the rest of the product. */
.status-dot {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--v-ink-2);
}

.status-dot::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}

.status-connected { color: var(--v-ink-2); }
.status-pending { color: var(--v-watch); }
.status-error { color: var(--v-critical); }
.status-disconnected { color: var(--v-ink-faint); }

/* Diagnostics readouts. */
.admin-bar-row {
  display: grid;
  grid-template-columns: minmax(8rem, 14rem) 1fr auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-2) 0;
  font-size: var(--text-sm);
}

.admin-bar-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--v-ink-2);
}

/* Loading and failure, in the product's own voice. */
.admin-loading {
  padding: var(--space-10);
  text-align: center;
  color: var(--v-ink-3);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.admin-notice:not([hidden]),
.admin-error-line {
  padding: var(--space-3) var(--space-4);
  border: 1px solid color-mix(in srgb, var(--v-critical) 34%, transparent);
  border-radius: var(--v-r-control);
  background: var(--v-critical-wash);
  color: var(--v-critical);
  font-size: var(--text-sm);
}

.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: var(--space-5);
}

@media (max-width: 720px) {
  .admin-bar-row { grid-template-columns: 1fr auto; }
  .admin-bar-track { grid-column: 1 / -1; }
}

/*
  The operator nav carries eight sections plus three account controls, which
  is three more than the customer bar and enough to overflow a 1440px window
  at the customer spacing. It gets its own tighter rhythm rather than a
  smaller window: truncating "Analytics" to "Analyt" is how a console starts
  looking unfinished.
*/
.admin-bar .app-nav {
  flex-wrap: nowrap;
  gap: 0;
  min-width: 0;
}

.admin-bar .app-nav a {
  padding: 0 10px;
  font-size: var(--text-xs);
  white-space: nowrap;
}

.admin-bar .app-nav a[aria-current='page']::after {
  left: 10px;
  right: 10px;
}

.admin-bar .app-bar-inner {
  gap: var(--space-4);
}

.admin-bar .app-account {
  gap: var(--space-2);
  flex: none;
}

.admin-bar .app-account .btn {
  padding: 5px 9px;
  font-size: var(--text-xs);
}

/* Below a laptop the console nav scrolls rather than wrapping into rows. */
@media (max-width: 1180px) {
  .admin-bar .nav-collapse {
    overflow-x: auto;
    scrollbar-width: thin;
  }

  .admin-bar .app-nav a { padding: 0 8px; }
}
