/* ==========================================================================
   DIG Ads advertiser portal, token system
   Task: advertiser-portal (WS6).  Register: product/tool, dense.
   Direction: industrial broadsheet.  Dark only, by brand decision.

   Every colour below is a live DIG token or a tint/shade of one.
   Sources: /data/projects/dig/apps/client-web/brand.generated.js
            /data/projects/dig/apps/client-web/tailwind.config.js

   RULES FOR THE BUILDER
   1. Styled files use var(--token) with NO fallback value. A missing token is a
      bug: add it here, do not inline a literal.
   2. There are no hex literals outside this file. None. Not in inline SVG either,
      where you use fill="var(--token)" / stroke="var(--token)".
   3. Do not add a light theme. There is no toggle. See brief.md cliche check 9.
   ========================================================================== */

:root {
  color-scheme: dark;

  /* --- 1. Ground and surface -------------------------------------------- */
  --field:            #141211;  /* content ground, warm near-black, shade of DIG main-dark */
  --chrome:           #1D2228;  /* left rail and app frame, DIG main-secondary verbatim */
  --panel:            #1E1D1D;  /* row hover, notice band, mock content, DIG main-dark verbatim */
  --panel-2:          #272524;  /* menu, tooltip, sticky footer bar */
  --panel-sunk:       #0E0D0C;  /* inset well: upload zone, code, preview backdrop */

  /* --- 2. Rules.  The separation ladder is whitespace, then a lightness
         shift, then one of these. Never a card border. -------------------- */
  --rule-faint:       #232120;  /* chart gridlines only */
  --rule:             #302C2A;  /* table row rules, section rules */
  --rule-strong:      #45403C;  /* page header rule, table head rule, panel edge */

  /* --- 3. Text.  Warm greys are content, cool grey is chrome and time. ---- */
  --text:             #F4F2EF;  /* 16.72:1 on --field */
  --text-2:           #ADA69E;  /*  7.76:1 on --field */
  --text-3:           #8492A6;  /*  5.91:1 on --field, DIG light-secondary verbatim */
  --text-4:           #6B6660;  /*  3.29:1 on --field, disabled and mock copy only */
  --text-on-panel:    #F4F2EF;  /* 15.05:1 on --panel */

  /* --- 4. Brand.  DIG primary ramp, verbatim. ---------------------------- */
  --brand-300:        #FBB676;
  --brand-400:        #F98D3E;
  --brand-500:        #F77626;
  --brand-600:        #E8530E;
  --brand-700:        #C03D0E;
  --brand-800:        #993113;
  --brand-900:        #7B2B13;
  --brand-950:        #3A1E10;  /* shade extension, meter track only */

  --accent:           #F77626;  /* primary action, brand,  6.74:1 on --field */
  --accent-hover:     #F98D3E;  /* on dark, hover brightens */
  --accent-press:     #E8530E;
  --accent-ink:       #231107;  /* 6.56:1 on --accent, 4.56:1 on --accent-press */
  --accent-wash:      rgba(247, 118, 38, 0.10);
  --accent-wash-2:    rgba(247, 118, 38, 0.18);

  /* --- 5. Status.  Held separate from the brand accent. Shape carries the
         meaning, these only reinforce it. See contract.md section 4. ------ */
  --status-draft:     #8C877F;  /*  5.24:1 */
  --status-review:    #6FA8E4;  /*  7.47:1 */
  --status-scheduled: #6FA8E4;
  --status-live:      #4ADE80;  /* 10.72:1, DIG success.400 verbatim */
  --status-live-halo: rgba(74, 222, 128, 0.22);
  --status-paused:    #B9B2A9;  /*  8.90:1 */
  --status-warn:      #F0B429;  /* 10.02:1, at risk, off pace, low balance */
  --status-critical:  #F0565C;  /*  5.51:1, exhausted, rejected, declined, error */
  --status-ended:     #8C877F;

  --warn-wash:        rgba(240, 180, 41, 0.18);
  --critical-wash:    rgba(240, 86, 92, 0.14);

  /* --- 6. Chart series.  Validated with the dataviz validator, dark mode,
         surface #141211. All six checks PASS. Worst adjacent CVD dE 18.1
         (deutan), worst adjacent normal-vision dE 20.0, all four inside the
         dark lightness band, all above 3:1 on --field.
         Under --pairs all the cap is the first THREE slots (worst pair
         dE 8.8). Past three: fold to Other or facet. Never generate a
         fifth hue. Slot 1 is DIG primary-600 unmodified. ----------------- */
  --series-1:         #E8530E;  /* money: spend, budget, delivery */
  --series-2:         #4A93DC;
  --series-3:         #33A96B;
  --series-4:         #9B7BE8;
  --series-1-wash:    rgba(232, 83, 14, 0.10);
  --series-2-wash:    rgba(74, 147, 220, 0.10);
  --series-3-wash:    rgba(51, 169, 107, 0.10);
  --series-4-wash:    rgba(155, 123, 232, 0.10);

  --series-deemph:    #6B6660;  /* context series in an emphasis chart, sparkline body */
  --series-target:    #8492A6;  /* the pacing target line, cool because it is time */

  /* Ordinal ramp, DIG primary ramp, --ordinal PASS: monotone L, every adjacent
     gap above 0.06, light end 2.49:1 on --field, hue spread 27 degrees.
     Meters and share-of-total fills only. */
  --ord-1:            #993113;
  --ord-2:            #C03D0E;
  --ord-3:            #E8530E;
  --ord-4:            #F77626;
  --ord-5:            #FBB676;

  /* Diverging, pacing delta only. Warm against cool, neutral midpoint. */
  --div-ahead:        #4A93DC;
  --div-mid:          #3A3634;
  --div-behind:       #F0B429;

  /* Chart chrome */
  --chart-grid:       #232120;  /* 1.33:1, recessive by design, solid never dashed */
  --chart-axis-ink:   #8492A6;
  --chart-ring:       #141211;  /* the 2px surface ring on dots, equals --field */
  --chart-gap:        #141211;  /* the 2px surface gap between touching fills */

  /* --- 7. Type ----------------------------------------------------------- */
  /* -apple-system first: it resolves to SF Pro, so Apple hardware pays zero
     bytes. The @font-face files are the fallback for everything else. */
  --font-ui: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --fw-light:         300;
  --fw-regular:       400;
  --fw-semibold:      600;
  --fw-bold:          700;

  /* Scale. Steps 4 to 6 and 7 to 8 are exactly x1.25. Step 6 to 7 skips a rung
     (x1.5625) so the jump to a figure is an extreme, not a middle.
     Steps 1 to 3 are the dense ladder a data table actually needs. */
  --fs-1:             0.6875rem;  /* 11px  column heads, axis ticks, units, ids */
  --fs-2:             0.8125rem;  /* 13px  table figures, dense meta */
  --fs-3:             0.875rem;   /* 14px  UI base: controls, cells, nav, buttons */
  --fs-4:             1rem;       /* 16px  prose, help text, field values */
  --fs-5:             1.25rem;    /* 20px  section heading, inventory board row */
  --fs-6:             1.5625rem;  /* 25px  secondary stat figure, wallet, OTP input */
  --fs-7:             2.4414rem;  /* 39px  page title, empty-state headline */
  --fs-8:             3.0518rem;  /* 49px  hero figure, exactly one per view */

  --lh-tight:         1.05;   /* --fs-7 and --fs-8 */
  --lh-heading:       1.2;    /* --fs-5, --fs-6 */
  --lh-ui:            1.35;   /* --fs-1 to --fs-3 */
  --lh-body:          1.55;   /* --fs-4 prose */

  --tr-display:      -0.02em; /* --fs-7, --fs-8 */
  --tr-heading:      -0.01em; /* --fs-5, --fs-6 */
  --tr-ui:            0em;
  --tr-label:         0.02em; /* --fs-1 semibold sentence-case labels. NOT uppercase. */
  --tr-figure:       -0.01em; /* mono figures, tightens JetBrains Mono in columns */
  --tr-otp:           0.32em; /* the one-time-code input only */

  --measure:          68ch;   /* prose max width */

  /* --- 8. Space, 4px base ------------------------------------------------ */
  --s-1:  0.25rem;   /*  4px */
  --s-2:  0.5rem;    /*  8px */
  --s-3:  0.75rem;   /* 12px */
  --s-4:  1rem;      /* 16px */
  --s-5:  1.5rem;    /* 24px */
  --s-6:  2rem;      /* 32px */
  --s-7:  3rem;      /* 48px */
  --s-8:  4rem;      /* 64px */
  --s-9:  6rem;      /* 96px */

  /* --- 9. Layout --------------------------------------------------------- */
  --rail-w:           232px;
  --field-max:        1440px;
  --field-pad-x:      var(--s-6);
  --field-pad-y:      var(--s-6);
  --gutter:           1.5rem;   /* 24px, the 12 column gutter */
  --cols:             12;
  --row-h:            44px;     /* table row, also the minimum touch target for a row */
  --control-h:        36px;
  --control-h-sm:     28px;
  --band-gap:         var(--s-7);  /* vertical gap between page bands */

  /* --- 10. Radius.  3px reads precise, which is what a ledger should read
          as. Controls only. --radius-data is the dataviz mark spec. -------- */
  --radius-1:         3px;   /* buttons, inputs, selects, notice bands, previews */
  --radius-data:      4px;   /* the rounded data-end on a bar, square at baseline */
  --radius-round:     50%;   /* status glyph discs only */

  /* --- 11. Elevation.  Light from above. No glow: a neon border on dark is on
          the anti-slop denylist and this system refuses it. ---------------- */
  --shadow-1:         0 1px 2px rgba(0, 0, 0, 0.40);
  --shadow-2:         0 12px 28px -12px rgba(0, 0, 0, 0.70);
  --shadow-3:         0 -8px 24px -12px rgba(0, 0, 0, 0.75);  /* sticky footer, casts up */
  --shadow-focus:     0 0 0 2px var(--field), 0 0 0 4px var(--accent-hover);
  --shadow-focus-on-accent: 0 0 0 2px var(--accent-ink), 0 0 0 4px var(--text);

  /* --- 12. Motion.  transform and opacity only. Never `all`. ------------- */
  --dur-1:            120ms;  /* hover, focus, row state */
  --dur-2:            200ms;  /* menu, tooltip, notice band */
  --dur-3:            320ms;  /* the load rule draw */
  --dur-stream:       2000ms; /* the refetch hairline sweep */
  --ease:             cubic-bezier(0.2, 0.8, 0.2, 1);
  --ease-out:         cubic-bezier(0, 0, 0.2, 1);
  --stagger:          28ms;   /* per-step animation-delay, capped at 12 steps */
  --i:                0;      /* stagger index, overridden inline per element, capped at 12 */
  --held-opacity:     0.55;   /* previous render during refetch */

  /* --- 13. Component constants the builder must not re-derive ----------- */
  --strip-sm-w:       120px;  /* flight strip, table row */
  --strip-sm-h:       12px;
  --strip-md-w:       320px;  /* flight strip, campaign detail */
  --strip-md-h:       24px;
  --strip-lg-h:       48px;   /* flight strip, review step, width is 100% */
  --spark-w:          96px;
  --spark-h:          24px;
  --spark-w-hero:     192px;
  --glyph:            10px;   /* status glyph box */
  --bar-h:            16px;   /* BarRank bar */
  --bar-h-max:        24px;
  --bar-band-gap:     8px;
  --split-h:          24px;   /* SplitBar */
  --meter-h:          4px;
  --meter-track:      var(--brand-950);
  --stroke-line:      2px;    /* every chart line */
  --stroke-spark:     1.5px;
  --stroke-grid:      1px;
  --dot-r:            4px;    /* end dot, plus a 2px --chart-ring */
  --hit-min:          24px;   /* minimum chart hover band width */
  --plot-inset-l:     56px;
  --plot-inset-r:     16px;
  --plot-inset-t:     12px;
  --plot-inset-b:     28px;
  --pace-tolerance:   10;     /* percent, past which the flight strip grows an amber wedge */

  /* --- 14. Harness compatibility.  The harness guarantees these names exist.
          Mapped, not duplicated, so there is one source of truth above.
          --text is 16.72:1 on --bg and --text-muted is 7.76:1, both clear. --- */
  --bg:               var(--field);
  --surface:          var(--panel);
  --surface-2:        var(--panel-2);
  --text-muted:       var(--text-2);
  --border:           var(--rule);
  --radius:           var(--radius-1);
  --font-display:     var(--font-ui);
  --font-body:        var(--font-ui);
  --dur:              var(--dur-1);
  --space-1: var(--s-2); --space-2: var(--s-3); --space-3: var(--s-4); --space-4: var(--s-5);
  --space-5: var(--s-6); --space-6: var(--s-7); --space-7: var(--s-8); --space-8: var(--s-9);
  --step--1: var(--fs-2); --step-0: var(--fs-4); --step-1: var(--fs-5); --step-2: var(--fs-6);
  --step-3: var(--fs-7); --step-4: var(--fs-8); --step-5: var(--fs-8);
}

/* --------------------------------------------------------------------------
   Reduced motion. Every duration collapses, every translation is removed.
   The load choreography degrades to an instant paint.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-1: 1ms;
    --dur-2: 1ms;
    --dur-3: 1ms;
    --dur-stream: 1ms;
    --stagger: 0ms;
  }
}

/* --------------------------------------------------------------------------
   Faces. BUILDER DECISION, recorded here because it changes the design's
   delivery, not its intent. See design/advertiser-portal/DECISIONS.md.

   No @font-face block ships. Both stacks resolve to system faces:

   1. No woff2 converter and no fontTools exist in this environment, so the
      .otf files cannot be converted. Shipping ~7 MB of .otf to render a
      dense data table is a worse outcome than the fallback.
   2. SF Pro is licensed by Apple for use on Apple platforms. DIG's own client
      serves the .otf files; re-serving them from a public advertiser portal
      extends that exposure to a new surface, and this is not the workstream
      that should decide to. `-apple-system` resolves to the SAME face on Apple
      hardware, with no file transfer and no licence question.
   3. JetBrains Mono is not present locally and cannot be fetched: the CSP and
      the sovereignty rule forbid a CDN, and nothing may be pulled at runtime.
      Every mono use here is functional (tabular figures), not decorative, so
      `ui-monospace, SFMono-Regular, Menlo, Consolas, monospace` carries it, and
      all of those are tabular by construction.

   The typographic thesis survives intact: it rests on weight and size contrast
   (300 at 39px against 600 at 11px), not on a specific face. Revisit if a
   converter becomes available and the licence is cleared.
   -------------------------------------------------------------------------- */

/* ==========================================================================
   DIG Ads advertiser portal.
   Implements design/advertiser-portal/contract.md against tokens.css.

   Rules from contract section 0, enforced here:
   - var(--token) with NO fallback, everywhere. Zero hex literals below.
   - No card. Separation is whitespace, then a lightness shift, then a hairline.
   - No coloured strip down any edge of anything.
   - No pill badge. No icon library. No emoji.
   - No transition: all. transform and opacity only.
   - No light theme, no toggle.
   ========================================================================== */

/* tokens.css is inlined ahead of this file by the server. */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--field);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--fs-3);
  line-height: var(--lh-ui);
  font-weight: var(--fw-regular);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p, ol, ul, figure { margin: 0; }
ol, ul { padding: 0; list-style: none; }
a { color: inherit; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--radius-1); }

/* --- AppFrame, contract section 1 --------------------------------------- */

/*
 * `minmax(0, 1fr)`, not `1fr`, and it is the whole of AD-062's first cause.
 *
 * A grid track sized `1fr` still has `min-width: auto`, so it refuses to shrink below the
 * MIN-CONTENT width of what is in it. One wide thing on a screen — a 720-unit chart, a
 * ten-column table — therefore pushed the content column past the viewport and took the whole
 * page sideways with it, instead of scrolling inside its own container the way every table
 * here is already built to. Measured on `/portal`: 72px over at 320 and 17px at 375, from a
 * chart that has `width: 100%` and could have fitted perfectly.
 *
 * `minmax(0, …)` lets the track shrink, which is what makes the inner `overflow-x` scrollers
 * do their job. It is the single most common cause of a grid page scrolling sideways and it
 * is invisible until a narrow width is actually measured — which no `/portal/*` screen was
 * until AD-062, because the responsive gate's screen list is the `/console/*` set.
 */
.app { display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr); min-height: 100dvh; }

.rail {
  background: var(--chrome);
  position: sticky; top: 0; height: 100dvh;
  display: flex; flex-direction: column;
  padding: var(--s-5) var(--s-4);
  gap: var(--s-6);
}

.rail__mark {
  display: block; text-decoration: none;
  font-size: var(--fs-5); letter-spacing: var(--tr-heading); line-height: var(--lh-heading);
}
.rail__mark strong { font-weight: var(--fw-bold); color: var(--text); }
.rail__mark span { font-weight: var(--fw-light); color: var(--text-3); margin-left: 0.08em; }

.rail__nav { display: flex; flex-direction: column; gap: var(--s-1); }

.rail__item {
  display: flex; align-items: center;
  height: 32px; padding: 0 var(--s-3);
  border-radius: var(--radius-1);
  font-size: var(--fs-3); font-weight: var(--fw-regular);
  color: var(--text-2); text-decoration: none;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.rail__item:hover { background: var(--panel); color: var(--text); }
/* The active item takes the CONTENT ground colour so it reads as connected to
   the field. That is the mechanism. Not a stripe. */
.rail__item.is-active { background: var(--field); color: var(--text); font-weight: var(--fw-semibold); }

.rail__wallet { margin-top: auto; border-top: 1px solid var(--rule); padding-top: var(--s-4); }
.rail__wlabel {
  font-size: var(--fs-1); font-weight: var(--fw-semibold);
  color: var(--text-3); letter-spacing: var(--tr-label);
}
.rail__wvalue {
  font-weight: var(--fw-bold); font-size: var(--fs-6); color: var(--text);
  letter-spacing: var(--tr-heading); margin-top: var(--s-1);
}
.rail__wcover { font-size: var(--fs-1); color: var(--text-2); margin-bottom: var(--s-3); }
.rail__wallet .btn { width: 100%; justify-content: center; }
.rail__out { margin-top: var(--s-4); }

.field {
  background: var(--field);
  padding: var(--field-pad-y) var(--field-pad-x);
  max-width: var(--field-max);
  width: 100%;
  /* A grid item defaults to min-width:auto, which refuses to shrink below its content.
     A wide table then pushes the whole page sideways instead of scrolling inside its own
     container. This one line is what keeps `.tw { overflow-x: auto }` actually working. */
  min-width: 0;
}

/* --- Page header. The rule is the signature separator. ------------------- */

.phead { margin-bottom: var(--s-6); }
/* Wraps rather than overflows: at a narrow width the title and the page action cannot sit
   on one line, and a page that scrolls sideways is a reject. */
.phead__row {
  display: flex; align-items: flex-end; gap: var(--s-3) var(--s-5);
  justify-content: space-between; flex-wrap: wrap;
}
.phead h1 {
  font-size: var(--fs-7); font-weight: var(--fw-light);
  letter-spacing: var(--tr-display); line-height: var(--lh-tight);
}
.phead__right { display: flex; align-items: center; gap: var(--s-4); }
.stamp { font-family: var(--font-mono); font-size: var(--fs-1); color: var(--text-3); }

.phead__rule {
  height: 1px; background: var(--rule-strong); margin-top: var(--s-4);
  transform: scaleX(0); transform-origin: left;
  animation: rule-draw var(--dur-3) var(--ease) forwards;
}
@keyframes rule-draw { to { transform: scaleX(1); } }

.band {
  margin-bottom: var(--band-gap);
  min-width: 0;
  opacity: 0; transform: translateY(6px);
  animation: band-rise var(--dur-2) var(--ease) forwards;
}
@keyframes band-rise { to { opacity: 1; transform: none; } }

/* The stagger index, as classes rather than an inline custom property: `style-src 'self'`
   blocks a style attribute, and the load choreography is not worth relaxing a CSP for.
   Capped at 12, per the contract. */
.band--0  { animation-delay: 0ms; }
.band--1  { animation-delay: calc(1  * var(--stagger)); }
.band--2  { animation-delay: calc(2  * var(--stagger)); }
.band--3  { animation-delay: calc(3  * var(--stagger)); }
.band--4  { animation-delay: calc(4  * var(--stagger)); }
.band--5  { animation-delay: calc(5  * var(--stagger)); }
.band--6  { animation-delay: calc(6  * var(--stagger)); }
.band--7  { animation-delay: calc(7  * var(--stagger)); }
.band--8  { animation-delay: calc(8  * var(--stagger)); }
.band--9  { animation-delay: calc(9  * var(--stagger)); }
.band--10 { animation-delay: calc(10 * var(--stagger)); }
.band--11 { animation-delay: calc(11 * var(--stagger)); }
.band--12 { animation-delay: calc(12 * var(--stagger)); }

/* Utility classes, deliberately few. Each exists because a style attribute cannot. */
.u-mt-4 { margin-top: var(--s-4); }
.u-mt-5 { margin-top: var(--s-5); }
.u-mt-6 { margin-top: var(--s-6); }
.u-end  { align-self: end; }

@media (prefers-reduced-motion: reduce) {
  .phead__rule { transform: none; animation: none; }
  .band { opacity: 1; transform: none; animation: none; }
}

/* --- SectionHead, contract section 2 ------------------------------------ */

.sec { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
.sec h2 {
  font-size: var(--fs-1); font-weight: var(--fw-semibold);
  color: var(--text-3); letter-spacing: var(--tr-label);
}
.sec__rule { flex: 1; height: 1px; background: var(--rule); }
.sec__right { font-family: var(--font-mono); font-size: var(--fs-1); color: var(--text-3); }

.rule { height: 1px; background: var(--rule); margin: var(--s-5) 0; }

/* --- Button, contract section 3 ----------------------------------------- */

.btn {
  display: inline-flex; align-items: center;
  height: var(--control-h); padding: 0 var(--s-4);
  border-radius: var(--radius-1); border: none;
  font-family: var(--font-ui); font-size: var(--fs-3); font-weight: var(--fw-semibold);
  letter-spacing: var(--tr-ui); text-decoration: none; cursor: pointer;
  transition: background-color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease);
}
.btn--primary { background: var(--accent); color: var(--accent-ink); }
.btn--primary:hover { background: var(--accent-hover); }
.btn--primary:active { background: var(--accent-press); }
.btn--primary:focus-visible { box-shadow: var(--shadow-focus-on-accent); }

.btn--secondary { background: transparent; color: var(--text); border: 1px solid var(--rule-strong); }
.btn--secondary:hover { background: var(--panel); border-color: var(--text-3); }

.btn--quiet { background: none; color: var(--text-2); padding: 0; height: auto; }
.btn--quiet:hover { color: var(--text); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }

.btn:disabled {
  background: var(--panel-2); color: var(--text-4);
  border-color: transparent; cursor: not-allowed;
}
.btn-with-reason { display: inline-flex; align-items: center; gap: var(--s-3); }
.btn-reason { font-size: var(--fs-3); color: var(--text-2); }

.actions { display: flex; gap: var(--s-3); align-items: center; }

/* --- StatusMark, contract section 4 ------------------------------------- */

.st { display: inline-flex; align-items: center; gap: var(--s-2); }
.st > span { font-size: var(--fs-3); font-weight: var(--fw-regular); color: var(--text); }
.glyph { flex: none; display: block; }

/* --- FlightStrip, contract section 5 ------------------------------------ */

.strip { display: block; max-width: 100%; }
.strip--sm { width: var(--strip-sm-w); }
.strip--md { width: var(--strip-md-w); }
.strip--lg { width: 100%; }

/* --- StatFigure, contract section 6 ------------------------------------- */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--s-5); }
.stats--hero { grid-template-columns: minmax(260px, 1.4fr) repeat(auto-fit, minmax(150px, 1fr)); align-items: end; }

.stat__label {
  font-size: var(--fs-1); font-weight: var(--fw-semibold);
  color: var(--text-3); letter-spacing: var(--tr-label);
}
.stat__value {
  font-family: var(--font-ui); font-weight: var(--fw-bold);
  font-size: var(--fs-6); color: var(--text);
  letter-spacing: var(--tr-display); line-height: var(--lh-tight);
  margin-top: var(--s-2);
  /* Proportional figures deliberately: tabular digits make 121 look loose at 49px. */
}
.stat--hero .stat__value { font-size: var(--fs-8); }
.stat__delta { font-family: var(--font-mono); font-size: var(--fs-2); color: var(--text-2); margin-top: var(--s-2); }
.stat__delta.is-good { color: var(--status-live); }
.stat__delta.is-bad { color: var(--status-critical); }
.stat__spark { margin-top: var(--s-2); }
.stat__unavailable { font-size: var(--fs-4); color: var(--text-2); margin-top: var(--s-2); }
.stat__reason { font-size: var(--fs-1); color: var(--text-3); }

/* --- DataTable, contract section 7 -------------------------------------- */

.tw { overflow-x: auto; }
.tw table { width: 100%; border-collapse: collapse; }

.tw thead th {
  position: sticky; top: 0; z-index: 2; background: var(--field);
  font-size: var(--fs-1); font-weight: var(--fw-semibold); color: var(--text-3);
  letter-spacing: var(--tr-label); text-align: left;
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--rule-strong);
  white-space: nowrap;
}
.tw thead th:first-child { z-index: 3; }
.tw thead th.fig, .tw tbody td.fig, .tw tbody th.fig, .tw tfoot td.fig, .tw tfoot th.fig { text-align: right; }

.tw tbody th, .tw tbody td {
  padding: var(--s-3) var(--s-4);
  font-size: var(--fs-3); font-weight: var(--fw-regular); color: var(--text);
  text-align: left; vertical-align: middle;
}
.tw tbody tr { min-height: var(--row-h); }
.tw tbody tr + tr th, .tw tbody tr + tr td { border-top: 1px solid var(--rule); }
.tw tbody th[scope="row"] { position: sticky; left: 0; z-index: 1; background: var(--field); font-weight: var(--fw-regular); }
.tw tbody tr:hover th[scope="row"], .tw tbody tr:hover td { background: var(--panel); }
.tw tbody tr { transition: background-color var(--dur-1) var(--ease); }

.tw td.fig, .tw th.fig, .fig-cell {
  font-family: var(--font-mono); font-size: var(--fs-2);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tr-figure);
}
.tw thead th.fig { font-family: var(--font-ui); font-size: var(--fs-1); }

.tw tfoot td, .tw tfoot th {
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--rule-strong);
  font-family: var(--font-mono); font-size: var(--fs-2); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums; color: var(--text);
}

.tw .sub { display: block; font-size: var(--fs-1); color: var(--text-2); }
.tw .clamp { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.tw tr.is-draft th, .tw tr.is-draft td { color: var(--text-2); }
.tw tr.is-exhausted .spend { color: var(--status-critical); }
.tw .empty-cell { padding: var(--s-7) var(--s-4); font-size: var(--fs-4); color: var(--text-2); text-align: left; }

.rowlink { position: relative; color: inherit; text-decoration: none; }
.rowlink::after { content: ""; position: absolute; inset: 0; }
.tw tbody tr { position: relative; }

/* --- Charts, contract section 8 ----------------------------------------- */

/* Direct child only. A descendant selector here also caught the 8px legend swatches and
   blew each of them up to the full width of the chart. */
.chart > svg { display: block; width: 100%; height: auto; }
.chart .tick {
  font-family: var(--font-mono); font-size: var(--fs-1); fill: var(--chart-axis-ink);
  font-variant-numeric: tabular-nums;
}
.chart .endlabel {
  font-family: var(--font-ui); font-size: var(--fs-2); font-weight: var(--fw-semibold); fill: var(--text);
}
.chart .barlabel { font-family: var(--font-ui); font-size: var(--fs-3); fill: var(--text); }
.chart .barvalue {
  font-family: var(--font-mono); font-size: var(--fs-2); fill: var(--text);
  font-variant-numeric: tabular-nums;
}
.chart .splitlabel {
  font-family: var(--font-mono); font-size: var(--fs-2); fill: var(--field);
  font-variant-numeric: tabular-nums; font-weight: var(--fw-bold);
}
.chart .is-off { fill: var(--text-4); }

/* Rest state set in CSS, not only by the SVG opacity attribute: an attribute loses to any
   stylesheet rule, and relying on it makes the hidden state the fragile one. */
.tip { opacity: 0; transition: opacity var(--dur-1) var(--ease); pointer-events: none; }
.hit rect:hover + .tip, .hit rect:focus-visible + .tip { opacity: 1; }
.hit rect:focus-visible { outline: none; }

.legend { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-top: var(--s-3); }
.legend__item { display: inline-flex; align-items: center; gap: var(--s-2); font-size: var(--fs-3); color: var(--text); }
.legend__swatch { display: block; flex: none; }
.legend__value { font-family: var(--font-mono); font-size: var(--fs-2); font-variant-numeric: tabular-nums; color: var(--text); }
.legend--split { gap: var(--s-5); }
.legend--split .legend__name { color: var(--text-2); }

.twin { margin-top: var(--s-4); }
.twin summary { font-size: var(--fs-2); color: var(--text-2); cursor: pointer; }
.twin summary:hover { color: var(--text); }
.twin[open] summary { margin-bottom: var(--s-3); }

/* The meter is SVG: the fill fraction is a geometry attribute, which the CSP allows,
   rather than an inline width, which it does not. */
.meter { display: block; width: 100%; height: var(--meter-h); margin: var(--s-2) 0; }

.spark { display: block; }

/* --- Controls, contract section 10 -------------------------------------- */

.fld { margin-bottom: var(--s-4); }
.fld label, .fld legend {
  display: block; font-size: var(--fs-1); font-weight: var(--fw-semibold);
  color: var(--text-3); letter-spacing: var(--tr-label); margin-bottom: var(--s-1);
}
.fld input, .fld select {
  width: 100%; height: var(--control-h);
  background: var(--panel); border: 1px solid var(--rule-strong); border-radius: var(--radius-1);
  color: var(--text); font-family: var(--font-ui); font-size: var(--fs-3);
  padding: 0 var(--s-3);
  transition: border-color var(--dur-1) var(--ease);
}
.fld input::placeholder { color: var(--text-4); }
.fld input:focus-visible, .fld select:focus-visible { border-color: var(--accent); box-shadow: var(--shadow-focus); }
.fld.is-invalid input { border-color: var(--status-critical); }
.fld__err { font-size: var(--fs-2); color: var(--text); margin-top: var(--s-1); }
.fld__help { font-size: var(--fs-2); color: var(--text-2); margin-top: var(--s-1); }
.fld input:disabled, .fld select:disabled { background: var(--panel-2); color: var(--text-4); }

.fld select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 6 5'%3E%3Cpath d='M0 0 L6 0 L3 5 Z' fill='%238492A6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right var(--s-3) center; background-size: 6px 5px;
  padding-right: var(--s-6);
}

.money-input { position: relative; display: flex; align-items: center; }
.money-input span {
  position: absolute; left: var(--s-3); color: var(--text-3); font-family: var(--font-mono); font-size: var(--fs-3);
}
.money-input input {
  font-family: var(--font-mono); text-align: right; font-variant-numeric: tabular-nums;
  padding-left: var(--s-6);
}

.fld--set { border: none; padding: 0; margin-bottom: var(--s-5); }
.checks { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--s-2); }
.check { display: flex; align-items: center; gap: var(--s-2); font-size: var(--fs-3); color: var(--text); cursor: pointer; }
.check input { position: absolute; opacity: 0; width: 16px; height: 16px; margin: 0; }
.check__box {
  width: 16px; height: 16px; flex: none;
  border: 1px solid var(--rule-strong); border-radius: var(--radius-1);
  display: grid; place-items: center;
  transition: background-color var(--dur-1) var(--ease);
}
.check__box svg { opacity: 0; }
.check input:checked + .check__box { background: var(--accent); border-color: var(--accent); }
.check input:checked + .check__box svg { opacity: 1; }
.check input:focus-visible + .check__box { box-shadow: var(--shadow-focus); }

.filters {
  display: flex; gap: var(--s-3); align-items: flex-end; flex-wrap: wrap;
  padding-bottom: var(--s-4); border-bottom: 1px solid var(--rule); margin-bottom: var(--s-5);
}
.filters .fld { margin-bottom: 0; min-width: 150px; }

.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0 var(--s-5); }

/* --- Notice, contract section 11 ---------------------------------------- */
/* No coloured edge on this component. The glyph at the left is the only colour. */

.notice {
  background: var(--panel); border-radius: var(--radius-1); padding: var(--s-4);
  display: flex; gap: var(--s-3); align-items: flex-start; margin-bottom: var(--s-5);
}
.notice--warn { background: var(--warn-wash); }
.notice--critical { background: var(--critical-wash); }
.notice__headline { font-size: var(--fs-4); font-weight: var(--fw-semibold); color: var(--text); }
.notice__text { font-size: var(--fs-3); color: var(--text-2); max-width: var(--measure); margin-top: var(--s-1); }
.notice__actions { display: flex; gap: var(--s-2); margin-top: var(--s-3); }
.notice .glyph { margin-top: 3px; }

.empty { padding: var(--s-6) 0; }
.empty__headline { font-size: var(--fs-7); font-weight: var(--fw-light); letter-spacing: var(--tr-display); line-height: var(--lh-tight); }
.empty__sentence { font-size: var(--fs-4); color: var(--text-2); margin-top: var(--s-3); max-width: var(--measure); }
.empty__action { margin-top: var(--s-5); }

/* --- StepFlow, contract section 12 -------------------------------------- */

.steps { display: flex; gap: var(--s-6); position: relative; margin-bottom: var(--s-6); }
.steps::before { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px; background: var(--rule); }
.steps li {
  position: relative; background: var(--field); padding-right: var(--s-3);
  display: flex; align-items: center; gap: var(--s-2);
}
.steps__n { font-family: var(--font-mono); font-size: var(--fs-1); color: var(--text-3); }
.steps__label { font-size: var(--fs-3); color: var(--text-2); }
.steps .is-done .steps__label { color: var(--text); }
.steps__done { width: 6px; height: 6px; background: var(--accent); display: inline-block; }
.steps .is-current .steps__label {
  color: var(--text); font-weight: var(--fw-semibold);
  box-shadow: inset 0 -2px 0 0 var(--accent);
}
.steps .is-next .steps__n, .steps .is-next .steps__label { color: var(--text-4); }

/* The wizard fills the viewport so its sticky footer sits at the bottom of the window
   rather than halfway up it when a step is short. */
.field { display: flex; flex-direction: column; }
.wizard { flex: 1; display: flex; flex-direction: column; }
.wizard .stepbar { margin-top: auto; }

.stepbar {
  position: sticky; bottom: 0; background: var(--panel-2);
  border-top: 1px solid var(--rule-strong); box-shadow: var(--shadow-3);
  padding: var(--s-3) var(--field-pad-x);
  margin: var(--s-6) calc(var(--field-pad-x) * -1) 0;
  display: flex; align-items: center; gap: var(--s-3);
}
.stepbar__spacer { margin-left: auto; }

/* --- CreativePreview, contract section 13 -------------------------------- */

.previews { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 320px)); gap: var(--s-6); }
.pv__frame { position: relative; background: var(--panel-sunk); border-radius: var(--radius-1); overflow: hidden; }
.pv__frame img { display: block; width: 100%; height: auto; }
.pv__disclosure {
  position: absolute; top: var(--s-2); left: var(--s-2);
  background: var(--panel); padding: 2px var(--s-2); border-radius: var(--radius-1);
  font-size: var(--fs-1); font-weight: var(--fw-semibold); color: var(--text-3);
  letter-spacing: var(--tr-label);
}
.pv__mock { background: var(--panel); padding: var(--s-3); }
.pv__mockline { height: 8px; background: var(--text-4); opacity: 0.35; border-radius: 1px; margin-bottom: var(--s-2); }
.pv__mockline.is-short { width: 45%; }
.pv__h { font-size: var(--fs-3); color: var(--text); font-weight: var(--fw-semibold); margin-top: var(--s-2); }
.pv__b { font-size: var(--fs-2); color: var(--text-2); margin-top: var(--s-1); }
.pv__track { height: 2px; background: var(--rule-strong); position: relative; }
.pv__track span { position: absolute; inset: 0 70% 0 0; background: var(--accent); }
.pv__overlay { position: absolute; bottom: var(--s-3); right: var(--s-2); font-family: var(--font-mono); font-size: var(--fs-1); color: var(--text); }
.pv__note { font-size: var(--fs-1); color: var(--text-2); margin-top: var(--s-2); }

.dropzone {
  background: var(--panel-sunk); border: 1px dashed var(--rule-strong); border-radius: var(--radius-1);
  padding: var(--s-6); text-align: center; color: var(--text-2); font-size: var(--fs-3);
}

/* --- Sign in ------------------------------------------------------------ */

body.bare { display: grid; place-items: center; min-height: 100dvh; padding: var(--s-5); }
.signin { width: 100%; max-width: 380px; }
.signin__mark { font-size: var(--fs-5); letter-spacing: var(--tr-heading); margin-bottom: var(--s-7); display: block; text-decoration: none; }
.signin__mark strong { font-weight: var(--fw-bold); color: var(--text); }
.signin__mark span { font-weight: var(--fw-light); color: var(--text-3); margin-left: 0.08em; }
.signin h1 { font-size: var(--fs-7); font-weight: var(--fw-light); letter-spacing: var(--tr-display); line-height: var(--lh-tight); margin-bottom: var(--s-5); }
.signin .btn { width: 100%; justify-content: center; margin-top: var(--s-2); }
.signin__hint { font-size: var(--fs-2); color: var(--text-2); margin-top: var(--s-5); }
.signin__hint code { font-family: var(--font-mono); color: var(--text); }

/* --- Two-column bands --------------------------------------------------- */

/*
 * `min(320px, 100%)`, not a bare `320px`, and it is AD-062's second cause.
 *
 * `minmax(320px, 1fr)` reads as "at least 320px", and it means it: below 320px of AVAILABLE
 * space the track still claims 320 and hangs off the side. On `/portal` at a 320 viewport the
 * rail leaves 232px of field, the track insisted on 320, and the page scrolled 72px sideways —
 * carrying a chart that has `width: 100%` and would have fitted.
 *
 * `min(320px, 100%)` keeps the intent (two columns as soon as there is room for two) and drops
 * the floor to the container when there is not. Applied here and to the two other grids whose
 * floors exceed the narrowest field this console is measured at.
 */
.split2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: var(--s-6); }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: var(--s-2) var(--s-5); font-size: var(--fs-3); }
.kv dt { color: var(--text-3); font-size: var(--fs-1); font-weight: var(--fw-semibold); letter-spacing: var(--tr-label); align-self: center; }
.kv dd { margin: 0; color: var(--text); font-family: var(--font-mono); font-size: var(--fs-2); font-variant-numeric: tabular-nums; }

.prose { font-size: var(--fs-4); line-height: var(--lh-body); color: var(--text-2); max-width: var(--measure); }
.prose + .prose { margin-top: var(--s-3); }

/* --- Responsive: the rail collapses, no drawer, no overlay -------------- */

@media (max-width: 960px) {
  .app { grid-template-columns: 56px minmax(0, 1fr); }
  .rail { padding: var(--s-4) var(--s-2); gap: var(--s-5); }
  .rail__mark span, .rail__wallet, .rail__out { display: none; }
  .rail__item { justify-content: center; padding: 0; font-weight: var(--fw-semibold); font-size: var(--fs-1); }
  .rail__full {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .rail__abbr { display: inline; }
  :root { --field-pad-x: var(--s-4); }
}
@media (min-width: 961px) { .rail__abbr { display: none; } }

/* ── creative review queue (ops) ──────────────────────────────────────────── */
/* One card per creative awaiting approval. The preview sits beside the facts a
   reviewer needs, because a decision made without seeing the ad is not review. */
/*
 * `minmax(0, 1fr)` for the same reason `.app` needs it: an implicit grid track will not shrink
 * below its content, so one review card whose own two-column layout wants 250px made the list
 * 250px wide inside a 232px field and the page scrolled 2px. Two pixels is still sideways.
 */
.rv__list { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-6); }
.rv {
  display: grid; grid-template-columns: minmax(min(200px, 100%), 300px) minmax(0, 1fr); gap: var(--s-6);
  background: var(--panel); border: 1px solid var(--rule);
  border-radius: var(--radius-1); padding: var(--s-5);
}
/* `align-self: start` so the sunk panel hugs the creative instead of being stretched to the
   height of the facts column beside it, which left a tall dead slab under a 640x360 asset and
   read as a half-loaded image. */
.rv__preview {
  position: relative; align-self: start; background: var(--panel-sunk);
  border-radius: var(--radius-1); overflow: hidden;
}
.rv__preview img { display: block; width: 100%; height: auto; }
.rv__name { margin: 0 0 var(--s-3); font-size: var(--fs-4); }
.rv__meta {
  display: grid; grid-template-columns: max-content 1fr;
  gap: var(--s-2) var(--s-4); margin: 0 0 var(--s-4);
}
.rv__meta dt { color: var(--text-3); font-size: var(--fs-1); letter-spacing: var(--tr-label); }
.rv__meta dd { margin: 0; font-size: var(--fs-2); overflow-wrap: anywhere; }
.rv__meta code { font-family: var(--font-mono, monospace); font-size: var(--fs-1); }
.rv__actions { display: grid; gap: var(--s-3); margin-top: var(--s-4); }
.rv__note { display: grid; gap: var(--s-2); }
.rv__note span { color: var(--text-3); font-size: var(--fs-1); }
.rv__note input {
  background: var(--panel-sunk); border: 1px solid var(--rule); color: var(--text-1);
  border-radius: var(--radius-1); padding: var(--s-2) var(--s-3); font: inherit;
}
.rv__buttons { display: flex; gap: var(--s-3); }

@media (max-width: 720px) {
  .rv { grid-template-columns: 1fr; }
}

/*
 * The hero stat row stops being a hero when there is no room for one.
 *
 * `.stats--hero` reserves a 260px lead track and lays the rest out at 150px minimum, which
 * needs about 434px before the page padding. Under that it does not wrap — it overhangs, and
 * the whole page scrolls sideways with it: measured at 26px over at 480, 131px at 375 and
 * 186px at 320 on `/portal/content`, and the same on every portal screen that leads with one.
 *
 * The AD-021/AD-031 sweeps never caught it because their screen list is the `/console/*` set;
 * every `/portal/*` screen built since has been outside the gate's reach (AD-062).
 *
 * Below 600px the lead track is given up and the row becomes the ordinary auto-fit grid. 601
 * and up are untouched, because they measure clean and a fix that moves a passing width is a
 * fix that needs its own evidence.
 */
@media (max-width: 600px) {
  .stats--hero { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
}
