/* ==========================================================================
   PriceSync design tokens + component layer
   --------------------------------------------------------------------------
   GLOBALLY AVAILABLE, SELECTIVELY APPLIED.

   The :root custom properties below are loaded on every page, but they are
   inert on their own — nothing changes appearance until an element opts in by
   sitting inside `.ps-scope`. Today only /competitors carries that class.
   This is deliberate: MainLayout is the DefaultLayout for all 21 pages, so an
   unscoped `body { font: ... }` here would restyle the entire app in one go.

   To adopt this layer on another page, wrap its markup in `.ps-scope`.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. FONTS — self-hosted, no CDN.
   The droplet must render without reaching fonts.gstatic.com: an external
   render-blocking request is a real cost on a 405-row grid, and an outage
   there should never mean an unstyled pricing page.
   Both families are OFL. Latin subset only.
   -------------------------------------------------------------------------- */
@font-face { font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 400;
             font-display: swap; src: url('fonts/jakarta-400.woff2') format('woff2'); }
@font-face { font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 500;
             font-display: swap; src: url('fonts/jakarta-500.woff2') format('woff2'); }
@font-face { font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 600;
             font-display: swap; src: url('fonts/jakarta-600.woff2') format('woff2'); }
@font-face { font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 700;
             font-display: swap; src: url('fonts/jakarta-700.woff2') format('woff2'); }
@font-face { font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 800;
             font-display: swap; src: url('fonts/jakarta-800.woff2') format('woff2'); }

@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400;
             font-display: swap; src: url('fonts/jetbrains-400.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500;
             font-display: swap; src: url('fonts/jetbrains-500.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 600;
             font-display: swap; src: url('fonts/jetbrains-600.woff2') format('woff2'); }

/* --------------------------------------------------------------------------
   2. TOKENS — named for ROLE, never for value.
   `--ink-2` stays correct when the grey changes; `--gray-400` does not.
   -------------------------------------------------------------------------- */
:root {
  /* Ink — text, darkest to lightest */
  --ink-1: #0f172a;   /* headings, primary figures */
  --ink-2: #334155;   /* body text */
  --ink-3: #64748b;   /* secondary / meta */
  --ink-4: #94a3b8;   /* de-emphasised, placeholder */

  /* Surface — backgrounds, lowest to highest elevation */
  --surface-page:  #f6f7f9;
  --surface-card:  #ffffff;
  --surface-sunk:  #f1f5f9;  /* inset wells, sticky header fill */
  --surface-hover: #eff6ff;

  /* Line */
  --line-soft:   #e8ecf1;   /* interior grid rules */
  --line:        #d8dee7;   /* component borders */
  --line-strong: #b9c2ce;   /* zone dividers */

  /* Provenance — the semantic core of the observation model.
     Feed and manual must be distinguishable at a glance and in the hover. */
  --feed:        #7c3aed;   /* SKUlytics purple */
  --feed-soft:   #f3effe;
  --feed-line:   #ddd0fb;
  --manual:      #c2620a;   /* human-entered orange */
  --manual-soft: #fdf3e7;
  --manual-line: #f5ddba;

  /* Status */
  --good:      #15803d;
  --good-soft: #eaf6ee;
  --warn:      #b45309;
  --bad:       #b91c1c;

  /* Column-zone bands. The zone header carries the colour so the table's own
     header can stay light — a dark header competes with the dark sidebar. */
  --zone-week:  #eef4ff;
  --zone-week-line: #c7d9f7;
  --zone-hist:  #f4f2f7;
  --zone-hist-line: #d9d2e4;
  --zone-calc:  #edf6f0;
  --zone-calc-line: #c4e2ce;

  /* Spacing scale — 4px base */
  --s-1: 4px;   --s-2: 8px;   --s-3: 12px;  --s-4: 16px;
  --s-5: 20px;  --s-6: 24px;  --s-7: 32px;  --s-8: 40px;

  /* Radii */
  --r-sm: 5px;  --r-md: 8px;  --r-lg: 12px; --r-pill: 999px;

  /* Shadows — exactly two */
  --shadow-raise: 0 1px 2px rgba(15, 23, 42, .06), 0 1px 3px rgba(15, 23, 42, .04);
  --shadow-float: 0 4px 12px rgba(15, 23, 42, .10), 0 2px 4px rgba(15, 23, 42, .06);

  /* ------------------------------------------------------------------------
     TYPOGRAPHY

     Fleet standard is 18px body with a 14px hard floor: h1 32 / h2 28 / h3 24.
     That holds for ALL page chrome — buttons, labels, filters, tabs, headers.

     ===== DOCUMENTED EXCEPTION — DO NOT "FIX" THIS =====
     18px does not survive a 15-column grid. Grid DATA sits at 15px in
     JetBrains Mono with font-variant-numeric: tabular-nums, so price columns
     align digit-for-digit and a 4-figure price cannot shift its neighbours.
     The smallest meta text (week sublabels, provenance captions) sits at 14px
     and NEVER goes below.

     This is a deliberate, bounded exception for tabular data only. Raising
     --fs-grid to 18px will reintroduce the horizontal scroll this layout was
     built to control. If you are here to "make the fonts consistent",
     the answer is no — the chrome is already consistent at 18px.
     ==================================================== */
  --fs-h1:    32px;
  --fs-h2:    28px;
  --fs-h3:    24px;
  --fs-body:  18px;   /* all chrome */
  --fs-grid:  15px;   /* EXCEPTION: tabular data only */
  --fs-meta:  14px;   /* HARD FLOOR — nothing smaller, anywhere */

  --font-sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Control metrics — buttons and inputs share these so a toolbar row lines up */
  --control-h:  38px;
  --control-px: 14px;
}

/* --------------------------------------------------------------------------
   3. SCOPE — opt-in application
   -------------------------------------------------------------------------- */
.ps-scope {
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  color: var(--ink-2);
  line-height: 1.45;
}
.ps-scope h1 { font-size: var(--fs-h1); font-weight: 800; color: var(--ink-1); letter-spacing: -.02em; }
.ps-scope h2 { font-size: var(--fs-h2); font-weight: 700; color: var(--ink-1); letter-spacing: -.01em; }
.ps-scope h3 { font-size: var(--fs-h3); font-weight: 700; color: var(--ink-1); }
.ps-scope .ps-meta { font-size: var(--fs-meta); color: var(--ink-3); }

/* --------------------------------------------------------------------------
   4. COMPONENTS — buttons in three weights, inputs, panels
   -------------------------------------------------------------------------- */
.ps-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  height: var(--control-h); padding: 0 var(--control-px);
  font-family: var(--font-sans); font-size: var(--fs-body); font-weight: 600;
  line-height: 1; white-space: nowrap;
  border-radius: var(--r-md); border: 1px solid transparent;
  cursor: pointer; text-decoration: none;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.ps-btn:disabled, .ps-btn[disabled] { opacity: .45; cursor: not-allowed; }
.ps-btn:focus-visible { outline: 2px solid var(--feed); outline-offset: 2px; }

/* SOLID — reserved for the one irreversible action on the page (Push to Planner).
   Exactly one solid button per screen; if a second appears, one of them is wrong. */
.ps-btn-solid { background: var(--ink-1); color: #fff; border-color: var(--ink-1); }
.ps-btn-solid:hover:not(:disabled) { background: #1e293b; }

/* GHOST — repeatable, reversible actions (Sync from Planner, Check prices). */
.ps-btn-ghost { background: var(--surface-card); color: var(--ink-2); border-color: var(--line); }
.ps-btn-ghost:hover:not(:disabled) { background: var(--surface-sunk); border-color: var(--line-strong); }

/* CHIP — filter pills. Same height as the others so the toolbar row is flat. */
.ps-btn-chip {
  height: var(--control-h); padding: 0 var(--s-3);
  border-radius: var(--r-pill); background: var(--surface-card);
  color: var(--ink-3); border-color: var(--line); font-weight: 500;
}
.ps-btn-chip:hover:not(:disabled) { background: var(--surface-sunk); color: var(--ink-2); }
.ps-btn-chip.is-on { background: var(--ink-1); color: #fff; border-color: var(--ink-1); font-weight: 600; }

.ps-input {
  height: var(--control-h); padding: 0 var(--s-3);
  font-family: var(--font-sans); font-size: var(--fs-body); color: var(--ink-2);
  background: var(--surface-card);
  border: 1px solid var(--line); border-radius: var(--r-md);
}
.ps-input::placeholder { color: var(--ink-4); }
.ps-input:focus { outline: none; border-color: var(--feed); box-shadow: 0 0 0 3px var(--feed-soft); }

.ps-panel {
  background: var(--surface-card); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-raise);
}

.ps-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
.ps-toolbar .form-check-input { width: 17px; height: 17px; margin-top: 0; }
.ps-scope .ps-toolbar label { font-size: var(--fs-body); color: var(--ink-2); margin: 0; }

/* --------------------------------------------------------------------------
   5. WEEK RAIL — the panel above the grid

   NOTE: every selector below is written scoped BY HAND. An earlier automated
   pass that prefixed selectors with a regex also prefixed the continuation
   lines of multi-line rules, producing declarations like
   `.ps-scope overflow: auto;`. CSS error recovery silently drops the first
   declaration on such a line, which is how .cpt-wrap lost `overflow` (the page
   scrolled instead of the grid) and .cp-input lost `width: 100%` (inputs fell
   back to the browser's 20-character default, giving ~260px retailer columns).
   Do not re-scope this file mechanically.
   -------------------------------------------------------------------------- */
.ps-scope .wk-rail { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-6); padding: var(--s-4) var(--s-5); margin-bottom: var(--s-3); }
.ps-scope .wk-rail-window { font-size: var(--fs-h3); font-weight: 700; color: var(--ink-1); }
.ps-scope .wk-rail-sub { font-size: var(--fs-meta); color: var(--ink-3); }
.ps-scope .wk-meters { display: flex; gap: var(--s-5); flex-wrap: wrap; }
.ps-scope .wk-meter { min-width: 148px; }
/* gap: the label and value are the two ends of a space-between row; without an
   explicit gap a long name butts straight into the figure ("Home Depot225/405"). */
.ps-scope .wk-meter-top { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-3); font-size: var(--fs-meta); margin-bottom: var(--s-1); }
.ps-scope .wk-meter-name { font-weight: 600; color: var(--ink-2); white-space: nowrap; }
.ps-scope .wk-meter-val { color: var(--ink-3); font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ps-scope .wk-meter-track { height: 6px; border-radius: var(--r-pill); background: var(--surface-sunk); overflow: hidden; }
.ps-scope .wk-meter-fill { height: 100%; border-radius: var(--r-pill); background: var(--feed); }
.ps-scope .wk-gap { margin-left: auto; text-align: right; }
.ps-scope .wk-gap-n { font-size: var(--fs-h3); font-weight: 700; font-family: var(--font-mono); color: var(--bad); }

/* --------------------------------------------------------------------------
   6. THE GRID

   CONTAINMENT CONTRACT: the horizontal scroll belongs to .cpt-wrap and nothing
   above it. .cpt is `width: max-content` (wider than the viewport by design —
   Description is not truncated to force a fit), so .cpt-wrap MUST be both
   `overflow: auto` AND width-limited. An `overflow: auto` box with no width
   constraint just grows to its content and the PAGE scrolls instead, dragging
   the toolbar and week rail off-screen.
   `min-width: 0` on the wrap defeats the flex/grid `min-width: auto` default,
   which would otherwise let the table push its ancestor wider.
   -------------------------------------------------------------------------- */
.ps-scope .cpt-wrap {
  overflow: auto;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  max-height: calc(100vh - 320px);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface-card);
}
.ps-scope .cpt { border-collapse: separate; border-spacing: 0; }
/* fixed layout + max-content belong ONLY to the main grid. The Price Changes tab
   reuses .cpt with an inline width:100% and no colgroup; giving it fixed layout would
   split its 11 columns evenly and wreck that table. */
.ps-scope .cpt-grid { width: max-content; table-layout: fixed; }

.ps-scope .cpt th,
.ps-scope .cpt td {
  font-size: var(--fs-grid);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;   /* digit-for-digit price alignment */
  padding: 3px 8px;
  white-space: nowrap;
  overflow: hidden;
  border-right: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
}
/* Chrome inside the grid (headers and labels) stays in the sans face. */
.ps-scope .cpt thead th { font-family: var(--font-sans); font-weight: 600; }

/* Zone band row — the colour lives here so the column header can stay light. */
.ps-scope .cpt thead tr.zone-row th {
  font-size: var(--fs-meta);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  padding: 6px 8px;
  text-align: center;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 4;
}
.ps-scope .zone-week { background: var(--zone-week); color: #1e40af; border-top: 3px solid var(--zone-week-line); }
.ps-scope .zone-hist { background: var(--zone-hist); color: #5b3f86; border-top: 3px solid var(--zone-hist-line); }
.ps-scope .zone-calc { background: var(--zone-calc); color: #14653a; border-top: 3px solid var(--zone-calc-line); }
.ps-scope .zone-id   { background: var(--surface-sunk); color: var(--ink-3); border-top: 3px solid var(--line); }

/* Light sticky column header — replaces the old dark bar, which competed with
   the dark sidebar. Sits below the zone band, hence top: 30px. */
.ps-scope .cpt thead tr.col-row th {
  position: sticky;
  top: 30px;
  z-index: 3;
  background: var(--surface-sunk);
  color: var(--ink-2);
  border-bottom: 1px solid var(--line-strong);
  vertical-align: bottom;
}
/* Two-line header box. Every retailer/history head reserves the same height so
   the row stays even whether the label wraps ("Home / Depot") or not ("Lowe's"). */
.ps-scope .col-stack { display: block; min-height: 2.5em; line-height: 1.25; }
.ps-scope .col-sub { display: block; font-size: var(--fs-meta); font-weight: 400; color: var(--ink-3); }

/* ---- COLUMN WIDTHS ----------------------------------------------------------
   table-layout: fixed, so these are authoritative and a long Description can no
   longer stretch a column. Retailer/history widths are sized to the widest
   realistic value: "1,049.00" is 8 glyphs; JetBrains Mono advance is 0.6em, so
   at 15px that is 8 x 9px = 72px, + 16px cell padding + 1px border = 89px. 92px
   gives a little headroom without wasting a column.
   ---------------------------------------------------------------------------- */
:root { --col-retailer: 92px; --col-hist: 92px; }
.ps-scope .cpt-grid .col-retailer { width: var(--col-retailer); min-width: var(--col-retailer); max-width: var(--col-retailer); }
.ps-scope .cpt-grid .col-hist     { width: var(--col-hist);     min-width: var(--col-hist);     max-width: var(--col-hist); }
.ps-scope .cpt-grid .col-cat      { width: 92px; }
.ps-scope .cpt-grid .col-decided  { width: 96px; }
.ps-scope .cpt-grid .col-msrp     { width: 96px; }
.ps-scope .cpt-grid .col-lowest   { width: 110px; }
.ps-scope .cpt-grid .col-where    { width: 64px; }
.ps-scope .cpt-grid .col-margin   { width: 84px; }

/* Sticky left context columns: Brand / Model / Description never scroll away.
   Widths are pinned and the left offsets derive from the same variables — never
   hand-type a `left`, that is how sticky columns drift into overlapping. */
:root { --stick-1-w: 74px; --stick-2-w: 132px; --stick-3-w: 240px; }
.ps-scope .cpt-grid .stick { position: sticky; background: var(--surface-card); z-index: 2; }
.ps-scope .cpt-grid thead .stick { background: var(--surface-sunk); }
/* Sticky header cells must out-stack the ORDINARY header cells, which scroll
   underneath them. `.col-row th` is specificity 0-3-3, so a plain
   `thead .stick { z-index }` (0-3-1) loses and the scrolled Cat/Decided/MSRP
   headers paint over the pinned Brand/Model/Description. Match the specificity. */
.ps-scope .cpt-grid thead tr.col-row th.stick  { z-index: 6; }
.ps-scope .cpt-grid thead tr.zone-row th.stick { z-index: 7; }
.ps-scope .cpt-grid .stick-1 { left: 0; width: var(--stick-1-w); min-width: var(--stick-1-w); max-width: var(--stick-1-w); }
.ps-scope .cpt-grid .stick-2 { left: var(--stick-1-w); width: var(--stick-2-w); min-width: var(--stick-2-w); max-width: var(--stick-2-w); }
.ps-scope .cpt-grid .stick-3 { left: calc(var(--stick-1-w) + var(--stick-2-w)); width: var(--stick-3-w); min-width: var(--stick-3-w); max-width: var(--stick-3-w); box-shadow: 1px 0 0 var(--line-strong); }

.ps-scope .cpt tbody tr:hover td { background: var(--surface-hover); }
.ps-scope .cpt tbody tr:hover td.stick { background: var(--surface-hover); }

.ps-scope .num { text-align: right; }
.ps-scope .desc-cell { overflow: hidden; text-overflow: ellipsis; font-family: var(--font-sans); font-size: var(--fs-meta); color: var(--ink-3); }
.ps-scope .cat-cell { font-family: var(--font-sans); font-size: var(--fs-meta); color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; }

/* Cell provenance — feed vs manual must read at a glance. */
.ps-scope .cell-feed { background: var(--feed-soft); }
.ps-scope .cell-feed .cp-input { color: var(--feed); font-weight: 600; }
.ps-scope .cell-manual { background: var(--manual-soft); box-shadow: inset 3px 0 0 var(--manual); }
.ps-scope .cell-manual .cp-input { color: var(--manual); font-weight: 700; }
.ps-scope .cell-both::after { content: ''; position: absolute; top: 2px; right: 2px; width: 5px; height: 5px; border-radius: 50%; background: var(--feed); }
.ps-scope .cpt td.has-dot { position: relative; }

/* width: 100% is load-bearing. Without it an <input> falls back to its default
   size=20 (~20 characters) and drags the whole column out to ~260px. */
.ps-scope .cp-input {
  width: 100%;
  min-width: 0;
  border: none;
  background: transparent;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--fs-grid);
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  padding: 1px 2px;
}
.ps-scope .cp-input:focus { outline: 2px solid var(--feed); outline-offset: -2px; background: var(--surface-card); }
.ps-scope .cp-input.stale { color: var(--ink-4); font-style: italic; }

.ps-scope .lp { background: var(--good-soft) !important; font-weight: 700; color: var(--good); }
.ps-scope .rc { border-bottom: 2px dashed var(--warn) !important; }   /* price only in cart */
.ps-scope .wk-low { text-align: right; color: var(--ink-3); }
.ps-scope .src-cell { text-align: center; font-weight: 700; color: var(--good); font-family: var(--font-sans); }

/* Price Changes tab — stat cards. Scoped: .stat-card is also used by Claims,
   Snapshots and SalespersonView and is defined in app.css. */
.ps-scope .stat-card { background: var(--surface-card); border: 1px solid var(--line); border-left-width: 4px; border-radius: var(--r-md); padding: var(--s-3) var(--s-4); cursor: pointer; min-width: 128px; transition: box-shadow .12s ease, border-color .12s ease; }
.ps-scope .stat-card:hover { box-shadow: var(--shadow-raise); }
.ps-scope .stat-card-active { box-shadow: var(--shadow-float); border-color: var(--line-strong); }
.ps-scope .stat-card .sc-n { font-size: var(--fs-h3); font-weight: 700; font-family: var(--font-mono); color: var(--ink-1); }
.ps-scope .stat-card .sc-l { font-size: var(--fs-meta); color: var(--ink-3); }
