/* =========================================================================
   Inventory Module — Design System
   Style: Data-Dense Dashboard
   Fonts: Fira Code (headings), Fira Sans (body)
   ========================================================================= */

:root {
  /* Brand */
  --color-primary:        #4C74A6;   /* cobalt-600 */
  --color-primary-600:    #3E6394;
  --color-primary-700:    #35608F;
  --color-primary-50:     #E7EEF6;
  --color-secondary:      #8FA6C0;
  --color-cta:            #5E86B8;   /* sky-500 */
  --color-cta-600:        #4C74A6;
  --color-cta-50:         #EAF0F7;

  /* Surfaces */
  --color-bg:             #e8e8e8;   /* app background — airy cobalt tint */
  --color-body:           #cfedff;
  --color-surface:        #FFFFFF;
  --color-surface-alt:    #F5F8FA;
  --color-border:         #E4E9EE;
  --color-border-strong:  #D3DBE2;
  --color-table-head:     #E7EEF3;   /* every table header, every page — themed below */

  /* Text */
  --color-text:           #2A323B;   /* deep cobalt ink */
  --color-text-muted:     #5C6673;   /* slate-600 */
  --color-text-subtle:    #8794A1;

  /* Semantic */
  --color-success:        #3F9E82;
  --color-success-50:     #E3F3EE;
  --color-warn:           #CF9A3A;
  --color-warn-50:        #F7EFDB;
  --color-danger:         #D06A5A;
  --color-danger-50:      #FAE9E5;
  --color-info:           #4C74A6;
  --color-info-50:        #E7EEF6;

  /* Elevation */
  --shadow-sm:  0 1px 2px 0 rgb(15 23 42 / 0.04);
  --shadow:     0 1px 3px 0 rgb(15 23 42 / 0.06), 0 1px 2px -1px rgb(15 23 42 / 0.04);
  --shadow-md:  0 4px 6px -1px rgb(15 23 42 / 0.07), 0 2px 4px -2px rgb(15 23 42 / 0.05);
  --shadow-lg:  0 10px 15px -3px rgb(15 23 42 / 0.08), 0 4px 6px -4px rgb(15 23 42 / 0.04);

  /* Layout */
  --sidebar-w:        161px;
  --sidebar-w-collapsed: 64px;
  --header-h:         56px;
  --radius-sm:        5px;
  --radius:           5px;
  --radius-md:        5px;
  --radius-lg:        5px;
  --border-input-1: #D3DBE2;
  --color-value-tnum: #35608F;
  --bg-input-1: #FFFFFF;
  --bg-page-2: #f7f7f7;

  /* Shell chrome (sidebar / header / scrollbar / search) — themeable; see THEME LAYER below.
     Default values equal the original hardcoded chrome colors, so the Default theme is unchanged. */
  --color-sidebar-bg:          #1E3A66;
  --color-sidebar-border:      #17304F;
  --color-sidebar-divider:     rgba(255,255,255,0.08);
  --color-sidebar-heading:     #F3F7FC;
  --color-sidebar-section:     #6E82A0;
  --color-sidebar-text:        #AEBED6;
  --color-sidebar-hover-bg:    rgba(255,255,255,0.06);
  --color-sidebar-hover-text:  #FFFFFF;
  --color-sidebar-active-bg:   rgba(255,255,255,0.12);
  --color-sidebar-active-text: #FFFFFF;
  --color-sidebar-accent:      #6BA8DE;
  --color-header-bg:           rgba(255,255,255,0.85);
  --color-search-bg:           #fcfeec;
  --color-scrollbar:           #CBD5E1;
  --color-scrollbar-hover:     #94A3B8;
  --brand-gradient:            linear-gradient(135deg,#4C74A6,#6BA8DE);
  --bg-page-1: #f2f2f2;
}

/* =========================================================================
   THEME LAYER
   Switch the portal palette by setting data-theme on <html> (done in js/app.js
   from localStorage['vava-theme']). No attribute / "default" = the current look.
   Each block redefines the SAME token set as :root above — copy one to add a
   theme. Palette source of record: css/vava-palettes.css.
   Scope: core "palette swap" (surfaces, text, borders, accent + shell). A few
   hardcoded component tints (some table rows, badge text/borders) are not yet
   tokenized, so Graphite (dark) themes the shell + cards but may leave a few
   light accents — a known follow-up.
   ========================================================================= */

/* ---- Modern (light, cool slate + blue) ---- */
[data-theme="modern"] {
  --color-primary: #2563EB; --color-primary-600: #1D4FD8; --color-primary-700: #1E40AF; --color-primary-50: #DFE9FB;
  --color-secondary: #5B6B80;
  --color-cta: #2563EB; --color-cta-600: #1D4FD8; --color-cta-50: #DFE9FB;
  --color-bg: #E9EEF4; --color-body: #E9EEF4; --color-surface: #FFFFFF; --color-surface-alt: #F7F9FC;
  --color-border: #D8E0EA; --color-border-strong: #CBD5E1; --color-table-head: #E4EBF4;
  --color-text: #334155; --color-text-muted: #5B6B80; --color-text-subtle: #8B99AD;
  --color-success: #059669; --color-success-50: #DCF3EA;
  --color-warn: #D97706; --color-warn-50: #FBEED6;
  --color-danger: #DC2626; --color-danger-50: #FBE0E0;
  --color-info: #2563EB; --color-info-50: #E3EDFC;
  --border-input-1: #CBD5E1; --color-value-tnum: #1E40AF; --bg-input-1: #EDF1F6;
  --color-sidebar-bg: #F7F9FC; --color-sidebar-border: #D8E0EA; --color-sidebar-divider: #E4EAF1;
  --color-sidebar-heading: #0F172A; --color-sidebar-section: #8B99AD; --color-sidebar-text: #5B6B80;
  --color-sidebar-hover-bg: #EDF1F6; --color-sidebar-hover-text: #0F172A;
  --color-sidebar-active-bg: #DFE9FB; --color-sidebar-active-text: #1E40AF; --color-sidebar-accent: #2563EB;
  --color-header-bg: rgba(255,255,255,0.85);
  --color-search-bg: #EDF1F6;
  --color-scrollbar: #CBD5E1; --color-scrollbar-hover: #94A3B8;
  --brand-gradient: linear-gradient(135deg,#2563EB,#5B8CFF);
  --bg-page-1: #E9EEF4;   /* page surface — themed (was leaking :root's light value) */
}

/* ---- Graphite (dark, blue-gray + electric blue) ---- */
[data-theme="graphite"] {
  --color-primary: #5B8CFF; --color-primary-600: #5B8CFF; --color-primary-700: #8AB0FF; --color-primary-50: #1D2637;
  --color-secondary: #7E8CA2;
  --color-cta: #5B8CFF; --color-cta-600: #5B8CFF; --color-cta-50: #1D2637;
  --color-bg: #0B0E14; --color-body: #0B0E14; --color-surface: #131826; --color-surface-alt: #0E121C;
  --color-border: #232C3E; --color-border-strong: #2A344A; --color-table-head: #1B2333;
  --color-text: #C2CCDB; --color-text-muted: #7E8CA2; --color-text-subtle: #55637A;
  --color-success: #34D399; --color-success-50: #152A22;
  --color-warn: #FBBF24; --color-warn-50: #2A2413;
  --color-danger: #F87171; --color-danger-50: #2C1A1C;
  --color-info: #5B8CFF; --color-info-50: #152036;
  --wiz-idbar-bg: rgba(91,140,255,0.14);   /* Product id-bar: soft blue on dark; light themes fall back to #E7F0FF */
  --wiz-idbar-border: rgba(91,140,255,0.34);
  --border-input-1: #2A344A; --color-value-tnum: #8AB0FF; --bg-input-1: #1A2130;
  --color-sidebar-bg: #0E121C; --color-sidebar-border: #232C3E; --color-sidebar-divider: rgba(255,255,255,0.06);
  --color-sidebar-heading: #F1F5F9; --color-sidebar-section: #55637A; --color-sidebar-text: #C2CCDB;
  --color-sidebar-hover-bg: rgba(255,255,255,0.05); --color-sidebar-hover-text: #F1F5F9;
  --color-sidebar-active-bg: #1D2637; --color-sidebar-active-text: #F1F5F9; --color-sidebar-accent: #5B8CFF;
  --color-header-bg: rgba(19,24,38,0.85);
  --color-search-bg: #1A2130;
  --color-scrollbar: #2A344A; --color-scrollbar-hover: #3A465E;
  --brand-gradient: linear-gradient(135deg,#3E6394,#5B8CFF);
  --bg-page-1: #0B0E14;   /* page surface — themed (was leaking :root's light value) */
}

/* ---- Sandstone (warm bone neutrals + forest green) ---- */
[data-theme="sandstone"] {
  --color-primary: #15693F; --color-primary-600: #125836; --color-primary-700: #0F4A2D; --color-primary-50: #E6EFE7;
  --color-secondary: #79716B;
  --color-cta: #15693F; --color-cta-600: #125836; --color-cta-50: #E6EFE7;
  --color-bg: #F1EDE5; --color-body: #F1EDE5; --color-surface: #FFFFFF; --color-surface-alt: #FAF8F3;
  --color-border: #E0D9CB; --color-border-strong: #D6CFC0; --color-table-head: #EFE9DC;
  --color-text: #44403C; --color-text-muted: #79716B; --color-text-subtle: #A39A8B;
  --color-success: #15803D; --color-success-50: #E4F1E7;
  --color-warn: #B45309; --color-warn-50: #F6E9D9;
  --color-danger: #C2410C; --color-danger-50: #F7E2D7;
  --color-info: #15693F; --color-info-50: #E9F1EA;
  --border-input-1: #D6CFC0; --color-value-tnum: #0F4A2D; --bg-input-1: #EFEAE0;
  --color-sidebar-bg: #FAF8F3; --color-sidebar-border: #E0D9CB; --color-sidebar-divider: #EBE6DB;
  --color-sidebar-heading: #1C1917; --color-sidebar-section: #A39A8B; --color-sidebar-text: #79716B;
  --color-sidebar-hover-bg: #EFEAE0; --color-sidebar-hover-text: #1C1917;
  --color-sidebar-active-bg: #E6EFE7; --color-sidebar-active-text: #0F4A2D; --color-sidebar-accent: #15693F;
  --color-header-bg: rgba(255,255,255,0.85);
  --color-search-bg: #EFEAE0;
  --color-scrollbar: #D6CFC0; --color-scrollbar-hover: #B8AE9C;
  --brand-gradient: linear-gradient(135deg,#15693F,#2E8B57);
  --bg-page-1: #F1EDE5;   /* page surface — themed (was leaking :root's light value) */
}

/* -------------------------------------------------------------------------
   Base
   ------------------------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  overflow: hidden;
  font-family: 'Hanken Grotesk', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--color-text);
  background: var(--color-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6, .font-mono {
  font-family: 'Schibsted Grotesk', system-ui, sans-serif;
  letter-spacing: -0.02em;
}

h1 { font-size: 22px; font-weight: 600; }
h2 { font-size: 17px; font-weight: 600; }
h3 { font-size: 15px; font-weight: 600; }

a { color: var(--color-primary-600); text-decoration: none; }
a:hover { color: var(--color-primary-700); text-decoration: none; }

button, [role="button"] { cursor: pointer; }

::-webkit-scrollbar         { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb   { background: var(--color-scrollbar); border-radius: 5px; border: 2px solid var(--color-bg); }
::-webkit-scrollbar-thumb:hover { background: var(--color-scrollbar-hover); }
::-webkit-scrollbar-track   { background: transparent; }

:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* -------------------------------------------------------------------------
   App shell
   ------------------------------------------------------------------------- */
.app {
  position: fixed;
  inset: 0;
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  overflow: hidden;
}

.app.collapsed { grid-template-columns: var(--sidebar-w-collapsed) 1fr; }

/* Collapsed sidebar — big icons only, labels hidden */
.app.collapsed .sidebar-brand { overflow: hidden; background: url('../img/vava-sales-logomark.svg') center / auto 28px no-repeat; }
.app.collapsed .sidebar-brand img { display: none; }
.app.collapsed .sidebar-section { font-size: 0; padding: 10px 0 0; margin-bottom: 0; }
.app.collapsed .nav-link { justify-content: center; gap: 0; font-size: 0; padding: 9px 0; }
.app.collapsed .nav-link svg { width: 20px; height: 20px; }
.app.collapsed .sidebar-footer { justify-content: center; padding: 12px 0; }
.app.collapsed .sidebar-footer > div,
.app.collapsed .sidebar-footer > .icon-btn { display: none; }

/* Sidebar */
.sidebar {
  height: 100%;
  background: var(--color-sidebar-bg);
  color: var(--color-sidebar-text);
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--color-sidebar-border);
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--color-sidebar-divider);
  color: var(--color-sidebar-heading);
  font-family: 'Hanken Grotesk', sans-serif;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.02em;
  height: var(--header-h);
}

.sidebar-brand .brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 5px;
  background: var(--brand-gradient);
  color: #fff;
  flex-shrink: 0;
}

.sidebar-section {
  padding: 14px 12px 1px;
  margin-bottom: -4px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-sidebar-section);
  font-family: 'Hanken Grotesk', sans-serif;
}

.sidebar-nav { padding: 4px 8px 16px; flex: 1; overflow-y: auto; }

.nav-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 5px;
  color: var(--color-sidebar-text);
  font-size: 13px;
  margin-top: 3px;
  margin-bottom: 1px;
  transition: background 150ms ease, color 150ms ease;
  text-decoration: none;
  position: relative;
}

.nav-link:hover { background: var(--color-sidebar-hover-bg); color: var(--color-sidebar-hover-text); text-decoration: none; }
.nav-link.active {
  background: var(--color-sidebar-active-bg);
  color: var(--color-sidebar-active-text);
}
.nav-link.active::before {
  content: '';
  position: absolute;
  left: -3px;
  top: 4px;
  bottom: 4px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--color-sidebar-accent);
}

.nav-link svg { width: 16px; height: 16px; flex-shrink: 0; }

.nav-badge {
  margin-left: auto;
  font-size: 10px;
  background: var(--color-danger);
  color: #fff;
  padding: 1px 6px;
  border-radius: 5px;
  font-family: 'Hanken Grotesk', sans-serif;
}

.sidebar-footer {
  padding: 12px;
  border-top: 1px solid var(--color-sidebar-divider);
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Support link pinned at the bottom of the sidebar (sits below the flex:1 nav) */
.sidebar-support { padding: 6px 8px 10px; border-top: 1px solid var(--color-sidebar-divider); }
.sidebar-support .nav-link { margin: 4px 0 0; }

.avatar {
  width: 32px; height: 32px;
  border-radius: 999px;
  background: var(--brand-gradient);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 600;
  font-size: 12px;
  font-family: 'Hanken Grotesk', sans-serif;
  flex-shrink: 0;
}

/* Main column */
.main { display: flex; flex-direction: column; min-width: 0; overflow: hidden; }

/* Header */
.header {
  position: sticky;
  top: 0;
  z-index: 30;
  height: var(--header-h);
  background: var(--color-header-bg);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 20px;
}

.breadcrumb {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--color-text-muted);
  font-size: 13px;
  font-family: 'Hanken Grotesk', sans-serif;
}
.breadcrumb a { color: var(--color-text-muted); }
.breadcrumb .sep { color: #CBD5E1; }
.breadcrumb .current { color: var(--color-text); font-weight: 500; }

.header-search {
  flex: 1;
  max-width: 420px;
  position: relative;
}
.header-search input {
  width: 100%;
  padding: 7px 12px 7px 34px;
  border: 1px solid var(--color-border);
  background: var(--color-search-bg);
  border-radius: 5px;
  font: inherit;
  font-size: 13px;
  color: var(--color-text);
}
.header-search input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(37,99,235,0.15);
}
.header-search svg {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--color-text-subtle);
}

.header-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }

.icon-btn {
  width: 34px; height: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 5px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text-muted);
  transition: background 150ms, color 150ms, border-color 150ms;
  position: relative;
}
.icon-btn:hover { background: var(--color-primary-50); color: var(--color-primary-700); }
.icon-btn svg { width: 18px; height: 18px; }
.icon-btn .dot {
  position: absolute; top: 6px; right: 6px;
  width: 8px; height: 8px; border-radius: 999px;
  background: var(--color-danger);
  border: 2px solid #fff;
}

/* Page */
.page { padding: 20px 24px 40px; background: var(--color-bg); flex: 1; min-height: 0; overflow-y: auto; }

/* Page footer — one grey line, always parked at the bottom of the page: the page
   becomes a flex column so `margin-top:auto` pushes the footer down when the content
   is short, and children keep their natural height (`flex: none`) so a tall page
   still scrolls instead of squashing. The footer box drops the page's bottom padding and
   takes the same 56px height as the sidebar's Support block, so the copyright line sits
   on the Support item's line. */
.page:has(> .page-footer) { display: flex; flex-direction: column; padding-bottom: 0; }
.page:has(> .page-footer) > * { flex: none; }
.page-footer {
  margin-top: auto;
  justify-content: flex-end;
  align-items: center;
  min-height: 56px;
  padding: 0;
  border-top: 1px solid var(--color-border);
  color: var(--color-text-subtle);
  font-size: 10px;
}

.page-header {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 16px;
  margin-bottom: 18px; flex-wrap: wrap;
}
.page-title { margin: 0; }
.page-subtitle {
  margin: 4px 0 0;
  color: var(--color-text-muted);
  font-size: 13px;
}
.page-actions { display: flex; gap: 8px; align-items: stretch; }
/* let the settings-gear button fill the row height so it matches sibling buttons */
.page-settings { display: flex; }

/* -------------------------------------------------------------------------
   Cards / KPI
   ------------------------------------------------------------------------- */
.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.card-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--color-border);
}
.card-header h2,
.card-header h3 { margin: 0; }
.card-header .sub {
  color: var(--color-text-subtle);
  font-size: 12px;
  margin-top: 2px;
}
.card-body { padding: 16px; }
.card-footer {
  padding: 10px 16px;
  border-top: 1px solid var(--color-border);
  display: flex; align-items: center; justify-content: space-between;
  font-size: 12px; color: var(--color-text-subtle);
}

/* Selectable product-type tiles (New Product wizard) — lift them off the white
   parent card with a stronger border + drop shadow, and a hover affordance. */
.type-grid > button.card {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-md);
  transition: border-color 150ms ease, box-shadow 150ms ease, transform 150ms ease;
}
.type-grid > button.card:hover {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-lg);
  transform: translateY(-1px);
}
/* Keep the tiles in scale with the later wizard steps — tighter box, smaller icon,
   balanced rows. Column count = tile count when ≤ 4, otherwise ceil(count / 2) so the
   tiles split into two even rows — 6 tiles → 3 per row (set via grid-3 in the markup). */
.type-grid { gap: 12px; }
.type-grid > button.card .card-body { padding: 12px; }
.type-grid > button.card .thumb { width: 45px; height: 45px; }
.type-grid > button.card .thumb svg { width: 26px; height: 26px; }
/* Icon and type name share one row — name sits to the right of its own icon. */
.type-grid > button.card .type-head { display: flex; align-items: center; gap: 10px; }
.type-grid > button.card .divider { margin: 9px 0; }
/* Label and value sit tight against each other, centred as a pair — labels
   right-aligned, values left-aligned, so all three rows line up. */
.type-grid .type-spec {
  display: grid;
  grid-template-columns: max-content minmax(0, max-content);
  justify-content: center;
  gap: 6px;
}
.type-grid .type-spec > .subtle { text-align: right; }

.kpi {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  /* Fluid: full padding on a wide screen, tighter as the row narrows, so four
     cards keep their row instead of one dropping below. */
  padding: clamp(10px, 0.5vw + 5px, 14px) clamp(11px, 0.7vw + 4px, 16px);
  display: flex; flex-direction: column; gap: 4px;
  position: relative;
  overflow: hidden;
  transition: box-shadow 200ms, transform 200ms;
}
.kpi:hover { box-shadow: var(--shadow-md); }
.kpi .label {
  font-size: 11px;
  font-family: 'Hanken Grotesk', sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-subtle);
}
.kpi .value {
  font-family: 'Hanken Grotesk', sans-serif;
  /* Scales down with the viewport so a narrow row costs width, not a wrap. */
  font-size: clamp(19px, 0.6vw + 12px, 24px);
  font-weight: 600;
  color: var(--color-text);
  line-height: 1.1;
}
.kpi .meta {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--color-text-muted);
}
/* A KPI figure that carries a unit or qualifier (e.g. Stock: 112 In) — number
   first, label after, tight together rather than spread across the card. */
.kpi-part { display: inline-flex; align-items: baseline; gap: 5px; }
.kpi-part-label { font-size: 13px; color: var(--color-text-muted); }
/* Safety net: KPI meta icons must never fall back to Lucide's 24px default. */
.kpi .meta svg { width: 14px; height: 14px; flex-shrink: 0; }
.kpi .meta .trend-up,
.kpi .meta .trend-down,
.kpi .meta .trend-flat { display: inline-flex; align-items: center; gap: 3px; }

/* KPI summary — a page's KPI cards folded behind a one-line glance bar. Collapsed by
   default: the glance answers the question most visits have, and the cards are one
   click away for the breakdown, so the table starts near the top of the page.
   Used by Stock, Catalog, Pricing and Warehouses; the toggle lives in js/app.js. */
.kpi-summary { margin-bottom: 14px; }
.kpi-summary__bar {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 9px 14px;
  cursor: pointer;
  font-family: inherit;
}
.kpi-summary__bar:hover { border-color: var(--color-border-strong); }
.kpi-summary__glance { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; font-size: 13px; color: var(--color-text-muted); }
.kpi-glance-item { display: inline-flex; align-items: center; gap: 6px; }
.kpi-glance-item strong { color: var(--color-text); font-weight: 700; font-variant-numeric: tabular-nums; }
.kpi-glance-item svg { width: 14px; height: 14px; color: var(--color-warn); }
.kpi-glance-sep { width: 1px; height: 15px; background: var(--color-border); }
.kpi-summary__cta { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--color-primary-700); white-space: nowrap; }
.kpi-summary__cta svg { width: 15px; height: 15px; transition: transform 160ms ease; }
.kpi-summary__cards { margin-top: 12px; }
.kpi-summary[data-kpis-collapsed] .kpi-summary__cards { display: none; }
.kpi-summary:not([data-kpis-collapsed]) .kpi-summary__cta svg { transform: rotate(180deg); }

.trend-up   { color: var(--color-success); }
.trend-down { color: var(--color-danger); }
.trend-flat { color: var(--color-text-subtle); }

/* Compact spec pairs — label and value read as one unit instead of being spread to
   opposite edges of a row. `.spec-strip` = one read-only panel of behaviour defaults,
   split by rules — deliberately NOT separate chips, which read as selectable filters;
   `.spec-list` = two-up grid of tight label → value pairs (summaries). */
.section-h { margin: 0 0 10px; }
.spec-strip {
  display: flex; flex-wrap: wrap;
  background: var(--color-table-head);
  border: 1px solid var(--color-border-strong);
  border-radius: 7px;
  overflow: hidden;
}
.spec-strip .spec {
  display: inline-flex; align-items: baseline; gap: 7px;
  padding: 9px 15px;
  border-right: 1px solid var(--color-border-strong);
}
.spec-strip .spec:last-child { border-right: 0; }
.spec-k { font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-text-muted); white-space: nowrap; }
.spec-v { font-size: 14px; font-weight: 700; color: var(--color-text); }
.spec-note { margin: 10px 0 0; font-size: 12px; line-height: 1.5; color: var(--color-text-subtle); }
.spec-list {
  display: grid; margin: 0;
  grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr);
  gap: 9px 10px; align-items: baseline;
}
.spec-list dt { font-size: 12px; color: var(--color-text-muted); text-align: right; }
.spec-list dd { margin: 0; font-size: 13px; font-weight: 600; color: var(--color-text); }
@media (max-width: 1100px) {
  .spec-list { grid-template-columns: max-content minmax(0, 1fr); }
}

/* Wizard Step 3 (Options & Behavior) — the step was three bare columns of labels on a
   white card: nothing framed a control, and half the row was empty. Each group now sits
   in a bordered panel with a tinted header on a tinted card body, so the step reads as
   solid blocks (figure) on a background (ground) instead of floating text. */
.opt-body { background: var(--color-surface-alt); border-radius: 0 0 var(--radius-md) var(--radius-md); }
.opt-sec + .opt-sec { margin-top: 20px; }
.opt-sec-h { display: flex; align-items: center; gap: 10px; margin: 0 0 12px; }
.opt-sec-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none;
  border-radius: 7px;
  background: var(--color-primary-50);
  color: var(--color-primary-700);
}
.opt-sec-icon svg { width: 16px; height: 16px; }
.opt-sec-h h3 { margin: 0; font-size: 15px; }
.opt-sec-h .sub { margin-top: 2px; font-size: 12px; color: var(--color-text-subtle); }
/* Two panels per row, equal height — a short control group can't leave a half-empty row. */
.opt-split { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; }
.opt-panel {
  display: flex; flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: 7px;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.opt-panel-h {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 13px;
  background: var(--color-surface-alt);
  border-bottom: 1px solid var(--color-border-strong);
  font-size: 12px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--color-text);
}
.opt-panel-h .unit { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--color-text-muted); }
/* The panel header already draws the rule above the table, and the panel border closes it below. */
.opt-panel .table-wrap { border-top: 0; }
.opt-panel .data-table tbody tr:last-child td { border-bottom: 0; }
.opt-panel-b { padding: 13px; }
/* One control per row, hairline-separated, so a label always belongs to the control under it. */
.opt-panel-b > .field + .field { margin-top: 13px; padding-top: 13px; border-top: 1px solid var(--color-border); }
.opt-hint { margin: 6px 0 0; font-size: 12px; line-height: 1.45; color: var(--color-text-subtle); }
/* A value this step shows but does not edit (it comes from the template). */
.opt-value {
  padding: 8px 12px; font-size: 13px;
  background: var(--color-surface-alt);
  border: 1px dashed var(--color-border-strong);
  border-radius: 5px;
  color: var(--color-text);
}
/* Retail is the base every % price type derives from — tinted so it reads as the anchor. */
.data-table tbody tr.opt-base-row,
.data-table tbody tr.opt-base-row:hover { background: var(--color-primary-50); }
.data-table tbody tr.opt-base-row td { font-weight: 600; color: var(--color-primary-700); }
.opt-tag { margin-left: 6px; font-size: 12px; font-weight: 500; color: var(--color-text-muted); }
@media (max-width: 1100px) {
  .opt-split { grid-template-columns: minmax(0, 1fr); }
}

/* -------------------------------------------------------------------------
   Buttons
   ------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  border-radius: 5px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text);
  cursor: pointer;
  transition: background 150ms ease, color 150ms ease, border-color 150ms ease, box-shadow 150ms ease;
  white-space: nowrap;
}
.btn svg { width: 16px; height: 16px; }

.btn-primary {
  background: var(--color-primary);
  color: #fff;
  box-shadow: 0 1px 2px rgba(29,78,216,0.20);
}
.btn-primary:hover { background: var(--color-primary-600); }
.btn-primary:focus-visible { box-shadow: 0 0 0 3px rgba(37,99,235,0.30); }

.btn-cta {
  background: var(--color-cta);
  color: #fff;
}
.btn-cta:hover { background: var(--color-cta-600); }

/* The secondary button carries a FACE, not just a border. On `--color-surface` it
   used to be `--color-surface` itself — a white chip on a white card, separated by
   one hairline — so it read as absent rather than quiet. `--color-surface-alt` is
   the step every theme already defines away from its card colour (and in dark it
   steps DOWN, #0E121C under #131826), so the tint works in both directions without
   a per-theme override. The hairline shadow is what carries the button when it
   sits ON a `--color-surface-alt` strip (`.opt-body`, `.sf-dns`), where the tint
   has nothing to separate from. Hover brightens to the card colour: the button
   lifts toward the surface instead of sinking into it. */
.btn-outline {
  border-color: var(--color-border-strong);
  color: var(--color-text);
  background: var(--color-surface-alt);
  box-shadow: 0 1px 1px rgba(16,24,40,0.05);
}
.btn-outline:hover {
  border-color: var(--color-primary);
  color: var(--color-primary-700);
  background: var(--color-surface);
}

.btn-ghost { color: var(--color-text-muted); }
.btn-ghost:hover { background: var(--color-primary-50); color: var(--color-primary-700); }

.btn-danger {
  background: var(--color-danger);
  color: #fff;
}
.btn-danger:hover { background: #DC2626; }

/* Excel-green download button — signals the target is a downloadable .xlsx. Hoisted
   byte-identical from ten pages (the import wizards, the imports list, onboarding, and
   stock.html, which wrote the same declarations without spaces after the colons).
   Literal hexes moved verbatim; every other variant above uses tokens, so these are
   probable drift — but a restyle is a separate change. */
.btn-excel { background: #E9F5EE; color: #1E7145; border-color: #B7DFC6; }
.btn-excel:hover { background: #DBEEE2; border-color: #1E7145; color: #1E7145; }

/* Excel action WITHOUT the green face — a green fill collides with the Available status
   subtab three inches away in the same toolbar, so the signal moves to the asset: the same
   XLS sheet the page-header Export control uses, arrow reversed (img/ImportXLS.png).
   18px rather than the 16px of `.btn svg` — this is a raster carrying a readable "XLS"
   tab, and it loses that tab a pixel sooner than a one-stroke lucide glyph loses its shape. */
.btn-xls img { width: 18px; height: 18px; }

/* Link-buttons (<a class="btn">) must not inherit the global a:hover blue text + underline. */
a.btn:hover { text-decoration: none; }
.btn-primary:hover, .btn-cta:hover, .btn-danger:hover { color: #fff; }

/* A disabled button must read as unavailable, not merely refuse the click — the
   variant hover colors are cancelled so nothing invites a second attempt. */
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary:disabled:hover { background: var(--color-primary); }
.btn-cta:disabled:hover { background: var(--color-cta); }
.btn-danger:disabled:hover { background: var(--color-danger); }
.btn-outline:disabled:hover { border-color: var(--color-border); color: inherit; background: var(--color-surface-alt); }
.btn-ghost:disabled:hover { background: transparent; color: var(--color-text-muted); }
.btn-excel:disabled:hover { background: #E9F5EE; border-color: #B7DFC6; }

.btn-sm { padding: 5px 9px; font-size: 12px; }
.btn-sm svg { width: 14px; height: 14px; }
.btn-lg { padding: 9px 14px; font-size: 14px; }
.btn-block { display: flex; width: 100%; justify-content: center; }
.btn-xl { padding: 12px 16px; font-size: 14px; }

/* Commit action — deliberately brighter than --color-primary so the one step that
   actually creates something reads as what is about to happen. Fixed accent: it stays
   the same vivid blue in every theme. */
.btn-bright {
  background: #1D6BFF;
  border-color: #1D6BFF;
  color: #fff;
  box-shadow: 0 1px 3px rgba(29,107,255,0.40);
}
.btn-bright:hover { background: #0B57E3; border-color: #0B57E3; }
.btn-bright:focus-visible { box-shadow: 0 0 0 3px rgba(29,107,255,0.35); }

/* -------------------------------------------------------------------------
   Forms
   ------------------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field label {
  font-size: 12px;
  font-family: 'Hanken Grotesk', sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  font-weight: 500;
}
/* Always-mandatory field marker. Static, unlike the wizard's toggleable `.req-star` — that one
   asks whether an attribute should be required; this one states that a field is. */
/* 2× the 12px label: an asterisk glyph inks far smaller than its nominal size, so at
   label size it reads as a speck. line-height:0 keeps the label row height unchanged,
   and the glyph's natural position high in the em box lifts it clear of the caps. */
.field label .required {
  color: var(--color-danger);
  font-weight: 700;
  margin-left: 3px;
  font-size: 24px;
  line-height: 0;
  vertical-align: -2px;
}
/* Number inputs: no spinner arrows anywhere in the app. */
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.input, .select, .textarea {
  width: 100%;
  padding: 8px 12px;
  font: inherit;
  font-size: 13px;
  color: var(--color-text);
  background-color: #EBEFF4;
  border: 1px solid #A9B5C2;
  border-radius: 5px;
  transition: border-color 120ms, box-shadow 120ms, background-color 120ms;
}
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  background-color: var(--color-surface);
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(37,99,235,0.15);
}
.input::placeholder, .textarea::placeholder { color: #94A3B8; }
/* A control switched off by another answer (e.g. Match type once a bundle is not sequenced). */
.input:disabled, .select:disabled, .textarea:disabled {
  background-color: var(--color-surface-alt);
  color: var(--color-text-subtle);
  cursor: not-allowed;
}

/* Search inputs get a light yellow tint everywhere (header + table toolbars). */
input[type="search"] { background: var(--color-search-bg); }
.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748B' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 32px;
}

/* Type-to-search value picker — an input that filters an existing value list as you
   type and offers to add what you typed as a new company-level entry.
   Markup: <div class="combo"><input class="input" data-combo="A|B|C"></div> */
.combo { position: relative; display: inline-block; width: 168px; max-width: 100%; }
.combo--sm { width: 112px; }
.combo > .input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748B' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 32px;
}
.combo-menu {
  position: absolute; z-index: 40;
  top: calc(100% + 4px); left: 0; min-width: 100%; max-width: min(260px, 80vw);
  padding: 4px;
  max-height: 232px; overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: 5px;
  box-shadow: var(--shadow-lg);
}
.combo-opt {
  display: block; width: 100%;
  padding: 6px 8px;
  border: 0; border-radius: 5px;
  background: none;
  font: inherit; font-size: 13px; text-align: left; white-space: nowrap;
  color: var(--color-text);
  cursor: pointer;
}
.combo-opt:hover, .combo-opt.is-active { background: var(--color-primary-50); }
.combo-opt mark { background: none; color: var(--color-primary-700); font-weight: 600; }
.combo-opt.is-add {
  margin-top: 4px; padding-top: 8px;
  border-top: 1px solid var(--color-border);
  border-radius: 0;
  color: var(--color-primary-700); font-weight: 500;
}
.combo-empty { padding: 6px 8px; font-size: 12px; color: var(--color-text-subtle); }
/* Touch: options reach the 44px tap target on tablet and phone. */
@media (max-width: 768px) { .combo-opt { padding: 12px 8px; } }

.input-group { display: flex; align-items: stretch; }
/* The field shrinks, the unit never does — otherwise a narrow group wraps "$ / sqft". */
.input-group .input { border-radius: 5px 0 0 5px; border-right: 0; flex: 1 1 auto; min-width: 0; }
.input-group .addon {
  flex: none; white-space: nowrap;
  display: inline-flex; align-items: center; padding: 0 12px;
  border: 1px solid var(--color-border-strong);
  border-radius: 0 5px 5px 0;
  background: var(--color-surface-alt);
  color: var(--color-text-muted);
  font-size: 12px;
  font-family: 'Hanken Grotesk', sans-serif;
}

.toolbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 16px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}
.toolbar .search { position: relative; min-width: 240px; flex: 1; max-width: 320px; }
.toolbar .search input { padding-left: 32px; }
.toolbar .search svg {
  position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
  width: 14px; height: 14px; color: var(--color-text-subtle);
}
.toolbar .spacer { flex: 1; }

/* Table add — blue "+" in a table toolbar; the table's add action. Replaces the
   external page-header "New X" button. It is ALWAYS the first toolbar child —
   immediately to the LEFT of the search box when there is one. The add action is a
   fixed, always-available control, so it belongs at the toolbar's fixed edge; the
   search box grows and shrinks with the viewport, and keeping it to the right of the
   button means the "+" lands on the same pixel on every page and at every width. */
.btn-add { padding-left: 8px; padding-right: 8px; flex: none; }
.btn-add svg { width: 16px; height: 16px; }

/* Toolbar filter toggle — icon-only, sized to match the .btn-add plus button it sits
   beside (same box metrics ⇒ an even run of controls after the search box). */
.toolbar [data-filter-toggle] { padding: 7px 8px; flex: none; }
.toolbar [data-filter-toggle] svg { width: 16px; height: 16px; }
/* Active (filters applied) state — funnel with a bold × overlay (~3× the built-in filter-x ×). */
.toolbar [data-filter-toggle] .filter-x-icon { position: relative; display: inline-flex; width: 16px; height: 16px; }
.toolbar [data-filter-toggle] .filter-x-icon__x {
  position: absolute; top: -5px; right: -6px;
  width: 15px; height: 15px; stroke-width: 3;
  color: var(--color-danger);
}

/* Filter chip */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  border-radius: 5px;
  background: var(--color-primary-50);
  color: var(--color-primary-700);
  font-size: 12px;
  font-family: 'Hanken Grotesk', sans-serif;
  border: 1px solid #D6E2FA;
}
.chip button {
  background: transparent; border: 0; color: inherit; line-height: 0; padding: 0; cursor: pointer;
}
.chip button:hover { color: var(--color-danger); }

/* Applied-filters strip — sits directly under the toolbar and lists what is actually
   selected, one removable chip per value, plus a Clear All. It is injected by app.js
   after every toolbar holding a [data-filter-toggle], so a page never hand-writes it.
   Hidden entirely when nothing is selected: an empty strip is a row of dead space. */
.active-filters {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 16px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface-alt);
  font-size: 13px;
}
.active-filters[hidden] { display: none; }
.active-filters__chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; flex: 1; }
.af-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 6px 3px 9px;
  background: var(--color-primary-50); color: var(--color-primary-700);
  border: 1px solid #D6E2FA; border-radius: 5px;
  font-size: 12px; font-weight: 600; line-height: 1.4;
}
.af-chip button {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; background: transparent; border: 0; cursor: pointer;
  color: var(--color-primary-700); opacity: 0.65;
}
.af-chip button:hover { opacity: 1; }
.af-chip button svg { width: 13px; height: 13px; }
.active-filters__clear {
  margin-left: auto; padding: 0; flex: none;
  background: transparent; border: 0; cursor: pointer;
  font-family: inherit; font-size: 13px; font-weight: 500;
  color: var(--color-primary-700);
}
.active-filters__clear:hover { color: var(--color-primary); }

/* Faceted filter chips — every option visible, click to toggle (replaces filter dropdowns).
   No selection in a group = "any". Selected state matches .subtab.active / .badge-primary. */
/* Two columns that PACK, not two columns that align. A CSS grid sizes each row to its
   tallest cell, so a one-line group (Region: two chips) sitting beside a two-line group
   (Category: seven chips) left a hole exactly the height of the difference, and the next
   group waited for the row below to start. Multi-column lets each group begin directly
   under the previous one in its own column, so short groups close up automatically.
   It also gives the divider for free: column-rule is a real, continuous rule painted in
   the gap, replacing an :nth-child(even of …) selector plus negative margins that had to
   fake one — and which could not have known which column a group landed in once the
   groups stopped being laid out in fixed pairs. */
.filter-grid { columns: 2; column-gap: 44px; column-rule: 2px solid var(--color-border-strong); }
.filter-group { break-inside: avoid; margin-bottom: 26px; }
/* A full-width group interrupts the flow — columns above it, columns below it — which is
   why span-2 groups are written first or last in the dialog, never in the middle. */
.filter-group.span-2 { column-span: all; }
/* The margin-bottom of the last group in a column does not survive the column break, so a
   spanning group lands flush against the columns above it (0px on Stock, 8px on Pricing —
   balancing rounding, not a rule). State the gap on the spanner instead. Not on a leading
   spanner, which has nothing above it to clear. */
.filter-group.span-2:not(:first-child) { margin-top: 26px; }
/* Starts the second column at this group. `columns: 2` otherwise balances by height, so a
   facet lands in whichever column that pass happens to favour — and chip wrapping differs
   per dialog, so the same facet could sit left on one page and right on the next. These
   dialogs declare their split instead of inferring it. */
.filter-group.col-start { break-before: column; -webkit-column-break-before: column; }
/* The dialog body sits one shade below the chips, so the chips read as raised boxes on a
   panel instead of white-on-white outlines. */
.modal-body:has(> .filter-grid) { background: var(--color-surface-alt); }
.fg-label {
  display: block; margin-bottom: 8px;
  font-size: 12px; font-family: 'Hanken Grotesk', sans-serif;
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--color-text-muted); font-weight: 500;
}
.fchips { display: flex; flex-wrap: wrap; gap: 8px; }
.fchip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 13px; font-size: 13px; font-family: inherit; line-height: 1.1;
  color: var(--color-text-muted);
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-strong);
  border-radius: 5px; cursor: pointer;
  transition: background 120ms, color 120ms, border-color 120ms, box-shadow 120ms;
}
.fchip:hover { color: var(--color-primary-700); border-color: var(--color-primary); }
/* On the tinted dialog panel the chips invert: surface-white box, full-strength label. Three
   readable levels — panel, box, value — instead of two near-identical greys. */
.filter-grid .fchip:not(.on) {
  background: var(--color-surface); color: var(--color-text);
  border-color: var(--color-border-strong); box-shadow: var(--shadow-sm);
}
.filter-grid .fchip:not(.on):hover { color: var(--color-primary-700); border-color: var(--color-primary); }
/* Selected = a tinted state, not a button. Solid primary is reserved for buttons, so the
   chosen chip takes the same primary-50 fill + primary-700 label as .subtab.active, with a
   primary hairline for a defined edge. */
.fchip.on {
  background: var(--color-primary-50); color: var(--color-primary-700);
  border-color: var(--color-primary); font-weight: 600; box-shadow: var(--shadow-sm);
}
/* The count pill shares the chip's own tint once selected, so it flips to surface (same
   move as .subtab.active .count). */
.fchip.on .row-count { background: var(--color-surface); }

/* Subsection select-all / clear-all — pair with a multi-select .fchips group. Sits at the
   far right of the subsection label row; `.on` = every chip in that group is selected. */
.fg-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.fg-head .fg-label { margin-bottom: 0; }
.fg-all {
  flex: none; padding: 2px 6px; border: none; border-radius: 5px; cursor: pointer;
  font-family: 'Hanken Grotesk', sans-serif; font-size: 12px; font-weight: 600;
  letter-spacing: 0.02em; color: var(--color-primary-700); background: none;
  transition: background 120ms, color 120ms, box-shadow 120ms;
}
.fg-all:hover { color: var(--color-primary); background: var(--color-primary-50); }
.fg-all.on { color: var(--color-primary-700); background: var(--color-primary-50); box-shadow: var(--shadow-sm); }

/* -------------------------------------------------------------------------
   Tables
   ------------------------------------------------------------------------- */
.table-wrap {
  overflow-x: auto;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
}
/* Column widths are declared, never inferred from the data. `table-layout: fixed`
   makes the header row's widths authoritative for the whole column, so a longer or
   shorter value wraps to a second line instead of moving the column.

   Those widths are percentages that sum to 100%, so a table always exactly fills its
   container: there is no leftover for the browser to redistribute (fixed layout
   spreads any slack equally across the columns, which silently defeats declared
   widths), and no single column has to soak it up and sit next to a void.

   Resizing the window scales every column by the same factor, between the bounds
   described at the width block below. Because the scaling is uniform, tables that
   swap in the same slot (tab-siblings — the stock tabs) can share a denominator:
   their common columns then carry identical percentages, and since both tables render
   at the same total width, the shared leading columns start at the same x AND the
   shared trailing columns stay anchored to the same right edge. Switching tabs moves
   nothing. Widths never depend on content, so data can't shift anything either. */
.data-table { table-layout: fixed; --table-pad-x: 12px; }
.data-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
/* Shared table-header typography — column headers AND a table's own title label
   (.table-title) use the exact same font size, weight, case and tracking. */
.data-table thead th,
.table-title {
  font-family: 'Hanken Grotesk', sans-serif;
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.data-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--color-table-head);
  text-align: left;
  color: var(--color-text-muted);
  padding: 10px var(--table-pad-x);
  border-bottom: 1px solid var(--color-border);
  /* A long header wraps to a second line rather than widening its column. */
  white-space: normal;
  overflow-wrap: break-word;
}
/* A table/panel's title label above the table (e.g. "RECENT CATALOG ACTIVITY").
   Same header typography as the columns; keeps default text color for prominence. */
.table-title { color: var(--color-text); }
.data-table thead th.sortable { cursor: pointer; user-select: none; }
.data-table thead th.sortable:hover { color: var(--color-primary-700); }
.data-table thead th.sortable .sort-ind {
  display: inline-block; margin-left: 4px; opacity: 0.4;
}
.data-table thead th.sortable.sort-asc .sort-ind,
.data-table thead th.sortable.sort-desc .sort-ind { opacity: 1; color: var(--color-primary); }

.data-table tbody td {
  padding: 10px var(--table-pad-x);
  /* The row boundary is the ::after mark below, not this border. The border stays
     declared, transparent, so every row still reserves its 1px — row heights and the
     mark's `bottom:-1px` anchor land exactly where they did before. */
  border-bottom: 1px solid transparent;
  color: var(--color-text);
  vertical-align: middle;
  /* Overlong words (SKUs, URLs, hashes) break instead of pushing the column wider. */
  overflow-wrap: break-word;
  position: relative;
}
/* ---- Row separator -------------------------------------------------------
   A full-width hairline at #F1F5F9 is a 3% step off white and disappears once a
   row is two lines tall, so rows read as one block. Each cell instead carries a
   mark that holds --color-border-strong for the first 35% of its own column and
   decays across the rest: the boundary is stated where each column's content
   starts, and is spent before it reaches the next.

   Inset both ends by the cell's own --table-pad-x so the mark begins where the
   thumbnail and text begin, and so contiguous cells leave a clean gap at every
   column boundary rather than forming one unbroken rule.

   Proportional stops, not pixels: every column holds full weight for the same
   share of its own width, so a narrow column and a wide one read as the same
   gesture at different scales. Tokens, not a literal grey, so it follows all
   four themes.

   Safe as a global: `position:relative` on cells re-anchors nothing (the only
   absolutely-positioned cell contents sit inside their own positioned wrappers,
   and in-cell row menus are position:fixed), and the existing `thead th::after`
   column-flex marker is on thead, so there is no collision. */
.data-table tbody td::after {
  content: ""; position: absolute; bottom: -1px; height: 1px;
  left: var(--table-pad-x); right: var(--table-pad-x);
  background: linear-gradient(to right,
    var(--color-border-strong) 0%,
    var(--color-border-strong) 35%,
    var(--color-border) 65%,
    transparent 100%);
}
/* The last row needs no separator — the card's own edge already closes the table,
   and a mark there reads as a row waiting for one that never comes. */
.data-table tbody tr:last-child td::after { content: none; }
/* Dense picker tables opt back into a solid row border of their own (the five
   import pages set `.tbl-dense tbody td { border-bottom-color: … }`), so they must
   not also carry the mark — that would draw the boundary twice. */
.data-table.tbl-dense tbody td::after { content: none; }
.data-table tbody tr { transition: background 120ms ease; }
.data-table tbody tr:hover { background: #FAFAFE; }
.data-table tbody tr.selected { background: var(--color-primary-50); }

/* ---- Dense picker tables (the five import pages) --------------------------
   Hoisted from catalog-import, catalog-import-v2, pricing-import, stock-import
   and stock-import-v2, where all four rules were byte-identical. Verbatim: the
   two literal hexes below are not tokens, which looks like drift rather than
   intent, but a restyle is a separate change.

   Position matters. These tables are "data-table tbl-dense", so every rule here
   ties on specificity with a .data-table rule above and won only because it came
   later in the document. It must stay below them. */
.tbl-dense td, .tbl-dense th { padding-top: 6px; padding-bottom: 6px; }
.tbl-dense tbody tr:nth-child(even) { background: #E4EBF4; }
.tbl-dense tbody td { border-bottom-color: var(--color-border); }
.tbl-dense tbody tr:hover { background: #DAE3F0; }

/* ---- Import run state (the five import pages) -----------------------------
   The spinner + progress bar shown while an import runs. Hoisted byte-identical
   from the same five pages, keyframes included — they are referenced by nothing
   else in the surface. */
.import-run { text-align: center; padding: 44px 24px; }
.import-spinner { width: 52px; height: 52px; margin: 0 auto 18px; border-radius: 50%; border: 4px solid var(--color-primary-50); border-top-color: var(--color-primary); animation: imp-spin .8s linear infinite; }
@keyframes imp-spin { to { transform: rotate(360deg); } }
.import-progress { width: 280px; max-width: 100%; height: 8px; margin: 22px auto 0; background: var(--color-primary-50); border-radius: 5px; overflow: hidden; }
.import-progress > span { display: block; height: 100%; width: 0; background: var(--color-primary); border-radius: 5px; animation: imp-fill 4s ease-out forwards; }
@keyframes imp-fill { to { width: 100%; } }

.data-table tbody tr.selected:hover { background: #D6E2FA; }

.data-table td.num,
.data-table th.num { text-align: right; font-family: 'Hanken Grotesk', sans-serif; font-variant-numeric: tabular-nums; }
.data-table td.center,
.data-table th.center { text-align: center; }

/* Numbers never wrap — a broken figure is unreadable. Long text columns may. */
.data-table td.num,
.data-table th.num { white-space: nowrap; }

/* The rightmost column is right-aligned, and its right padding is the same
   --table-pad-x the first column uses on the left, so the table's content is
   inset by an identical amount on both edges. Declared once here rather than
   per-cell in the markup. */
.data-table thead th:first-child,
.data-table tbody td:first-child { padding-left: var(--table-pad-x); }
.data-table thead th:last-child,
.data-table tbody td:last-child {
  text-align: right;
  padding-right: var(--table-pad-x);
}
/* A right-aligned last cell still lays its own contents out left-to-right, so
   flex/inline-flex content (badges, icon clusters) has to be pushed over too. */
.data-table tbody td:last-child > .cell-stack { align-items: flex-end; }
.data-table tbody td:last-child > .cell-media,
.data-table tbody td:last-child > .row-actions { justify-content: flex-end; }

/* .entry-grid is a data-entry component whose base styles live in the pages that
   use it, but the last-column rule is global, so it is declared once here. Its
   columns are already data-independent (every cell is a fixed-size input), so it
   needs the alignment and the symmetric edge inset, not fixed layout. */
.entry-grid { --table-pad-x: 8px; }
.entry-grid th:first-child,
.entry-grid td:first-child { padding-left: var(--table-pad-x); }
.entry-grid th:last-child,
.entry-grid td:last-child {
  text-align: right;
  padding-right: var(--table-pad-x);
}

/* -- Column widths: fluid shares, bounded ---------------------------------
   Columns are sized as a SHARE of the table (a percentage on the <th>), so they
   resize with the browser instead of holding a fixed pixel width. The share is
   per-table because a proportional share depends on that table's own column set,
   so it lives on the cell; the .col-* classes below stay for what they name and
   for alignment.

   Each column's bounds come from its own size: it may shrink to 50% of it and
   grow to 250%, past which the text wraps rather than the column widening. Since
   every column scales by the same factor, the whole set reaches its minimum at one
   table width and its maximum at another — so both bounds are enforced by a single
   min-width/max-width on the table, which is also the only place they CAN be
   enforced: max-width is ignored on a table column outright, min-width works only
   on <col>, and calc()/min()/max()/clamp() are all ignored under fixed layout.

   Below the table's min-width .table-wrap scrolls; above its max-width the table
   stops growing. .col-flex marks the column that takes up the slack when sibling
   tables (tab-swapped views) share a denominator so their common columns line up. */
.data-table .col-actions   { text-align: right; }
/* Two-icon action cluster (2 x 34px .icon-btn) — needs to stay on one line. */
.data-table .col-actions-2 { text-align: right; white-space: nowrap; }

.sku { font-family: 'Hanken Grotesk', sans-serif; font-size: 12px; color: var(--color-text-muted); }

/* row meta layout */
.cell-stack {
  display: flex; flex-direction: column; gap: 1px; min-width: 0;
}
.cell-stack .main { color: var(--color-text); font-weight: 500; }
.cell-stack a.main { color: var(--color-primary-700); text-decoration: none; }
.cell-stack a.main:hover { color: var(--color-primary-700); text-decoration: none; }
.cell-stack .sub { color: var(--color-text-subtle); font-size: 11px; }

.cell-media {
  display: flex; align-items: center; gap: 10px; min-width: 0;
}
.thumb {
  width: 36px; height: 36px;
  border-radius: 5px;
  background: #E6EEFC;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: 'Hanken Grotesk', sans-serif;
  font-weight: 600;
  color: var(--color-primary-700);
  font-size: 11px;
  flex-shrink: 0;
  border: 1px solid #D6E2FA;
}

/* -------------------------------------------------------------------------
   Badges / Status pills
   ------------------------------------------------------------------------- */
.badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px;
  border-radius: 5px;
  font-size: 11px;
  font-family: 'Hanken Grotesk', sans-serif;
  font-weight: 500;
  border: 1px solid transparent;
  white-space: nowrap;
}
.badge .dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }

.badge-success { background: var(--color-success-50); color: #047857; border-color: #A7F3D0; }
.badge-warn    { background: var(--color-warn-50);    color: #B45309; border-color: #FDE68A; }
.badge-danger  { background: var(--color-danger-50);  color: #B91C1C; border-color: #FECACA; }
.badge-info    { background: var(--color-info-50);    color: #1D4ED8; border-color: #BFDBFE; }
.badge-neutral { background: #F1F5F9; color: #475569; border-color: #E2E8F0; }
.badge-primary { background: var(--color-primary-50); color: var(--color-primary-700); border-color: #D6E2FA; }

/* Stock progress bar */
.stock-bar {
  position: relative;
  height: 6px;
  border-radius: 5px;
  background: #F1F5F9;
  overflow: hidden;
  min-width: 80px;
}
.stock-bar > span {
  position: absolute; inset: 0;
  background: var(--color-success);
  border-radius: 5px;
  transition: width 200ms ease;
}
.stock-bar.warn > span   { background: var(--color-warn); }
.stock-bar.danger > span { background: var(--color-danger); }

/* -------------------------------------------------------------------------
   Pagination
   ------------------------------------------------------------------------- */
.pagination {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 16px;
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  font-size: 12px;
  color: var(--color-text-muted);
}
.pager { display: flex; align-items: center; gap: 4px; }
.pager button {
  min-width: 28px; height: 28px;
  border-radius: 5px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  font: inherit;
  font-size: 12px;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: all 120ms;
}
.pager button:hover { border-color: var(--color-primary); color: var(--color-primary-700); }
.pager button.active {
  background: var(--color-primary);
  color: #fff;
  border-color: var(--color-primary);
}
.pager button:disabled { opacity: 0.4; cursor: not-allowed; }

/* -------------------------------------------------------------------------
   Tabs
   ------------------------------------------------------------------------- */
/* Fluid tab row. Every dimension below is clamp(min, <slope>vw + <base>, max),
   tuned so a 1280px viewport reproduces the previous fixed values exactly (13px
   type, 14px side padding, 6px icon gap, 4px tab gap, 24px icon) — it grows on
   wide monitors and gives back space on narrow ones instead of scrolling sooner.
   Floor is 12px type / 18px icon: below that the row is unreadable, so overflow-x
   takes over. */
.tabs {
  display: flex; gap: clamp(2px, 0.16vw + 1.2px, 5px);
  border-bottom: 1px solid var(--color-border);
  /* Hug the panel below: small gap under the row, larger space above (from the
     preceding element) — so the row reads as grouped with the content it controls,
     matching the .subtabs 8px. */
  margin-bottom: 8px;
  /* Narrow viewports scroll the strip rather than squeezing it. Without this the
     flex items compress below their content: the longest label wraps to two lines
     and the icon — an SVG, so shrinkable — distorts. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.tab {
  /* Horizontal padding is the cheapest width in the strip: seven tabs pay it
     fourteen times over, so trimming ~2px a side buys back ~28px and is what
     keeps the row off a scrollbar in the 950–1100px band. */
  padding: clamp(8px, 0.55vw + 3px, 12px) clamp(7px, 0.62vw + 3.3px, 15px);
  font-size: clamp(12px, 0.35vw + 8.5px, 14px);
  color: var(--color-text-muted);
  border-bottom: 2px solid transparent;
  background: transparent;
  border-left: 0; border-right: 0; border-top: 0;
  cursor: pointer;
  transition: color 150ms, border-color 150ms;
  display: inline-flex; align-items: center; gap: clamp(4px, 0.3vw + 2.2px, 8px);
  font-family: inherit;
  /* Multi-word labels ("Tiles / Pavers") must not wrap — a two-line tab is taller
     than its siblings and knocks the whole row out of alignment. */
  white-space: nowrap;
  flex: none;
}
/* Keep the icon at its intrinsic size. Default flex-shrink lets the SVG compress
   horizontally when the row is tight, which visibly deforms grid-style glyphs. */
.tab svg {
  flex: none;
  width: clamp(15px, 1.15vw + 4px, 20px);
  height: clamp(15px, 1.15vw + 4px, 20px);
}
/* Per-glyph tuning for a tab whose icon needs it (Catalog's Pricing dollar-sign).
   Two adjustments, both scoped to this class — remove it from the button if the icon
   is ever swapped for one that fills its box at the default size.
   - Size: 25% down from the sibling tab icon, tracking the same fluid curve.
   - Gap: most tab icons fill their 24-unit viewBox to within ~2 units of each edge,
     but dollar-sign draws only to x=18, leaving ~4 units empty on the label side, so
     the same 6px gap reads wider than its neighbours'. 4 units at 18px is 3px, so
     that is what comes back off to make the OPTICAL distance match. */
.tab--icon-narrow svg {
  width: clamp(11.25px, 0.8625vw + 3px, 15px);
  height: clamp(11.25px, 0.8625vw + 3px, 15px);
  margin-right: -3px;
}
.tab:hover { color: var(--color-primary-700); }
/* A locked step in a wizard stepper — the tab still shows where the flow goes,
   it just can't be jumped to yet. */
.tab:disabled { opacity: 0.45; cursor: not-allowed; }
.tab:disabled:hover { color: var(--color-text-muted); }
/* A required field that is still blank. The message sits on the field itself —
   a summary in the wizard's navigation row names fields the user then has to hunt
   for, and competes with Back/Next for the same space. */
.field-req-note { margin: 5px 0 0; font-size: 12px; color: var(--color-danger); }
.field.is-missing .input,
.field.is-missing .select,
.field.is-missing .textarea { border-color: var(--color-danger); }
/* Summary of what another step is still missing, shown under the action it blocks
   (Create Product) — never in a navigation row. */
.step-hint { font-size: 12px; color: var(--color-danger); }
.tab.active {
  color: var(--color-primary-700);
  border-bottom-color: var(--color-primary);
  font-weight: 500;
  background: var(--color-primary-50);
  border-radius: 5px 5px 0 0;
}
/* ---- Attached: the strip joins the panel it controls --------------------
   The strip's own bottom rule becomes the panel's top edge and the active tab
   takes the panel surface, so tab and panel read as one continuous surface
   with nothing between them.

   The active indicator moves to the tab's TOP edge. On the bottom edge it
   draws a line exactly where the tab joins the panel — separating the two
   things it is meant to connect.

   Opt-in, because most strips do not qualify. Measured across all 25 `.tabs`
   in the portal: 6 sit above a two-column grid, where the panel is not one
   surface and a tab on the right of the strip would have nothing under it;
   the wizard steppers (import, receive, product-wizard, settings-api-v2) put
   a step header between the strip and the first card, 52–109px of it; and
   `stock.html` nests its card three levels inside a studio layout. Those keep
   the detached look. Nine strips qualify and carry this class.

   Panels are addressed with `~`, not `+`: only the first panel is the strip's
   adjacent sibling, so `+` would leave every other tab's panel with its top
   border intact and draw a line under the active tab as soon as you switched.

   `.tabs` computes `overflow-y: auto` — a non-visible `overflow-x` forces it —
   so the active tab cannot hang 1px below the strip to cover its rule without
   being clipped or raising a scrollbar. The strip carries 1px of bottom
   padding instead and the tab's overhang lands inside it. */
/* The strip gives up its own bottom rule and sits 1px INTO the panel, so the
   panel's own top border becomes the rule. That is what makes this work without
   knowing anything about the panel's structure: the rule is drawn by whatever is
   below, at whatever nesting depth, and the strip simply overlaps it. The active
   tab's 2px surface-coloured bottom border then paints over that 1px where the
   two meet, which is the whole join.

   The earlier version kept the strip's rule and removed the panel card's top
   border by selector. That needs one selector per layout, and panels differ
   *within* a page — clients.html reaches its card through `.card-header`,
   catalog-bundle through `.stack`, stock through the studio wrapper — so any
   list of selectors leaves some tab drawing a 2px double line. Overlapping is
   structure-independent and cannot double-draw. */
.tabs--attached {
  border-bottom: 0;
  margin-bottom: -1px;
  padding-bottom: 0;
  position: relative;
  z-index: 1;
}
.tabs--attached .tab.active {
  background: var(--color-surface);
  border-bottom-color: var(--color-surface);
  box-shadow: inset 0 2px 0 0 var(--color-primary);
}
/* The panel draws its own top edge, so the strip has something to sit on no
   matter what the panel opens with — a card, a sub-tab row, an alert banner, a
   toolbar. This is what makes the join independent of panel contents: the tab
   attaches to the PANEL, not to whatever happens to be first inside it.

   It is an inset shadow rather than a border on purpose. A border would add 1px
   to the panel's box and push a flush card down by that much, leaving the
   panel's rule and the card's own top border as two lines a pixel apart. An
   inset shadow paints inside the box without occupying space, so a flush card's
   border lands on exactly the same pixel row and the two coincide as one line. */
.tabs--attached ~ [data-tab-panel],
.tabs--attached ~ * > [data-tab-panel] {
  box-shadow: inset 0 1px 0 0 var(--color-border);
}

/* Cosmetic only, and deliberately best-effort: square off the top corners of the
   panel card so the active tab does not sit over a rounded corner. Where a
   selector does not reach, the result is a 7px rounded corner under the leftmost
   tab — not a broken join, which is why this list does not have to be complete. */
.tabs--attached ~ .card,
.tabs--attached ~ [data-tab-panel] > .card:first-child,
.tabs--attached ~ [data-tab-panel] > .cols-2-1 > .card,
.tabs--attached ~ [data-tab-panel] > .cols-2-1 > :is(.stack, .pm-stack) > .card:first-child {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
.tab .count,
.subtab .count {
  background: var(--color-primary-50);
  color: var(--color-primary-700);
  padding: 0 6px;
  border-radius: 5px;
  font-size: 11px;
  font-family: 'Hanken Grotesk', sans-serif;
}
/* On a product-type tab the count READS AS PART OF THE LABEL — "7 Blocks", "642 Slabs" —
   so it leads the label and is set in the label's own type, with no chip around it. The
   count span therefore comes first in the markup, after the icon.
   - No chip: the shared rule above gives every `.count` a tinted background, side padding
     and a radius. All three are unset here, so nothing boxes the number in.
   - Same type as the label: `font-size`, `font-family` and `color` are reset to `inherit`
     rather than restated, so the count tracks `.tab`'s own fluid 12→14px curve and its own
     colour automatically — muted while the tab is idle, primary-700 once it is active. The
     shared rule's `--color-primary-700` is dropped: nothing should set the figure apart from
     the word it qualifies, since the two are read as one phrase.
   - Tight: `.tab` is a flex row and its `gap` would space the number from its label exactly
     as far as it spaces the icon, which is what made the old trailing count read as a
     floating figure. Cancelling the gap and re-stating it as a fixed 4px pairs the two.
     Subtracting the same clamp the gap uses keeps that 4px true at every viewport instead
     of drifting from 4px to 8px; the icon keeps the full fluid gap. */
.tab .count {
  background: none;
  padding: 0;
  border-radius: 0;
  font-size: inherit;
  font-family: inherit;
  color: inherit;
  margin-right: calc(4px - clamp(4px, 0.3vw + 2.2px, 8px));
}

/* Segmented sub-tabs — a nested tab row on a grey track (e.g. Catalog › Pricing).
   The global tab JS ignores .subtab / [data-subtab-panel]; a [data-subtabs] handler
   toggles .active. The selected segment MUST read as clearly chosen — a primary
   ring + lift, never a bare white pill on the grey track (see "always contrast"). */
.subtabs {
  display: flex; gap: 4px; flex-wrap: wrap;
  padding: 4px;
  background: var(--color-surface-alt);
  border-radius: 5px;
  margin-bottom: 8px;
}
.subtab {
  padding: 7px 13px;
  font-size: 13px;
  color: var(--color-text-muted);
  background: transparent;
  border: 0; border-radius: 5px;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit;
  transition: background 120ms, color 120ms, box-shadow 120ms;
}
.subtab:hover { color: var(--color-primary-700); }
.subtab.active {
  background: var(--color-primary-50);
  color: var(--color-primary-700);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}
/* on the tinted active pill the count chip would blend — flip it to white */
.subtab.active .count { background: var(--color-surface); }

/* Choice variant — a form answer (Yes / No, Priced by, …), not tab navigation.
   Every option carries a fill of its own, never white: white is the page's own
   surface, so a white option reads as background rather than as a choice. The
   chosen one takes the stronger of the two fills — a primary tint plus a soft
   lift. Options run two per row, sized by their column, so a long label can't
   push a group to one-option-per-row in a narrow column. */
.subtabs--choice {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px;
  padding: 4px;
  /* Sized by its own options, not by the column — a Yes/No must not stretch across a
     card the way a four-option group does. Both options stay equal width (1fr each). */
  width: fit-content;
  max-width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 7px;
}
.subtabs--choice .subtab {
  justify-content: center;
  text-align: center;
  padding: 6px 10px;
  min-width: 72px;
  line-height: 1.2;
  background: var(--color-surface-alt);
}
.subtabs--choice .subtab:hover { background: var(--color-border); color: var(--color-primary-700); }
.subtabs--choice .subtab.active {
  background: var(--color-primary-50);
  color: var(--color-primary-700);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}

/* -------------------------------------------------------------------------
   Expandable table rows (per-item price overrides as children) + toggle switch
   ------------------------------------------------------------------------- */
.row-toggle {
  border: 0; background: transparent; cursor: pointer;
  color: var(--color-text-subtle);
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; width: 16px; height: 16px; flex: none;
  transition: transform 120ms, color 120ms;
}
.row-toggle svg { width: 15px; height: 15px; }
.row-toggle[aria-expanded="true"] { transform: rotate(90deg); color: var(--color-primary-700); }
.row-toggle-spacer { display: inline-block; width: 16px; flex: none; }
tr.price-child > td { background: var(--color-surface-alt); }
.child-branch { color: var(--color-text-subtle); flex: none; }

/* Row-level add — blue "+" to add a sub-price (override) to a product row.
   Hidden until the row is hovered / keyboard-focused (space is reserved, so no layout shift). */
.row-add {
  width: 20px; height: 20px; flex: none; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 5px;
  background: var(--color-primary); color: #fff;
  opacity: 0;
  transition: opacity 120ms ease, background 120ms ease;
}
tr:hover .row-add,
tr:focus-within .row-add { opacity: 1; }
.row-add:hover { background: var(--color-primary-600); }
.row-add svg { width: 13px; height: 13px; stroke-width: 2.6; }

/* Sub-price counter — shown only on products that have overrides */
.row-count {
  flex: none;
  font-size: 11px; font-weight: 600;
  color: var(--color-primary-700); background: var(--color-primary-50);
  padding: 0 7px; border-radius: 5px;
  font-variant-numeric: tabular-nums;
}

.switch-field {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--color-text-muted);
  cursor: pointer; user-select: none;
}
/* A control whose label runs to more than one row aligns to the *first* row —
   the main label — not to the middle of the block. The switch belongs to the
   setting's name; centred against a two- or three-line description it drifts
   down beside the explanation instead of the thing it switches. Scoped by
   `:has(.cell-stack)` so single-line toggles keep their centred alignment. */
.switch-field:has(.cell-stack) { align-items: flex-start; }
/* Same rule where the switch is a bare child of a generic .row rather than
   wrapped in its own .switch-field label (e.g. Settings › Product Types). */
.row:has(> .switch):has(> .cell-stack) { align-items: flex-start; }
/* Flush with the text box reads a touch high — a label's glyphs start below the
   top of their line box, the switch's pill does not. 1px puts the two on the
   same optical line. */
.switch-field:has(.cell-stack) > .switch,
.row:has(> .switch):has(> .cell-stack) > .switch { margin-top: 1px; }
.switch {
  position: relative; width: 34px; height: 20px; flex: none;
  border-radius: 5px; background: var(--color-border-strong);
  transition: background 140ms;
}
.switch::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: 4px;
  background: #fff; box-shadow: var(--shadow-sm);
  transition: transform 140ms;
}
.switch[aria-checked="true"] { background: var(--color-primary); }
.switch[aria-checked="true"]::after { transform: translateX(14px); }

/* -------------------------------------------------------------------------
   Alerts
   ------------------------------------------------------------------------- */
.alert {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px;
  border-radius: 5px;
  border: 1px solid;
  font-size: 13px;
}
.alert svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 1px; }
.alert-warn   { background: var(--color-warn-50);   color: #92400E; border-color: #FDE68A; }
.alert-info   { background: var(--color-info-50);   color: #1E40AF; border-color: #BFDBFE; }
.alert-danger { background: var(--color-danger-50); color: #991B1B; border-color: #FECACA; }

/* -------------------------------------------------------------------------
   Grid utilities (data-dense)
   ------------------------------------------------------------------------- */
.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.grid-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }

@media (max-width: 1100px) {
  .grid-4, .grid-5, .grid-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .grid-2, .grid-3, .grid-4, .grid-5, .grid-6 { grid-template-columns: 1fr; }
}

/* Tablet: KPI/stat rows flex-fill so trailing cards grow into the row instead
   of leaving an empty orphan slot (e.g. a 5-up row collapsing to 2 columns).
   The basis is what decides how many fit, not the content: a card's widest
   figure needs ~155px, but a 200px basis made four of them ask for 848px and
   drop the last onto a second row while every card still had ~100px spare.
   160px keeps a four-up row together down to a 688px container. */
@media (max-width: 1100px) {
  .grid:has(> .kpi) { display: flex; flex-wrap: wrap; }
  .grid:has(> .kpi) > .kpi { flex: 1 1 160px; }
}
@media (max-width: 640px) {
  .grid:has(> .kpi) > .kpi { flex: 1 1 100%; }
}

.cols-3-1 { display: grid; gap: 16px; grid-template-columns: 3fr 1fr; }
.cols-2-1 { display: grid; gap: 16px; grid-template-columns: 2fr 1fr; }
.cols-3-2 { display: grid; gap: 16px; grid-template-columns: 3fr 2fr; }
.cols-1-2 { display: grid; gap: 16px; grid-template-columns: 1fr 2fr; }
@media (max-width: 1100px) {
  .cols-3-1, .cols-2-1, .cols-3-2, .cols-1-2 { grid-template-columns: 1fr; }
}

.stack { display: flex; flex-direction: column; gap: 12px; }
.row   { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* -------------------------------------------------------------------------
   Misc
   ------------------------------------------------------------------------- */
.muted { color: var(--color-text-muted); }
.subtle { color: var(--color-text-subtle); }
.mono { font-family: 'Hanken Grotesk', sans-serif; }
.footer-link { color: inherit; text-decoration: none; }
.footer-link:hover { color: inherit; text-decoration: none; font-weight: 700; }
.tnum { font-variant-numeric: tabular-nums; }
.divider { height: 1px; background: var(--color-border); border: 0; margin: 12px 0; }

.dot-status { width: 6px; height: 6px; border-radius: 999px; display: inline-block; }
.dot-status.ok   { background: var(--color-success); }
.dot-status.warn { background: var(--color-warn); }
.dot-status.bad  { background: var(--color-danger); }

.kbd {
  display: inline-flex; align-items: center;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  border-bottom-width: 2px;
  font-family: 'Hanken Grotesk', sans-serif;
  font-size: 10px;
  color: var(--color-text-muted);
}

/* Bar gauge */
.bars { display: flex; align-items: flex-end; gap: 3px; height: 28px; }
.bars span {
  flex: 1;
  background: var(--color-primary);
  border-radius: 2px 2px 0 0;
  opacity: 0.9;
}

/* Donut placeholder */
.donut {
  width: 132px; height: 132px;
  border-radius: 999px;
  background:
    conic-gradient(var(--color-primary) 0 82%,
                   var(--color-secondary) 82% 94%,
                   var(--color-cta) 94% 98%,
                   #FBBF24 98% 100%);
  display: inline-flex; align-items: center; justify-content: center;
  position: relative;
}
.donut::after {
  content: '';
  position: absolute; inset: 18px;
  background: var(--color-surface);
  border-radius: 999px;
}
.donut-label {
  position: relative; z-index: 1; text-align: center;
  font-family: 'Hanken Grotesk', sans-serif;
}

/* Legend */
.legend { display: flex; flex-wrap: wrap; gap: 10px 14px; font-size: 12px; color: var(--color-text-muted); }
.legend .swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 6px; vertical-align: -1px; }

/* Drawer / Modal (kept simple, html-only) */
details.drawer { border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-surface); }
details.drawer > summary {
  list-style: none;
  padding: 10px 14px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: space-between;
  font-weight: 500;
}
details.drawer > summary::-webkit-details-marker { display: none; }
details.drawer[open] > summary { border-bottom: 1px solid var(--color-border); }
details.drawer .drawer-body { padding: 14px; }

/* Tablet (portrait): collapse the sidebar to an icon rail instead of hiding it,
   so navigation is never lost. Mirrors the .app.collapsed state. */
@media (max-width: 900px) {
  .app { grid-template-columns: var(--sidebar-w-collapsed) 1fr; }
  .sidebar-brand { overflow: hidden; background: url('../img/vava-sales-logomark.svg') center / auto 28px no-repeat; }
  .sidebar-brand img { display: none; }
  .sidebar-section { font-size: 0; padding: 10px 0 0; margin-bottom: 0; }
  .nav-link { justify-content: center; gap: 0; font-size: 0; padding: 9px 0; }
  .nav-link svg { width: 20px; height: 20px; }
  .nav-badge { display: none; }
  .sidebar-footer { justify-content: center; padding: 12px 0; }
  .sidebar-footer > div,
  .sidebar-footer > .icon-btn { display: none; }
}

/* Print-friendly basics (not used here) */
@media print {
  .sidebar, .header, .page-actions { display: none; }
}

body > div:nth-of-type(1) > main > header > label > input {
  border-color: var(--border-input-1);
  background-color: var(--color-search-bg);
}

.value.tnum {
  color: var(--color-value-tnum);
}

/* ============================================================
   TOPBAR USER CLUSTER (V1-style) — name over a pin+location line
   (click -> Switch Location dropdown) + avatar (click -> user menu).
   Ported from the V1 app shell (np.vavastone.com js/shell.js + css/shell.css).
   ============================================================ */
.topbar-user-cluster{display:flex;align-items:center;gap:10px;padding:4px 4px 4px 8px;border-radius:var(--radius-md);}
.topbar-user-info{display:flex;flex-direction:column;align-items:flex-end;line-height:1.15;gap:2px;}
.topbar-user-info .topbar-user-name{font-size:13px;font-weight:600;color:var(--color-text);}
.topbar-loc,.topbar-user{position:relative;}
.topbar-user-info .topbar-loc{display:inline-flex;align-items:center;gap:4px;background:transparent;border:0;padding:0;cursor:pointer;font-size:12px;color:var(--color-text-muted);}
.topbar-user-info .topbar-loc:hover{color:var(--color-primary);}
.topbar-user-info .topbar-loc .loc-pin{color:var(--color-primary);flex-shrink:0;}
.topbar-user-info .topbar-loc .location-picker__label{color:var(--color-primary);font-weight:500;white-space:nowrap;}
.location-picker__menu{position:absolute;top:calc(100% + 6px);left:auto;right:0;min-width:280px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);padding:4px;z-index:100;display:none;text-align:left;}
.topbar-loc.open .location-picker__menu{display:block;}
.location-picker__option{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border-radius:var(--radius-sm);cursor:pointer;}
.location-picker__option:hover,.location-picker__option.active{background:var(--color-surface-alt);}
.location-picker__opt-text{display:flex;flex-direction:column;}
.location-picker__opt-name{font-size:13px;font-weight:600;color:var(--color-text);}
.location-picker__opt-sub{font-size:11px;color:var(--color-text-muted);}
.dropdown-section-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--color-text-muted);padding:6px 10px 4px;}
.topbar-user-cluster > .topbar-user{display:inline-flex;align-items:center;background:transparent;border:0;padding:0;cursor:pointer;}
.topbar-user-cluster .avatar{width:28px;height:28px;}
.topbar-user-cluster > .topbar-user:hover .avatar{outline:2px solid color-mix(in oklab, var(--color-primary) 50%, transparent);outline-offset:1px;}
.dropdown-menu{position:absolute;top:calc(100% + 6px);right:0;min-width:200px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);padding:6px;z-index:100;display:none;text-align:left;}
.dropdown-menu.open{display:block;}
.dropdown-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--radius-sm);font-size:13px;color:var(--color-text);text-decoration:none;cursor:pointer;}
.dropdown-item:hover{background:var(--color-primary-50);}
.dropdown-divider{height:1px;background:var(--color-border);margin:4px 0;}
/* Page-level settings menus (dashboard/catalog/stock) — roomy, even rhythm; divider sits in the gap instead of carving a hole */
/* The menu is absolute, so the cluster has to be its containing block — every page
   that used this carried `position:relative` as an inline style to get it. */
.page-settings{position:relative;}
.page-settings .dropdown-item{padding:9px 12px;}
.page-settings .dropdown-divider{margin:0;}
/* Lucide ships icons at 24px. In a 13px menu row that reads as an icon list with
   labels attached rather than a menu; 15px puts the icon under the cap height. */
.page-settings .dropdown-item svg{width:15px;height:15px;flex:none;}
/* Sized to the longest entry ("Storefront URL & Domain") so the menu does not
   redraw at a different width from one page to the next. */
.page-settings .dropdown-menu{min-width:232px;}

/* Help menu — the wrapper, the rows and both dialogs are built by js/app.js.
   Mirrors .page-settings: the menu is absolute, so the wrapper is its containing block. */
.topbar-help{position:relative;}
.topbar-help .dropdown-menu{min-width:226px;}
.topbar-help .dropdown-item{padding:9px 12px;}
.topbar-help .dropdown-item svg{width:15px;height:15px;flex:none;}
.topbar-help .dropdown-divider{margin:0;}

/* Keyboard Shortcuts dialog. Key first, description second, tight — a key column
   wide enough for "Esc" keeps the descriptions on one left edge. */
.help-sc{margin:0;display:flex;flex-direction:column;}
.help-sc__row{position:relative;display:flex;align-items:center;gap:11px;padding:9px 0;}
.help-sc__key{flex:none;min-width:38px;}
.help-sc__desc{margin:0;font-size:13px;color:var(--color-text);}

/* What's New dialog */
.help-wn{display:flex;flex-direction:column;}
.help-wn__item{position:relative;display:flex;flex-direction:column;gap:3px;padding:16px 0;}
.help-wn__item:first-child{padding-top:0;}
.help-wn__when{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--color-text-subtle);}
.help-wn__title{font-size:13px;font-weight:600;color:var(--color-text);}
.help-wn__desc{margin:0;font-size:12px;line-height:1.55;color:var(--color-text-muted);}

/* Same gesture as the table row separator above: hold --color-border-strong for the
   first 35% and decay across the rest, so the boundary is stated where the row's
   content starts and is spent before the dialog's edge. A full-width hairline here
   read as a band behind the rows. The last row needs none — the dialog closes it. */
.help-sc__row::after,
.help-wn__item::after{
  content:"";position:absolute;bottom:0;left:0;right:0;height:1px;
  background:linear-gradient(to right,
    var(--color-border-strong) 0%,
    var(--color-border-strong) 35%,
    var(--color-border) 65%,
    transparent 100%);
}
.help-sc__row:last-child::after,
.help-wn__item:last-child::after{content:none;}

/* Themes submenu — injected into the profile dropdown by js/app.js. Flyout opens
   to the LEFT of the item (the profile menu is pinned to the top-right corner). */
.dropdown-submenu{position:relative;}
.dropdown-submenu__toggle{width:100%;justify-content:flex-start;background:transparent;border:0;font:inherit;}
.dropdown-submenu__caret{margin-left:auto;width:14px;height:14px;flex-shrink:0;}
.dropdown-submenu__panel{position:absolute;top:-6px;right:100%;margin-right:5px;min-width:168px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);padding:6px;z-index:110;display:none;}
.dropdown-submenu.open > .dropdown-submenu__panel{display:block;}
.theme-opt{width:100%;justify-content:space-between;background:transparent;border:0;font:inherit;text-align:left;}
.theme-opt .theme-check{width:15px;height:15px;flex-shrink:0;visibility:hidden;color:var(--color-primary-700);}
.theme-opt.active{color:var(--color-primary-700);font-weight:500;}
.theme-opt.active .theme-check{visibility:visible;}

/* The full-color logo (used on the light Modern/Sandstone sidebars) is wider than
   the white one — fit it to the brand area so it doesn't clip. Default/Graphite keep
   the white logo at its native size. */
[data-theme="modern"] .sidebar-brand img,
[data-theme="sandstone"] .sidebar-brand img{max-width:100%;height:auto;}

/* --- Graphite (dark) component tint fixes: icon tiles + status pills carry
   hardcoded light values that don't ride the token swap. Remap them onto
   graphite's own tokens. Light themes stay untouched (base rules unchanged). --- */
[data-theme="graphite"] .thumb         { background: var(--color-primary-50); border-color: var(--color-border-strong); }
[data-theme="graphite"] .badge-neutral { background: var(--bg-input-1); color: var(--color-text); border-color: var(--color-border-strong); }
[data-theme="graphite"] .badge-info    { color: var(--color-info);    border-color: var(--color-border-strong); }
[data-theme="graphite"] .badge-success { color: var(--color-success); border-color: var(--color-border-strong); }
[data-theme="graphite"] .badge-warn    { color: var(--color-warn);    border-color: var(--color-border-strong); }
[data-theme="graphite"] .badge-danger  { color: var(--color-danger);  border-color: var(--color-border-strong); }
[data-theme="graphite"] .badge-primary { border-color: var(--color-border-strong); }

/* --- Ensure the hidden attribute always hides (incl. flex/grid elements) --- */
[hidden] { display: none !important; }

/* Add-warehouse tile — mirrors the catalog price-type add tile */
.wh-add-tile { width:110px; display:flex; align-items:center; justify-content:center; min-height:120px; border:1.5px dashed var(--color-border-strong); background:transparent; box-shadow:none; color:var(--color-text-subtle); cursor:pointer; font:inherit; transition:border-color 120ms, color 120ms; }
.wh-add-tile:hover { border-color:var(--color-primary); color:var(--color-primary); }

/* -------------------------------------------------------------------------
   Modal / dialog — shared overlay primitive (opened via data-modal-open="id",
   closed via data-modal-close / backdrop / Escape; engine in js/app.js)
   ------------------------------------------------------------------------- */
.modal-overlay {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: flex-start; justify-content: center;
  /* Dialogs open ABOVE the vertical midline. On a tall monitor a truly centred box
     reads as low, and the eye is already in the upper half of the screen.
     The lift is bottom padding: `.modal` centres itself with auto margins inside
     this content box, so a deeper bottom inset raises it. Done with padding rather
     than a transform, which `modal-pop` overwrites on its final frame. */
  --modal-lift: 18vh;
  padding: 48px 16px calc(48px + var(--modal-lift)); overflow-y: auto;
  background: rgba(15, 23, 42, 0.44);
  animation: modal-fade 120ms ease-out;
}
@keyframes modal-fade { from { opacity: 0; } to { opacity: 1; } }
.modal {
  width: 100%; max-width: 520px; margin: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  /* The lift eats into the height a dialog may claim, so it is subtracted here —
     otherwise a full-height modal would push past the bottom inset and scroll the
     overlay. Falls back to 0 for any `.modal` used outside `.modal-overlay`. */
  display: flex; flex-direction: column; max-height: calc(100vh - 96px - var(--modal-lift, 0px));
  animation: modal-pop 140ms ease-out;
}
@keyframes modal-pop { from { transform: translateY(6px); opacity: 0; } to { transform: none; opacity: 1; } }
.modal-sm { max-width: 400px; }
.modal-lg { max-width: 760px; }
.modal-header {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--color-border); flex: none;
}
.modal-header h2 { margin: 0; font-size: 17px; }
.modal-header .sub { color: var(--color-text-subtle); font-size: 12px; margin-top: 2px; }
.modal-body { padding: 16px; overflow-y: auto; }
.modal-footer {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 12px 16px; border-top: 1px solid var(--color-border); flex: none;
}
.modal-footer .spacer { flex: 1; }
.modal-close {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border: 0; background: transparent; cursor: pointer;
  border-radius: var(--radius-sm); color: var(--color-text-muted);
}
.modal-close:hover { background: var(--color-surface-alt); color: var(--color-text); }
.modal-close svg { width: 18px; height: 18px; }
/* stacked form fields inside modals */
.modal-body .field + .field,
.modal-body .form-row + .field,
.modal-body .field + .form-row,
.modal-body .form-row + .form-row { margin-top: 14px; }
/* Fields laid out horizontally in a form-row must not inherit the stacked-field top margin. */
.modal-body .form-row > .field + .field { margin-top: 0; }

/* Page Settings › Options dialog — one flat list of per-user page defaults, no subsections.
   Toggles stack as full rows (.switch-field is inline-flex by default, which would
   otherwise run them together on one line). The first toggle needs breathing room from
   the controls above it whether those sit in a bare .field or a .form-row wrapper. */
.opt-dialog .switch-field { display: flex; padding: 7px 0; }
.opt-dialog .field + .switch-field,
.opt-dialog .form-row + .switch-field { margin-top: 12px; }
.opt-dialog__reset { margin-right: auto; }

/* Assign-salesperson picker rows (account-ownership visibility) */
.assign-opt {
  gap: 10px; align-items: center;
  padding: 8px 10px; border: 1px solid var(--color-border);
  border-radius: 5px; cursor: pointer;
  transition: background 120ms, border-color 120ms;
}
.assign-opt:hover { border-color: var(--color-primary); background: var(--color-surface-alt); }
.assign-opt:has(input:checked) {
  background: var(--color-primary-50); border-color: #D6E2FA;
}
[data-assign-hint] {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 11px; border-radius: 5px; font-size: 12px;
  background: var(--color-surface-alt); color: var(--color-text-muted);
}
[data-assign-hint][data-state="gated"] { background: var(--color-primary-50); color: var(--color-primary-700); }
[data-assign-hint] svg { width: 14px; height: 14px; flex: none; }
/* Switches sit directly in centered table cells here (no flex row) — give them a box */
#users-roles .switch { display: inline-block; vertical-align: middle; }
.form-row { display: flex; gap: 12px; }
.form-row > .field { flex: 1; }

/* Toast — transient action feedback */
.toast-host { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 300; display: flex; flex-direction: column; gap: 8px; align-items: center; }
.toast {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 14px; border-radius: var(--radius-md);
  background: var(--color-text); color: #fff; font-size: 13px; font-weight: 500;
  box-shadow: var(--shadow-lg); animation: toast-in 160ms ease-out;
}
.toast svg { width: 16px; height: 16px; }
.toast.is-success { background: var(--color-success); }
@keyframes toast-in { from { transform: translateY(8px); opacity: 0; } to { transform: none; opacity: 1; } }
.wh-add-tile svg { width:48px; height:48px; stroke-width:3; }

/* ===================== STOCK REBUILD ADDITIONS ===================== */

/* Slide-over (right-anchored modal variant) */
/* Slide-overs start below the top bar (header stays visible); centered modals unaffected. */
/* Edge-anchored and full-height, so the centred dialogs' upward lift does not apply. */
.slideover-overlay { top: var(--header-h); align-items: stretch; justify-content: flex-end; padding: 0; --modal-lift: 0px; }
.slideover {
  width: min(500px, 100%); max-width: 100%; height: 100%; max-height: 100%;
  margin: 0; border-radius: 0; border: 0; border-left: 1px solid var(--color-border);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; animation: slideover-in 180ms ease-out;
}
@keyframes slideover-in { from { transform: translateX(24px); opacity: .6 } to { transform: none; opacity: 1 } }
.slideover-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 16px; border-bottom: 1px solid var(--color-border); flex: none;
}
.slideover-head .sub { color: var(--color-text-subtle); font-size: 12px; margin-top: 3px; }
.slideover-body { padding: 16px; overflow-y: auto; flex: 1; }
.slideover-foot {
  display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 16px;
  border-top: 1px solid var(--color-border); flex: none; background: var(--color-surface-alt);
}

/* Slide-over product gallery — hero + up to 4 thumbnails, all crops of one --img source */
.so-gallery { margin-bottom: 16px; }
.so-gallery-hero {
  width: 100%; aspect-ratio: 1.7 / 1; border-radius: 5px; border: 1px solid var(--color-border);
  background-image: var(--img); background-size: cover; background-position: center; background-repeat: no-repeat;
}
.so-gallery-thumbs { display: flex; gap: 8px; margin-top: 8px; }
.so-thumb {
  width: 56px; height: 56px; flex: none; padding: 0; cursor: pointer;
  border-radius: 5px; border: 1px solid var(--color-border);
  background-image: var(--img); background-size: cover; background-repeat: no-repeat;
}
.so-thumb:hover { border-color: var(--color-border-strong); }
.so-thumb.active { border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary-50); }

/* Tree (hierarchical detail) */
.tree { display: flex; flex-direction: column; gap: 2px; font-size: 13px; }
.tree-node { display: flex; flex-direction: column; }
.tree-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 5px; }
.tree-row:hover { background: var(--color-surface-alt); }
.tree-toggle {
  flex: none; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: transparent; cursor: pointer; color: var(--color-text-muted); border-radius: 5px;
}
.tree-toggle svg { width: 15px; height: 15px; }
.tree-ico { width: 15px; height: 15px; color: var(--color-text-subtle); flex: none; }
.tree-label { color: var(--color-primary-700); text-decoration: none; font-weight: 500; }
.tree-label:hover { text-decoration: none; color: var(--color-primary); }
.tree-meta { margin-left: auto; color: var(--color-text-subtle); font-size: 12px; }
.tree-children {
  margin-left: 15px; padding-left: 12px; border-left: 1px solid var(--color-border);
  display: flex; flex-direction: column; gap: 2px; margin-top: 2px;
}
.tree-spacer { width: 20px; flex: none; }

/* Import step-flow */
.step-flow { display: flex; align-items: center; gap: 0; margin-bottom: 16px; }
.step { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.step-num {
  flex: none; width: 28px; height: 28px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 13px; background: var(--color-surface-alt);
  color: var(--color-text-muted); border: 1px solid var(--color-border-strong);
}
.step.is-current .step-num { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }
.step.is-done .step-num { background: var(--color-success); color: #fff; border-color: var(--color-success); }
.step-label { font-size: 13px; font-weight: 500; white-space: nowrap; }
.step.is-current .step-label { color: var(--color-primary-700); }
.step-line { flex: 1; height: 1px; background: var(--color-border-strong); margin: 0 12px; }

/* Dropzone (import upload) */
.dropzone {
  border: 1.5px dashed var(--color-border-strong); border-radius: 5px;
  background: var(--color-primary-50); padding: 32px; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.dropzone .dz-ico { width: 40px; height: 40px; color: var(--color-primary); }

/* Media Uploader — up to 5 Images + 2 Videos per stock item.
   Tiles, counter, buttons and dropzone are all built by js/media-uploader.js;
   the page only writes <div class="media-up" data-media-up> + an optional seed list. */
.media-up-ui { display: flex; flex-direction: column; gap: 10px; }

.media-up-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.media-up-count { font-size: 12px; color: var(--color-text-muted); }
.media-up-count b { color: var(--color-text); font-weight: 600; }
.media-up-count .sep { margin: 0 7px; color: var(--color-border-strong); }
.media-up-actions { display: flex; gap: 6px; margin-left: auto; }

.media-up-grid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
}
.media-up-grid:empty { display: none; }

.media-tile {
  position: relative; aspect-ratio: 1.4 / 1; overflow: hidden;
  border: 1px solid var(--color-border); border-radius: var(--radius);
  background-color: var(--color-surface-alt);
  background-size: cover; background-repeat: no-repeat;
}
.media-tile--image { cursor: grab; }
.media-tile.is-dragging { opacity: .4; }
.media-tile.is-drop-target { border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary-50); }
.media-tile video { display: block; width: 100%; height: 100%; object-fit: cover; background: #000; }

.media-tile-layer { position: absolute; inset: 0; }

.media-tile-badge {
  position: absolute; left: 6px; top: 6px;
  display: inline-flex; align-items: center; gap: 4px;
  height: 20px; padding: 0 7px; border-radius: var(--radius);
  background: var(--color-primary); color: #fff;
  font-size: 12px; font-weight: 600; line-height: 1;
}
.media-tile-badge--video { background: rgba(20,22,26,.72); font-weight: 500; }
.media-tile-badge i, .media-tile-badge svg { width: 12px; height: 12px; }

.media-tile-acts {
  position: absolute; right: 5px; top: 5px;
  display: flex; gap: 4px; opacity: 0; transition: opacity .12s;
}
.media-tile:hover .media-tile-acts,
.media-tile:focus-within .media-tile-acts { opacity: 1; }
.media-tile-act {
  width: 26px; height: 26px; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid rgba(255,255,255,.35); border-radius: var(--radius);
  background: rgba(20,22,26,.62); color: #fff;
}
.media-tile-act:hover { background: rgba(20,22,26,.88); }
.media-tile-act--danger:hover { background: var(--color-danger); border-color: var(--color-danger); }
.media-tile-act i, .media-tile-act svg { width: 14px; height: 14px; }

.media-tile-name {
  position: absolute; inset: auto 0 0 0; padding: 12px 7px 4px;
  background: linear-gradient(to top, rgba(20,22,26,.8), rgba(20,22,26,0));
  color: #fff; font-size: 12px; line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.media-up-drop {
  display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap;
  padding: 14px; text-align: center;
  border: 1.5px dashed var(--color-border-strong); border-radius: var(--radius);
  background: var(--color-surface-alt);
  font-size: 13px; color: var(--color-text-muted);
}
.media-up-drop.is-over { border-color: var(--color-primary); background: var(--color-primary-50); }
.media-up-drop .dz-ico { width: 20px; height: 20px; color: var(--color-primary); }
.media-up.is-full .media-up-drop { display: none; }

@media (max-width: 768px) {
  .media-up-grid { grid-template-columns: repeat(auto-fill, minmax(124px, 1fr)); }
  /* Tile actions are hover-revealed on desktop; on touch there is no hover, so they stay
     visible and grow to the 44px minimum tap target. */
  .media-tile-acts { opacity: 1; }
  .media-tile-act { width: 44px; height: 44px; }
  .media-tile-act i, .media-tile-act svg { width: 18px; height: 18px; }
  .media-up-actions .btn { min-height: 44px; }
}

@media (max-width: 480px) {
  .media-up-actions { width: 100%; margin-left: 0; }
  .media-up-actions .btn { flex: 1; justify-content: center; }
}

/* Responsive 16:9 video embed (video-help modal) */
.video-embed { position: relative; width: 100%; aspect-ratio: 16 / 9; border-radius: 5px; overflow: hidden; background: #000; }
.video-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* Product photo thumbnails (1.7 : 1) + person face avatars — background-image set by app.js.
   font-size:0 hides the initials visually while keeping them in the DOM for table search. */
.thumb.is-photo {
  width: 61px;                 /* 1.7 : 1 against the 36px base height */
  background-size: cover; background-position: center; background-repeat: no-repeat;
  color: transparent; font-size: 0; border-color: rgba(0,0,0,.08);
}
.thumb.is-face, .avatar.is-face {
  background-size: cover; background-position: center 30%; background-repeat: no-repeat;
  color: transparent; font-size: 0; border-color: rgba(0,0,0,.08);
}

body > div:nth-of-type(1) > main > div {
  background-color: var(--bg-page-1);
}

/* ---- Import entry grid + image tiles --------------------------------------
   The inline editable grid in the import slide-overs, and the thumbnail grid
   beside it. Hoisted out of nine pages' <style> blocks (the eight import-*
   pages and stock.html), where every copy was byte-identical.
   Two rules stayed behind on purpose, because they are NOT identical:
     - `table.entry-grid .input` is absent from import-warehouse.html, which
       still has entry-grid inputs; hoisting it would resize them.
     - `table.entry-grid td.col-x` carries `text-align`/`padding-right` on the
       import pages but not on stock.html. Only `width` is shared, so only
       `width` is here. */
.entry-grid-wrap { overflow-x: auto; border: 1px solid var(--color-border); border-radius: 5px; }
table.entry-grid { width: 100%; border-collapse: collapse; }
table.entry-grid th {  text-align: left; white-space: nowrap; font-size: 11px; text-transform: uppercase;  letter-spacing: .04em; color: var(--color-text-muted); font-weight: 600;  padding: 8px 8px; background: var(--color-table-head); border-bottom: 1px solid var(--color-border);  }
table.entry-grid td { padding: 5px 6px; border-bottom: 1px solid var(--color-border); vertical-align: middle; }
table.entry-grid tbody tr:last-child td { border-bottom: 0; }
table.entry-grid td.col-id .input { min-width: 108px; }
table.entry-grid td.col-prod .input { min-width: 124px; }
.entry-remove { background: none; border: 0; cursor: pointer; color: var(--color-text-subtle); display: inline-flex; padding: 4px; border-radius: 5px; }
.entry-remove:hover { color: var(--color-danger, #dc2626); background: var(--color-surface-alt); }
.entry-remove svg { width: 15px; height: 15px; }
.img-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 10px; margin-top: 14px; }
.img-tile { border: 1px solid var(--color-border); border-radius: 5px; overflow: hidden; background: var(--color-surface); }
.img-tile .ph { height: 86px; background: var(--color-primary-50); display: flex; align-items: center; justify-content: center; color: var(--color-primary-700); }
.img-tile .cap { font-size: 11px; padding: 5px 8px; color: var(--color-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  table.entry-grid td.col-x { width: 34px; }
