/* ==========================================================================
 * CIP — Ignition CX brand palette
 * --------------------------------------------------------------------------
 * Colours sourced from reference_ignition_palette.md (ignitioncx.com
 * Elementor kit). Typography: Poppins via Google Fonts.
 * ========================================================================== */

/* Two fonts:
 *   Poppins  — branding / app chrome (was already in use)
 *   Inter    — data-dense table cells. Inter is purpose-built for UI
 *              and renders crisply at small sizes where Poppins (a
 *              display sans) gets pinched and hard to read.
 * Both pulled from one Google Fonts request to keep the network cost low. */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Poppins:wght@400;500;600&display=swap');

:root {
  --ig-navy:        #162a3c;
  --ig-navy-deep:   #0b1a26;
  --ig-navy-darker: #131e2f;
  --ig-off-white:   #edeff2;
  --ig-white:       #ffffff;
  --ig-slate:       #53606b;
  --ig-slate-2:     #54595f;
  --ig-text:        #7a7a7a;
  --ig-border:      #dfe1e5;
  --ig-blue:        #6ec1e4;
  --ig-teal:        #00d2a9;
  --ig-green:       #61ce70;
  --ig-red:         #c0392b;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--ig-off-white);
  color: #1f2933;
  font-family: 'Poppins', system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.45;
}

code { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; }

/* ---------- Header ---------- */
.app-header {
  /* Header pinning is primarily handled by the flex layouts below
     (body.fixed-viewport for data pages, body.scroll-page for admin
     pages). This sticky is a harmless fallback for any page that uses
     neither — pins the header on plain document scroll. */
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 24px;
  background: var(--ig-navy);
  color: var(--ig-white);
  padding: 12px 24px;
  border-bottom: 3px solid var(--ig-teal);
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--ig-white);
  text-decoration: none;
  font-weight: 600;
}
.brand-logo {
  height: 32px;
  width: auto;
  display: block;
}
.brand-tag {
  font-size: 18px;
  letter-spacing: 0.05em;
  color: var(--ig-teal);
}
.brand-divider {
  width: 1px;
  height: 22px;
  background: rgba(255, 255, 255, 0.3);
}
.brand-name {
  font-weight: 500;
  font-size: 20px;
  letter-spacing: 0.01em;
  color: var(--ig-off-white);
}

.topnav { display: flex; gap: 4px; }
.nav-link {
  color: var(--ig-off-white);
  text-decoration: none;
  padding: 6px 14px;
  border-radius: 4px;
  font-weight: 500;
}
.nav-link:hover     { background: rgba(255, 255, 255, 0.08); }
.nav-link.active    { background: var(--ig-navy-deep); color: var(--ig-teal); }

/* "More" overflow menu in the header — groups Help / Security / Change history
   so the nav doesn't crowd. Native <details>/<summary> = no JS needed. */
.nav-more { position: relative; }
.nav-more > summary {
  list-style: none;
  cursor: pointer;
  color: var(--ig-off-white);
  padding: 6px 14px;
  border-radius: 4px;
  font-weight: 500;
  white-space: nowrap;
  user-select: none;
}
.nav-more > summary::-webkit-details-marker { display: none; }
.nav-more > summary::marker { content: ""; }
.nav-more > summary:hover { background: rgba(255, 255, 255, 0.08); }
.nav-more[open] > summary,
.nav-more > summary.active { background: var(--ig-navy-deep); color: var(--ig-teal); }
.nav-more-panel {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 6px;
  min-width: 190px;
  background: var(--ig-white);
  border: 1px solid var(--ig-border);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  padding: 6px;
  z-index: 1000;
  display: flex;
  flex-direction: column;
}
.nav-more-item {
  display: block;
  color: var(--ig-navy);
  text-decoration: none;
  padding: 8px 12px;
  border-radius: 4px;
  font-weight: 500;
  white-space: nowrap;
}
.nav-more-item:hover  { background: var(--ig-off-white); }
.nav-more-item.active { background: var(--ig-navy-deep); color: var(--ig-teal); }

.user-strip {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 13px;
}
.user-email { color: var(--ig-off-white); }
.logout {
  color: var(--ig-blue);
  text-decoration: none;
}
.logout:hover { text-decoration: underline; }

/* Header-mounted Reload button. Triggers /admin/refresh-pipeline.
   Styled to read as part of the navy header chrome, not as a form
   control — outline + teal hover, no fill so it doesn't compete
   visually with the nav links or the Sign out link. */
.btn-header-reload {
  background: transparent;
  color: var(--ig-off-white);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 4px;
  font: 500 12px/1 inherit;
  padding: 6px 12px;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.btn-header-reload:hover {
  background: rgba(0, 210, 169, 0.18);
  border-color: var(--ig-teal);
  color: var(--ig-teal);
}
.btn-header-reload:active   { transform: translateY(1px); }
.btn-header-reload:disabled { opacity: 0.5; cursor: wait; }

/* ---------- Main ---------- */
.app-main {
  max-width: 100%;
  padding: 18px 24px 80px;
}

/* ---------- Filters ---------- */
.filters {
  background: var(--ig-white);
  border: 1px solid var(--ig-teal);
  border-radius: 6px;
  padding: 14px 18px;
  margin-bottom: 14px;
}
.filters form {
  display: flex;
  gap: 18px;
  align-items: flex-end;
  flex-wrap: wrap;
}
.filter-col {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 240px;
  /* Allow popovers from inside this column to escape it without being clipped */
  position: relative;
}
.period-col select,
.period-col input {
  font-family: inherit;
  font-size: 14px;
  padding: 6px 10px;
  border: 1px solid var(--ig-teal);
  border-radius: 4px;
  background: var(--ig-white);
}
.period-col select:focus,
.period-col input:focus {
  outline: none;
  border-color: var(--ig-teal);
  box-shadow: 0 0 0 2px rgba(0, 210, 169, 0.18);
}
.filter-label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.filter-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ig-slate);
  font-weight: 500;
}
.filter-actions { display: inline-flex; gap: 4px; }
.filter-actions button {
  font-family: inherit;
  font-size: 11px;
  padding: 2px 8px;
  border: 1px solid var(--ig-border);
  background: var(--ig-off-white);
  color: var(--ig-slate);
  border-radius: 3px;
  cursor: pointer;
}
.filter-actions button:hover {
  background: var(--ig-white);
  border-color: var(--ig-teal);
  color: var(--ig-navy);
}
.filter-search {
  font-family: inherit;
  font-size: 13px;
  padding: 5px 8px;
  border: 1px solid var(--ig-teal);
  border-radius: 3px;
  background: var(--ig-white);
}
.filter-search:focus {
  outline: none;
  border-color: var(--ig-teal);
  box-shadow: 0 0 0 2px rgba(0, 210, 169, 0.18);
}
/* ---------- Dropdown (multi-select popover) ----------
 * Trigger looks like a native <select>: full-width pill with a caret on
 * the right. Panel positions absolutely below it; only the panel scrolls,
 * not the page. Closed by default; .is-open on the wrapper reveals it. */
.dropdown { position: relative; }
.dropdown-trigger {
  font-family: inherit;
  font-size: 14px;
  padding: 6px 10px;
  border: 1px solid var(--ig-teal);
  border-radius: 4px;
  background: var(--ig-white);
  width: 100%;
  min-width: 220px;
  cursor: pointer;
  text-align: left;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.dropdown-trigger:hover { border-color: var(--ig-navy); }
.dropdown.is-open .dropdown-trigger {
  border-color: var(--ig-teal);
  box-shadow: 0 0 0 2px rgba(0, 210, 169, 0.18);
}
.dropdown-summary {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ig-navy);
}
.dropdown-caret {
  color: var(--ig-slate);
  font-size: 11px;
  flex-shrink: 0;
}

/* The popover panel. Width auto-grows up to a sensible max so long
 * agent names don't get truncated. z-index sits above the sticky
 * table header (z-index 2). */
.dropdown-panel {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-width: 420px;
  background: var(--ig-white);
  border: 1px solid var(--ig-border);
  border-radius: 4px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  padding: 8px;
  z-index: 50;
}
.dropdown.is-open .dropdown-panel { display: block; }

.dropdown-panel-header {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 6px;
}
.dropdown-panel-header .filter-actions {
  align-self: flex-end;
}

/* Checkbox list inside the popover. Scoped to .dropdown-panel so it
 * only applies in the new dropdown context — older callers would
 * have used the now-removed always-visible variant. */
.checkbox-list {
  border: 1px solid var(--ig-border);
  background: var(--ig-white);
  border-radius: 3px;
  max-height: 240px;
  overflow-y: auto;
  padding: 4px 0;
}
.check-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 10px;
  font-size: 13px;
  cursor: pointer;
  user-select: none;
}
.check-item:hover    { background: #eef5fb; }
.check-item input    { margin: 0; cursor: pointer; }
.check-item span     { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---------- Export button ----------
 * Pushed to the right edge of the filter row via margin-left: auto on
 * the wrapping .export-col. Teal-on-navy primary action style — strong
 * enough to be the obvious "do this when you're done filtering" action. */
.export-col {
  margin-left: auto;
  align-self: flex-end;
}
.btn-export {
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 8px 18px;
  background: var(--ig-teal);
  color: var(--ig-navy);
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}
.btn-export:hover  { background: #00b797; }
.btn-export:active { transform: translateY(1px); }

/* ---------- HTMX indicator ----------
 * htmx adds .htmx-request to the element with hx-indicator while a
 * request is in flight. We hide the spinner by default and show it
 * during requests. */
.busy {
  /* visibility (not display) so the indicator RESERVES its width even while
     hidden. With display:none it popped into flow on every request and shoved
     the "Hide R0" toggle sideways each reload. */
  visibility: hidden;
  color: var(--ig-slate);
  font-style: italic;
  font-size: 13px;
}
.htmx-request .busy,
.htmx-request.busy { visibility: visible; }

/* ---------- Results meta ---------- */
.result-meta {
  margin: 6px 2px 10px;
  color: var(--ig-slate);
  font-size: 13px;
}
.result-meta strong { color: var(--ig-navy); }

/* ---------- Table ----------
 * The wrapper is the scroll container in BOTH dimensions. That's what
 * makes the thead's `position: sticky; top: 0` actually pin during
 * vertical scroll — sticky works relative to the nearest scrollable
 * ancestor. If you let the page body do the vertical scroll instead,
 * the header drifts off the top under the app header.
 *
 * max-height is computed so the table fills the remaining viewport
 * below the app header + filter strip + the result-meta line. Tweak
 * if you change the filter layout. */
.table-scroll {
  overflow: auto;
  max-height: calc(100vh - 240px);
  border: 1px solid var(--ig-border);
  border-radius: 6px;
  background: var(--ig-white);
}
.weekly-table {
  border-collapse: separate;  /* sticky thead needs separate borders or
                                 the top border draws over scrolled cells */
  border-spacing: 0;
  width: 100%;
  /* Poppins for brand consistency with the rest of the app. tnum gives
     tabular lining figures — currency stacks align on the decimal even
     when digits vary in width. Poppins supports tnum from v3+. */
  font-family: 'Poppins', system-ui, sans-serif;
  font-size: 13px;
  font-feature-settings: 'tnum' 1, 'lnum' 1;
  color: #1f2933;
}
.weekly-table thead th {
  background: var(--ig-teal);
  color: var(--ig-navy);
  position: sticky;
  top: 0;
  z-index: 2;
  /* Sticky thead loses its bottom border when cells scroll under it.
     box-shadow paints a 1px line that travels with the sticky cell.
     Navy-tinted instead of white because the bg is light teal now. */
  box-shadow: inset 0 -1px 0 rgba(22, 42, 60, 0.20);
}
.weekly-table th {
  padding: 8px 10px;
  text-align: left;
  font-weight: 600;
  white-space: nowrap;
  /* Vertical separator between header cells — navy-tinted at 40% so it
     reads clearly against the teal bg. Picks up the thicker
     .col-group-start divider where the header crosses a section. */
  border-right: 1px solid rgba(22, 42, 60, 0.40);
}
/* Note: th.num intentionally has NO text-align rule. Headers are
   centred globally via the .weekly-table thead th block lower down.
   Data cells (td.num) get right-align via the rule directly below. */
.weekly-table td {
  padding: 6px 10px;
  /* Horizontal row separators in the brand teal — softened with alpha
     so they don't overpower the conditional-format cell backgrounds
     (red/yellow/green) sitting underneath. Full teal at 1px reads as
     too heavy here; ~35% alpha is the sweet spot. */
  border-top: 1px solid rgba(0, 210, 169, 0.35);
  /* Vertical grid lines between body cells — uses the brand border
     colour (--ig-border = #dfe1e5) for a Power BI / Excel-like grid
     that's clearly visible without overpowering the data. */
  border-right: 1px solid var(--ig-border);
  white-space: nowrap;
}
.weekly-table td.num   { text-align: right; font-variant-numeric: tabular-nums; }
.weekly-table td.neg   { color: var(--ig-red); }

/* Explicit background on every body cell — required for the frozen
 * columns below to opaquely cover content scrolling under them. Without
 * this, odd rows are transparent and the scrolling cells bleed through. */
.weekly-table tbody td                    { background: var(--ig-white); }
.weekly-table tbody tr:nth-child(even) td { background: #f7f8fa; }
.weekly-table tbody tr:hover td           { background: #eef5fb; }

/* ----- Column groups -----
 * Thicker vertical divider where each section starts (Tenure, Sales,
 * DC, Activations, QA, Deductions, TOTAL). Header divider is whiter
 * because the header background is navy; body divider is slate.
 *
 * The TOTAL column gets a stronger visual treatment because it's the
 * column most users scan for first. Teal header pill + warm yellow
 * cell tint + bold text. The hover/stripe rules above use `td` selectors
 * (not `tr`) so this background wins where it's set. */
.weekly-table thead th.col-group-start {
  /* Navy-tinted divider against the teal thead bg. */
  border-left: 2px solid rgba(22, 42, 60, 0.30);
}
.weekly-table tbody td.col-group-start {
  border-left: 2px solid #c8d0d8;
}

/* TOTAL header inverts the new teal palette — navy bg with teal text —
 * so the column still pops as the headline number despite the rest of
 * the thead also being teal. Body cell remains warm-yellow / bold. */
.weekly-table thead th.col-total {
  background: var(--ig-navy);
  color: var(--ig-teal);
  font-weight: 600;
  /* Override the navy-on-teal group-start divider with a teal-on-navy one. */
  border-left: 2px solid rgba(0, 210, 169, 0.40);
}
.weekly-table tbody td.col-total {
  background: #fff7d6;
  font-weight: 600;
  border-left: 2px solid var(--ig-teal);
}
/* Keep TOTAL's tint distinct even on hover/striped rows. */
.weekly-table tbody tr:nth-child(even) td.col-total { background: #fff2c2; }
.weekly-table tbody tr:hover td.col-total           { background: #ffeaa3; }

/* ----- Conditional formatting (mirrors Karien's workbook) -----
 *
 * Specificity note: the row-stripe rule above (`tbody tr:nth-child(even) td`)
 * has specificity 0,2,3. Our fmt-* selectors use `tbody tr td.fmt-X` (also
 * 0,2,3) and rely on coming LATER in the cascade to win — so don't move
 * these above the stripe rules.
 *
 * Hover (`tbody tr:hover td`, specificity 0,3,3) still wins over fmt-*
 * so the user keeps a clear row-selection indicator on mouse-over. */

/* 3-color scale: red → yellow → green, 5 discrete bands.
 * Hex values match Excel's default 3-color scale colour stops closely
 * enough that the CIP page and Karien's sheet feel like the same artefact. */
.weekly-table tbody tr td.fmt-pct-1 { background: #f8696b; }  /* < 25%  red */
.weekly-table tbody tr td.fmt-pct-2 { background: #fcaa7b; }  /* 25-50% orange */
.weekly-table tbody tr td.fmt-pct-3 { background: #ffe684; }  /* 50-70% yellow */
.weekly-table tbody tr td.fmt-pct-4 { background: #b8d97a; }  /* 70-90% light green */
.weekly-table tbody tr td.fmt-pct-5 { background: #63be7b; }  /* >= 90% green */

/* Target threshold cells (DC % (T), Activations % (T)) — single static
 * highlight matching Karien's cream-yellow. Distinct from the achievement
 * yellow above so the two columns read as different concepts. */
.weekly-table tbody tr td.fmt-target { background: #fff5cc; }

/* Bonus > 0 → light green pill, Penalty > 0 → light red pill.
 * Both fall through to no formatting when the value is 0 / null. */
.weekly-table tbody tr td.fmt-bonus   { background: #d4edda; }
.weekly-table tbody tr td.fmt-penalty { background: #f8d7da; }

/* ----- Frozen left columns (AD / Full Name / TL / Campaign / Tenure) -----
 *
 * Scoped to `.weekly-table.weekly-freeze` only. Other pages (daily, mtd,
 * final) use plain .weekly-table and get content-fit column widths — see
 * the auto-fit block below.
 *
 * Sticky cells pin to the left edge of .table-scroll as the user scrolls
 * horizontally. Each column needs an explicit width AND a `left` offset
 * equal to the SUM of all previous frozen columns. If the widths drift,
 * the offsets break — that's why every frozen column has hard
 * width/min-width/max-width set.
 *
 * Stacking order:
 *   z-index 1  — frozen body cells (above scrolling body cells)
 *   z-index 2  — non-frozen header cells (already set above)
 *   z-index 3  — frozen header cells (intersection of sticky-top + sticky-left)
 *
 * The shadow on the 5th column's right edge paints a soft depth cue
 * over the next column so users perceive the freeze line, similar to
 * Excel / Power BI table panes. */
.weekly-table.weekly-freeze thead th:nth-child(-n+5),
.weekly-table.weekly-freeze tbody td:nth-child(-n+5) {
  position: sticky;
  overflow: hidden;
  text-overflow: ellipsis;
}
.weekly-table.weekly-freeze tbody td:nth-child(-n+5) { z-index: 1; }
.weekly-table.weekly-freeze thead th:nth-child(-n+5) { z-index: 3; }

/* Per-column widths + cumulative left offsets.
 * Running total: 0 → 100 → 300 → 400 → 530 → (right edge 630) */
.weekly-table.weekly-freeze th:nth-child(1),
.weekly-table.weekly-freeze td:nth-child(1) {
  left: 0;
  width: 100px; min-width: 100px; max-width: 100px;
}
.weekly-table.weekly-freeze th:nth-child(2),
.weekly-table.weekly-freeze td:nth-child(2) {
  left: 100px;
  width: 200px; min-width: 200px; max-width: 200px;
}
.weekly-table.weekly-freeze th:nth-child(3),
.weekly-table.weekly-freeze td:nth-child(3) {
  left: 300px;
  width: 100px; min-width: 100px; max-width: 100px;
}
.weekly-table.weekly-freeze th:nth-child(4),
.weekly-table.weekly-freeze td:nth-child(4) {
  left: 400px;
  width: 130px; min-width: 130px; max-width: 130px;
}
.weekly-table.weekly-freeze th:nth-child(5),
.weekly-table.weekly-freeze td:nth-child(5) {
  left: 530px;
  width: 100px; min-width: 100px; max-width: 100px;
  /* Right-edge shadow paints the visual depth cue for the freeze line.
     Negative spread (-4px) confines the soft edge to the right side
     instead of bleeding both ways. */
  box-shadow: 4px 0 6px -4px rgba(0, 0, 0, 0.18);
}

.empty {
  background: var(--ig-white);
  border: 1px dashed var(--ig-border);
  padding: 24px;
  border-radius: 6px;
  color: var(--ig-slate);
}

/* ---------- Footer ---------- */
.app-footer {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 8px 24px;
  background: var(--ig-navy-darker);
  color: var(--ig-off-white);
  font-size: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.app-footer code { color: var(--ig-teal); }


/* ====================================================================
   /explain — per-agent narrative breakdown
   --------------------------------------------------------------------
   One explain-card per agent in the filtered set. Each card has a
   header (name + tenure + final total) and a column of step rows
   that walk through each adjustment (target, DC, activations, QA,
   clawbacks, etc.) with the running total on the right.

   Steps are colour-tinted: green for positive uplift, red for
   reductions, default for no-change rows. Helps the reader scan the
   "where did the R600 go" question in one glance.
   ==================================================================== */
.explain-card {
  background: var(--ig-white);
  border: 1px solid var(--ig-border);
  border-radius: 6px;
  margin: 14px 0;
  padding: 0;
  box-shadow: 0 1px 2px rgba(22, 42, 60, 0.04);
  overflow: hidden;
}

.explain-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  padding: 14px 18px;
  background: var(--ig-navy);
  color: var(--ig-white);
}
.explain-header h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--ig-white);
}
.explain-sub {
  margin: 4px 0 0;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.72);
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.explain-sub .sep { color: rgba(255, 255, 255, 0.32); }

.explain-total {
  text-align: right;
  flex-shrink: 0;
}
.explain-total .total-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.7);
}
.explain-total .total-value {
  font-size: 22px;
  font-weight: 700;
  color: var(--ig-teal);
}
.explain-total.total-zero .total-value { color: rgba(255, 255, 255, 0.45); }

/* ----- Eight-line per-agent breakdown ----- */
.explain-lines {
  padding: 4px 0;
}
.explain-line {
  display: grid;
  grid-template-columns: 200px 1fr 140px;
  align-items: start;
  gap: 18px;
  padding: 12px 18px;
  border-top: 1px solid var(--ig-border);
  font-size: 13px;
}
.explain-line:first-child { border-top: none; }
.explain-line .line-label {
  font-weight: 600;
  color: var(--ig-navy);
  padding-top: 1px;
}
.explain-line .line-body {
  color: var(--ig-slate, #5a6470);
  line-height: 1.45;
}
.explain-line .line-formula strong {
  color: var(--ig-navy);
  font-weight: 600;
}
.explain-line .line-source {
  margin-top: 4px;
  font-size: 11.5px;
  color: var(--ig-slate-2, #7a8390);
  font-style: italic;
}
.explain-line .line-amount {
  text-align: right;
  font-family: 'Poppins', system-ui, sans-serif;
  font-feature-settings: 'tnum' 1, 'lnum' 1;
  font-weight: 600;
  color: var(--ig-navy);
  padding-top: 1px;
}

/* Direction tints. Matches the column-format gradient palette so the
   visual language reads as part of the same app. */
.explain-line.line-pos { background: rgba(46, 204, 113, 0.06); }
.explain-line.line-pos .line-amount { color: #1a6b3a; }
.explain-line.line-neg { background: rgba(231, 76, 60, 0.06); }
.explain-line.line-neg .line-amount { color: #922b1f; }
.explain-line.line-note .line-amount { color: var(--ig-slate-2, #7a8390); }

/* Final row — sits at the bottom of every card, restates the headline
   total with a teal divider so the eye lands there. */
.explain-line.line-final {
  background: var(--ig-off-white, #f5f7fa);
  border-top: 2px solid var(--ig-teal);
  font-weight: 600;
}
.explain-line.line-final .line-amount {
  font-size: 15px;
  color: var(--ig-navy);
}

/* Narrow screens — stack the grid to one column. */
@media (max-width: 720px) {
  .explain-line {
    grid-template-columns: 1fr;
    gap: 4px;
  }
  .explain-line .line-amount { text-align: left; }
}


/* ====================================================================
   /runs — commission run approval workflow (Business Overview §7)
   ==================================================================== */

/* Status pill — same shape as the rest of the app's chrome, colour-
   coded by workflow position. Used on /runs and /runs/<id>. */
.status-pill {
  display: inline-block;
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 600;
  border-radius: 99px;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.status-pill.status-submitted {
  background: rgba(241, 196, 15, 0.18);
  color: #6c5500;
}
.status-pill.status-hocc {
  background: rgba(46, 134, 171, 0.15);
  color: #1f5f80;
}
.status-pill.status-approved {
  background: rgba(46, 204, 113, 0.18);
  color: #1a6b3a;
}
.status-pill.status-rejected {
  background: rgba(231, 76, 60, 0.18);
  color: #922b1f;
}

/* Approval timeline on /runs/<id>. Three stacked steps:
   submitted → HoCC → Finance. Each step renders different state
   depending on whether it's done, current, or skipped/rejected. */
.run-timeline {
  margin: 18px 0 8px;
  border: 1px solid var(--ig-border);
  border-radius: 6px;
  background: var(--ig-white);
  overflow: hidden;
}
.run-step {
  display: grid;
  grid-template-columns: 56px 1fr;
  gap: 16px;
  padding: 14px 18px;
  border-top: 1px solid var(--ig-border);
}
.run-step:first-child { border-top: none; }

.run-step .run-step-bullet {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 15px;
  background: var(--ig-off-white, #f5f7fa);
  color: var(--ig-slate-2, #7a8390);
  border: 2px solid var(--ig-border);
}
.run-step-done    .run-step-bullet { background: var(--ig-teal); color: var(--ig-navy); border-color: var(--ig-teal); }
.run-step-current .run-step-bullet { background: rgba(46, 134, 171, 0.20); color: #1f5f80; border-color: #1f5f80; }
.run-step-skip    .run-step-bullet { opacity: 0.4; }

.run-step .run-step-title {
  font-weight: 600;
  color: var(--ig-navy);
  margin-bottom: 4px;
}
.run-step .run-step-meta {
  color: var(--ig-slate, #5a6470);
  font-size: 13px;
}
.run-step .run-step-meta code {
  background: var(--ig-off-white, #f5f7fa);
  padding: 1px 5px;
  border-radius: 3px;
  font-size: 11px;
}
.run-step .run-step-notes {
  margin-top: 6px;
  padding: 8px 10px;
  background: var(--ig-off-white, #f5f7fa);
  border-left: 3px solid var(--ig-teal);
  font-size: 13px;
  color: var(--ig-navy);
}

/* Inline approval form — used in the current-step body. */
.run-approve-form {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 8px;
  flex-wrap: wrap;
}
.run-approve-notes {
  font-family: inherit;
  font-size: 13px;
  padding: 6px 10px;
  border: 1px solid var(--ig-teal);
  border-radius: 4px;
  background: var(--ig-white);
  flex: 1;
  min-width: 200px;
}

/* Reject panel — sits below the timeline. Red border to make it clear
   this is a destructive action. */
.run-reject {
  margin: 14px 0 8px;
  padding: 12px 18px;
  border: 1px solid rgba(231, 76, 60, 0.40);
  border-left: 4px solid #c0392b;
  background: rgba(231, 76, 60, 0.04);
  border-radius: 6px;
}
.run-reject form {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin: 0;
}
.btn-reject {
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 7px 16px;
  background: var(--ig-white);
  color: #922b1f;
  border: 1px solid #c0392b;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}
.btn-reject:hover  { background: rgba(231, 76, 60, 0.10); }
.btn-reject:active { transform: translateY(1px); }

/* Generic teal "Open" link in the runs list table. */
.btn-link {
  font-size: 12px;
  font-weight: 600;
  color: var(--ig-teal);
  text-decoration: none;
  padding: 4px 10px;
  border: 1px solid var(--ig-teal);
  border-radius: 4px;
  display: inline-block;
}
.btn-link:hover { background: rgba(0, 210, 169, 0.10); }


/* ====================================================================
   Fixed-viewport layout (Excel-style page)
   --------------------------------------------------------------------
   Applied to <body> on the four data pages (daily/weekly/mtd/final) via
   the `fixed-viewport` class. Goal: the page itself never scrolls —
   header + filter bar stay pinned at the top, footer stays at the
   bottom, and ONLY the table body scrolls vertically (and horizontally
   on /weekly).

   Layout chain:
     body (height: 100vh, flex column, overflow hidden)
       app-header     — fixed height
       app-main       — flex 1, takes remaining height, flex column
         h2 / filters — fixed height
         table-wrap   — flex 1, flex column
           result-meta — fixed height
           table-scroll — flex 1, the scroller
       app-footer     — fixed height

   The flex `min-height: 0` lines are not decorative — without them
   flex children refuse to shrink below their content height, which
   defeats the whole layout and the page starts scrolling again.
   ==================================================================== */
body.fixed-viewport {
  margin: 0;
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body.fixed-viewport .app-header { flex: 0 0 auto; }

body.fixed-viewport .app-footer {
  /* Drop position:fixed when the parent is a flex column — the footer
     slots naturally as the last child of body. */
  position: static;
  flex: 0 0 auto;
}

body.fixed-viewport .app-main {
  flex: 1 1 auto;
  min-height: 0;
  padding: 14px 24px 12px;     /* tighter padding — every pixel of
                                  table area matters in this mode */
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Page chrome that should not stretch — natural height each. */
body.fixed-viewport .app-main > h2,
body.fixed-viewport .app-main > p,
body.fixed-viewport .filters,
body.fixed-viewport .result-meta { flex: 0 0 auto; }

/* The HTMX swap target (id="daily-table-wrap" etc.) wraps result-meta
   + table-scroll. It needs to inherit the remaining height and pass it
   down. Attribute selector matches any id ending in "-table-wrap". */
body.fixed-viewport [id$="-table-wrap"] {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* The actual scroller. Override the legacy max-height cap that was
   keyed to a guessed top-offset (calc(100vh - 240px)). */
body.fixed-viewport [id$="-table-wrap"] .table-scroll {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  margin-bottom: 0;
}

/* ====================================================================
   scroll-page — admin / narrative pages (config, products, security,
   change history, explain, approval flow). Same pinned-header model as
   fixed-viewport, but the whole .app-main scrolls (no inner table-wrap).
   Pins header + footer via flex and makes .app-main the only scroller —
   bulletproof, unlike `position: sticky` which silently failed on the
   tall /config page (the header scrolled away once a campaign loaded).
   ==================================================================== */
body.scroll-page {
  margin: 0;
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body.scroll-page .app-header { flex: 0 0 auto; }
body.scroll-page .app-footer {
  position: static;          /* drop the default position:fixed */
  flex: 0 0 auto;
}
body.scroll-page .app-main {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;            /* the only scroller — header/footer stay put */
  padding: 18px 24px 18px;   /* no fixed-footer clearance needed now */
}


/* ====================================================================
   Sticky sum/avg footer + R0-payout filter (Task #170 + #171)
   --------------------------------------------------------------------
   Server-rendered <tfoot><tr class="totals-row"> sits at the bottom of
   each .weekly-table. Cell values are filled client-side by
   recomputeFooter() in app.js based on the data-agg attribute on each
   <th>. Sticky positioning is relative to .table-scroll (the scroll
   ancestor) so the totals stay visible while the body scrolls past.

   On /weekly the first 5 cells in tfoot also need to be sticky-left
   like the existing frozen tbody cells — extended below.
   ==================================================================== */
.weekly-table tfoot td {
  position: sticky;
  bottom: 0;
  background: var(--ig-off-white, #f5f7fa);
  font-weight: 600;
  color: var(--ig-navy);
  border-top: 2px solid var(--ig-teal);
  padding: 8px 10px;
  z-index: 2;
  /* Sticky tfoot loses its top border when rows scroll under it for
     the same reason sticky thead did — paint a shadow that travels. */
  box-shadow: inset 0 1px 0 rgba(22, 42, 60, 0.12);
}
.weekly-table tfoot td.num { text-align: right; }

/* The body's .fmt-final cells use rgba(... , 0.05) — transparent enough
   that the sticky footer cell on the SAME column would show the body
   row right through it. Override with an opaque equivalent here.
   Selector specificity (0,2,2) > `.weekly-table td.fmt-final` (0,2,1)
   so this wins for tfoot cells without affecting tbody cells. */
.weekly-table tfoot td.fmt-final {
  background-color: #e6eff5;     /* opaque blend of fmt-final over off-white */
  color: var(--ig-navy);
}

/* Frozen-column extension for /weekly: tfoot cells 1–5 stick to the
   left edge as the table scrolls horizontally, matching the tbody
   pattern further up the stylesheet.
   z-index MUST exceed the NON-frozen tfoot cells, which the
   `.weekly-table tfoot td` rule above already sets to z=2 (for
   bottom-sticky). At equal z-index, a scrolling total cell — later in
   DOM order — paints OVER the pinned frozen cells, so the totals appear
   to slide past the frozen columns (the reported bug). z=3 keeps the
   frozen totals on top; it doesn't clash with the z=3 thead because the
   two pin to opposite edges (top vs bottom) and never overlap. */
.weekly-table.weekly-freeze tfoot td:nth-child(-n+5) {
  position: sticky;
  /* `left` values are inherited from the existing
     `.weekly-table.weekly-freeze td:nth-child(N)` rules — selector
     matches td regardless of parent (tbody vs tfoot). */
  z-index: 3;
  background: var(--ig-off-white, #f5f7fa);
}
/* The 5th frozen tfoot cell gets the same right-edge depth shadow as
   the 5th tbody cell. */
.weekly-table.weekly-freeze tfoot td:nth-child(5) {
  box-shadow: 4px 0 6px -4px rgba(0, 0, 0, 0.18),
              inset 0 1px 0 rgba(22, 42, 60, 0.12);
}

/* "Hide R0 payouts" filter — rows hidden via row-hidden class so the
   client-side footer recompute can skip them without losing them from
   the DOM. */
.weekly-table tbody tr.row-hidden { display: none; }
/* /explain renders per-agent cards, not table rows — the same Hide-R0 toggle
   hides cards whose headline total is R0 / negative (flagged server-side with
   .explain-total.total-zero). */
.explain-card.row-hidden { display: none; }

/* R0 toggle checkbox — sits in the filter strip alongside the other
   filter-col items. Vertically aligned with the 36px-tall pickers. */
.filter-col.r0-toggle-col {
  min-width: auto;
  justify-content: flex-end;
}
.filter-col.r0-toggle-col label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  font-size: 13px;
  color: var(--ig-navy);
  cursor: pointer;
}
.filter-col.r0-toggle-col input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--ig-teal);
  cursor: pointer;
}


/* ====================================================================
   /security · quick-edit inline form (Task #167)
   --------------------------------------------------------------------
   The per-row "Quick edit" cell on /security holds a role dropdown +
   active checkbox + Save button. Before this block the three controls
   used three different sizes and styling defaults — the Save button
   ended up looking tiny next to the dropdown. Here we lock them all
   to a single 28px-high inline scale + teal-bordered look so the row
   reads as a single coherent action.

   Scaled down vs the main .filters chrome (36px tall) because this is
   a per-row inline action, not a page-level filter — but still uses
   the same border colour + button accent so they're visually related.
   ==================================================================== */
.quick-edit-form {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
}
.quick-edit-control {
  font-family: inherit;
  font-size: 12px;
  height: 28px;
  padding: 0 8px;
  border: 1px solid var(--ig-teal);
  border-radius: 4px;
  background: var(--ig-white);
  color: var(--ig-navy);
  cursor: pointer;
}
.quick-edit-check {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 28px;
  font-size: 12px;
  color: var(--ig-navy);
  cursor: pointer;
}
.quick-edit-check input[type="checkbox"] {
  width: 14px;
  height: 14px;
  accent-color: var(--ig-teal);
  margin: 0;
}
.quick-edit-save {
  /* Inherits teal bg + navy text from .btn-save; just shrink to the
     row's 28px scale. Bumping font-weight 500→600 because at 12px
     the lighter weight reads as washed-out next to the dropdown. */
  height: 28px;
  padding: 0 14px;
  font-size: 12px;
  font-weight: 600;
}


/* ====================================================================
   Page loading indicator (Task #168)
   --------------------------------------------------------------------
   Thin teal bar pinned to the top of the viewport. Visible while a
   page is loading — either a full navigation (clicking a nav link)
   or an HTMX request. Driven by JS in app.js: it sets
   #page-loader.visible on start and clears it on completion.

   The bar animates a soft left-to-right shimmer so the user reads
   "something is happening" without committing to a specific %.
   ==================================================================== */
#page-loader {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: rgba(0, 210, 169, 0.18);
  z-index: 9999;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
}
#page-loader.visible {
  opacity: 1;
}
#page-loader.visible::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 40%;
  background: var(--ig-teal);
  animation: page-loader-slide 1.1s ease-in-out infinite;
}
@keyframes page-loader-slide {
  0%   { left: -40%; }
  100% { left: 100%; }
}

/* ---------- Phase A page additions (daily / mtd / final) ---------- */

/* Row tint by attendance class on the /daily table. Subtle so the table
   still reads cleanly; tint applies via background-color override only. */
.weekly-table tr.row-absent td {
  background-color: rgba(231, 76, 60, 0.06);
}
.weekly-table tr.row-leave td {
  background-color: rgba(241, 196, 15, 0.08);
}

/* Hashed SA ID column on /final. Renders the full SHA256 (64 hex chars),
   which is wider than the 280px column cap that applies to the rest of
   the table — escape the cap here so the value isn't ellipsised.
   Keep the same Poppins / 13px font as the rest of the table; just
   dim the text colour slightly so the long hash doesn't visually
   compete with the rand values next to it. */
.weekly-table td.id-hash {
  color: var(--ig-slate);
  cursor: help;
  max-width: none;
  white-space: nowrap;
  overflow: visible;
  text-overflow: clip;
}

/* ====================================================================
   Phase A.8 — sortable + resizable columns
   ==================================================================== */

/* Header cells are click-targets for sort. Cursor hint + hover bg. */
.weekly-table thead th {
  cursor: pointer;
  user-select: none;
  /* Reserve right-edge space for the resize handle and the sort arrow.
     Padding-right of 22px = ~8px handle + ~14px arrow. */
  padding-right: 22px;
  position: sticky;          /* re-asserted from base — Edit didn't remove */
}
.weekly-table thead th:hover {
  background: var(--ig-teal-hover, #b6e3d4);
}

/* Sort arrow indicator — rendered via ::after. Defaults to ↕ (neutral),
   flips to ▲ / ▼ when the th has the data-sort-dir attr. */
.weekly-table thead th::after {
  content: '↕';
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 9px;
  opacity: 0.35;
  pointer-events: none;
}
.weekly-table thead th[data-sort-dir="asc"]::after  { content: '▲'; opacity: 0.9; }
.weekly-table thead th[data-sort-dir="desc"]::after { content: '▼'; opacity: 0.9; }
/* Suppress sort affordance on columns explicitly marked unsortable. */
.weekly-table thead th[data-no-sort]::after { content: ''; }
.weekly-table thead th[data-no-sort]        { cursor: default; }
.weekly-table thead th[data-no-sort]:hover  { background: var(--ig-teal); }

/* Resize handle — invisible 6px strip on the right edge. Cursor flips
   to col-resize on hover. JS attaches mousedown to start drag. */
.weekly-table thead th .col-resize-handle {
  position: absolute;
  top: 0;
  right: 0;
  width: 6px;
  height: 100%;
  cursor: col-resize;
  user-select: none;
  z-index: 3;
}
.weekly-table thead th .col-resize-handle:hover {
  background: rgba(0, 0, 0, 0.1);
}
/* While dragging, paint the active handle solid teal so the user sees
   exactly which column is being resized. */
.weekly-table thead th.is-resizing .col-resize-handle {
  background: var(--ig-teal);
  opacity: 0.7;
}

/* ====================================================================
   Phase A.9 — conditional formatting preset
   (extends existing .fmt-pct-1..5, .neg, row-absent, row-leave classes)
   ==================================================================== */

/* Final / MTD payable column — boldface so the bottom-line number pops. */
.weekly-table td.fmt-final {
  font-weight: 600;
  background: rgba(46, 134, 171, 0.05);   /* very light blue tint */
}

/* 3-band pct preset for /mtd and /final (50% / 80% thresholds). Uses
   distinct class names from weekly's .fmt-pct-1..5 (5-band) so they
   don't visually clash. */
.weekly-table td.fmt-pct-low  { background: rgba(231, 76, 60, 0.18); color: #922b1f; }
.weekly-table td.fmt-pct-mid  { background: rgba(241, 196, 15, 0.20); color: #6c5500; }
.weekly-table td.fmt-pct-high { background: rgba(46, 204, 113, 0.18); color: #1a6b3a; }

/* Filter-strip download button — matches .btn-export from /weekly so all
   4 pages have the same call-to-action styling. Teal bg + navy text. */
.btn-download {
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 8px 18px;
  background: var(--ig-teal);
  color: var(--ig-navy);
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.btn-download:hover  { background: #00b797; }
.btn-download:active { transform: translateY(1px); }

/* Reload — secondary action, same shape as Download but inverted
   colour (white bg + teal border + navy text) so it doesn't compete
   visually with the primary Download CTA. */
.btn-reload {
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 7px 16px;          /* -1px each side to match the teal-bordered selects */
  background: var(--ig-white);
  color: var(--ig-navy);
  border: 1px solid var(--ig-teal);
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.btn-reload:hover  { background: rgba(0, 210, 169, 0.10); }
.btn-reload:active { transform: translateY(1px); }

/* ====================================================================
   Auto-fit columns (daily / mtd / final)
   --------------------------------------------------------------------
   .weekly-table without the .weekly-freeze modifier gets browser-native
   content-fit column widths. white-space:nowrap prevents wrapping so
   the column sizes to the longest single-line value.

   Padding 6/10 leaves enough breathing room around values without
   over-stretching. Resize handles still work — the user can drag a
   header to override the auto-size if needed.
   ==================================================================== */
.weekly-table:not(.weekly-freeze) {
  /* table-layout auto is the default but be explicit so a future change
     to fixed-layout elsewhere doesn't silently break auto-fit. */
  table-layout: auto;
}
/* Data cells only — single-line so values don't break. Padding + cap +
   ellipsis on the cell value. HEADERS are intentionally excluded here
   so the later .weekly-table thead th block can wrap them; combining
   them would force this rule to lose specificity-wise. */
.weekly-table:not(.weekly-freeze) tbody td {
  white-space: nowrap;
  padding: 6px 10px;
  /* Cap any individual column at 280px so a runaway value (long full
     name, multi-campaign pipe-delimited string) doesn't push the rest
     of the table off-screen. Resize handle still lets you go bigger. */
  max-width: 280px;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Headers (non-frozen tables) — match the cell padding so columns line
   up vertically. Wrapping behaviour comes from the .weekly-table thead
   th block further down. */
.weekly-table:not(.weekly-freeze) thead th {
  padding: 6px 10px;
}

/* ====================================================================
   Wrap-able headers — applies to all .weekly-table instances
   --------------------------------------------------------------------
   By default the table's nowrap rule sized columns to the longer of
   (header text, longest data value). Long headers like "Weekly Comm-
   Total After Att %" pushed a column wider than its short numeric
   values needed.

   Solution: let thead th wrap at word boundaries; keep tbody td single-
   line so values never break. Centre + tighter line-height makes
   multi-line headers read cleanly.

   This rule sits AFTER the .weekly-table:not(.weekly-freeze) block
   above, so it overrides the single-line header rule there.
   ==================================================================== */
.weekly-table thead th {
  white-space: normal;
  overflow-wrap: break-word;   /* break long single tokens if needed */
  word-break: normal;
  vertical-align: middle;
  text-align: center;
  line-height: 1.2;
  /* Sensible min-width so single-word headers don't compress to ~20px
     just because their data is one digit. */
  min-width: 56px;
}

/* Numeric columns: right-align the data but keep the header centred
   for visual symmetry across the row of headers. */
.weekly-table td.num { text-align: right; }

/* Frozen columns on /weekly still need single-line headers because
   ellipsis is their fallback when the hardcoded width is too tight. */
.weekly-table.weekly-freeze thead th:nth-child(-n+5) {
  white-space: nowrap;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ====================================================================
   Admin nav links (Config / Security / Audit) — only rendered when
   is_admin is true in the route context. Visually tinted teal so they
   read as "tools" distinct from the standard read-page nav.
   ==================================================================== */
.nav-divider {
  width: 1px;
  height: 18px;
  background: rgba(255, 255, 255, 0.2);
  margin: 0 4px;
  align-self: center;
}
.nav-link.nav-admin {
  color: var(--ig-teal);
}
.nav-link.nav-admin:hover {
  background: rgba(0, 210, 169, 0.15);
}
.nav-link.nav-admin.active {
  background: var(--ig-navy-deep);
  color: var(--ig-teal);
}

/* Admin form layouts (Config + Security pages) ---------------------- */
.admin-form {
  background: var(--ig-white);
  border: 1px solid var(--ig-border);
  border-radius: 6px;
  padding: 18px 20px;
  margin-bottom: 16px;
}
.admin-form-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 12px;
}
.admin-form-row label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  color: var(--ig-slate);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.admin-form-row input[type="text"],
.admin-form-row input[type="email"],
.admin-form-row input[type="number"],
.admin-form-row select,
.admin-form-row textarea {
  font-family: inherit;
  font-size: 14px;
  padding: 6px 10px;
  border: 1px solid var(--ig-teal);
  border-radius: 4px;
  background: var(--ig-white);
  text-transform: none;
  letter-spacing: 0;
  color: #1f2933;
  font-weight: 400;
}
.admin-form-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  margin: 2px 0;
  accent-color: var(--ig-teal);
}
/* Global save button — teal/navy, matches .btn-download / .btn-export.
   Used by the admin pages (config, security) AND by the new-week
   picker inside .filters. Scoping was previously `.admin-form .btn-save`
   which excluded the .filters wrapper — moving to a global selector. */
.btn-save {
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 8px 18px;
  background: var(--ig-teal);
  color: var(--ig-navy);
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
}
.btn-save:hover  { background: #00b797; }
.btn-save:active { transform: translateY(1px); }

/* ============ /config page save-all bar ============
   One sticky bar at the top + a normal one at the bottom. The top bar
   stays visible while the admin scrolls through canvases so Save is
   always one click away. The bottom bar is a regular block so the page
   has a clear "done" point. */
.save-all-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 12px 16px;
  background: var(--ig-off-white, #f5f7fa);
  border: 1px solid #dde3eb;
  border-radius: 4px;
  margin: 14px 0;
}
.save-all-bar-top {
  /* NOT sticky. It used to be, but sticky only began actually engaging once
     scroll-page made .app-main a real scroll container — which made this bar
     pin and catch the config content scrolling beneath it (the reported bug).
     The nav stays pinned now and the bottom save bar covers save-at-end, so
     this top bar just scrolls with the content. */
  margin-top: 0;
}
.save-all-bar-bottom {
  margin-top: 26px;
}
.save-all-bar .save-all-bar-context {
  font-size: 13px;
  color: var(--ig-navy);
  flex: 1;
  min-width: 0;
}
.save-all-bar .btn-save {
  /* A touch bigger than inline saves so this reads as the primary action */
  padding: 10px 22px;
  font-size: 14px;
  font-weight: 600;
}

/* Change-history "Record / Was / Now" cells. Render the value as a
   stack of "Field: value" lines (server-side via the json_kvp filter).
   We use white-space: pre-line so the newlines from the filter break
   into visible rows. */
.weekly-table td.audit-kvp {
  font-size: 12px;
  line-height: 1.45;
  max-width: 320px;
  white-space: pre-line;
  vertical-align: top;
  color: var(--ig-navy);
}

/* Legacy raw-JSON cell style — kept for any caller still using it but
   the audit page now uses .audit-kvp above. */
.weekly-table td.audit-json {
  font-family: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;
  font-size: 11px;
  max-width: 320px;
  white-space: pre-wrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ====================================================================
   /login — branded landing page
   --------------------------------------------------------------------
   Stands alone (no app-header, no nav) because the user isn't
   authenticated yet. Centred card on the brand off-white background.
   ==================================================================== */
.login-body {
  margin: 0;
  min-height: 100vh;
  background: linear-gradient(135deg, var(--ig-navy) 0%, #0e1f2e 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.login-card {
  background: var(--ig-white);
  border-top: 4px solid var(--ig-teal);
  border-radius: 8px;
  padding: 0 0 32px;            /* logo strip handles its own top padding */
  width: 100%;
  max-width: 420px;
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.35);
  text-align: center;
  overflow: hidden;              /* clip the strip's bg to the card radius */
}
/* Navy strip behind the logo so the wide banner logo's white middle
   text shows up. Spans the full card width and bleeds to the card's
   rounded top corners. */
.login-logo-strip {
  background: var(--ig-navy);
  padding: 22px 0 18px;
  margin-bottom: 26px;
}
.login-logo {
  height: 48px;
  width: auto;
  display: block;
  margin: 0 auto;
}
/* Re-introduce the side padding for everything below the strip, since
   we removed it from .login-card so the strip could span edge-to-edge. */
.login-card > *:not(.login-logo-strip) {
  padding-left: 36px;
  padding-right: 36px;
}
.login-title {
  font-size: 22px;
  font-weight: 600;
  color: var(--ig-navy);
  margin: 0 0 8px;
  letter-spacing: 0.01em;
}
.login-sub {
  color: var(--ig-slate);
  font-size: 14px;
  margin: 0 0 26px;
}

.login-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 100%;
  padding: 12px 20px;
  background: var(--ig-navy);
  color: var(--ig-white);
  text-decoration: none;
  border-radius: 4px;
  font-weight: 500;
  font-size: 15px;
  transition: background 0.15s ease;
}
.login-btn:hover  { background: #0e2032; }
.login-btn:active { transform: translateY(1px); }

/* Microsoft logo: 4 colored squares in a 2x2 grid (no SVG dep). */
.ms-logo {
  display: inline-grid;
  grid-template-columns: 9px 9px;
  grid-template-rows: 9px 9px;
  gap: 2px;
}
.ms-logo span { display: block; }

.login-error {
  background: rgba(231, 76, 60, 0.10);
  border: 1px solid rgba(231, 76, 60, 0.30);
  color: #922b1f;
  padding: 10px 14px;
  border-radius: 4px;
  margin-top: 18px;
  font-size: 13px;
  text-align: left;
}

.login-fineprint {
  margin-top: 26px;
  font-size: 11px;
  color: var(--ig-slate);
  line-height: 1.5;
}

/* ====================================================================
   /config — responsive grid for the editable CFG sections.
   --------------------------------------------------------------------
   Narrow sections (sales targets, attendance rule, daily penalty,
   referral bands, QA incentives, etc.) sit side-by-side on wide
   screens. Wide sections (payout ladder, VAS payouts, debicheck bands
   — these have more columns and need horizontal room) span 2 grid
   cells via `.config-section.span-2`.

   `auto-fit` + minmax(420px, 1fr) means the grid collapses to a single
   column below ~840px wide (a single section fills the row), and
   expands to 2 or more columns as the viewport grows.
   ==================================================================== */
.config-grid {
  display: grid;
  /* auto-fit (not auto-fill) so a canvas with only 1–2 sections doesn't
     leave a phantom empty column on the right. The remaining tracks
     stretch to fill the canvas width, which gives wide sections like
     Attendance Rule and Debicheck the room they need without horizontal
     scrolling. */
  grid-template-columns: repeat(auto-fit, minmax(460px, 1fr));
  gap: 14px;
  margin-top: 18px;
  align-items: start;     /* short cards don't stretch to match neighbours */
}
.config-section {
  margin: 0 !important;       /* override .admin-form's margin-bottom */
  padding: 14px 16px;
  /* Contain content so a long row can't poke into the next grid cell.
     Tables inside scroll horizontally only when truly necessary. */
  overflow: hidden;
  min-width: 0;
}
.config-section h3 {
  font-size: 15px;
  font-weight: 600;
  color: var(--ig-navy);
  margin: 0 0 6px;
}
.config-section.span-2 {
  /* Wider sections take 2 grid cells when there's room. */
  grid-column: span 2;
}
@media (max-width: 1100px) {
  .config-section.span-2 { grid-column: span 1; }
}

/* Explainer text under each section heading — was 12px / slate which
   read as nearly invisible. Bump to 13px and use a darker slate. */
.config-section .section-explainer {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--ig-slate-2, #54595f);
  line-height: 1.45;
}
.config-section .section-explainer code {
  color: var(--ig-navy);
  background: var(--ig-off-white);
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 11px;
}

/* Section table — fit naturally to its grid cell. Tables that genuinely
   need more room (8-col debicheck etc.) get horizontal scroll INSIDE
   the card via the wrapper below — never overflow into the next cell. */
.config-section table.weekly-table {
  width: 100%;
  table-layout: auto;
  display: table;
}
/* Body cells stay tight + single-line so number inputs read naturally.
   Headers wrap + center so long labels like
   "Penalty % (negative = deduction)" don't get clipped. */
.config-section table.weekly-table th,
.config-section table.weekly-table td {
  padding: 3px 6px;
  max-width: none;
  overflow: visible;
  text-overflow: clip;
}
.config-section table.weekly-table th {
  white-space: normal;          /* wrap header labels */
  text-align: center;
  vertical-align: middle;
  line-height: 1.25;
  font-size: 11.5px;
}
.config-section table.weekly-table td {
  white-space: nowrap;          /* keep input rows on a single line */
  vertical-align: middle;
}

/* Read-only PK display cell — band names and similar identifiers are
   shown formatted (via the |nice_case filter) with their raw value
   carried back to the server via a sibling hidden input. No editable
   box means narrower cell + no confusion about whether renaming a band
   is supported (it isn't — admins delete + re-add a row to rename). */
.config-section table.weekly-table td.pk-display {
  font-size: 12px;
  font-weight: 500;
  color: var(--ig-navy);
  background: #f8fafc;
  text-align: center;
  padding: 5px 8px;
}
.config-section table.weekly-table input[type="text"],
.config-section table.weekly-table input[type="number"],
.config-section table.weekly-table select {
  /* `auto` width lets the browser size to content; min-width keeps the
     cell tappable. The cells then negotiate widths inside the table's
     auto layout — single-digit values get a 60px cell, longer notes
     get more — no input forced to 100% of cell. */
  width: auto;
  min-width: 60px;
  box-sizing: border-box;
  padding: 3px 6px;
  font-size: 12px;
  border: 1px solid var(--ig-border);
  border-radius: 3px;
  background: var(--ig-white);
  font-family: inherit;
  /* Match input height — by default <select> uses the OS chrome which
     is taller than our text inputs. */
  height: 24px;
  line-height: 1;
}
.config-section table.weekly-table input[type="number"] {
  text-align: right;
  max-width: 110px;
}
.config-section table.weekly-table input[type="text"] {
  max-width: 200px;
}

/* Change-history (/admin/audit) grouped-burst expander. The summary is
   one line; expanding reveals the individual audit records in a compact
   sub-table. Keeps the page readable when a single save touched 40 rows. */
.audit-group > summary { list-style: revert; padding: 2px 0; }
.audit-subtable { border-collapse: collapse; }
.audit-subtable th,
.audit-subtable td {
  border-top: 1px solid var(--ig-border, #e2e8f0);
  padding: 3px 8px 3px 0;
  vertical-align: top;
}
.audit-subtable th { color: var(--ig-slate); font-weight: 500; }

/* Percent inputs (activation / DebiCheck bands). The admin types a
   whole-number % (85, 30); the value is stored as a 0-1 decimal. The
   "%" suffix sits right after the input so it's unambiguous you're
   entering a percentage, not a decimal. */
.config-section table.weekly-table input.pct-input {
  max-width: 72px;
  text-align: right;
}
.pct-suffix {
  margin-left: 3px;
  font-size: 11px;
  color: var(--ig-slate);
}

/* Read-only descriptive cells (e.g. NOTES on bundle deal-code rows).
   Same visual treatment as pk-display but left-aligned and full-width
   so longer descriptions don't get truncated. To edit one of these
   the admin deletes the row and re-adds it. */
.config-section table.weekly-table td.note-display {
  font-size: 12px;
  color: var(--ig-slate);
  background: #f8fafc;
  text-align: left;
  padding: 5px 10px;
  white-space: nowrap;
  min-width: 240px;
}
/* Tighter Save / Del column — they're not the focus of the row. */
.config-section table.weekly-table .btn-save {
  padding: 3px 8px;
  font-size: 11px;
}

/* If a row still genuinely outgrows its card (very wide config like
   Attendance Rule with 6 checkbox/input cells in one row), allow
   horizontal scroll inside the table wrapper rather than spilling. */
.config-section .table-wrap {
  overflow-x: auto;
  width: 100%;
}

/* ====================================================================
   /config — canvas panels
   --------------------------------------------------------------------
   Each canvas is a visual grouping of related CFG sections (Performance,
   Tier Ladders, Per-Sale Payouts, Attendance). Heading + description
   sit above an inner grid of the canvas's section cards.
   ==================================================================== */
.canvas-panel {
  background: #f5f7f9;
  border: 1px solid var(--ig-border);
  border-radius: 8px;
  padding: 16px 18px 18px;
  margin: 0 0 20px;
  /* Tinted bg so the inner white section cards stand out against it */
}
.canvas-header {
  border-bottom: 1px solid var(--ig-border);
  padding-bottom: 10px;
  margin-bottom: 14px;
}
.canvas-title {
  margin: 0 0 4px;
  font-size: 18px;
  font-weight: 600;
  color: var(--ig-navy);
  letter-spacing: 0.01em;
}
.canvas-description {
  margin: 0;
  font-size: 13px;
  color: var(--ig-slate-2, #54595f);
  line-height: 1.45;
  max-width: 880px;
}
/* Reset the .config-grid margin inside a canvas — the canvas-header
   already supplies the top spacing. */
.canvas-panel .config-grid {
  margin-top: 0;
}
/* Section cards inside a canvas: stay white so they pop against the
   grey canvas background. */
.canvas-panel .config-section {
  background: var(--ig-white);
  box-shadow: 0 1px 2px rgba(22, 42, 60, 0.04);
}

/* New-week picker — match the teal-bordered input style used elsewhere
   for text + date inputs (not just selects). The .period-col rule
   above only targeted `select`; extend to inputs too. */
.filters .filter-col input[type="text"],
.filters .filter-col input[type="email"],
.filters .filter-col input[type="number"],
.filters .filter-col input[type="date"] {
  font-family: inherit;
  font-size: 14px;
  padding: 6px 10px;
  border: 1px solid var(--ig-teal);
  border-radius: 4px;
  background: var(--ig-white);
  color: #1f2933;
  height: 36px;
  box-sizing: border-box;
}
.filters .filter-col input[type="text"]:focus,
.filters .filter-col input[type="email"]:focus,
.filters .filter-col input[type="number"]:focus,
.filters .filter-col input[type="date"]:focus {
  outline: none;
  border-color: var(--ig-teal);
  box-shadow: 0 0 0 2px rgba(0, 210, 169, 0.18);
}
/* "Create & clone" button in the new-week picker — let it sit at the
   bottom of its filter-col like a labelled action. */
.filters .filter-col .btn-save {
  height: 36px;
  padding: 0 18px;
}

/* ====================================================================
   /dashboard — executive overview (KPI cards + charts + WoW tables)
   ==================================================================== */
.dash-head { margin-bottom: 10px; }
.dash-filters {
  display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 20px;
}
.dash-sub { margin: 4px 0 0; color: var(--ig-slate-2, #54595f); font-size: 13px; }
.dash-wip {
  background: rgba(0, 210, 169, 0.08); border: 1px solid var(--ig-border, #e2e8f0);
  border-radius: 6px; padding: 10px 14px; margin-bottom: 18px;
  font-size: 13px; color: var(--ig-navy);
}
.dash-wip-note { color: var(--ig-slate); font-size: 12px; margin-left: 6px; }
.dash-filter { display: flex; flex-direction: column; gap: 4px; }
.dash-filter select {
  font-family: inherit; font-size: 14px; padding: 6px 10px;
  border: 1px solid var(--ig-teal); border-radius: 4px;
  background: var(--ig-white); height: 36px; min-width: 200px;
}

/* KPI cards */
.kpi-grid {
  display: grid;
  /* Deterministic column counts (stepped down in the responsive block at
     the foot of this file) — fixed, balanced rows at each breakpoint
     instead of a continuous auto-fit reflow. 12-across only on very wide
     screens; 6 / 4 / 3 / 2 below. */
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 10px; margin-bottom: 22px;
}
.kpi-card {
  background: var(--ig-white); border: 1px solid var(--ig-border, #e2e8f0);
  border-radius: 6px; padding: 12px 13px; box-shadow: 0 1px 2px rgba(22, 42, 60, 0.04);
  min-width: 0;  /* allow grid items to shrink below content width, not overflow */
}
.kpi-label {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ig-slate); margin-bottom: 6px; line-height: 1.25;
}
.kpi-value { font-size: 21px; font-weight: 700; color: var(--ig-navy); line-height: 1.1; }
.kpi-delta {
  display: inline-block; margin-top: 7px; font-size: 11px; font-weight: 600;
  padding: 2px 7px; border-radius: 10px; white-space: nowrap;
}
.kpi-up   { color: #0a7d57; background: rgba(0, 210, 169, 0.14); }
.kpi-down { color: #b42318; background: rgba(211, 51, 51, 0.12); }
.kpi-flat { color: var(--ig-slate); background: rgba(100, 116, 139, 0.12); }

/* Charts — fixed-height flex cards so Chart.js (maintainAspectRatio:false)
   has a concrete height to fill. */
.dash-charts {
  display: grid;
  /* Fixed, balanced column count (NOT auto-fit) so the charts keep a stable
     3x2 layout instead of re-flowing to a different count (4+2, 5+1, …) on
     every monitor width. Re-flows only at the two breakpoints below. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px; margin-bottom: 8px;
}
@media (max-width: 1280px) { .dash-charts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 760px)  { .dash-charts { grid-template-columns: minmax(0, 1fr); } }
.chart-card {
  background: var(--ig-white); border: 1px solid var(--ig-border, #e2e8f0);
  border-radius: 6px; padding: 12px 14px 14px; height: 300px;
  display: flex; flex-direction: column;
}
.chart-card h3 {
  flex: 0 0 auto; margin: 0 0 8px; font-size: 14px; font-weight: 600; color: var(--ig-navy);
}
.chart-card canvas { flex: 1 1 auto; min-height: 0; }

/* Dashboard summary tables */
.dash-section { margin: 26px 0 8px; font-size: 15px; font-weight: 600; color: var(--ig-navy); }
.dash-table-wrap {
  border: 1px solid var(--ig-border, #e2e8f0); border-radius: 6px;
  background: var(--ig-white); overflow-x: auto;
}
.dash-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.dash-table th, .dash-table td {
  padding: 8px 12px; border-bottom: 1px solid var(--ig-border, #eef2f6); white-space: nowrap;
}
.dash-table thead th {
  background: var(--ig-teal); color: var(--ig-navy); text-align: left; font-weight: 600;
}
.dash-table th.num, .dash-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.dash-table tbody tr:hover { background: #f5fbfa; }
.dash-table tr.dash-latest { background: rgba(0, 210, 169, 0.07); }
.wip-badge {
  display: inline-block; font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ig-navy); background: rgba(0, 210, 169, 0.18);
  padding: 1px 6px; border-radius: 8px; margin-left: 6px;
}

/* ====================================================================
   Responsive — laptop / tablet / phone
   --------------------------------------------------------------------
   Desktop-first: every rule above assumes a wide screen; these max-width
   blocks step the layout down at deliberate breakpoints (so the layout
   only changes at these widths, not continuously). The header is
   `flex: 0 0 auto` in both body layouts (fixed-viewport / scroll-page),
   so a header that wraps to extra rows just shrinks the scroll area below
   it — it does not break the pinned-header model.

   Breakpoints: 1700 (drop KPIs off one line) · 1200 (header wraps) ·
   1280 (charts 2-col, set inline above) · 768 (tablet/phone) · 480 (phone).
   The /explain grid keeps its own 720px rule; the /config sections grid
   uses auto-fit minmax(460px) which already collapses to one column.
   ==================================================================== */

/* --- KPI cards: balanced rows at each width (12 only on very wide) --- */
@media (max-width: 1700px) { .kpi-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
@media (max-width: 1280px) { .kpi-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 768px)  { .kpi-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 480px)  { .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* --- Header: drop the nav onto its own row before it overflows ---
   The admin nav (11 links) + brand + user-strip needs ~1900px to sit on
   one row, so below that the nav moves to its own full-width row beneath
   brand + user-strip. The nav row itself wraps (flex-wrap), so the header
   can never overflow horizontally at any width. Screens wider than 1900px
   keep the original single-row header. */
@media (max-width: 1900px) {
  .app-header { flex-wrap: wrap; gap: 8px 16px; }
  .topnav { order: 3; flex-basis: 100%; flex-wrap: wrap; }
}
@media (max-width: 1200px) {
  .app-header { padding: 10px 16px; }
  .brand-name { font-size: 16px; }
  .brand-logo { height: 26px; }
}

/* --- Tablet / phone --- */
@media (max-width: 768px) {
  .app-main { padding: 14px 14px 60px; }
  body.fixed-viewport .app-main { padding: 10px 12px 10px; }

  /* Nav becomes a single horizontal-scroll strip (keeps the header short
     instead of wrapping into several rows on a phone). */
  .topnav {
    flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: thin; gap: 2px; padding-bottom: 2px;
  }
  .nav-link { padding: 6px 10px; white-space: nowrap; }
  .nav-divider { display: none; }

  /* Trim the user strip — drop the email label, keep Reload + Sign out. */
  .user-email { display: none; }
  .btn-header-reload { padding: 6px 9px; }

  /* Filters + dropdowns stack full-width instead of sitting on fixed mins. */
  .filters form { gap: 12px; }
  .filter-col { min-width: 0; width: 100%; }
  .dropdown-trigger { min-width: 0; }
  .period-col select, .filter-search { width: 100%; }

  /* Dashboard filter bar stacks full-width too. */
  .dash-filter { width: 100%; }
  .dash-filter select { width: 100%; }
}

/* --- Small phone --- */
@media (max-width: 480px) {
  .app-header { padding: 8px 12px; gap: 6px 10px; }
  .brand-name { font-size: 14px; }
  .kpi-value { font-size: 19px; }
}
