/* ============================================================================
   payco.css — canonical Payco design system. Single source of truth for every
   page. Tokens extracted from index.html (the luxury marketing front) so the
   app behind it matches the brand instead of feeling like a separate product.
   ========================================================================== */

/* ---------- page transitions ----------
   Cross-document view transitions: the browser snapshots the outgoing page, snapshots
   the incoming one, and animates between them. No SPA router, no framework, no
   hydration — the navigation is still a plain link and a plain document.

   Named elements are pulled OUT of the root cross-fade and animated on their own, so
   the header does not fade out and back in on every click: it simply stays, and the
   segmented control's thumb glides from the old section to the new one because the
   browser sees the same-named element in two places and tweens it. That is the whole
   trick — the highlight isn't "animated to the next page", it's the same object.

   Everything here is progressive enhancement. A browser without view transitions
   (Firefox today, older Safari) ignores all of it and navigates exactly as before. */
@view-transition { navigation: auto; }
/* Same colour as body, deliberately. These were #f5f7fb and #fbfcfe, and on any page whose
   content is shorter than the viewport the difference paints as a hard horizontal seam right
   across the page where the body ends. Measured at 1600x950: operations.html body 566px,
   crm.html 465px, analytics.html 792px, messages.html 874px — a visible two-tone page on all
   four. html carries the colour so the view-transition snapshot has a background too. */
html{background:var(--paper,#fbfcfe)}
/* snappy: old page snaps out, new page lands in ~1 frame-feel — no quarter-second dissolve */
::view-transition-old(root){animation-duration:.06s}
::view-transition-new(root){animation-duration:.11s}

.pc-header{view-transition-name:pc-header}
.pc-viewbar{view-transition-name:pc-viewbar}
/* Thumb names are assigned per group in payco-nav.js — a view-transition-name must be
   unique in a document, and there are three controls on screen. */

/* The body cross-fade. Short, and with a whisper of upward travel: long enough to read
   as motion, far too short to wait on. Apple's own page changes sit around 200ms. */
::view-transition-old(root){animation:pcFadeOut .16s cubic-bezier(.4,0,1,1) both}
::view-transition-new(root){animation:pcFadeIn .22s cubic-bezier(0,0,.2,1) both}
@keyframes pcFadeOut{to{opacity:0;transform:translateY(-3px)}}
@keyframes pcFadeIn{from{opacity:0;transform:translateY(6px)}}

/* The header holds still while the body changes underneath it. */
::view-transition-group(pc-header),::view-transition-group(pc-viewbar){animation-duration:.22s}
::view-transition-old(pc-header),::view-transition-new(pc-header),
::view-transition-old(pc-viewbar),::view-transition-new(pc-viewbar){animation:none;mix-blend-mode:normal}

@media(prefers-reduced-motion:reduce){
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation-duration:.01ms !important}
}

/* ---------- the fallback that does the actual work ----------
   Everything above is Chrome-and-friends only, and it is invisible when unsupported —
   which means "smooth" cannot be built on it. So the incoming content fades up on its
   own, in every browser, with no JS and no cost: a page that appears is softer than a
   page that snaps, and 200ms is under the threshold where waiting registers.

   payco-nav.js marks <html class="pc-vt"> from the pagereveal event when a real view
   transition IS running, and this stands down — the browser is already doing it.

   Opacity only, deliberately. A transform here would make these elements the containing
   block for their position:fixed descendants (the rank switcher, the assist widget) and
   quietly break them. Opacity creates a stacking context and nothing worse. */
/* The incoming content fades up as ONE compositor layer. Without the layer promotion
   the container's children paint at slightly different times — the card background
   lands first (white), then its 3px gradient accent bar streams in a frame later,
   which read as a "white then blue" flicker on the KPI cards. will-change:opacity
   forces the whole subtree onto one layer so it composites in a single pass, and the
   accent is there from frame one. Ends on opacity:1 (no 'both' holding a 0 frame). */
@keyframes pcSoftIn{from{opacity:0}to{opacity:1}}
html:not(.pc-vt) main,
html:not(.pc-vt) .shell,
html:not(.pc-vt) .pc-wrap,
html:not(.pc-vt) .wrap{animation:pcSoftIn .2s cubic-bezier(0,0,.2,1) forwards;will-change:opacity}
@media(prefers-reduced-motion:reduce){
  html:not(.pc-vt) main,html:not(.pc-vt) .shell,html:not(.pc-vt) .pc-wrap,html:not(.pc-vt) .wrap{animation:none;will-change:auto}
}
:root{
  /* ink / paper */
  --ink:#0a1628; --ink-soft:#2a3a5c; --ink-mute:#5a6885;
  --paper:#fbfcfe; --paper-warm:#f4f6fb; --white:#fff;
  --line:#dce3f0; --line-soft:#eaeff7;
  /* brand */
  --blue:#1a4dd6; --blue-deep:#0b2a8a; --blue-electric:#3b82ff; --blue-cyan:#6dd5fa;
  --gradient:linear-gradient(135deg,#0b2a8a 0%,#1a4dd6 35%,#3b82ff 70%,#6dd5fa 100%);
  /* --gradient is a BACKGROUND ramp: it runs to #6dd5fa, which is correct behind white button
     text and unreadable AS text — 1.67:1 on white. It was nevertheless used as a text fill via
     background-clip on 13 pages, so every glyph painted from the last third of the ramp faded
     into the page; the worst case was index.html's hero headline, whose third line measured
     2.75:1 against the photo behind it. Text fills use this ramp instead: same brand move,
     light end capped at #2a63d8 (5.42:1 on white, 4.6:1 on the palest hero sky). If you are
     filling GLYPHS, reach for this one — never --gradient. */
  --gradient-text:linear-gradient(135deg,#0b2a8a 0%,#1a4dd6 45%,#2a63d8 100%);
  --gradient-text-flow:linear-gradient(90deg,#0b2a8a,#1a4dd6,#2158cf,#2a63d8,#2158cf,#1a4dd6,#0b2a8a);
  --gradient-soft:linear-gradient(135deg,rgba(11,42,138,.04) 0%,rgba(109,213,250,.06) 100%);
  /* semantic */
  --ok:#0e7a4e; --ok-bg:#e7f5ee; --warn:#b3401f; --warn-bg:#fdece8;
  /* --amber was #9a6b00, which failed AA in the two places it is actually used: 4.26:1 on its own
     --amber-bg (integrations' "Confirm" status label) and 4.37:1 on the --bg page background.
     #8f6300 is the same hue two steps darker and clears 4.5:1 on amber-bg, white and --bg alike. */
  --amber:#8f6300; --amber-bg:#fdf3e0; --info:#1a4dd6; --info-bg:#eaf1ff;
  /* elevation */
  --shadow-sm:0 1px 2px rgba(10,22,40,.04),0 1px 1px rgba(10,22,40,.06);
  --shadow-md:0 4px 24px rgba(11,42,138,.08),0 1px 2px rgba(10,22,40,.04);
  --shadow-lg:0 20px 60px rgba(11,42,138,.12),0 4px 12px rgba(10,22,40,.06);
  /* geometry */
  --radius-sm:10px; --radius:16px; --radius-lg:28px; --maxw:1240px;
  /* the sliver — see the section below */
  --sliver:linear-gradient(90deg,#0b2a8a,#1a4dd6,#3b82ff,#6dd5fa);
  --sliver-warn:linear-gradient(90deg,#6e150c,#9a2018,#b3401f,#e0663f);
  --sliver-h:3px; --sliver-h-hover:4px;
  --sliver-ease:.18s cubic-bezier(.4,0,.2,1);
  /* type */
  --font:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  --mono:'SF Mono','JetBrains Mono',ui-monospace,monospace;
  /* ---- legacy aliases ----
     These five names came from agents.css, which no page has loaded for a long time. That made
     every `var(--electric)` and `var(--mut)` written against it invalid at computed-value time:
     the declaration is dropped, so `background` fell back to transparent and `color` fell back to
     the inherited ink. It is silent — nothing errors, the rule just stops existing — which is why
     it survived. Two of the casualties were in this very file (.pc-drop-menu a::before and
     .pc-burger-sec a::before), so the nav's leading accent bar animated and painted nothing on
     every page on the site. Aliased here rather than rewritten at ~16 call sites: one definition,
     no behavioural diff to review, and pages that still say --electric keep working.

     --mut now ALIASES --ink-mute rather than keeping agents.css's own #5b6474. It was held apart
     because #5b6474 is the darker grey and aliasing to the old --ink-mute (#6b7a99, 4.31:1) would
     have lightened working text under AA. That reason expired when --ink-mute became #5a6885
     (5.59:1 on white, 5.17:1 on --paper-warm — its worst case still clears AA), so the two greys
     no longer need to differ, and one muted grey means "muted" cannot drift between two pages.
     The ~30 call sites that wrote var(--mut,#5b6474) rendered that value via the fallback; they
     now resolve through this alias instead, so the fallback is dead weight rather than a second
     source of truth. Do not re-fork them: a page that redeclares --mut in its own :root silently
     opts out of every future contrast fix made here. */
  --electric:var(--blue); --navy:var(--blue-deep); --cyan:var(--blue-cyan);
  --mut:#5a6885; --bg:#f5f7fb;

  /* ── THE VIEWPORT LAW ─────────────────────────────────────────────────────────────────────
     FOUR REGIONS, AND NOTHING ELSE MAY RESERVE SPACE.

         │ header — full bleed, nothing narrows it                                    │
         │ left rail │ gap │            centre            │ gap │ right rail │

     Everything below is that sentence written as arithmetic. There are exactly two numbers —
     the rail's width and the gutter — and every position on the page is derived from them, so
     a console cannot acquire a private opinion about where the middle of the window is.

     WHAT THIS REPLACES, AND WHY. The block that stood here reserved a FLYOUT LANE on the right:
     380px of panel + 12px + 8px, opened at and above 1440px, on top of the rail. It was reserved
     whether or not a flyout was open — which is what pushed every console leftward and left the
     dead white strip the operator reported. MEASURED at 403ba53, admin.html, both rails up:
         width   left gutter   right gutter   centre off viewport centre
          1280      28px          28px            0px      (below the old 1440 line)
          1440      28px         400px         -186px
          1920      28px         400px         -186px
          2560      38px         410px         -186px
     and on the seven other consoles sampled the right gutter ran to 592px and the centre sat
     386px left of the window's own centre. A flyout is an OVERLAY now (§ .pc-hud-panel-anchored,
     css/payco-hud.css) and reserves nothing, so the lane is deleted rather than retuned.

     --pc-rail READS THE LEFT RAIL'S OWN TOKEN. `--rail-w` is css/payco-system.css:154 and it is
     what `.pc-sidenav` is actually sized with, so retuning it retunes the right rail, both
     gutters and the centre together and the two rails cannot drift apart. The 246px literal is
     the FALLBACK and never the value: twenty-three console pages — admin.html and
     operations.html among them — do not link that sheet and have no --rail-w to read.

     --pc-gap IS DECLARED HERE, ONCE, AND NOWHERE ELSE. css/payco-hud.css used to declare it as a
     flat 14px for its own panel packing; that token is `--pc-pack` now and this name belongs to
     the law. clamp(16,1.25vw,24) is 16px at 1280, 18px at 1440, 24px at 1920 and stays 24px above
     — the gutter grows with the desk and then stops, because past 1920 more air on the sides is
     width taken off the work.

     THE SWITCH IS THE OCCUPANCY, NOT A SELECTOR, and that is unchanged and still load-bearing:
     payco-hud.js publishes --pc-hud-right-open from a zero-height metric box inside the rail, so
     it is the rail's OPEN width in every state and 0px whenever the rail is not drawn — absent,
     hidden, refused by the nav's no-grant sweep, or released below the collapse line. Multiplying
     it by 100 turns it into a switch: min() clamps the reservation to the law's number whenever
     there is any rail at all, and to zero exactly when there is none. `:has()` was tried first and
     is the wrong question — it matches an element that is in the DOM, and a refused rail is in the
     DOM with display:none. outbox-console.html under a fully refused API took the whole gutter for
     a taskbar that was not drawn.

     AND THE SIZE FOLLOWS THE COLLAPSE AGAIN (F2, W3D). THIS REVERSES THE PARAGRAPH THAT STOOD
     HERE, and the reversal is the point rather than an oversight, so the old text is restated
     before it is withdrawn: the reservation was --pc-rail, a constant, so that toggling either
     rail changed the centre by zero pixels, because an earlier block had reserved the LIVE inset
     and the column jumped 164px under the reader's hands on every toggle.
     WHAT THAT CONSTANT ACTUALLY BOUGHT, MEASURED. It holds the centre still across a toggle, and
     it does so by paying a 246px rail on a page that is drawing an 82px strip. The skew the merge
     note at :731 defines — (host.x - bandL) - (bandR - host.right), with the band taken from the
     rails' OWN boxes — is therefore 0 only in the state the constant was named for. At 1440 on a
     console with the dock shut: bandR is 1358 (the strip's left edge), the column still ends at
     1176, and the skew is 18 - 182 = -164. The constant did not centre the page in both states.
     It centred it in one and hid the other behind a reservation nothing was drawing.
     SO THE WIDTH TERM IS THE LIVE INSET AND THE SWITCH IS UNCHANGED. --pc-hud-right is what
     payco-hud.js publishes for the rail it actually drew — 246 open, 82 shut, 0 absent — so the
     band's right edge is the dock's rendered left edge at every moment, which is the same edge
     the skew is measured from and the same edge tests/viewportlaw.mjs now reads. --pc-lane-on
     still carries the OCCUPANCY SWITCH off --pc-hud-right-open, and it has to stay that way:
     -open is 0 exactly when no rail is drawn, while -right is 82 on a shut rail that is very much
     there, so a switch built on -right could not tell `shut` from `absent`.
     THE FALLBACK IS --pc-rail AND IT IS VERY NEARLY DEAD CODE. Before payco-hud.js publishes,
     --pc-lane-on is 0 and the min() takes the switch, so this arm is unreachable at cold load; it
     is kept at the constant so that a rail which publishes -open without -right reserves the wide
     number rather than none.
     THE TOGGLE NOW MOVES THE COLUMN BY DESIGN, which is what F2 asks for and what the
     `moves the centre by zero pixels` assertions were written to forbid. See the arming block at
     the padding below for the half of this that is NOT safe to do naively. */
  --pc-rail:var(--rail-w,246px);
  --pc-gap:clamp(16px,1.25vw,24px);
  --pc-lane-left:calc(var(--pc-rail) + var(--pc-gap));
  --pc-lane-on:calc(var(--pc-hud-right-open,var(--pc-hud-right,0px)) * 100);
  --pc-lane-right:min(var(--pc-lane-on),calc(var(--pc-hud-right,var(--pc-rail)) + var(--pc-gap)));

  /* AND THE LEFT BAND IS THE GAP, CARRYING THE RIGHT RAIL'S OWN SWITCH. Written as a min() for
     exactly the reason --pc-lane-right is written as one: one declaration covers a page that
     draws the right rail and a page that does not, with no selector to keep the two in step.
     --pc-lane-on is 0px until payco-hud.js publishes a rail box and a very large number after it
     does, so this resolves to 0px on the marketing tier and to --pc-gap on a console.
     IT IS THE GAP AND NOT rail + gap, AND THAT DIFFERENCE IS THE WHOLE OF THE DEFECT LANE U WAS
     SENT AT. What this pays for on the left of a page that is not `.pc-hasside` is the air beside
     the content column; there is no left rail under it to clear — that is what the class means —
     so the band owed there is one gap. --pc-lane-right is rail + gap because it has a 246px rail
     to clear, and spending it on this side reserved a rail that was never drawn. */
  --pc-lane-gutter:min(var(--pc-lane-on),var(--pc-gap));

  /* ── THE PANEL, AND THERE IS ONE OF IT ────────────────────────────────────────────────────
     "Panels: full center width, ONE radius token, ONE padding token, ONE inter-panel gap."
     MEASURED FIRST, at 1440 on all twenty-nine consoles, every visible box carrying one of the
     site's panel classes, keyed on its computed radius and padding — EIGHT distinct shapes:
         35 boxes   16px / 20px          the plurality, and the one below
         12 boxes   18px / 18px
          6 boxes   16px / 18px 16px
          1 each    14px/18px·16px  ·  16px/28px 22px  ·  28px/37.44px  ·  16px/0  ·  0/0
     A reader moving between two consoles was reading two different components, and none of the
     eight was wrong anywhere — each was a page's own taste, stated in its own <style>.
     THE RADIUS IS --radius, NOT A NEW NUMBER. payco.css:107 has said 16px since the beginning and
     css/payco-system.css:114 mirrors it; the plurality shape was already drawing it. Only the
     padding needed a name.
     THE GAP IS --gap-grid's 16px FOR THE SAME REASON: it is the site's existing panel-to-panel
     gap and the console grids that already declare a gap mostly declare that one. */
  /* THE STRIP. 246/3 = 82px, the same divisor and the same arithmetic css/payco-hud.css:93 uses
     for --pc-rail-w-shut, reading the same --pc-rail so the two cannot drift to different widths.
     It is the width a minimised right rail draws at, and between 1100px and 1279px it is the width
     the right rail draws at unconditionally — see the band below. */
  --pc-rail-strip:calc(var(--pc-rail) / 3);
  --pc-panel-radius:var(--radius,16px);
  --pc-panel-pad:20px;
  --pc-panel-gap:var(--gap-grid,16px);
}

/* ==============================================================================================
   THE DARK THEME — THE SAME RE-POINT css/payco-system.css SECTION 1b ALREADY MAKES, IN THE SHEET
   THAT THIRTY-THREE PAGES ACTUALLY LOAD.                                          (W11 LANE 2)
   ==============================================================================================
   MEASURED FIRST, in headless chromium, at 1440, on all 99 root pages: set data-theme="dark" on
   <html> after the HUD's boot restore has landed, read the composited body/header/surface
   colours back, and compare them against the light pass. Fifty-one pages did not move a single
   pixel, and the split is exactly one stylesheet wide:

       payco.css + css/payco-system.css   44 pages   dark works
       payco.css alone                    33 pages   NOTHING HAPPENS   <-- this block
       payco-marketing.css                17 pages   nothing happens (see below)
       neither                             4 pages   dark works (own tokens)

   admin.html, operations.html, support.html, underwriting.html, merchant360.html, integrations.html,
   messages.html, calendar.html, forum*.html, directory*.html and twenty more link payco.css and
   NOT the system sheet, and payco.css contained the string "data-theme" exactly zero times. Those
   pages carry the corner stack, the corner stack carries a theme button, and pressing it flipped
   an attribute that no rule in their cascade read. The button worked; the theme did not exist.

   WHY THE FIX IS HERE AND NOT A NEW <link>. The alternative was to add css/payco-system.css to the
   thirty-three. That sheet is the whole design system — two thousand lines of component rules —
   and dropping it onto pages whose layouts were never measured against it restyles them wholesale.
   The token names below are payco.css's OWN, declared eleven lines up; re-pointing them changes
   colour and nothing else, on pages that already resolve every surface through them.

   THE VALUES ARE NOT NEW AND ARE NOT THIS LANE'S TO INVENT. Every declaration is copied
   value-for-value from css/payco-system.css:296-375, which is where they were measured against
   the AA floor and where tests/darktheme.mjs holds them. Two dark themes with two sets of numbers
   would be worse than one page that stays light, so this is a COPY, deliberately, and it is
   marked as one: the block is bounded by the two banners below so a suite can compare the bytes
   rather than trust that nobody drifted them.

   NOT PORTED, AND WHY: --shadow-deck (css/payco-system.css:367) — payco.css never declares it, so
   there is nothing here to re-point. The four --gradient* ramps — they are FILLS behind white
   text and carry their own contrast across both themes, which css/payco-system.css:308-312 states
   as the one thing the HUD contract asks be preserved.

   STILL LIGHT AFTER THIS BLOCK: the seventeen payco-marketing.css pages. That sheet themes the
   .mnav marketing nav under [data-theme="dark"] and declares no :root tokens at all — those pages
   paint from their own inline palettes. See docs/L2-NOTES.md; it is a design job, not a re-point,
   and it is not what this block is.
   ==============================================================================================*/
/* ── PAYCO DARK TOKENS · MIRROR OF css/payco-system.css §1b — BEGIN ─────────────────────────── */
[data-theme="dark"]{
  /* ---- ink / paper ---- css/payco-hud.css:85-90 ---- */
  --ink:var(--pc-ink,hsl(210 22% 96%));
  --ink-soft:var(--pc-ink-soft,hsl(214 14% 80%));
  --ink-mute:var(--pc-ink-mute,hsl(216 11% 64%));
  --paper:var(--pc-surface-2,hsl(220 16% 13%));
  --paper-warm:var(--pc-surface-3,hsl(220 14% 18%));
  --white:var(--pc-surface-2,hsl(220 16% 13%));
  --line:var(--pc-line,hsl(218 13% 26%));
  --line-soft:var(--pc-line-soft,hsl(219 14% 21%));

  /* ---- brand ---- css/payco-hud.css:91-94 ---- */
  --blue:var(--pc-accent,hsl(218 100% 66%));
  --blue-deep:var(--pc-accent-hi,hsl(200 96% 70%));
  --blue-electric:var(--pc-accent-hi,hsl(200 96% 70%));
  --blue-cyan:var(--pc-accent-cyan,hsl(196 93% 70%));

  /* ---- the accent tint ---- */
  --info:var(--pc-accent,hsl(218 100% 66%));
  --info-bg:var(--pc-accent-soft,hsl(218 100% 66% / .16));

  /* ---- the failure ramp ---- css/payco-hud-widgets.css:62-64 ---- */
  --warn:var(--pc-danger,rgb(255 138 110));
  --warn-bg:var(--pc-danger-wash,rgb(255 138 110 / .14));

  /* ---- the success and caution ramps ---- */
  --ok:var(--pc-ok,hsl(152 48% 53%));
  --ok-bg:var(--pc-ok-bg,hsl(152 48% 53% / .14));
  --amber:var(--pc-amber,hsl(40 75% 54%));
  --amber-bg:var(--pc-amber-bg,hsl(40 75% 54% / .14));

  /* ---- elevation ---- css/payco-hud.css:97-98 ---- */
  --shadow-sm:var(--pc-shadow,0 1px 2px hsl(220 30% 2% / .5),0 14px 38px hsl(220 30% 2% / .5));
  --shadow-md:var(--pc-shadow,0 1px 2px hsl(220 30% 2% / .5),0 14px 38px hsl(220 30% 2% / .5));
  --shadow-lg:var(--pc-shadow-lg,0 28px 80px hsl(220 30% 2% / .68),0 4px 16px hsl(220 30% 2% / .5));

  /* ---- legacy aliases ----
     --electric / --navy / --cyan are already var() of the four brand names above and follow them
     for free. --mut and --bg are LITERALS at the two lines above this block, not aliases, so they
     have to be re-pointed by hand or every var(--mut) call site keeps the light grey. */
  --mut:var(--pc-ink-mute,hsl(216 11% 64%));
  --bg:var(--pc-surface,hsl(220 18% 9%));
}
/* ── PAYCO DARK TOKENS · MIRROR OF css/payco-system.css §1b — END ───────────────────────────── */

/* THE TWO SURFACES A TOKEN RE-POINT CANNOT REACH, because they are painted from literals rather
   than from the names above, and they are the two the reader looks at first.

   1. html's own background. payco.css:26 paints it #fbfcfe as a literal — behind a dark <body>
      that shows as a white band under short pages and behind the overscroll bounce.
   2. .pc-header. It is a translucent near-white wash (rgba(251,252,254,.86)) with a light
      hairline, declared as literals for the blur to read correctly. Measured on admin.html in
      dark before this rule: header background rgba(251,252,254,.86) against --ink-mute text —
      the header stayed white while everything under it went to hsl(220 18% 9%).
   Both are repaired against the same --pc-* names the block above uses, and both are inert on
   light because they are qualified by the attribute. */
html[data-theme="dark"]{background:var(--pc-surface,hsl(220 18% 9%))}
[data-theme="dark"] .pc-header{
  background:var(--pc-surface-2,hsl(220 16% 13%));
  border-bottom-color:var(--pc-line,hsl(218 13% 26%));
  color:var(--pc-ink,hsl(210 22% 96%))}

/* ---- THE HEADER'S THREE OTHER SURFACES, AND WHY THE TOKEN BLOCK ABOVE CANNOT REACH THEM ------
   `.pc-drop-trig` (:373), `.pc-drop-menu` (:386) and `.pc-burger-panel` (:427) paint themselves
   from LIGHT LITERALS — #f2f5fa and #fff twice — while their text is var(--ink-soft), which the
   block above correctly re-points to a near-WHITE in dark. Near-white ink on a near-white pill is
   what that combination produces, and it is not hypothetical: measured in chromium at 1440 with
   data-theme="dark" on settings.html and disputes.html, six header controls —

       Dashboards · Community · Marketplace · Load Board · Jobs · Calendar

   — came back at 1.49:1 and 1.45:1 against a 4.5 floor. That is the whole staff navigation of
   every console, unreadable, on the theme its own toggle turns on.

   THE VALUES ARE payco-marketing.css:418-423's, NOT NEW ONES. That sheet draws the SAME pill for
   the public half of the site (.mnav .links > a, itself commented "the app header's pill, so the
   two halves of the site agree") and it was given this exact repair; payco.css never was. The
   header is a two-stylesheet seam and this is the second half of it, so the numbers are copied
   rather than chosen — --pc-surface-3 behind, --pc-ink-soft in front, --pc-line around, which
   measures 8.9:1.

   THE TWO PANELS ARE THE SAME BUG ONE CLICK LATER. Nothing measures them in a screenshot because
   a dropdown is closed until it is opened, which is exactly why they were missed: #fff behind
   var(--ink-soft) links is 1.05:1. Their shadows are re-pointed too — a shadow tuned as
   rgba(11,27,58,.12) on white is invisible on a near-black surface, so the panel loses the edge
   that tells a reader it is floating above the bar. */
[data-theme="dark"] .pc-drop-trig{
  background:var(--pc-surface-3,hsl(220 14% 18%)) padding-box;
  border-color:var(--pc-line,hsl(218 13% 26%));
  color:var(--pc-ink-soft,hsl(214 14% 80%))}
[data-theme="dark"] .pc-drop-menu,
[data-theme="dark"] .pc-burger-panel{
  background:var(--pc-surface-2,hsl(220 16% 13%));
  border-color:var(--pc-line,hsl(218 13% 26%));
  box-shadow:var(--pc-shadow-lg,0 28px 80px hsl(220 30% 2% / .68),0 4px 16px hsl(220 30% 2% / .5))}

/* ---- GREY AND LOW-OPACITY TEXT ---------------------------------------------------------------
   The muted greys themselves are re-pointed by the token block above and clear the floor on the
   surfaces they sit on. What does NOT survive a theme flip is ink that was written as an ALPHA of
   black: `rgba(10,22,40,.x)` is a dark ink fading toward whatever is behind it, and behind it is
   now near-black too, so the text fades toward invisible instead of toward grey. These are the
   ones in this sheet that carry running copy rather than a hairline or a shadow. */
[data-theme="dark"] .pc-id .pc-idlink,
[data-theme="dark"] .pc-id #pcSignOut{color:var(--pc-ink-mute,hsl(216 11% 64%))}
[data-theme="dark"] .pc-burger-trig span{background:var(--pc-ink-mute,hsl(216 11% 64%))}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
/* The app background is --bg, not --paper. 17 of the 21 signed-in pages were already painting
   #f5f7fb — 14 via `body{background:var(--bg)}` and three by redeclaring --paper to #f5f7fb in
   their own :root — while crm/integrations/messages forced var(--paper) with !important and
   checklist used #fff, so three different backgrounds shipped across sibling console pages and
   navigating Operations -> CRM visibly shifted the page. Declaring the majority value once here
   lets every one of those per-page rules be deleted. --paper stays the near-white for surfaces
   that sit ON this background (cards, drops), which is the distinction that got lost. */
body{margin:0;font-family:var(--font);background:var(--bg);color:var(--ink);
  font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;letter-spacing:-.006em}
/* NATIVE CONTROLS FOLLOW THE THEME — `color-scheme` was declared nowhere in this
   tree before this line. Measured, not assumed: getComputedStyle(<html>).colorScheme
   read `normal` on every page in the repo, in BOTH themes. `normal` means the UA
   paints its own widgets from the LIGHT scheme, so on [data-theme="dark"] the
   scrollbar track, the <select> popup, the date picker, and every checkbox and radio
   rendered white-on-white against a near-black page. Two declarations, no colour
   invented, no element restyled: the UA is simply told which scheme it is painting
   into. Light is stated as well as dark so a reader whose OS is dark does not get
   auto-darkened widgets on the light theme this product ships by default. */
/* THE BREADCRUMB LINK, RAISED OFF THE FLOOR. payco-nav.js:3725 injects
   `.pc-crumb a{color:var(--blue-electric,#1a4dd6)}` at run time. Its FALLBACK is
   right and its TOKEN is wrong: --blue-electric is #3b82ff, the FOCUS colour
   (css/payco-system.md §1.1 names it that), and #3b82ff on --bg #f5f7fb measures
   3.36:1 — under the 4.5 AA floor for body text. tests/designfix.mjs reports it on
   invoice.html, jobs-post.html and reconcile.html and it is on every console page a
   crumb is drawn on. --blue is 6.36:1 in light and follows the theme in dark, so one
   token fixes both. `nav.pc-crumb a` is (0,1,2) against the injected rule's (0,1,1),
   which is what lets a <link> beat a <style> the nav appends after it. The one-word
   fix at payco-nav.js:3725 is filed as a REQUEST; this rule is correct either way. */
nav.pc-crumb a{color:var(--blue)}

html{color-scheme:light}
[data-theme="dark"]{color-scheme:dark}

a{color:var(--blue);text-decoration:none}
h1,h2,h3,h4{margin:0;font-weight:700;letter-spacing:-.02em;color:var(--ink)}
::selection{background:rgba(59,130,255,.18)}

/* ---------- skip link ----------
   Every page here is a sticky header with a full nav in front of the content, and there was no
   way past it from the keyboard — tab, tab, tab, through every section and dropdown, on all 52
   pages, before reaching anything the page is actually about. Off-screen until focused, so it
   costs nothing visually; z-index clears the header's 50. Injected by payco-nav.js for the pages
   that load it, and written into the markup directly for the ones that don't. */
.pc-skip{position:absolute;left:12px;top:-60px;z-index:60;background:var(--white);color:var(--blue-deep);
  font:inherit;font-size:13px;font-weight:700;padding:12px 16px;min-height:44px;box-sizing:border-box;
  display:inline-flex;align-items:center;border-radius:0 0 10px 10px;
  border:1px solid var(--line);border-top:0;box-shadow:var(--shadow-md);text-decoration:none;
  transition:top .16s cubic-bezier(0,0,.2,1)}
.pc-skip:focus{top:0}
@media(prefers-reduced-motion:reduce){.pc-skip{transition:none}}

/* ---------- app header ---------- */
/* The horizontal inset and the gap are payco-marketing.css's `.mnav` numbers verbatim
   (clamp(18px,3vw,40px) and 20px), not near-misses. Both bars were already 74px and frosted,
   so crossing from pricing.html into the product looked right — until you watched the
   WORDMARK, which moved 6px left at 1440 (34px inset against 40) and 4px at 1280, on every
   marketing→app click. Same clamp, same first pixel, at every width. */
.pc-header{background:rgba(251,252,254,.86);backdrop-filter:blur(5px);
  -webkit-backdrop-filter:blur(5px);border-bottom:1px solid var(--line);
  padding:0 clamp(18px,3vw,40px);height:74px;display:flex;align-items:center;gap:20px;
  position:sticky;top:0;z-index:50}
/* the index signature — a static gradient hairline under the bar (no animation loop) */
.pc-header::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;
  background:linear-gradient(90deg,#0b2a8a,#1a4dd6,#3b82ff,#6dd5fa,#3b82ff,#1a4dd6,#0b2a8a);opacity:.4;pointer-events:none}
/* No overflow:hidden (it clipped the nav dropdowns) and no animated sheen — a
   forever-looping gradient sweep burns CPU for a decoration nobody asked to keep. */
/* `aspect-ratio` reserves the logo's box before the image decodes. Without it the header laid out
   at 0px wide and reflowed the moment the bytes landed — the logo, the tag and the whole nav row
   all jumped. It lives in CSS rather than as width/height on the 30 <img> tags because a width
   ATTRIBUTE is a presentational hint for the CSS width property, which makes it the flex BASE SIZE
   in this flex row: setting the intrinsic size on the tag made the logo enter layout wanting its
   full pixel width and flex-shrink settled it at ~990px instead of 163px, on 46 pages. Declaring
   only the ratio leaves width:auto to resolve as height x ratio = 163px, which is what it already
   painted at — same pixels, reserved earlier.
   640/157 is logo.png's real intrinsic size. It previously read 7982/1957, the dimensions of the
   original master, which stopped being true when that 207 KB master was replaced by this 11 KB
   export; the two ratios differ by 0.055% so nothing moved visibly, but a hard-coded ratio that
   no longer matches the file it describes is a trap for whoever edits this next. If logo.png is
   re-exported again, this pair has to move with it. */
/* 44px, which is what payco-marketing.css's header comment has always claimed this number
   was ("the canonical numbers are the APP header's ... 44px logo") while this rule said 40.
   The marketing bar is the one that shipped 44, so a visitor crossing into the product saw
   the wordmark shrink 4px and rise 2px in a bar that was otherwise the same height. */
/* `width:auto` is load-bearing, not tidying. A width ATTRIBUTE on an <img> is a presentational
   hint for the width PROPERTY, and this rule set only the height — so the 38 tags carrying
   width="163" pinned the box at 163px while the height moved to 44, painting the wordmark
   163x44 (ratio 3.705) against its real 640/157 (4.076): a 9% horizontal squash on every app
   page, invisible on its own and obvious the moment you cross from a marketing page showing the
   same mark at 179x44. Stating width:auto drops the hint and lets aspect-ratio resolve it. The
   tags were re-stamped 179x44 to match, so the box is still reserved before the bytes land. */
.pc-logo{height:44px;width:auto;aspect-ratio:640/157;display:block;position:relative;z-index:1}
/* Phones, at payco-marketing.css's breakpoint and its number rather than this file's 560px
   layer: the wordmark was 44px here and 34px on every marketing page at the same viewport,
   which is the width where the difference shows most because the bar is most of the screen.
   The tap target does not shrink with it — .pc-home below holds the 44px floor, exactly as
   `.mnav .logo` does around the marketing header's own 34px image. */
@media(max-width:600px){.pc-logo{height:34px}}
/* Logo → home, with a glow that only paints on hover (no animation loop, so idle CPU
   is zero). drop-shadow is GPU-composited; transform gives a hair of lift.
   min-height is stated HERE, not only in the ≤560px layer where it used to live alone: the
   image is 34px from 600px down, so between 600 and 561 the home link — the most-tapped
   control on any app page — was a 34px target with nothing holding it open. */
.pc-home{display:inline-flex;align-items:center;position:relative;z-index:1;border-radius:8px;
  min-height:44px;
  transition:transform .22s cubic-bezier(.16,1,.3,1),filter .22s ease}
.pc-home:hover{transform:translateY(-1px);
  filter:drop-shadow(0 0 10px rgba(59,130,255,.5)) drop-shadow(0 0 3px rgba(109,213,250,.4))}
.pc-home:active{transform:translateY(0)}
/* The page title in the bar is the page's <h1> on consoles that have no other heading — see the
   canon assertion in tests/completion.mjs. `margin:0` is load-bearing: an <h1> carries a UA
   margin of 0.67em, and as a flex item that margin is NOT collapsed, so it would shove the tag
   off the vertical centre of a 74px bar. font-size below already overrides the UA 2em. */
.pc-tag{position:relative;z-index:1;background:var(--gradient);color:#fff;font-size:12.5px;
  font-weight:800;padding:5px 13px;border-radius:999px;letter-spacing:.01em;white-space:nowrap;
  margin:0}
.pc-nav{flex:1;margin-left:0;display:flex;align-items:center;justify-content:flex-end;position:static;z-index:50}
/* ---------- the pill cluster ----------
   This was position:absolute, centred on the PAGE. That is why it could only ever OVERLAP the
   identity cluster and the page tag, never push them — three different layout systems in one
   row — and why the fold breakpoint had to be re-guessed three times (1180 for five pills,
   1479 for six, "clean only from 1700" for seven) before it was replaced by a JS measurement.
   The measurement was correct and the result was still bad: measured on forum.html with a
   9-character name, the seven pills were shown at >=1700px and FOLDED AT 1600, 1440 AND
   1280px, so the two commonest laptop widths had no visible primary navigation at all — only
   the hamburger. A longer display name folded it wider still.
   In flow the cluster is a flex sibling of the identity, so the row shares space instead of
   stacking on itself: it centres in what is left over, and it cannot collide with anything.
   Pills keep their own width (flex:none) and the cluster reports overflow honestly via
   scrollWidth, which is what fitBar() now measures. Centred between the logo and the identity
   rather than on the page centre is the trade, and it is worth a navigation bar that exists. */
/* NO overflow:hidden HERE. It was added to make scrollWidth report the cluster's overflow for
   fitBar(), and it clipped every nav dropdown: measured on admin.html at 1920px, the open menu
   was 535px tall and extended 537px below this box, and checkVisibility() returned false — the
   menus were entirely invisible. Line ~133 of this file already records that overflow:hidden on
   the header "clipped the nav dropdowns"; this was that bug, reintroduced one element lower.
   fitBar() now sums the children's own widths instead of asking for a scroll measurement, so
   nothing here needs to clip. */
.pc-navcenter{position:static;transform:none;flex:1 1 auto;min-width:0;
  display:flex;gap:8px;align-items:center;justify-content:center;z-index:50}
.pc-navcenter>*{flex:none}
.pc-navright{display:flex;gap:12px;align-items:center;position:relative;z-index:50}
.pc-nav a[data-gated]{display:none}

/* ---------- bar buttons: outlined pills, View-as gradient ----------
   Defined outline at rest; on hover the pill fills with the same blue→teal gradient as
   the View-as button; the active pill holds that gradient. Fixed height keeps every
   trigger — <a> or <button> — on an identical box so the bar cannot misalign. */
.pc-drop{position:relative}
.pc-drop-trig{margin:0;position:relative;display:inline-flex;align-items:center;justify-content:center;gap:6px;
  font:inherit;line-height:1;font-size:14px;font-weight:600;color:var(--ink-soft);
  background:#f2f5fa padding-box;border:1px solid var(--line);border-radius:999px;
  height:38px;padding:0 18px;cursor:pointer;white-space:nowrap;text-decoration:none;
  transition:background .22s cubic-bezier(.4,0,.2,1),color .18s ease,border-color .22s ease,box-shadow .22s ease}
.pc-drop-trig:hover{background:var(--gradient) border-box;border-color:#2255d4;color:#fff;
  box-shadow:0 4px 14px rgba(26,77,214,.28)}
.pc-drop-trig svg{opacity:.55;transition:transform .24s cubic-bezier(.4,0,.2,1),opacity .18s}
.pc-drop-trig:hover svg{opacity:.9}
/* active category / current solo link — the gradient pill, held */
.pc-drop.active .pc-drop-trig,.pc-drop-trig.pc-drop-solo.on{background:var(--gradient) border-box;
  border-color:#2255d4;color:#fff;box-shadow:0 4px 14px rgba(26,77,214,.3)}
.pc-drop.active .pc-drop-trig svg,.pc-drop-trig.pc-drop-solo.on svg{opacity:.9}
.pc-drop.open .pc-drop-trig{background:var(--gradient) border-box;border-color:#2255d4;color:#fff}
.pc-drop.open .pc-drop-trig svg{transform:rotate(180deg);opacity:.95}
.pc-drop-menu{position:absolute;top:calc(100% + 8px);left:0;min-width:200px;background:#fff;
  border:1px solid var(--line);border-radius:16px;
  box-shadow:0 1px 2px rgba(11,27,58,.04),0 8px 20px rgba(11,27,58,.1),0 20px 44px rgba(11,27,58,.12);
  padding:7px;display:none;flex-direction:column;gap:1px;z-index:60;transform-origin:top left}
.pc-drop.open .pc-drop-menu{display:flex;animation:pcMenuIn .2s cubic-bezier(.16,1,.3,1)}
@keyframes pcMenuIn{from{opacity:0;transform:translateY(-6px) scale(.97)}to{opacity:1;transform:none}}
.pc-drop-menu a{position:relative;color:var(--ink-soft);font-size:13.5px;font-weight:500;padding:10px 13px 10px 15px;
  border-radius:10px;text-decoration:none;white-space:nowrap;overflow:hidden;
  transition:background .16s ease,color .16s ease}
/* Leading accent bar slides in from the left on hover — a considered touch that costs
   only a transform, no repaint of a gradient.
   --electric is NOT declared in this file's :root; it is declared in the inline <style> of 22
   pages and nowhere else. On the other 14 pages that wear this nav — analytics, calendar,
   checklist, commissions, crm, integrations, messages, onboarding, operations, pos, referral,
   settings, training, underwriting — `var(--electric)` had no value and no fallback, so the
   whole `background` declaration was invalid at computed-value time and this bar never painted.
   The same hover worked or did not work depending on which page you were on. The fallback names
   the token this file DOES own, so the accent is now a property of the component. */
.pc-drop-menu a::before{content:"";position:absolute;left:0;top:6px;bottom:6px;width:3px;border-radius:2px;
  background:var(--electric,var(--blue-electric));transform:translateX(-6px);opacity:0;transition:transform .18s ease,opacity .18s ease}
.pc-drop-menu a:hover{background:var(--paper-warm);color:var(--ink)}
.pc-drop-menu a:hover::before{transform:translateX(0);opacity:1}
.pc-drop-menu a.on{background:var(--gradient);color:#fff}
.pc-drop-menu a.on::before{opacity:0}

/* ---------- master hamburger ----------
   Discreet by default — three hairlines, no border, no box. It only asserts itself on
   hover and when open. The panel holds every category grouped, for completeness. */
.pc-burger{position:relative;margin-left:6px}
.pc-burger-trig{margin:0;display:inline-flex;flex-direction:column;justify-content:center;gap:4px;width:34px;height:34px;
  padding:0 8px;background:none;border:0;border-radius:9px;cursor:pointer;transition:background .18s ease}
.pc-burger-trig span{display:block;height:1.5px;border-radius:2px;background:var(--ink-mute);
  transition:transform .26s cubic-bezier(.4,0,.2,1),opacity .18s ease,background .18s ease}
.pc-burger-trig span:nth-child(2){width:70%;margin-left:auto}
.pc-burger-trig:hover{background:var(--paper-warm)}
.pc-burger-trig:hover span{background:var(--ink-soft)}
.pc-burger-trig:hover span:nth-child(2){width:100%}
.pc-burger.open .pc-burger-trig span{background:var(--ink-soft)}
.pc-burger.open .pc-burger-trig span:nth-child(1){transform:translateY(5.5px) rotate(45deg)}
.pc-burger.open .pc-burger-trig span:nth-child(2){opacity:0}
.pc-burger.open .pc-burger-trig span:nth-child(3){width:100%;transform:translateY(-5.5px) rotate(-45deg)}
.pc-burger-panel{position:absolute;top:calc(100% + 10px);right:0;width:min(304px,86vw);background:#fff;
  border:1px solid var(--line);border-radius:18px;
  box-shadow:0 1px 2px rgba(11,27,58,.04),0 10px 26px rgba(11,27,58,.12),0 26px 56px rgba(11,27,58,.16);
  padding:15px;display:none;max-height:min(74vh,560px);overflow:auto;z-index:60}
.pc-burger.open .pc-burger-panel{display:block;animation:pcMenuIn .22s cubic-bezier(.16,1,.3,1)}
.pc-burger-id{display:flex;align-items:center;gap:9px;padding:2px 4px 13px;margin-bottom:9px;
  border-bottom:1px solid var(--line);font-size:14px;color:var(--ink)}
.pc-burger-sec{margin-top:12px}
.pc-burger-sec:first-of-type{margin-top:0}
.pc-burger-h{font-size:12px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-mute);padding:0 4px;margin-bottom:5px}
.pc-burger-sec a{display:block;position:relative;color:var(--ink-soft);font-size:14px;font-weight:500;
  padding:10px 11px 10px 13px;border-radius:10px;text-decoration:none;overflow:hidden;
  transition:background .16s ease,color .16s ease}
/* Same missing token as .pc-drop-menu a::before above — see the note there. */
.pc-burger-sec a::before{content:"";position:absolute;left:0;top:7px;bottom:7px;width:3px;border-radius:2px;
  background:var(--electric,var(--blue-electric));transform:translateX(-6px);opacity:0;transition:transform .18s ease,opacity .18s ease}
.pc-burger-sec a:hover{background:var(--paper-warm);color:var(--ink)}
.pc-burger-sec a:hover::before{transform:translateX(0);opacity:1}
.pc-burger-sec a.on{background:var(--gradient);color:#fff}
.pc-burger-sec a.on::before{opacity:0}
@media(prefers-reduced-motion:reduce){
  .pc-drop.open .pc-drop-menu,.pc-burger.open .pc-burger-panel{animation:none}
  .pc-drop-trig svg,.pc-burger-trig span,.pc-drop-menu a,.pc-burger-sec a{transition:none}}
.pc-sep{width:1px;height:22px;background:var(--line);margin:0 3px}
/* ---------- admin sub-nav ----------
   A segmented tab bar directly under the header on admin console pages. Sticks just
   below the 60px header so the pages stay one click apart while scrolling. Scrolls
   horizontally on narrow screens rather than pushing the page wider. */
/* ---------- admin portal sidebar (same language as merchant/agent portals) ---------- */
/* The console nav carries 13 destinations, which need ~878px. A 900px-tall window leaves it 826
   and a 1280x720 laptop leaves 646 — so it ALWAYS overflows, and it was overflowing silently:
   overflow-y:auto with no fade, so the 13th item (Settings) sat sliced in half at the bottom edge
   and read as "the list ends here" rather than "scroll me". On the Settings page the clipped item
   was the ACTIVE one. Two parts to the fix, and only the second is visible:
     padding-bottom  — the last item can clear the edge instead of being cut mid-word;
     the gradient     — a scroll shadow via `background-attachment:local`. The layer is pinned to
                        the CONTENT, so it is parked out of sight while the list fits and slides
                        into view at the bottom edge only while there is more to scroll. That is
                        the property a plain ::after fade does not have: it would tint the bottom
                        item even when the nav fits, which is why this is a background and not an
                        overlay. */
/* THE LEFT RAIL IS THE LAW'S TOKENS, NOT THREE LITERALS (OPERATOR SPEC, THE VIEWPORT LAW).
   This said `top:74px; width:246px` and, below, `border-right:1px`. css/payco-system.css:835
   states the SAME rule with `top:var(--header-h); width:var(--rail-w); border-right:var(--hair)`,
   and the system sheet loads second — so the left rail was 246px with a ONE-pixel border on the
   thirty-three consoles that link payco.css alone and 246px with a TWO-pixel border on the
   forty-four that also link the system sheet. Measured at 1440: admin.html left rail border-right
   1px, settings.html 2px, and the right rail (border-left:var(--pc-hair) = var(--hair,2px)) drew
   2px on both — so the "mirrored exactly" pair differed by a pixel on half the site, in the one
   property a reader sees as the seam between the rail and the page.
   --pc-rail is the law's width token and it reads --rail-w with 246px as its fallback, so this
   line now says the same thing the system sheet's does whichever sheets a page happens to link. */
.pc-sidenav{position:fixed;left:0;top:var(--header-h,74px);bottom:0;width:var(--pc-rail);z-index:40;
  overflow-y:auto;contain:layout paint;
  background:
    linear-gradient(var(--white) 30%,rgba(255,255,255,0)) top/100% 26px no-repeat local,
    linear-gradient(rgba(255,255,255,0),var(--white) 70%) bottom/100% 26px no-repeat local,
    radial-gradient(farthest-side at 50% 0,rgba(10,22,40,.14),rgba(10,22,40,0)) top/100% 9px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%,rgba(10,22,40,.14),rgba(10,22,40,0)) bottom/100% 9px no-repeat scroll,
    var(--white);
  border-right:var(--hair,2px) solid var(--line);padding:20px 14px 26px}
.pc-sidenav h4{margin:2px 12px 12px;font-size:12px;text-transform:uppercase;letter-spacing:1.4px;
  font-weight:800;color:var(--ink-mute)}
.pc-sidenav a{display:flex;align-items:center;gap:13px;width:100%;font-size:14.5px;font-weight:700;
  letter-spacing:-.1px;color:var(--ink);text-decoration:none;border:1px solid transparent;border-radius:12px;
  padding:10px 12px;margin-bottom:2px;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s}
.pc-sidenav a:hover{background:var(--paper-warm,#f3f6fd);border-color:var(--line)}
.pc-sidenav a.on{background:linear-gradient(90deg,rgba(11,42,138,.1),rgba(26,77,214,.05));
  color:var(--blue-deep);font-weight:800;border-color:rgba(26,77,214,.18)}
.pc-sidenav .ico{width:38px;height:38px;border-radius:11px;background:#eef1fa;display:grid;place-items:center;
  flex:none;color:var(--blue-deep);transition:.2s}
.pc-sidenav .ico svg{width:20px;height:20px}
.pc-sidenav a.on .ico{background:var(--gradient);color:#fff;box-shadow:0 4px 12px rgba(26,77,214,.3)}
/* ── THE RIGHT GUTTER IS RESERVED ON <body>, ONCE, FOR EVERY PAGE ────────────────────────────
   WHY <body> AND NOT EACH PAGE'S OWN COLUMN. The rule below this one only reaches .wrap, main
   and .pc-wrap on a page that carries .pc-hasside. Of the 39 pages measured painting under the
   right rail, twenty-four do not match that: settings, bugs, comms, messages, restricted and
   member-directory hang their content off a full-bleed .pc-shell; directory, jobs, loadboard and
   marketplace off their own sticky bars; agent, merchant and referral off .pc-viewbar; invoices,
   invoice and pos off .pc-wrap.wide with no left rail at all, because they are merchant pages
   and the LEFT rail is staff furniture while the RIGHT one is not. <body> is the one element all
   of them have, and padding it narrows every normal-flow box inside at once.

   PADDING, NEVER MARGIN — the same reasoning css/payco-hud.css §8b wrote down before payco-hud.js
   stopped making this reservation. A margin on <body> leaves the gutter painted in <html>'s
   colour on every page that puts its background on <body>, which is most of them. Padding keeps
   the page's own background under the rail, where the rail is translucent and needs it.

   AND IT MOVES NOTHING THAT IS FIXED: not .pc-sidenav (fixed, left:0), not header.pc-header, not
   the HUD's own rails, not the cookie bar, not the assist bubble. That is the whole set of things
   that must not move, and it is exactly the set padding on <body> cannot touch.

   NO PRESENCE SELECTOR IS NEEDED, and the one that was here first was wrong. --pc-lane-right
   carries its own switch (see --pc-lane-on above): it is 0px whenever the rail takes no space, so
   this rule may be stated unconditionally and costs a page with no HUD exactly nothing.
   AT 1280px AND ABOVE ONLY, because css/payco-hud.css §9 releases both side rails below it — belt
   and braces with the switch, and the line a reader looks for. It was 1101px while the rails left
   at 1100px; both moved together (OPERATOR SPEC, "below 1280 both rails auto-collapse"). */
/* `screen and`, BECAUSE PAPER HAS NO RAILS (measured). The viewport law is about the VIEWPORT: two
   fixed rails, the gutters between them and the centre they leave. None of that exists in print —
   position:fixed chrome is not printed, and a sheet of paper has no 1280px breakpoint — but an
   unscoped width query is still evaluated against the page box, so the law's reservations were
   being applied to the printed document. The visible cost was that the paper depended on the
   screen that asked for it: tests/mobilew8.mjs prints the same document from a 375px phone and a
   1280px desk and compares them, and measured 973px of DOM from the phone against 950px from the
   desk, in the same 718x950 page box. Scoping both of the law's queries to `screen` puts the two
   back in agreement and leaves css/payco-print.css the only thing that decides what paper looks
   like, which is where that decision belongs. */
@media screen and (min-width:1280px){
  /* `html body`, NOT `body`, AND THE EXTRA ELEMENT IS LOAD-BEARING. Fifteen consoles reset their
     own box in a page <style> — `body{margin:0;padding:0;…}` on disputes, funnel, leads, orgtree,
     outbox-console, proposal, referrals, statements, storefront, w9, campaigns, and a literal
     16/24px on partner-onboard, forum-register, login, partner-activate. A page <style> loads
     AFTER this sheet, so at equal specificity it wins and the gutter silently becomes zero: a
     media query adds nothing to specificity and neither does source position when the count
     differs. MEASURED at 1440 with a bare `body` selector: 15 of the 71 rail-drawing pages asked
     for 528px and carried 0. One more element in the selector is (0,0,2) against their (0,0,1)
     and settles it everywhere at once — the same reason css/payco-hud.css writes `html body` for
     its own overrides. */
  /* NO TRANSITION ON THIS PADDING, AND THE REASON IS NOT TASTE (HUD v4, MERGE).
     It carried `transition:padding-right var(--pc-slow)` and that animated ONE HALF of a pair.
     The gutter is spent twice — as padding here, and as the negative margin that gives the site
     header and the full-bleed bands their width back — and only this half was animated, so for the
     length of the transition the two disagreed and the document scrolled sideways by the
     difference. Sampled every frame on analytics.html at 1440, cold load:
         t=  1ms   body padding-right   0   header 1440   body.scrollWidth 1440
         t=107ms   body padding-right   0   header 1936   body.scrollWidth 1936   <- +496px
         t=198ms   body padding-right 417   header 1519   body.scrollWidth 1519
         t=299ms   body padding-right 483   header 1453   body.scrollWidth 1453
         t=398ms   body padding-right 495   header 1441   body.scrollWidth 1441
         t=498ms   body padding-right 496   header 1440   body.scrollWidth 1440   settled
     — a horizontal scrollbar on every console page for about 275ms of every load, and tests/
     consolelayout.mjs caught it on twenty pages because it settles 250ms after the rail appears,
     which lands inside that window. The lane's own probes waited 700ms and a rAF pair and saw
     nothing, which is the whole lesson: a transient is invisible to a prober that waits it out.
     ANIMATING BOTH HALVES WOULD NOT FIX IT. The first frame is the token going from unset to
     496px, and the margin resolves it a recalc before the padding's transition starts — the
     107ms row above is exactly that, padding still 0 with the margin already applied. Removing
     the transition makes both sides land in the same style recalc, which is the only state in
     which they cannot disagree. A gutter that slides in on load was also a layout shift nobody
     asked for, on a codebase that measures CLS. */
  html body{padding-right:var(--pc-lane-right)}
  /* A page that opted into css/payco-hud.css §8's .pc-hud-inset already pads itself for the same
     rail. Doing it twice is 528px of gutter on a 1440 desk. */
  html body:has(> .pc-hud-inset),html body.pc-hud-inset{padding-right:0}

  /* ── AND THE LEFT BAND IS RESERVED THE SAME WAY, ON A PAGE THAT HAS NO LEFT RAIL ──────────
     THE HALF OF THE LAW THAT WAS NEVER WRITTEN, and it is the operator's "marketplace, the load
     board and the calendar push left". The right gutter above is spent on <body> UNCONDITIONALLY
     and is switched by the right rail's own occupancy; the left one is spent as a margin on the
     content container and is gated on `body.pc-hasside`, which payco-nav.js adds only on the
     twenty-nine pages its own TOOLS/PAGE_SECTION/RAIL_ONLY tables call consoles. So a page that
     draws the right rail and NOT the left one paid one gutter and not the other, and its column
     sat exactly half the lane left of the window's centre.
     MEASURED at 1440 with a full-grant Owner, across all 105 root pages: 65 draw the right rail,
     and on 34 of them the content column's centre line was 132px left of the window's own —
     marketplace, member-directory, forum, directory, statements, storefront, team, training, w9,
     book, campaigns, my-project and twenty-two more — with funnel.html at 255px. The column was
     not merely off-centre: with padding-left 0 it ran flush into the window edge with no air on
     that side and 264px of it on the other. tests/viewportlaw.mjs was green through all of it,
     because the page set it measures is derived from payco-nav.js's rail tables and NONE of the
     thirty-four is in them — the suite never looked.
     ONE SWITCH, TWO WIDTHS — AND THE FIRST DRAFT OF THIS BLOCK GOT THE SECOND HALF WRONG. It
     spent --pc-lane-right here, on the argument that one token spent twice cannot disagree with
     itself. The SWITCH is shared and that half was right. The WIDTH is not: --pc-lane-right is
     rail + gap because it has a 246px rail to clear, and on the left of a page that is not
     `.pc-hasside` there is no rail to clear — that is what the class means. So the band owed is
     the GAP, and reserving rail + gap instead did not centre those pages, it moved each of them
     246px to the RIGHT of the window's centre having found them 246px to the left of it.
     MEASURED at BASE 8cddcd5 with a full-grant Owner and the band taken from the rails' own
     boxes — skew = (host.x - bandL) - (bandR - host.right), 0 == centred — and identical at 1280,
     1440, 1920 and 2560:
         +246   TWENTY pages, and the brief that sent this lane named ten of them —
                forum · forum-section · forum-thread · marketplace · marketplace-sell ·
                loadboard · loadboard-post · jobs · jobs-post · restricted  (the ten)
                directory · directory-profile · team · training · statements · storefront ·
                w9 · book · campaigns · my-project                          (and these ten)
           +0   admin · crm · member-directory · comms · orgtree — the rail pages, which take the
                `.pc-hasside` arm below and were never part of this
                index · pricing — the marketing tier, which draws no right rail, so --pc-lane-on
                is 0 and this line pays nothing: padding 0px/0px on both trees at all four widths
     THE SECOND TEN ARE WHY THE BAND IS SPENT HERE AND NOT ON A LIST OF CONTAINERS. Seven of them
     (directory, statements, storefront, w9, book, campaigns, my-project) could not be measured at
     all until tests/viewportlaw.mjs's CENTRE_SEL was told what their wrappers are called — the
     suite reported no centre and its §7 assertions read `!a.centre || …`, which is a pass. A rule
     on <body> reaches a page whose container nobody has named; a rule on a container list does
     not, and the container list is the thing that goes stale.
     ONE RAIL, NOT A RANGE. loadboard and jobs first measured +512 and +524, which reads as a
     second copy of the band on a page <style>; it is not. tests/viewportlaw.mjs's container list
     did not name `.shell`, so the search fell through to `main` — which on those two pages is the
     RIGHT-HAND TRACK of a `grid-template-columns:250px 1fr` filter shell and is off-centre by the
     width of the filter rail, by design. Named correctly they are +246 like the rest and 0 after.
     SO THE BAND IS --pc-lane-gutter, WHICH IS THE GAP WITH --pc-lane-right'S OWN SWITCH ON IT. It
     still carries the occupancy switch, so it is still 0px on a page that draws no right rail and
     this rule still costs the whole marketing tier exactly nothing — measured, index.html and
     pricing.html: padding 0/0 before and after, at all four widths.
     PADDING ON <body>, FOR THE REASON THE RIGHT-HAND NOTE ABOVE GIVES AT LENGTH. Every container
     on the thirty-four is `margin:0 auto` under a max-width that does not bind at these widths
     (.wrap 1240px, .md-page min(1560px,96vw), .shell 1480px), so narrowing the box they live in
     centres every one of them with no rule that has to name them — and it reaches the pages whose
     top-level box the law's container list was never extended to.
     ONE MECHANISM, NOT TWO THAT AGREE. The first draft of this block excluded `body.pc-hasside`,
     because that class pays the same band as a margin on its container two hundred lines below,
     and paying it twice is 528px. Both are correct and land the column in the same place — which
     turned out to be the problem rather than the proof, because the class arrives AFTER first
     paint and the band therefore changed hands between two mechanisms mid-load. See the note on
     the container rule for what that measured. So the exclusion is gone: BOTH bands are spent
     here, on <body>, where the right one has always been spent.
     AND THE HANDOVER ON A CONSOLE IS UNCHANGED BY THIS, WHICH WAS MEASURED RATHER THAN REASONED
     ABOUT. The paragraph this replaces said the two arms resolved to the same 264px so nothing
     moved when the class landed; the worry about changing this line was that a smaller value here
     would open a jump that the old one closed. It does not, and the reason is the ORDER. Every
     frame of a cold load sampled from a rAF installed before any page script, empty storage so no
     `data-pc-skel-side` pre-empts anything, 1440, both trees:
         admin.html    BASE and BRANCH, identical:  0px/0px  ->  264px/0px (.pc-hasside)
                                                             ->  264px/264px (the HUD publishes)
         crm.html      BASE and BRANCH, identical:  the same three states
         marketplace   BASE     0px/0px -> 264px/264px       BRANCH  0px/0px -> 18px/264px
     payco-nav.js adds the class BEFORE payco-hud.js publishes a rail box, so on a console
     --pc-lane-on is still 0 at the moment of the handover and this line reads 0px under either
     spelling — the class has always taken padding-left from 0 to 264 in one step, and it still
     does. The value here is only ever visible on a page that never gets the class, which is
     exactly the page this rule is for. The pre-paint arm below is what removes the console's
     handover, and it is untouched.
     `data-pc-skel-side` IS LISTED WITH THE CLASS for the same reason it is listed with it
     everywhere else in this sheet: it is js/theme.js's pre-paint copy of exactly that fact, and
     while both are present they must set the same value or the frame they overlap in is a jump. */
  html body{padding-left:var(--pc-lane-gutter)}
  /* AND ON A PAGE WHOSE LEFT RAIL HAS LANDED, THE BAND IS THE LAW'S CONSTANT RATHER THAN THE
     RIGHT RAIL'S SWITCH. The two resolve to the same 264px whenever both rails are drawn, which
     is every console; they part company in exactly one state, and it is a state that exists:
     a page with the section rail up and NO right rail — the HUD refused, or a document that never
     mounted it — where --pc-lane-right is 0px and the column would run under a rail that is
     plainly there. --pc-lane-left is a constant and cannot do that. `data-pc-skel-side` is
     js/theme.js's pre-paint copy of the same class and is listed with it for the reason the
     container rule below gives: while both are present they must set the same value. */
  html body.pc-hasside,html[data-pc-skel-side="1"] body{padding-left:var(--pc-lane-left)}
  html body:has(> .pc-hud-inset),html body.pc-hud-inset{padding-left:0}

  /* ── AND THE FIVE PAGES THAT RESERVE FOR THE RIGHT RAIL THEMSELVES MUST STOP ───────────────
     orgtree, org-team, funnel, comms and staff-chat carry a page <style> block that makes its own
     reservation — `--pc-rail-r:var(--pc-hud-right,0px)` plus a `--pc-gutter` of its own — and
     tests/adminpagesd.mjs requires that block to be BYTE-IDENTICAL across all five, so it may not
     be edited on one of them. With the band above spent on <body> the block spends the right rail
     a SECOND time. MEASURED on funnel.html at 1440, which is the one of the five that draws no
     left rail and is therefore the only one this arm reaches: body box 264..1176 (912 wide), the
     block's own --pc-page-w resolving to min(1440, 912 - 246 - 2x37.4) = 591px, and .fn-wrap
     landing at 301.4..892.5 — 123px left of the window's centre, in a column two thirds the width
     it is entitled to.
     THE DOUBLED CLASS IS THE SAME IDIOM THE `.pc-hasside` ARM TWO HUNDRED LINES BELOW USES, AND
     FOR THE SAME REASON: the page block scores (0,2,2) and loads after this sheet, so at equal
     specificity source order gives it the page. Naming the class twice is (0,4,2) here and
     settles it without editing a block that has to stay identical in five places.
     THE PAGE BLOCK IS DEAD ON THESE PAGES, NOT CONTRADICTED — it still owns them below 1280,
     where this query does not run and where there is no rail to reserve for either. */
  html body .pc-hud-page.pc-hud-page{
    /* THE MARGIN IS A FLOOR, NOT A GUTTER, AND IT IS THE max() THE LAW ALREADY USES ON THE RIGHT
       (payco.css, `html body.pc-hasside .pc-hud-page`). The band above is spent as padding on
       <body>, so on a page that draws the right rail this pays out 0 and the container simply
       fills the box it has been given. On a page where the rail is REFUSED — the nav's no-grant
       sweep, or a harness answering /api/whoami 403 — --pc-lane-right is 0px, <body> pays no
       padding, and without this the container would run flush into both window edges: MEASURED
       under a 403 fixture at 1920, orgtree.html and funnel.html 0px of margin on each side against
       the 33.3/240px their own block gave them, which is tests/adminpagesd.mjs's "not a box jammed
       against one side" and it is right to call it that. Then, and only then, max() pays the gap. */
    margin-left:max(0px,calc(var(--pc-gap) - var(--pc-lane-right)));
    margin-right:max(0px,calc(var(--pc-gap) - var(--pc-lane-right)));
    max-width:none;width:auto;
    padding-left:0;padding-right:0}
  /* AND THE LEFT FLOOR IS ALREADY PAID ON A PAGE WHOSE RAIL HAS LANDED. <body>'s padding-left is
     --pc-lane-left there — rail PLUS gap, a constant that is never less than the gap — so the
     max() above would be a SECOND gap stacked inside the first. MEASURED under
     tests/adminpagesd.mjs's own fixture at 1280 with the rail up: 32px from the rail's edge to
     the box against 16px from the box to the window, on all four of orgtree, org-team, funnel and
     comms, and 48 against 24 at 1920 — which is that suite's "SYMMETRIC" assertion, and it is
     right to call it. */
  html body.pc-hasside .pc-hud-page.pc-hud-page,
  html[data-pc-skel-side="1"] body .pc-hud-page.pc-hud-page{margin-left:0}

  /* ── THE HEADER IS NOT CONTENT, AND IT IS NOT FIXED EITHER ─────────────────────────────────
     css/payco-hud.css §8b's note says a gutter on <body> "moves nothing that is position:fixed —
     not the rails, not the header". Two of those three are true. `header.pc-header` is
     position:STICKY, which is in normal flow, so the gutter took 528px off the site header on all
     64 pages that wear it: MEASURED at 1440, header [0..912] against a 1440 client width, with
     the page's own background painted in the 528px beyond it. In dark theme that is a bright strip
     down the right of every console.
     RESTORED WHOLE, WITH NO COMPENSATING PADDING, BECAUSE THE HEADER IS NEVER UNDER THE RAIL.
     payco-hud.js pins the right dock at `top` = the header's measured bottom: measured at 1440,
     rail [74..767] and header [0..74] at viewport height 1000, rail [74..826] at 1400 — the two
     boxes share an edge and never an area. So the header's right-hand controls (the identity
     block, Sign out) belong at the window edge where they have always been, and pulling them into
     the lane would move furniture the reader navigates by to make room for a panel that cannot
     reach them. The negative margin is exactly the gutter, so the two cancel at every width and
     through the rail's collapse.
     THIS IS TRUE OF `header.pc-header` AND OF NOTHING ELSE, and that was measured rather than
     assumed. The rail's top is whatever payco-hud.js measured the page chrome to be: across the
     71 rail-drawing pages at 1440 it is 74px on the 61 that wear this header and 0px on the ten
     that do not (the auth doors, the two advisor landings, programs.html's marketing bar). On
     those ten the rail starts at the top of the window and a full-width bar puts its right-hand
     control straight under the taskbar — measured on programs.html, `a.btn.btn-g` "Register" at
     [1307..1400 , y 15..59] against a rail at x1312, y0..740. So the marketing tier's copy of
     this rule (payco-marketing.css) uses the border form below instead, and this one names the
     one header it is safe for. */
  html body header.pc-header{margin-right:calc(-1 * var(--pc-lane-right))}
  /* AND THE SAME RESTORATION ON THE LEFT, UNDER THE SAME GATE AS THE PADDING THAT CAUSES IT.
     The header is sticky, so it is in normal flow and the new left band takes the same width off
     it that the right one did — the identical defect this rule was written for, one side over.
     The gate is repeated rather than combined so the two halves can be read as a pair: whichever
     side the padding is spent on, the header gets it back.
     AND THE NEGATIVE IS THE BAND, NOT A NUMBER THAT MATCHED IT ON A GOOD DAY. It tracks
     --pc-lane-gutter because that is what the padding above is; when the two were allowed to be
     written as two expressions that agreed, they stopped agreeing — see the note at :691 for the
     275ms scrollbar the last such pair produced. Left at --pc-lane-right this would have been
     246px wider than the padding it undoes: MEASURED at 1440 with the padding corrected and this
     line not, header x = -246 against a client width of 1440, i.e. the identity block and the
     wordmark clipped off the left of every non-console page on the site. */
  html body header.pc-header{margin-left:calc(-1 * var(--pc-lane-gutter))}
  html body.pc-hasside header.pc-header,html[data-pc-skel-side="1"] body header.pc-header{
    margin-left:calc(-1 * var(--pc-lane-left))}

  /* ── THE BANDS BELOW THE HEADER: BACKGROUND TO THE EDGE, CONTENT IN THE LANE ───────────────
     A footer, a persona view-bar and the browse pages' sticky filter bars are full-bleed bands
     whose whole job is to reach both window edges — cut at 912px they read as a broken page. But
     unlike the header they DO occupy the rail's vertical span, so their content must stay in the
     lane or it goes back under the taskbar. Both at once, with a TRANSPARENT BORDER rather than
     padding: background-clip defaults to border-box, so the band paints under its own border out
     to the window edge, while box-sizing:border-box takes the border off the content box — and
     the band's OWN padding, which is 40px on a header and 24/30/32 on these, survives untouched
     inside it. Writing this as `padding-right` instead would have had to REPLACE that padding
     with a number this rule cannot read, and the bands differ. */
  html body > footer,html body > .pc-viewbar,html body > .dir-bar,
  html body > .jbbar,html body > .lbbar,html body > .mkbar{
    margin-right:calc(-1 * var(--pc-lane-right));
    border-right:var(--pc-lane-right) solid transparent;
  }
  /* THE SAME BANDS, THE SAME TRANSPARENT BORDER, ON THE LEFT. `.mkbar` and `.lbbar` are the
     marketplace's and the load board's own sticky filter bars, so two of the three pages named in
     the operator's item 1 are bands as well as columns: without this the bar paints to the window
     edge while the column under it starts 264px in, which is a worse seam than the one being
     closed. Border rather than padding for the reason the note above gives — these bands carry
     their own 24/30/32px padding and this rule cannot read it.
     THE WIDTH IS --pc-lane-gutter FOR THE REASON THE HEADER PAIR ABOVE GIVES: it is the band
     <body> actually spends on this side, and a restoration written against any other expression
     is a second number that has to be kept in step by hand. `.mkbar` and `.lbbar` sit on two of
     the pages this lane measured at +246, so they are the bars most likely to show it. */
  html body > footer,html body > .pc-viewbar,html body > .dir-bar,
  html body > .jbbar,html body > .lbbar,html body > .mkbar{
    margin-left:calc(-1 * var(--pc-lane-gutter));
    border-left:var(--pc-lane-gutter) solid transparent;
  }
  html body.pc-hasside > footer,html body.pc-hasside > .pc-viewbar,html body.pc-hasside > .dir-bar,
  html body.pc-hasside > .jbbar,html body.pc-hasside > .lbbar,html body.pc-hasside > .mkbar,
  html[data-pc-skel-side="1"] body > footer,html[data-pc-skel-side="1"] body > .pc-viewbar,
  html[data-pc-skel-side="1"] body > .dir-bar,html[data-pc-skel-side="1"] body > .jbbar,
  html[data-pc-skel-side="1"] body > .lbbar,html[data-pc-skel-side="1"] body > .mkbar{
    margin-left:calc(-1 * var(--pc-lane-left));
    border-left:var(--pc-lane-left) solid transparent;
  }

  /* ── THE TOGGLE ANIMATES. THE LOAD STILL DOES NOT. ─────────────────────────────────── (F2, W3D)
     THE NOTE AT :681 REMOVED `transition:padding-right` AND ITS MEASUREMENT STANDS. The gutter is
     spent TWICE — as padding on <body>, and as the negative margin (and transparent border) that
     gives the header and the full-bleed bands their width back — and animating one half made the
     two disagree for the length of the transition: a horizontal scrollbar for ~275ms of every
     cold load, caught by tests/consolelayout.mjs on twenty pages. That note also says animating
     BOTH halves would not fix it, and for the event it was measuring that is correct: at cold
     load the first frame is the token going from UNSET to a width, the margin resolves it a
     recalc before the padding's transition starts, and no amount of symmetry closes a gap that
     opens before either transition begins.
     THE TOGGLE IS NOT THAT EVENT, AND THAT IS THE WHOLE OF WHY THIS IS SAFE. On a toggle both
     tokens are already defined and both change in the SAME style recalc, from one number to
     another — 246+gap to 82+gap. There is no unset frame for the margin to win. So the condition
     is not "never animate", it is the two the cold-load failure actually names:
       (1) BOTH HALVES OR NEITHER — every property that spends --pc-lane-right, --pc-lane-gutter
           or --pc-lane-left carries the identical duration and easing, so they cannot drift; and
       (2) NOT AT LOAD — the transition is ARMED, not declared. Nothing here transitions until
           <html> carries data-pc-lane-armed="1", which payco-hud.js sets on mount AFTER its first
           publish() (see the interface request in this lane's report — that file is Lane T's).
           Until it does, this block is inert and the behaviour is byte-for-byte what :681 left.
     IT FAILS CLOSED. An unarmed page, a page whose dock never mounts, and a page whose dock is
     refused by the nav's no-grant sweep all keep the untransitioned band. The attribute is the
     only thing that turns motion on, and it is set by the one file that knows the rail is real.
     REDUCED MOTION TURNS IT BACK OFF IMMEDIATELY BELOW, rather than being spelled as a negated
     media query here — the sheet states its transitions and then withdraws them under
     `prefers-reduced-motion:reduce` in five places already, and this follows that shape. */
  html[data-pc-lane-armed="1"] body,
  html[data-pc-lane-armed="1"] body header.pc-header,
  html[data-pc-lane-armed="1"] body > footer,
  html[data-pc-lane-armed="1"] body > .pc-viewbar,
  html[data-pc-lane-armed="1"] body > .dir-bar,
  html[data-pc-lane-armed="1"] body > .jbbar,
  html[data-pc-lane-armed="1"] body > .lbbar,
  html[data-pc-lane-armed="1"] body > .mkbar{
    transition:padding-right var(--pc-slow,.34s) var(--pc-ease,ease),
               padding-left var(--pc-slow,.34s) var(--pc-ease,ease),
               margin-right var(--pc-slow,.34s) var(--pc-ease,ease),
               margin-left var(--pc-slow,.34s) var(--pc-ease,ease),
               border-right-width var(--pc-slow,.34s) var(--pc-ease,ease),
               border-left-width var(--pc-slow,.34s) var(--pc-ease,ease);
  }
  @media(prefers-reduced-motion:reduce){
    html[data-pc-lane-armed="1"] body,
    html[data-pc-lane-armed="1"] body header.pc-header,
    html[data-pc-lane-armed="1"] body > footer,
    html[data-pc-lane-armed="1"] body > .pc-viewbar,
    html[data-pc-lane-armed="1"] body > .dir-bar,
    html[data-pc-lane-armed="1"] body > .jbbar,
    html[data-pc-lane-armed="1"] body > .lbbar,
    html[data-pc-lane-armed="1"] body > .mkbar{transition:none}
  }
}

/* ── THE CONTENT COLUMN, CENTRED BETWEEN THE RAILS ───────────────────────────────────────────
   The left gutter is the rail plus the standard air; the right gutter is already off this box,
   spent as padding on <body> above, so this side asks for nothing. Whatever is left over once the
   column has taken its cap is SPLIT, which is what makes the column centred in the band rather
   than parked against the left rail: at 1920 the cap binds and the slack is real, at 1440 it does
   not and the column simply fills the lane.
   100vw AND NOT 100%: the slack is a property of the WINDOW, and 100% here would resolve against
   the already-padded body box and subtract the right gutter twice. 100vw counts the scrollbar,
   which overstates the slack by ~15px — half of that lands in each margin, so the error is 7px of
   air and never an overflow. This is the arithmetic admin.html:304 has been using since HUD v3;
   it is hoisted here so that every console shares one copy of it. */
/* THE SAME RULE, ONE FRAME EARLIER (OPERATOR SPEC, HUD v4.1 §4). `body.pc-hasside` is added by
   payco-nav.js when the section rail lands — after first paint — so the column is drawn full width
   and then jumps 274px inward. js/theme.js stamps `data-pc-skel-side` on <html> before the first
   paint from what this page's chrome settled on last time, and this selector applies the identical
   declarations from it. The two are listed together rather than duplicated so they can never
   disagree: while both are present they set the same values, and when the class arrives the
   attribute has already done its job. */
/* `html body.pc-hasside`, NOT `body.pc-hasside`, AND THE EXTRA `html` IS THE WHOLE POINT.
   css/payco-system.css:857 states this same rule with the numbers typed into it —
   `margin-left:274px; margin-right:28px; max-width:min(1240px,calc(100vw - 302px))` — at (0,2,0),
   and it is linked AFTER payco.css on every page that links it at all. So on the forty-four
   consoles that carry the system sheet the law below was silently losing to a 274/28 hardcode on
   source order, while the thirty-three that do not carry it obeyed the law: ONE selector, two
   different geometries, decided by a <link> tag. (The skeleton arm never had this problem —
   `html[data-pc-skel-side="1"] body .wrap` is (0,2,2) and already outranked it, which is why the
   first painted frame and the settled frame disagreed on those pages.) `html body.pc-hasside`
   is (0,3,0) and settles it everywhere at once, without editing a sheet this lane does not own.
   THE SYSTEM SHEET'S COPY IS NOW DEAD RATHER THAN WRONG, and it should be deleted by whoever owns
   it — see docs/A-NOTES.md, "seams handed on".

   MAX-WIDTH IS GONE, NOT RETUNED. The centre is what the two rails and the two gutters leave, and
   nothing else: a cap would put slack back on the sides, and slack on the sides is the dead strip
   this whole pass exists to remove. --pc-lane-cap and the --pc-lane-slack split that shared it out
   are deleted with it, along with the four page-local --nv-cap opinions that were competing with
   them (admin, operations, and the .pc-wrap.wide pages).

   THE RIGHT MARGIN IS A FLOOR, NOT A GUTTER, and it is written as max() for the same reason
   --pc-lane-right is written as min(): so one declaration covers both worlds without a selector.
   The right gutter is spent on <body> as padding — rail plus gap — so this side normally asks for
   nothing and max() resolves to 0. On a console whose right rail is not drawn at all (refused by
   the nav's no-grant sweep, or a page that never mounted the HUD) that padding is 0px, and without
   this the column would run flush to the window edge with no air on one side and 246+gap on the
   other. Then, and only then, max() pays out the gap. */

/* ══ THE WHOLE OF THE LAW IS A SCREEN CONCERN ═════════════════════════════════════════════════
   Everything from here to the close of this block — the centre column, the panel shape, the
   inter-panel gap and the two width queries nested inside — is wrapped in `@media screen`.
   PAPER HAS NO RAILS, and it has no viewport either: --pc-gap is clamp(16px,1.25vw,24px), so an
   unscoped rule makes the printed document a function of the window that asked for it. MEASURED
   by tests/mobilew8.mjs, which prints the same page from a 375px phone and a 1280px desk and
   compares the two: 973px of DOM from the phone against 950px from the desk, and a description
   cell 186.8px wide against 182.6px, in the same 718x950 page box. Scoping the two width queries
   alone closed the cell difference and left 2px of DOM; the vw term reaches the unconditional
   rules too, so the wrapper goes round all of them. css/payco-print.css is then the only thing
   that decides what paper looks like, which is where that decision belongs. */
@media screen {
/* THE CENTRE IS EVERY CONSOLE'S OWN CONTAINER, NAMED, and the list is derived rather than
   guessed: every root page that draws the left rail was opened and its top-level content box read
   off the document. Twenty-one of the twenty-nine put `id="pc-content"` on it and carry one of
   `.wrap`, `main`, `.pc-wrap`, `.pc-main`, `.m360`, `.rc-shell` or `.ob-wrap`; the other eight
   name their own — `.ib-wrap` (intake), `.lb-wrap` (leads), `.px-wrap` (proposal), `.op-shell`
   (ops-projects), `.dp-wrap` (disputes). `.cc-head` is staff.html's page title, which is a
   SIBLING of its `.wrap` rather than a child, so it is a second top-level box on that one page
   and is named here for the same reason. Those five were the pages the old three-class list never
   reached, so each had built its OWN rail reservation in its own <style> — intake's is
   `padding-left:calc(max(var(--pc-rail),var(--pc-hud-left)) + var(--pc-gut))` — and each was a
   separate opinion about where the rails are, maintained by hand, on five pages that are
   byte-compared against each other by tests/adminpagesb.mjs.
   `#pc-content` IS DELIBERATELY NOT IN THIS LIST. payco-nav.js:2117 injects an EMPTY focusable
   div with that id straight after the header on any page that lacks one — the skip-link target —
   so on exactly the eight pages that need naming, `#pc-content` is a zero-height div that is not
   the content box. Selecting it would law the wrong element and leave the right one free.
   PADDING AND MAX-WIDTH ARE RESET HERE RATHER THAN DELETED FROM TWENTY-NINE <style> BLOCKS.
   `html body.pc-hasside .ib-wrap` is (0,3,1) and every page-local container rule on this site is
   (0,1,0), so the law wins in the cascade without this lane reaching into a page's own styling
   for a number that is about to stop existing. The page rules are dead, not contradicted. */
html[data-pc-skel-side="1"] body .wrap,html[data-pc-skel-side="1"] body main,
html[data-pc-skel-side="1"] body .pc-wrap,html[data-pc-skel-side="1"] body .pc-main,
html[data-pc-skel-side="1"] body .m360,html[data-pc-skel-side="1"] body .rc-shell,
html[data-pc-skel-side="1"] body .ob-wrap,html[data-pc-skel-side="1"] body .ib-wrap,
html[data-pc-skel-side="1"] body .lb-wrap,html[data-pc-skel-side="1"] body .px-wrap,
html[data-pc-skel-side="1"] body .op-shell,html[data-pc-skel-side="1"] body .dp-wrap,
html[data-pc-skel-side="1"] body .cc-head,
html body.pc-hasside .wrap,html body.pc-hasside main,html body.pc-hasside .pc-wrap,
html body.pc-hasside .pc-main,html body.pc-hasside .m360,html body.pc-hasside .rc-shell,
html body.pc-hasside .ob-wrap,html body.pc-hasside .ib-wrap,html body.pc-hasside .lb-wrap,
html body.pc-hasside .px-wrap,html body.pc-hasside .op-shell,html body.pc-hasside .dp-wrap,
html body.pc-hasside .cc-head,
/* `.pc-hud-page.pc-hud-page`, AND THE REPEAT IS THE POINT — the same idiom css/payco-hud.css uses
   for `.pc-hud.pc-hud[data-hud="right"]`. Four consoles (orgtree, org-team, funnel, comms) carry a
   HUD stylesheet block that tests/adminpagesd.mjs requires to be BYTE-IDENTICAL across all five of
   its pages, so it may not be edited on some of them and not others — and inside it is
   `body.pc-hasside main.pc-hud-page{…}`, which scores (0,2,2), exactly what this rule scores, in a
   page <style> that loads after this sheet. At equal specificity source order decides and the page
   won: measured on comms.html at 1440, centre 283.44..1138.55 against the law's 264..1176.
   Naming the class twice is (0,3,2) and settles it without touching a block that has to stay
   identical in five places. The block still owns these pages when NO rail is drawn, which is what
   it is for and what this rule is gated against. */
html body.pc-hasside .pc-hud-page.pc-hud-page{
  /* ── THE BAND IS NOT PAID HERE ANY MORE, AND THAT IS ITEM 3, NOT ITEM 1 (WAVE 2 LANE I) ─────
     This said `margin-left:var(--pc-lane-left)`, and the band above says the same number as
     `padding-left` on <body>. Both are correct and the column lands in the same place either
     way — which is the problem, because `body.pc-hasside` arrives AFTER first paint and the band
     therefore CHANGED HANDS between two mechanisms that agree on the answer. Nothing the reader
     can see moves; the browser's layout-instability observer disagrees, because the box that
     hands it over is a real box. MEASURED at 1440 in chromium on a tab whose desk record was
     already warm, first-paint rect against settled rect: calendar.html, settings.html and
     messages.html each reported `div.pc-shell dx-264` at t=124ms and CLS 0.1900 / 0.0717 / 0.1407
     with dx 0 and dw 0 on the content column itself — a shift of the transparent full-bleed
     wrapper those six consoles hang everything off, and only of that.
     SO THERE IS ONE MECHANISM NOW, NOT TWO THAT AGREE. Both bands are spent as padding on <body>,
     where the right one has always been spent, and nothing about the reservation changes when the
     class lands. The margins below are the gap FLOOR only, which is the max() the right-hand side
     has always used: they pay out on a console with no right rail at all, where <body> pays no
     padding, and resolve to 0 everywhere else. */
  /* THE LEFT MARGIN IS ZERO, NOT THE max() ITS TWIN ON THE RIGHT IS, AND THE ASYMMETRY IS THE
     POINT. This rule is gated on `body.pc-hasside`, and on such a page <body>'s padding-left is
     --pc-lane-left — rail plus gap, a constant that can never be smaller than the gap. There is
     nothing left for a floor to pay for, and paying it anyway is a second gap inside the first.
     The right side keeps its max() because <body>'s padding-RIGHT is --pc-lane-right, which is
     0px on a console whose right rail is not drawn, and that is exactly when the floor is due. */
  margin-left:0;
  margin-right:max(0px,calc(var(--pc-gap) - var(--pc-lane-right)));
  max-width:none;width:auto;
  /* AND NOTHING STACKS ON TOP OF THE GUTTER. Every console wrapper on the site carries its own
     `padding:0 clamp(18px,2.5vw,44px)` — written when .wrap was a centred marketing column and
     the padding WAS the gutter. With the law reserving --pc-gap on both sides that padding is a
     second gutter inside the first, so a panel's visible edge sat 18-44px inside the centre and
     the "identical gaps" the law asks for were identical only to a probe that measured the box
     rather than what is drawn in it. The law's words are "no page-local padding stacking on top"
     and "panels: full center width"; this is that, stated once rather than deleted from twenty-
     nine <style> blocks that would drift back. The VERTICAL padding is untouched — it is the
     page's own rhythm and the law says nothing about it. */
  padding-left:0;padding-right:0}
/* ── AND THE CAP COMES OFF WITHOUT WAITING FOR THE CLASS EITHER (OPERATOR SPEC, WAVE 2 ITEM 3) ─
   THE LAST FIRST-PAINT DISAGREEMENT, and it is a WIDTH rather than a position. The rule above
   deletes the container's own cap and its own horizontal padding — "MAX-WIDTH IS GONE, NOT
   RETUNED" — and it does so through `body.pc-hasside`, which lands after the document is parsed.
   So a console whose container carries a cap paints once inside it and once without it. MEASURED
   at 1920 in chromium with both bands already reserved before first paint: calendar.html
   main#pc-content.pc-main at x340 w1240 on the first painted frame against x270 w1380 settled —
   the 1240 being `.pc-main{max-width:var(--maxw)}` twenty lines below, released a moment later.
   THE RESETS ONLY. The MARGINS stay above, gated, because they are the band and the band is paid
   on <body> now; these three declarations are the page's own opinion being switched off, and the
   answer to "should this column carry a cap" does not depend on whether a rail happens to have
   landed yet. Applying them a frame earlier is the whole change.
   THE SAME CONSOLE-ONLY LIST AS THE SUB-1100 BLOCK BELOW, AND FOR THE SAME REASON: `.wrap` and
   `main` are the marketing tier's containers too, and taking the cap off index.html's column is
   not this lane's to do. Consoles whose container is one of those two keep the class gate. */
html body .pc-main,html body .m360,html body .rc-shell,html body .ob-wrap,
html body .ib-wrap,html body .lb-wrap,html body .px-wrap,html body .op-shell,
html body .dp-wrap,html body .cc-head{
  max-width:none;width:auto;padding-left:0;padding-right:0}

/* ---- BELOW 1280 BOTH RAILS AUTO-COLLAPSE, SO THE CENTRE STAYS DOMINANT (OPERATOR SPEC) -------
   The line moved from 1100px, where it was drawn when the right rail did not exist. Two rails and
   two gutters cost 492 + 2 x 16 = 524px, and at 1279 that leaves a 755px column carrying a console
   built for tables — so below the line the rails stand down, the centre takes the window less one
   gap a side, and the left rail's contents stay reachable through the header's burger exactly as
   they have since 1100px was the line. css/payco-hud.css §9 releases both rails at the same width
   and payco-hud.js's PaycoHUD.available() answers it, so there is one number in three places and
   a suite that asserts they agree.
   WHAT IT COSTS, and it is a real cost rather than a free move: between 1100px and 1279px the
   HUD's tool rail used to be drawn and is now released, so its widgets are unreachable on a window
   that width until it is widened. That is flagged for the operator in docs/A-NOTES.md rather than
   decided here. */
@media screen and (max-width:1279px){.pc-sidenav{display:none}
  html[data-pc-skel-side="1"] body .wrap,html[data-pc-skel-side="1"] body main,
  html[data-pc-skel-side="1"] body .pc-wrap,html[data-pc-skel-side="1"] body .pc-main,
  html[data-pc-skel-side="1"] body .m360,html[data-pc-skel-side="1"] body .rc-shell,
  html[data-pc-skel-side="1"] body .ob-wrap,html[data-pc-skel-side="1"] body .ib-wrap,
  html[data-pc-skel-side="1"] body .lb-wrap,html[data-pc-skel-side="1"] body .px-wrap,
  html[data-pc-skel-side="1"] body .op-shell,html[data-pc-skel-side="1"] body .dp-wrap,
  html[data-pc-skel-side="1"] body .cc-head,
  html body.pc-hasside .wrap,html body.pc-hasside main,html body.pc-hasside .pc-wrap,
  html body.pc-hasside .pc-main,html body.pc-hasside .m360,html body.pc-hasside .rc-shell,
  html body.pc-hasside .ob-wrap,html body.pc-hasside .ib-wrap,html body.pc-hasside .lb-wrap,
  html body.pc-hasside .px-wrap,html body.pc-hasside .op-shell,html body.pc-hasside .dp-wrap,
  html body.pc-hasside .cc-head,
  /* AND `.pc-hud-page` IS RELEASED HERE TOO, WITH THE SAME DOUBLED CLASS IT IS RESERVED WITH.
     The reservation above is unconditional, so without this line a phone kept a 262px gutter for a
     rail that stands down at 1279 — `body.pc-hasside` survives the breakpoint, only the rail does
     not. MEASURED at 375px before this line: org-team.html body.scrollWidth 411 of 375, comms.html
     and staff-chat.html 388 of 375, against a baseline of zero overflowing pages in an 89-page
     sweep. A reservation has to be released wherever the thing it reserves for is. */
  html body.pc-hasside .pc-hud-page.pc-hud-page{
    /* A CAP AND AUTO MARGINS, NEVER A MARGIN ON A width:100% BOX. Several console containers are
       `width:100%` in their own <style>, and a margin spent outside a box that already fills its
       container is an overflow rather than a gutter. MEASURED at 375px with the margin form:
       orgtree.html and funnel.html, body.scrollWidth 391 of 375, against a baseline of zero
       overflowing pages in an 89-page sweep. */
    max-width:calc(100% - 2 * var(--pc-gap));margin-left:auto;margin-right:auto;
    padding-left:0;padding-right:0}}

/* ---- BELOW 1100 THE RELEASE DOES NOT WAIT FOR A CLASS (OPERATOR SPEC, WAVE 2 ITEM 4) --------
   THE SECOND SHIFT, AND IT IS THE ONLY ONE LEFT ON A PHONE. The release above is stated for
   `body.pc-hasside`, which payco-nav.js adds after the document is parsed — so a console below
   1100 paints its column edge to edge and then insets it by one gap a side when the class lands.
   MEASURED at 375, 768 and 1024 in chromium with every other reservation already made:
   calendar.html and admin.html first-paint x0 w375 against a settled x16 w343, dx 16 and dw -32,
   at all three widths. It is 16px rather than 264, and it is still a second shift, which is what
   item 4 says there may not be.
   THE SAME DECLARATIONS, WITH NO GATE, ON THE CLASSES THAT ARE CONSOLES BY NAME. Every selector
   below is a container that exists on console pages and nowhere else — checked, not assumed:
   .pc-main on eight, .m360 .rc-shell .ob-wrap .ib-wrap .lb-wrap .px-wrap .op-shell .cc-head on
   one apiece, .dp-wrap on two, .pc-hud-page on five, and not one of them on a marketing page.
   `.wrap` and `main` are DELIBERATELY ABSENT: those two are the whole marketing tier's containers
   as well as several consoles', and `padding-left:0` on index.html's column at 375 takes the
   page's own gutter off. They keep the class gate above, and admin.html — a `.wrap` console that
   is not this lane's page — keeps its 16px step until whoever owns it names its container.
   AND ONLY BELOW 1100, NOT BELOW 1280. Between the two the right rail is drawn as its strip and
   the band below reserves for it on <body>; an ungated container rule there would be a second
   gap inside the first. This block stops where that one starts. */
@media screen and (max-width:1099px){
  html body .pc-main,html body .m360,html body .rc-shell,html body .ob-wrap,
  html body .ib-wrap,html body .lb-wrap,html body .px-wrap,html body .op-shell,
  html body .dp-wrap,html body .cc-head,html body .pc-hud-page.pc-hud-page{
    max-width:calc(100% - 2 * var(--pc-gap));margin-left:auto;margin-right:auto;
    padding-left:0;padding-right:0}}

/* ---- 1100-1279: THE RIGHT RAIL COLLAPSES TO ITS STRIP RATHER THAN LEAVING (OPERATOR RULING) --
   THE COST THE LAW FLAGGED, AND THE OPERATOR'S ANSWER TO IT. Moving the collapse line from 1100
   to 1280 made the centre dominant on a half-screen window and took the HUD's TOOL rail with it,
   so between 1100px and 1279px its widgets were unreachable until the window was widened.
   docs/A-NOTES.md §1 flagged that as a real cost rather than deciding it; the ruling is that a
   half-screen window is a normal way to work and losing the tool rail there is a regression.
   So in that band the right rail is DRAWN AS ITS 82px STRIP and the lane RESERVES the strip. The
   left rail still stands down — its contents are reachable through the header's burger, which is
   what has been true since 1100px was the line — so the band costs 82px + one gap rather than the
   524px the full pair costs, and the centre stays dominant at 1180px against the 755px it would
   have had with both rails drawn.
   THE RESERVATION IS A CAP AND AUTO MARGINS, NOT A MARGIN, for the reason the release above
   records: several console containers are `width:100%` in their own <style>, and a margin spent
   outside a box that already fills its container is an overflow rather than a gutter. The left
   margin is the gap; max-width takes the rest less the strip; margin-right:auto absorbs
   gap + strip on the right, which is exactly what the fixed strip sits in.
   THREE FILES STATE THIS BAND and tests/viewportlaw.mjs §0 asserts they agree: here, in
   css/payco-hud.css §9, and in payco-hud.js's available(). */
@media screen and (min-width:1100px) and (max-width:1279px){
  html[data-pc-skel-side="1"] body .wrap,html[data-pc-skel-side="1"] body main,
  html[data-pc-skel-side="1"] body .pc-wrap,html[data-pc-skel-side="1"] body .pc-main,
  html[data-pc-skel-side="1"] body .m360,html[data-pc-skel-side="1"] body .rc-shell,
  html[data-pc-skel-side="1"] body .ob-wrap,html[data-pc-skel-side="1"] body .ib-wrap,
  html[data-pc-skel-side="1"] body .lb-wrap,html[data-pc-skel-side="1"] body .px-wrap,
  html[data-pc-skel-side="1"] body .op-shell,html[data-pc-skel-side="1"] body .dp-wrap,
  html[data-pc-skel-side="1"] body .cc-head,
  html body.pc-hasside .wrap,html body.pc-hasside main,html body.pc-hasside .pc-wrap,
  html body.pc-hasside .pc-main,html body.pc-hasside .m360,html body.pc-hasside .rc-shell,
  html body.pc-hasside .ob-wrap,html body.pc-hasside .ib-wrap,html body.pc-hasside .lb-wrap,
  html body.pc-hasside .px-wrap,html body.pc-hasside .op-shell,html body.pc-hasside .dp-wrap,
  html body.pc-hasside .cc-head,
  html body.pc-hasside .pc-hud-page.pc-hud-page{
    max-width:calc(100% - 2 * var(--pc-gap) - var(--pc-rail-strip));
    margin-left:var(--pc-gap);margin-right:auto;
    padding-left:0;padding-right:0}
  /* ---- AND THE SAME BAND, ON A PAGE WITH NO LEFT RAIL TO BE `.pc-hasside` FOR ----------------
     The rule above reaches a page through `body.pc-hasside` and a container the law happens to
     name; the thirty-four pages the note at the top of this query is about are neither. In this
     band the right rail is DRAWN as its 82px strip, and they reserved nothing for it.
     MEASURED at 1100 with a full-grant Owner, strip at x=1018: marketplace.html, loadboard.html
     and member-directory.html each painted their content box out to x=1100 — 82px of every one of
     them under the taskbar, which is the defect the reservation above exists to prevent, on the
     pages the reservation above cannot see.
     THE ASYMMETRY IS THE BAND'S OWN AND IS COPIED RATHER THAN INVENTED: one gap on the left, one
     gap plus the strip on the right, which is exactly what `margin-left:var(--pc-gap)` and
     `margin-right:auto` resolve to above. So a page reached by this rule and a page reached by
     that one land on the same two insets — measured, funnel.html and admin.html both 41px left of
     the window centre in this band, which is the band as designed and not a defect. */
  html:not([data-pc-skel-side="1"]) body:not(.pc-hasside){
    padding-left:var(--pc-gap);
    padding-right:calc(var(--pc-gap) + var(--pc-rail-strip))}
  /* AND THE HEADER GETS THIS BAND'S PADDING BACK TOO, FOR THE REASON THE PAIR ABOVE GIVES.
     (WAVE 2 INTEGRATION.) The two header restorations further up are keyed to --pc-lane-left and
     --pc-lane-right, and in THIS band both of those resolve to 0px — the reservation here is paid
     in --pc-gap and --pc-rail-strip instead. So the header alone kept the inset: MEASURED at 1100
     on the merged tree, <body> padding 16px / 98px and the wordmark at x=49 against index.html's
     x=33, on 41 pages at once (tests/headerparity.mjs, 2850/0 -> 2809/41, every one of them
     "@1100: logo left edge matches index"). The bar is full-bleed on every other width and it has
     to be full-bleed here; the right rail is fixed and paints over it, which is what the sibling
     margin-right restoration above already relies on. */
  html:not([data-pc-skel-side="1"]) body:not(.pc-hasside) header.pc-header{
    margin-left:calc(-1 * var(--pc-gap));
    margin-right:calc(-1 * (var(--pc-gap) + var(--pc-rail-strip)))}
  html body:has(> .pc-hud-inset),html body.pc-hud-inset{padding-left:0;padding-right:0}}

/* ── THE PANELS THEMSELVES ────────────────────────────────────────────────────────────────────
   ONE RADIUS AND ONE PADDING, STATED ONCE, ON THE TWO CLASSES THAT ARE ACTUALLY THE SITE'S PANEL
   VOCABULARY. `.pc-card` is the platform's card (payco.css:931, css/payco-system.css:648) and
   `.card` is what nine consoles wrote instead before it existed; between them they are every box
   the measurement above found. `.panel`, `.sec` and `.set-sec` are DELIBERATELY NOT HERE — the
   first is integrations.html's display:none tab body, the other two are settings sections that
   operator item 14 rebuilds, and none of the three is a card.
   WHY THIS OUTRANKS THE PAGES RATHER THAN REPLACING THEM. `html body.pc-hasside .card` is (0,3,0)
   against a page <style>'s (0,1,0), so the shape is settled without this lane editing twenty-nine
   files to remove a declaration each. A page keeps every other thing it says about its cards —
   background, border colour, the sliver gradient analytics.html paints across the top — and loses
   only the two properties that are the law's.
   AND ONLY ON A CONSOLE. `body.pc-hasside` is the gate: the marketing tier's `.card` is a
   different object on a page this lane does not own, and it keeps its own shape.
   min-width:0 IS NOT DECORATION. A panel is nearly always a grid or flex child, and a grid child's
   default `min-width:auto` is its min-CONTENT width — one long table cell and the track refuses to
   shrink, which is how a console ends up wider than the window with nothing visibly overflowing.
   It is the cheapest half of "the page never scrolls horizontally" and it belongs with the panel,
   not with the page that happens to hold one. */
html body.pc-hasside .pc-card,html body.pc-hasside .card{
  border-radius:var(--pc-panel-radius);
  padding:var(--pc-panel-pad);
  min-width:0}
/* THE INTER-PANEL GAP, on the centre container. `gap` is inert on a box that is neither grid nor
   flex, so this is stated unconditionally and costs the containers that stack with margins
   exactly nothing. */
html body.pc-hasside .wrap,html body.pc-hasside main,html body.pc-hasside .pc-wrap,
html body.pc-hasside .pc-main,html body.pc-hasside .m360,html body.pc-hasside .rc-shell,
html body.pc-hasside .ob-wrap,html body.pc-hasside .ib-wrap,html body.pc-hasside .lb-wrap,
html body.pc-hasside .px-wrap,html body.pc-hasside .op-shell,html body.pc-hasside .dp-wrap{
  gap:var(--pc-panel-gap)}
}  /* ══ end @media screen — the viewport law ══ */

/* ── THE HEADER'S HEIGHT IS RESERVED BEFORE THE BAR IS FILLED ─────────────────────────────────
   (OPERATOR SPEC, WAVE 2 ITEM 3 — "the FINAL layout — header height, both rail bands, both
   gutters, panel geometry — is reserved before first paint".)
   THE DEFECT. `header.pc-header` ships with an empty <nav> and payco-nav.js fills it after the
   document is parsed. On a desk that costs nothing — the bar is one row either way. On a narrow
   window the filled bar WRAPS, the header grows, and every box on the page moves down by the
   difference. MEASURED at 375 in chromium with the rail bands already reserved, so this is what
   was left: admin.html `div.card dy108, div#pc-content.wrap dy108` carrying 0.7661 of its 0.8618
   CLS; calendar.html `div.pc-shell dy108` at 0.1116 and a second 10px step at 0.7065;
   member-directory.html 0.1116 of its 0.1549; loadboard.html `div#pulse dy108` at 0.0563. The
   same 108px on four pages, because it is the same bar.
   min-height, NOT height, AND THE DIFFERENCE IS THE WHOLE SAFETY MARGIN. The token is a
   REMEMBERED number, and a remembered number can be too small — a page with one more nav row, a
   longer account name, a font that loaded late. A min-height that is short lets the header grow
   past it exactly as it does today; a `height` would clip the bar. It can also be too LARGE, and
   that costs a strip of empty header rather than a hidden control.
   ZERO IS THE FALLBACK AND IT IS INERT. --pc-hud-head-h is unset on every page that does not
   carry payco-hud.js and on every first-ever visit, where `min-height:0px` is what the header
   already has. payco-hud.js overwrites the token with the measured height on its first publish,
   so from then on this rule asks for exactly what the bar already is.
   AND IT IS STATED HERE RATHER THAN IN css/payco-system.css BECAUSE THAT SHEET IS NOT THIS LANE'S
   AND THE PROPERTY IS NEW. The header is a two-stylesheet seam — both sheets state .pc-header's
   geometry and the system sheet loads second — so a property DECLARED IN NEITHER can be added
   here without either sheet outranking the other. min-height is that property. */
header.pc-header{min-height:var(--pc-hud-head-h,0px)}

/* ---------- admin view bar ----------
   Sits under the header on the three portals. Admin+ only: pick a person, see their
   portal exactly as they see it. Amber while choosing, blue while inhabiting — the
   colour is the reminder that what is on screen is not yours. */
/* Content is centred as one cluster (not stretched edge-to-edge) so the search never
   drifts to the far right under the switcher. Boxes size to their content and grow for
   longer usernames up to a cap; the row wraps as a centred unit on narrow widths. */
.pc-viewbar{display:flex;align-items:center;justify-content:center;gap:10px 14px;flex-wrap:wrap;
  padding:11px clamp(16px,2.5vw,30px);font-size:13px;color:var(--ink-soft);text-align:center;
  background:linear-gradient(90deg,#e8efff,#eef4ff);border-bottom:1px solid rgba(26,77,214,.22);
  line-height:1.4;font-weight:600}
.pc-viewbar b{color:var(--ink);font-weight:800}
.pc-viewbar > b:first-child{font-size:14.5px;letter-spacing:-.2px;color:var(--blue-deep)}
.pc-viewbar > span{color:var(--ink-mute)}
.pc-viewbar input,.pc-viewbar select{width:auto;font-size:12.5px;padding:7px 11px;
  min-width:150px;max-width:340px}
.pc-viewbar.active{background:var(--info-bg);border-bottom-color:rgba(26,77,214,.22)}

/* ---------- guest sign-in box ---------- */
.adminbox{position:relative;background:#0b1b3a;color:#fff;border-radius:999px;
  padding:9px 17px;font-weight:800;font-size:13px;text-decoration:none;display:inline-flex;
  align-items:center;gap:7px;border:1px solid #1c3057;
  transition:background .2s ease,transform .2s cubic-bezier(.16,1,.3,1),box-shadow .2s ease}
.adminbox em{font-style:normal}
.adminbox:hover{background:#12305e;transform:translateY(-1px);box-shadow:0 6px 18px rgba(11,27,58,.28)}
.adminbox:active{transform:translateY(0)}
@media(max-width:700px){.pc-viewbar{font-size:12px}.pc-viewbar input,.pc-viewbar select{max-width:150px}}
.pc-id{position:relative;z-index:1;display:flex;align-items:center;gap:9px;font-size:12.5px;color:var(--ink-mute)}
/* The display name is the only unbounded thing in the header, and .pc-navcenter is
   absolutely positioned — different layout systems, so they overlap rather than push.
   A long name grew the identity leftward until it collided with the centred pills, and
   the collision width scaled with the NAME: measured 1314px for "Ann Admin", 1506px for
   "Sam Superadmin-Whitfield", 1662px for a 37-character one. Capping the name makes the
   threshold a fixed number instead of a function of who is signed in — without this the
   breakpoint below is a guess that a longer name silently invalidates. */
.pc-id b{color:var(--ink);font-weight:700;
  max-width:140px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* <=380px: .pc-navright measured 309px inside a 292px row, so it sat 3px off the left edge and
   the identity was clipped. Measured contents: "Signed in as" 54px, name 52px, rank chip 66px,
   Settings 50px, Sign out 50px, plus 9px gaps = 308px. The prefix is the one part that carries no
   information the two elements after it do not already carry — the name is right there and the
   chip names the rank — so it goes first, and the name keeps a tighter cap behind it.
   308 - 63 (prefix + its gap) = 245px, inside 292px, on one line, with the header height
   unchanged at 210px. Wrapping the cluster instead cost 57px of header on the smallest phone. */
@media(max-width:380px){
  .pc-id .pc-idpre{display:none}
  .pc-id b{max-width:82px}
}
.pc-chip{background:var(--gradient);color:#fff;font-size:12px;font-weight:800;
  padding:3px 9px;border-radius:999px;text-transform:uppercase;letter-spacing:.04em}
/* Settings + Sign out: the two things that belong to the PERSON rather than to a section,
   so they sit with the name instead of inside a rank-gated dropdown. Same weight as each
   other — neither is the primary action of any page they appear on. */
.pc-id .pc-idlink,.pc-id #pcSignOut{color:var(--ink-mute);font-weight:700;text-decoration:none;white-space:nowrap}
.pc-id .pc-idlink:hover,.pc-id #pcSignOut:hover{color:var(--blue)}
.pc-id .pc-idlink[aria-current=page]{color:var(--blue)}

/* ---------- layout ---------- */
.pc-wrap{max-width:var(--maxw);margin:0 auto;padding:clamp(16px,2.4vw,28px) clamp(16px,2.5vw,30px)}
/* .pc-shell/.pc-main existed only inside the ≤900px and ≤560px blocks further down, so above
   900px they were unstyled: no max-width, no horizontal padding. messages.html is the page that
   shows it — its .mx-wrap is a bordered, 18px-radius, shadowed card, and it sat flush against
   both viewport edges with the rounding clipped, stretching without limit on a wide monitor.
   calendar.html and settings.html had each patched around it with an inline style. Giving the
   pair the same contract as .pc-wrap fixes the page and retires both local workarounds; the
   mobile overrides below still win on narrow screens because they come later in the file. */
.pc-shell{display:block}
.pc-main{max-width:var(--maxw);margin:0 auto;padding:clamp(16px,2.4vw,28px) clamp(16px,2.5vw,30px)}
.pc-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:18px}
.pc-head h1{font-size:clamp(19px,2.4vw,25px)}
.pc-head .sub{color:var(--ink-mute);font-size:13px;margin-top:3px}

/* ---------- the sliver ----------
   The blue bar across the top of a panel. It had grown three incompatible
   implementations — a background layer, a child element with a lozenge radius, and a
   solid border-top with a per-card colour cycle — each wrong in a different way. One
   definition now, and every panel points at it.

   Two rules make it correct, and they are the whole fix:

   background-origin:border-box — a background image is positioned against the PADDING
     box by default, so on any panel with a 1px border the bar sat one pixel down with a
     hairline of border showing above it. That was "not flush with the top". Against the
     border box it starts at the true top edge and covers the border.

   background-clip:border-box (the default, left alone) — the bar is trimmed to the
     panel's OWN corner curve, so its ends taper exactly the way the corners do. No
     overflow:hidden, no pseudo-element, no hand-authored radius to get wrong. The
     lozenge shape came from trying to fake this with an elliptical border-radius on a
     3px-tall box; the browser scales radii that overflow the box, which is why it read
     as oblong.

   The hover is 3px to 4px over 180ms — no layout, no new paint surface, identical on
   every panel. It is triggered by raising --sliver-h rather than by re-declaring
   background-size, and that indirection is load-bearing: a panel's background is
   declared in its own page's <style>, which loads after this file, so a selector like
   `.wrap>.kpis` or `.cc-kpi.warn.alert` outranks anything written here and a
   background-size hover would silently lose. Nobody competes to SET --sliver-h — the
   pages only read it — so the variable lands whatever the specificity.

   @property makes it inherits:false, which stops a hovered panel from thickening the
   slivers of panels nested inside it (the ops deck holds KPI cells). The :root value is
   the safety net for engines without @property: without it var(--sliver-h) would be
   invalid, taking the whole background shorthand down with it and stripping the card's
   white fill — a far worse failure than a missing hover. */
@property --sliver-h{syntax:'<length>';inherits:false;initial-value:3px}
.pc-sliver{background:var(--sliver) top center/100% var(--sliver-h) no-repeat,var(--white);
  background-origin:border-box}
/* Panels declare their own base colour, so the shared half is origin + transition +
   hover. On a panel with no sliver every one of these is inert — background-size has
   no image to size — which is why the list can be generous without risk. */
.pc-sliver,.pc-card,.card,.panel,.deck,.detailband,.qadd,.cc-kpi,.kpis,.pc-kpi{
  background-origin:border-box;
  transition:background-size var(--sliver-ease),transform .14s,box-shadow .14s}
.pc-sliver:hover,.pc-card:hover,.card:hover,.panel:hover,.deck:hover,.detailband:hover,
.qadd:hover,.cc-kpi:hover,.kpis:hover,.pc-kpi:hover{--sliver-h:var(--sliver-h-hover)}
@media(prefers-reduced-motion:reduce){
  .pc-sliver,.pc-card,.card,.panel,.deck,.detailband,.qadd,.cc-kpi,.kpis,.pc-kpi{transition:none}}

/* ---------- cards ---------- */
.pc-card{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);
  padding:20px;box-shadow:var(--shadow-sm);position:relative}
.pc-card>h2,.pc-card>h3{font-size:14px;margin-bottom:14px;display:flex;align-items:center;gap:8px}
.pc-card.pad0{padding:0;overflow:hidden}
.pc-grid{display:grid;gap:16px}
@media(min-width:900px){.pc-2{grid-template-columns:1fr 1fr}.pc-3{grid-template-columns:repeat(3,1fr)}}

/* ---------- KPIs ---------- */
.pc-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}
.pc-kpi{background:var(--sliver) top center/100% var(--sliver-h) no-repeat,var(--white);
  border:1px solid var(--line);border-radius:var(--radius);
  padding:16px 18px;box-shadow:var(--shadow-sm);position:relative;overflow:hidden;cursor:default}
.pc-kpi.click{cursor:pointer}
.pc-kpi.click:hover{transform:translateY(-2px);box-shadow:var(--shadow-md)}
.pc-kpi b{font-size:27px;font-weight:800;display:block;line-height:1;letter-spacing:-.03em}
.pc-kpi span{color:var(--ink-mute);font-size:12px;font-weight:600;margin-top:5px;display:block}
.pc-kpi.alert{--sliver:linear-gradient(90deg,var(--warn),var(--warn))}
.pc-kpi.alert b{color:var(--warn)}
/* Legacy: the bar used to be this child element, carrying the elliptical radius that
   made it read as oblong. The KPI now paints its own sliver like every other panel, so
   the element is inert — kept selectable because three call sites still emit it and
   operations.html re-purposes it as a bottom bar inside the fused deck strip. */
.pc-kpi>.accent{display:none}

/* ---------- buttons ---------- */
.pc-btn{font:inherit;font-weight:700;font-size:13px;border:0;border-radius:999px;padding:10px 18px;
  background:var(--gradient);color:#fff;cursor:pointer;display:inline-flex;align-items:center;gap:7px;
  transition:transform .12s,box-shadow .15s;box-shadow:0 2px 10px rgba(26,77,214,.22)}
.pc-btn:hover{transform:translateY(-1px);box-shadow:0 6px 18px rgba(26,77,214,.32)}
.pc-btn:active{transform:translateY(0)}
.pc-btn.ghost{background:var(--paper-warm);color:var(--blue-deep);box-shadow:none}

/* ON PAPER, A CONTROL IS NOT CONTENT.
   tests/printstyles.mjs holds every printed document to "nothing on the paper is said with a
   background colour" — ink on white, because a tinted panel is information a photocopier and a
   fax both destroy. `.pc-btn` is injected by payco-nav.js at runtime rather than written into any
   page, so no page's own @media print block reached it, and proposal.html and intake.html both
   printed a ghost button carrying rgba(244,246,251,.086). It is a button: pressing it on a sheet
   of paper does nothing, so it does not belong on the sheet at all. Declared here, once, where the
   class is defined, so every page that prints inherits it rather than each repeating the rule. */
@media print{
  .pc-btn{display:none !important}
}
.pc-btn.ghost:hover{background:var(--line-soft)}
.pc-btn.warn{background:var(--warn-bg);color:var(--warn);box-shadow:none}
.pc-btn.sm{padding:6px 13px;font-size:12px}
.pc-btn:disabled{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none}

/* ---------- pills / status ---------- */
.pc-pill{display:inline-flex;align-items:center;gap:5px;padding:3px 11px;border-radius:999px;
  font-size:12px;font-weight:700;letter-spacing:.01em;white-space:nowrap}
.pc-pill.ok{background:var(--ok-bg);color:var(--ok)}
.pc-pill.warn{background:var(--warn-bg);color:var(--warn)}
.pc-pill.amber{background:var(--amber-bg);color:var(--amber)}
.pc-pill.info{background:var(--info-bg);color:var(--info)}
.pc-pill.mute{background:var(--paper-warm);color:var(--ink-mute)}
.pc-pill .dot{width:6px;height:6px;border-radius:50%;background:currentColor}

/* ---------- tables ---------- */
.pc-table{width:100%;border-collapse:collapse}
.pc-table th{text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-mute);font-weight:700;padding:11px 14px;border-bottom:1px solid var(--line);background:var(--paper-warm)}
.pc-table td{padding:12px 14px;border-bottom:1px solid var(--line-soft);font-size:13px}
.pc-table tbody tr{transition:background .12s;cursor:pointer}
.pc-table tbody tr:hover{background:var(--paper-warm)}
.pc-table tbody tr.sel{background:var(--info-bg)}
.pc-table tbody tr:last-child td{border-bottom:0}

/* ---------- text for assistive tech only ----------
   Several screens carry meaning in colour alone — the POS health dot, status chips — with the
   explanation parked in a title= that a touch device never shows. This is how that meaning gets
   said out loud without changing what anyone sees. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* ---------- compact tables (settings.html · "This device") ----------
   settings.html has offered "Compact tables in the consoles" since it shipped, wrote the
   choice to localStorage under `payco_prefs`, and nothing anywhere read the key — the toggle
   persisted, showed as checked, and changed nothing at all. payco-nav.js now applies
   `.pc-compact` from that pref on every console; this is what the class actually does.
   Density only — no colour, no size change to type that would break the type scale. */
.pc-compact .pc-table th{padding:7px 10px}
.pc-compact .pc-table td{padding:6px 10px;font-size:12.5px}
.pc-compact table th{padding:7px 10px}
.pc-compact table td{padding:6px 10px}
.pc-compact .pc-card{padding:14px}

/* ---------- forms ---------- */
label{display:block;font-size:12px;font-weight:600;color:var(--ink-soft);margin:12px 0 5px}
input:not([type=radio]):not([type=checkbox]):not([type=file]):not([type=color]):not([type=range]),select,textarea{width:100%;font:inherit;font-size:13.5px;color:var(--ink);
  border:1px solid var(--line);border-radius:var(--radius-sm);padding:10px 12px;background:var(--white);
  transition:border-color .15s,box-shadow .15s}
input:not([type=radio]):not([type=checkbox]):not([type=color]):not([type=range]):focus,select:focus,textarea:focus{outline:none;border-color:var(--blue-electric);
  box-shadow:0 0 0 3px rgba(59,130,255,.12)}

/* ---------- disclosure (inline expand — no page nav) ---------- */
.pc-disc{border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;margin-bottom:10px;background:var(--white)}
.pc-disc>summary{list-style:none;cursor:pointer;padding:12px 15px;display:flex;align-items:center;gap:10px;
  font-weight:600;font-size:13px;user-select:none;transition:background .12s}
.pc-disc>summary::-webkit-details-marker{display:none}
.pc-disc>summary:hover{background:var(--paper-warm)}
.pc-disc>summary .chev{margin-left:auto;transition:transform .2s;color:var(--ink-mute)}
.pc-disc[open]>summary .chev{transform:rotate(90deg)}
.pc-disc[open]>summary{border-bottom:1px solid var(--line-soft)}
.pc-disc .body{padding:15px}

/* ---------- training modules ----------
   These four rules lived in agents.css, which NO page linked — the file was superseded by this
   one and left behind. training.html is `.module`'s only consumer and loads payco.css only, so
   every module on that page rendered as a bare h3 + p with no card, no border, no padding, and
   the Completed/Mark-complete row unaligned. Rules moved here rather than re-linking a dead
   stylesheet: agents.css also carried a competing :root palette and the looping `tealsweep`
   sheen this file deliberately removed, so linking it would have dragged both back in. */
.module{border:1px solid var(--line);border-radius:14px;padding:18px;margin-bottom:14px;background:var(--white)}
.module h3{font-size:15px;margin:0 0 8px;color:var(--ink)}
.module p{color:var(--ink-mute);font-size:13px;margin:0;line-height:1.55}
.module .foot{display:flex;justify-content:space-between;align-items:center;margin-top:14px;gap:12px}

/* ---------- doc thumbnails (click to expand inline) ---------- */
.pc-docs{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px}
.pc-doc{border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;cursor:pointer;
  background:var(--paper-warm);transition:transform .14s,box-shadow .14s;position:relative}
.pc-doc:hover{transform:translateY(-2px);box-shadow:var(--shadow-md)}
.pc-doc .thumb{height:96px;display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg,#eef2fb,#e2e9f6);color:var(--blue-deep);font-size:26px}
.pc-doc .lbl{padding:9px 11px;font-size:12px;font-weight:600;border-top:1px solid var(--line)}
.pc-doc .lbl small{display:block;color:var(--ink-mute);font-weight:500;font-size:12px;margin-top:1px}

/* lightbox for expanded doc */
.pc-lb{position:fixed;inset:0;background:rgba(10,22,40,.72);backdrop-filter:blur(4px);
  display:none;align-items:center;justify-content:center;z-index:100;padding:32px}
.pc-lb.on{display:flex}
.pc-lb .frame{background:#fff;border-radius:var(--radius);overflow:hidden;max-width:92vw;max-height:90vh;
  box-shadow:var(--shadow-lg);display:flex;flex-direction:column}
.pc-lb .bar{padding:12px 18px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:12px;font-weight:700;font-size:13px}
.pc-lb .bar .x{margin-left:auto;cursor:pointer;color:var(--ink-mute);font-size:20px;line-height:1;border:0;background:0}
.pc-lb .content{overflow:auto;padding:0}
.pc-lb img,.pc-lb iframe{display:block;max-width:88vw;max-height:78vh;border:0}
.pc-lb iframe{width:80vw;height:78vh}

/* ---------- the refusal card ----------
   What a console page becomes for a rank that does not hold its section grant. Built by
   payco-nav.js, styled once here, so every console refuses in the same voice instead of
   each one inventing its own empty state — or, as they all did until 2026-07-28, drawing
   the whole console and letting the API return 403 into panels that then printed 0.
   It is a CARD, not a full-bleed error page: you are still signed in, still inside the
   platform, and the header above it still works. */
.pc-nogrant{max-width:520px;margin:clamp(28px,7vh,72px) auto;text-align:center;
  background:var(--sliver) top center/100% var(--sliver-h) no-repeat,var(--white);
  background-origin:border-box;border:1px solid var(--line);border-radius:var(--radius-lg,18px);
  padding:34px 30px 30px;box-shadow:0 1px 2px rgba(11,27,58,.04),0 14px 38px rgba(11,27,58,.06)}
.pc-nogrant svg{width:34px;height:34px;color:var(--blue);margin-bottom:12px}
/* h1 as of the heading-structure fix in payco-nav.js; h2 kept in the selector so an older
   cached copy of that file still gets the right size rather than the 2em UA default. */
.pc-nogrant h1,.pc-nogrant h2{font-size:18px;font-weight:800;letter-spacing:-.2px;margin:0 0 8px}
.pc-nogrant p{font-size:13px;line-height:1.6;color:var(--ink-mute);margin:0 auto;max-width:40ch}
.pc-nogrant p b{color:var(--ink-soft);font-weight:700}
.pc-nogrant-act{display:flex;gap:9px;justify-content:center;flex-wrap:wrap;margin-top:20px}

/* ---------- icons ----------
   The platform drew its glyphs as EMOJI until 2026-07-29 — a red flag on Report, a bell on
   notifications, a wastebasket on Delete, hearts and stars across the marketplace. Emoji are
   not an icon set: the artwork ships with the OS, so one button was a flat vector on a Mac,
   a glossy 3-D sticker on Windows and something else again on Android, at a size and
   baseline no stylesheet controls. They carry their own colour, which no theme can override,
   and a screen reader reads them by NAME ("triangular flag on post") rather than by job.
   PaycoIcon() in payco-nav.js draws them all instead. Sized in `em` so they follow the text
   they sit beside, and on currentColor so a button's own colour drives them — which is what
   makes one icon work on a ghost button, a danger button and a dark command bar. */
.pc-ico{width:1.05em;height:1.05em;flex:none;vertical-align:-.16em;display:inline-block}
/* Beside a label, not jammed against it. Buttons that are icon-ONLY set .pc-ico-only and get
   the gap back as padding, so the tap target stays square. */
button .pc-ico,a .pc-ico,.pc-btn .pc-ico{margin-right:.45em}
.pc-ico-only .pc-ico,button.pc-ico-only .pc-ico{margin-right:0}
/* ── AN ICON THAT IS ALONE IN ITS BOX IS NOT BESIDE A LABEL ──────────────────────────────────
   THE OPERATOR'S REPORT WAS "the left taskbar icons are off-centre", AND THIS LINE IS WHY.
   The rail's rows are `<a><span class="ico"><svg class="pc-ico"></span>Label</a>`: the LABEL gap
   is the flex `gap:13px` on the <a>, and the .ico tile centres the glyph with `place-items:
   center`. But the <a> is an `a`, so the rule directly above also fires and hangs .45em off the
   glyph's right — and a grid centres the MARGIN box, not the border box, so the glyph settles
   half that margin to the left of the tile it is supposed to be centred in.
   MEASURED at 1440, headless chromium, all 99 root pages, glyph centre against wrapper centre:
       span.ico       1102 instances on 29 pages   dx -3.38px   (margin 6.75px at 15px text)
       span.nv-ico      24 instances on  1 page    dx -3.16px   (margin 6.30px at 14px text)
       button.sd-new     1                         dx -3.00px
       .pc-hud-btn      63 (the HUD's own rails)   dx +2.92px
   Half of 6.75 is 3.375, which is the number, so this is arithmetic rather than a coincidence.
   `.pc-ico-only` was the existing opt-out and it is not the answer here: the thing that is icon-
   only is the .ico TILE, not the <a> around it, so the class would have to be hand-added to
   1,102 wrappers in 29 files and re-added by every lane that renders a rail row.
   WHY `:only-child` IS SAFE, AND HOW TO RE-CHECK IT. :only-child counts ELEMENTS, so it also
   matches `<button><svg class="pc-ico">Save</button>` — a bare text label — which is exactly the
   case the .45em exists for. So it was counted rather than assumed: across all 99 pages there are
   1,272 `.pc-ico:only-child`, and ZERO of them have a non-empty text sibling. Every icon+label
   pair in this tree wraps its label in an element. Re-run that count before widening this rule.
   THE GAP IS NOT LOST where it was doing work: a lone glyph in a box is centred by the box, and a
   lone glyph in a link that is genuinely icon-only should not carry a trailing margin either —
   that is the same thing .pc-ico-only says one wrapper at a time. */
button .pc-ico:only-child,a .pc-ico:only-child,.pc-btn .pc-ico:only-child{margin-right:0}
.pc-ico-lg{width:1.5em;height:1.5em}

/* ---------- misc ---------- */
.pc-mute{color:var(--ink-mute)}
.pc-mono{font-family:var(--mono);font-size:12px}
.pc-empty{text-align:center;color:var(--ink-mute);padding:30px 18px;font-size:13px}
.pc-kv{display:grid;grid-template-columns:minmax(120px,auto) 1fr;gap:8px 18px;font-size:13px}
.pc-kv b{color:var(--ink-soft);font-weight:600}
.pc-kv span{color:var(--ink)}
.pc-note{background:var(--info-bg);border:1px solid rgba(26,77,214,.18);border-radius:var(--radius-sm);
  padding:12px 14px;font-size:12.5px;color:var(--ink-soft);margin:10px 0}
.pc-bar{height:8px;background:var(--line-soft);border-radius:999px;overflow:hidden}
.pc-bar i{display:block;height:100%;background:var(--gradient);border-radius:999px;transition:width .5s cubic-bezier(.2,.8,.2,1)}
.hide{display:none!important}
/* Visually hidden, still announced. Measured across the platform: 17 pages had NO h1 at all —
   admin, agent, analytics, crm, operations, pos, support, referral, integrations, loadboard,
   the four marketplace pages and the three forum pages — so a screen-reader user landing on
   any of them had no document heading to orient by and no top level to navigate from.
   Where the page has a real, static title it was promoted to h1 instead of using this; this is
   for the consoles whose visible title is a runtime value (or which have no title element at
   all), where promoting a placeholder would make "Loading…" the heading of the document.
   clip-path over the old width:1px/overflow:hidden trick because it does not break text
   selection or cause a scroll jump when focused. */
.pc-sronly{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;border:0;
  clip-path:inset(50%);overflow:hidden;white-space:nowrap}

/* The centred pills are absolutely positioned, so they overlap the identity instead of
   pushing it — they cannot share a row, they can only miss each other. The pill cluster is
   ~697px and page-centred, the identity is capped at 348px on the right, so the two clear
   only when the viewport exceeds 2 x (348 + 34 + 348) = 1460px. Measured by sweeping the
   real header in a browser, not derived: 1460px was the last width that still collided.
   1480px is that number plus a little headroom.
   This was 1180px, which was correct for FIVE pills and silently wrong the moment a sixth
   was added — the failure is invisible until someone with a long name signs in on a laptop.
   If the pill count changes again, re-run the sweep; this number is a function of it. */
@media(max-width:1120px){
  /* The pills fold into the burger, which already holds every category grouped — that is
     what it is for. Falling back to FLOW instead (what this rule used to do at 1180px)
     does not work at six pills: the row's natural width is ~1412px, so at 1280px the
     identity was pushed 107px past the viewport and, because body is overflow-x:hidden,
     silently CLIPPED — "Sign out" was still in the DOM, still passing any scrollWidth
     check, and unreachable by mouse. Measured, not reasoned: elementFromPoint on the link
     returned a different element at 1200px and 1280px.
     Hiding them is also the only option that survives the pill count changing again; a
     shrink-to-fit tuned to six pills is wrong the moment there are seven. */
  .pc-navcenter{display:none}
}
/* ...and there are now seven, and 1479px was wrong, exactly as the note above predicted.
   Measured 2026-07-28 on the committed code, staff Superadmin, name capped at 140px:
   77px of overlap at 1480, 57px at 1520, 37px at 1560, 26px at 1600, clean only from 1700.
   The number had drifted again inside a day, and nothing failed loudly — which is the
   third time this breakpoint has been re-guessed.
   So it is not guessed any more. payco-nav.js MEASURES the built bar against the identity
   cluster and the page tag, and sets one of these two classes; the media query above stays
   as the pre-measurement default so nothing flashes overlapping on first paint. Both
   classes are (0,2,0) and beat it in either direction, which matters — the fold has to be
   able to say "these fit, show them" as well as "these don't". A pill added tomorrow, a
   longer tag, a different font: the measurement already covers it. */
.pc-navcenter.pc-fold{display:none}
.pc-navcenter.pc-nofold{display:flex}
/* One notch tighter between 1200 and 1560. Measured on forum.html as a Superadmin: the seven
   pills needed 785px and had 768px of room at 1440px — seventeen pixels short, which folded the
   entire primary navigation on the single commonest laptop width. Trimming the horizontal
   padding by 4px a side gives back 56px across seven pills and they fit with room to spare.
   Font size is untouched; only the padding moves, so the pills stay legible and still clear the
   44px touch floor set in the mobile layer. */
@media(min-width:1200px) and (max-width:1560px){
  /* Re-derived after the logo went 40px -> 44px to match the marketing header: the wordmark
     widened 163 -> 180px and took back exactly the margin this band had won, leaving the seven
     pills needing 700px in 699px at 1440. Measured again at 10px/4px: 666px needed, which fits
     1440 (699 available) AND 1366 (668) — so the two commonest laptop widths both keep a real
     navigation bar instead of a hamburger. Only the horizontal padding moves; the 38px pill
     height, and therefore the touch target, is untouched. */
  .pc-drop-trig{padding:0 10px}
  .pc-navcenter{gap:4px}
}
@media(max-width:900px){
  /* The pill cluster is a flex child now, and .pc-nav wraps here — so a cluster that "fits" on
     a line of its own made the header 189px tall at 900px and 768px, with the burger no longer
     hit-testable. It was harmless while the cluster was position:absolute (it never
     participated in this row) and it is not any more. Below the burger breakpoint the burger IS
     the navigation, so the cluster is out, unconditionally: !important because fitBar()'s
     .pc-nofold is (0,2,0) and would otherwise put it back. */
  .pc-navcenter,.pc-navcenter.pc-nofold{display:none!important}
  /* Vertical padding only. The horizontal inset stays the shared clamp — overriding it to a
     flat 14px here was the last place the wordmark's left edge disagreed with the marketing
     bar (14px against 27px at 900, 14 against 18 at 390), and the app bar needs the row to
     wrap, not to start further left than every page the visitor arrived from. */
  .pc-header{height:auto;padding:10px clamp(18px,3vw,40px);flex-wrap:wrap;row-gap:8px}
  .pc-nav{width:100%;margin-left:0;justify-content:flex-end;flex-wrap:wrap;gap:8px}
  .pc-drop-trig{font-size:13px;padding:7px 13px}
  /* .pc-id is width:100% inside .pc-navright, so navright's contents are always wider than
     it is. Without wrapping, a flex row justified to flex-end pushes that overflow off the
     LEFT edge — the burger measured x=-36px at 320px, i.e. the only navigation control on
     a phone was off-screen. It only surfaced once the pills stopped occupying the row. */
  .pc-navright{flex-wrap:wrap;justify-content:flex-end}
  /* .pc-id is width:100% of a container already narrower than its own content at 320px —
     measured 309px of identity inside 292px of room, so its left edge sat 3px off-screen and the
     name/chip/Settings row was clipped (overflow-x:clip hid the symptom, which is why nobody
     saw it). Wrapping the cluster fixes the width and costs 57px of header height on the
     smallest phone (210 -> 267px), which is trading one defect for another. The shortfall is
     17px, so the DISPLAY NAME gives it up instead: it is the only unbounded thing in the row and
     it is already ellipsised at 140px further up. One line, correct width, same height. */
  .pc-id{width:100%;order:3;min-width:0}
  .pc-table{display:block;overflow-x:auto;white-space:nowrap}
}

/* ---------- additions: components referenced by pages ---------- */
.pagehead{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:18px}
/* h1 OR h2: consoles whose only heading was a data-loaded panel now carry the page
   title in the header tag as their h1, so the pagehead title is an h2 there. Both
   spellings are the same visual role. */
.pagehead h1,.pagehead h2{font-size:clamp(19px,2.4vw,25px)}
.pagehead .sub{color:var(--ink-mute);font-size:13px;margin-top:3px}
.pc-row{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap}
.pc-row>div{flex:1;min-width:140px}
/* danger vs warn, bad vs warn. Both pairs used to be byte-identical, so a button that deletes
   something looked exactly like one that merely cautions, and an error pill looked exactly like
   an in-progress pill. The names carried the intent; nothing on screen did. Keeping warn as the
   soft tinted treatment and giving danger a solid fill + border is the cheapest honest split —
   destructive actions read as destructive at a glance, and no call site has to change. */
.pc-btn.danger{background:var(--warn);color:#fff;border:1px solid var(--warn);box-shadow:none}
.pc-btn.danger:hover{background:#8f3319;border-color:#8f3319}
.pc-pill.bad{background:var(--warn);color:#fff}
.pc-pill.cy{background:var(--info-bg);color:var(--blue)}
.empty{text-align:center;color:var(--ink-mute);padding:30px 18px;font-size:13px}
.mono{font-family:var(--mono);font-size:12px}
.foot{display:flex;justify-content:space-between;align-items:center;margin-top:14px;gap:12px}
.hint{font-size:12px;color:var(--ink-mute);margin-top:4px}
.err{color:var(--warn);font-size:12.5px;min-height:16px;margin:6px 0}
.cta{font:inherit;font-weight:700;font-size:13px;border:0;border-radius:999px;padding:10px 18px;
  background:var(--gradient);color:#fff;cursor:pointer;box-shadow:0 2px 10px rgba(26,77,214,.22)}
.cta:hover{transform:translateY(-1px)}
/* key/value grid — used by ops detail, merchant360, onboarding */
.kv{display:grid;grid-template-columns:minmax(120px,150px) 1fr;gap:8px 18px;font-size:13px;align-items:baseline}
.kv b{color:var(--ink-soft);font-weight:600}
.kv span{color:var(--ink)}

/* ---------- button behaviour ----------
   Deliberately NOT a reset of shape. Radius and padding stay where each page put them,
   because they carry that surface's density — a 10px action inside a dense hardware
   grid should not become a 999px marketing pill. What is shared is how a button
   RESPONDS: one lift, one shadow ramp, one press, one focus ring, one disabled state.
   That is the part users read as "same product", and it was the part that varied.

   Last block in the file so it settles payco.css's own earlier button rules. Pages load
   after this and can still override anything they genuinely need to. */
:root{
  --btn-shadow-hover:0 6px 18px rgba(26,77,214,.32);
  --btn-shadow-active:0 2px 8px rgba(26,77,214,.24);
}
.pc-btn,.cta,.btn,.go,.pri{
  transition:transform .12s cubic-bezier(.4,0,.2,1),box-shadow .18s ease,background-color .18s ease}
.pc-btn:hover,.cta:hover,.btn:hover,.go:hover,.pri:hover{
  transform:translateY(-1px);box-shadow:var(--btn-shadow-hover)}
.pc-btn:active,.cta:active,.btn:active,.go:active,.pri:active{
  transform:translateY(0);box-shadow:var(--btn-shadow-active)}
/* the quiet variants stay quiet — they lift, but they never grow a blue glow */
.pc-btn.ghost:hover,.pc-btn.warn:hover,.pc-btn.danger:hover{box-shadow:none}
.pc-btn:disabled,.cta:disabled,.btn:disabled,.go:disabled,button:disabled,
.pc-btn[aria-disabled=true],.cta[aria-disabled=true]{
  opacity:.45;cursor:not-allowed;transform:none;box-shadow:none}
/* Keyboard focus for the controls that had none. outline follows the element's own
   border-radius, so it fits a 999px pill and a 10px chip without being told which.
   Text inputs are deliberately excluded — the forms section already gives them a border
   shift plus a 3px glow on :focus, and stacking an outline on top of that reads as an
   error state rather than a focus one. */
/* The list used to stop at [tabindex], so input, select and textarea — the controls a keyboard
   user spends the most time in — got no ring from the one sitewide rule, only whatever each page
   happened to define. Most pages do swap the outline for a box-shadow ring and are fine; the ones
   that forgot had nothing at all behind them. Adding the three element types gives every form
   field a floor.
   :where() is kept deliberately: it contributes ZERO specificity, so this is a default that any
   page's own :focus treatment still overrides without needing !important. That is also its limit
   — a page rule doing `outline:0` with no replacement still wins, which is why such rules are
   worth fixing at the source rather than trying to out-specify them from here. */
:where(a,button,summary,input,select,textarea,[role=button],[tabindex]):focus-visible{
  outline:2px solid var(--blue-electric);outline-offset:2px}
@media(prefers-reduced-motion:reduce){
  .pc-btn,.cta,.btn,.go,.pri{transition:box-shadow .18s ease}
  .pc-btn:hover,.cta:hover,.btn:hover,.go:hover,.pri:hover,
  .pc-btn:active,.cta:active,.btn:active,.go:active,.pri:active{transform:none}}
/* section blocks inside detail panels */
.sec{border-top:1px solid var(--line-soft);margin-top:14px;padding-top:14px}
.sec:first-of-type{border-top:0;margin-top:0;padding-top:0}
.sec h4{margin:0 0 10px;font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-mute);font-weight:700}
/* misc restored */
.doc{display:flex;justify-content:space-between;align-items:center;font-size:13px;padding:7px 0;border-bottom:1px dashed var(--line)}
.doc em{font-style:normal;font-weight:700}.doc em.ok{color:var(--ok)}.doc em.no{color:var(--warn)}
.sugg{background:var(--info-bg);border:1px solid rgba(26,77,214,.18);border-radius:var(--radius-sm);padding:11px 13px;font-size:12.5px;margin-bottom:10px;color:var(--ink-soft)}
.mix{height:7px;border-radius:99px;background:var(--line-soft);overflow:hidden}
.mix i{display:block;height:100%;background:var(--gradient)}
.ev{font-size:12.5px;color:var(--ink-mute);padding:7px 0;border-bottom:1px dashed var(--line-soft)}
.ev b{color:var(--ink)}
.cfg-note{font-size:12.5px;color:var(--ink-mute);margin:8px 0 0}
.tbd{color:var(--ink-mute)}
.sig{display:flex;justify-content:space-between;gap:12px;font-size:13px;padding:6px 0;border-bottom:1px solid var(--line-soft)}
.sig:last-child{border-bottom:0}
.sigrow{display:flex;justify-content:space-between;gap:12px;align-items:center}
.rank-row{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--line-soft)}
.audit-table{width:100%;border-collapse:collapse;font-size:12.5px}
.audit-table th{text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:var(--ink-mute);padding:8px;border-bottom:1px solid var(--line)}
.audit-table td{padding:8px;border-bottom:1px solid var(--line-soft)}
.admin-group summary::-webkit-details-marker{display:none}
.admin-group[open] summary{margin-bottom:14px}
.navr{display:flex;gap:8px;align-items:center}
.back{color:var(--ink-mute);font-size:13px;font-weight:600}
/* legacy button aliases so pre-existing markup keeps working */
.btn,.act{font:inherit;font-weight:700;font-size:12.5px;border:0;border-radius:999px;padding:9px 16px;
  background:var(--paper-warm);color:var(--blue-deep);cursor:pointer;margin:0 5px 5px 0;transition:transform .12s}
.btn:hover,.act:hover{transform:translateY(-1px)}
.btn.go,.act.go,.btn.cta{background:var(--gradient);color:#fff;box-shadow:0 2px 10px rgba(26,77,214,.22)}
.btn.warn,.act.warn{background:var(--warn-bg);color:var(--warn)}
.read,.edit{font-size:12px;font-weight:700;padding:2px 8px;border-radius:999px}
.read{background:var(--info-bg);color:var(--blue)}
.edit{background:var(--ok-bg);color:var(--ok)}
/* wide variant for data-dense app pages (ops cockpit) — 1240 is a marketing width */
.pc-wrap.wide{max-width:1560px}

/* ---------- compact tables ----------
   The other half of settings.html's "Compact tables in the consoles" toggle. That checkbox wrote
   to localStorage and NOTHING read it, on any page — so the setting existed, persisted, and did
   nothing. payco-nav.js now puts body.pc-compact on every page wearing the shared header; this is
   what the class actually means. Padding and font-size only: no layout property changes, so a
   compact table cannot reflow a page differently from a normal one, only tighten it. */
body.pc-compact .pc-table th,body.pc-compact .pc-table td,
body.pc-compact table th,body.pc-compact table td{padding-top:6px;padding-bottom:6px}
body.pc-compact .pc-table td,body.pc-compact table td{font-size:12.5px}
body.pc-compact .audit-table th,body.pc-compact .audit-table td{padding-top:5px;padding-bottom:5px}

/* ============================================================
   MOBILE-NATIVE LAYER — cross-compatibility pass (≤560px)
   Goal: native-app feel. No sideways scroll, generous tap
   targets, thick readable type, spacious rhythm. Applied
   platform-wide via the shared stylesheet.
   ============================================================ */

/* Global guard: nothing may cause horizontal scroll, ever.
   `clip`, NOT `hidden`, and the one word is load-bearing. `overflow-x:hidden` makes both
   elements SCROLL CONTAINERS, and a scroll container is what `position:sticky` measures
   against — so every sticky element on every page that loads this file was sticking to a box
   that never scrolls, which is indistinguishable from not sticking at all. `clip` prevents the
   identical horizontal scrolling WITHOUT establishing a scroll container, so the guard this
   rule exists for is unchanged and sticky works. (`hidden` also coerces overflow-y to `auto`;
   `clip` leaves it `visible`, so the page scrolls on the viewport as it should.)
   Do not "simplify" this back to hidden — it fails silently and only in a browser.
   THE `hidden` DECLARATION BELOW IS A FALLBACK, NOT THAT BUG RETURNING. A browser too old for
   `clip` (Safari <=15 / iOS 15) discards it as invalid, and would otherwise be left with NO
   guard at all — a page that scrolls sideways on a phone is a worse failure than a nav that
   does not stick. Everything that understands `clip` takes the second declaration and
   overrides the first. Delete neither, and do not reorder them. */
html, body { max-width: 100%; overflow-x: hidden; overflow-x: clip; }
img, svg, video, canvas, table { max-width: 100%; }

@media (max-width: 560px) {
  /* Type: bump base so body text is comfortably legible, keep the
     bold/thick face the brand uses. iOS won't zoom inputs at ≥16px. */
  body { font-size: 15.5px; -webkit-text-size-adjust: 100%; }
  input, select, textarea, button { font-size: 16px !important; }

  /* Cards breathe, edge-to-edge but with inner padding. */
  .pc-card { padding: 16px 16px; border-radius: 14px; }
  .pc-main, .wrap, .pc-wrap { padding-left: 14px; padding-right: 14px; }

  /* Headings scale down but stay heavy. */
  h1 { font-size: clamp(20px, 6vw, 26px); line-height: 1.15; }
  h2 { font-size: clamp(17px, 5vw, 21px); line-height: 1.2; }
  h3 { font-size: 15.5px; }

  /* Tap targets: everything clickable ≥44px tall (Apple HIG). */
  .pc-btn, button, .btn, a.btn { min-height: 44px; padding: 12px 18px; }
  /* This line said 40px, quietly undercutting the rule directly above it on every page that
     wears the shared nav — so the platform-wide "≥44px" claim was never actually true for the
     header. Caught by tests/overflow.mjs, which measures in a real browser instead of trusting. */
  /* .pc-home is on this line because the fix above ENUMERATED SELECTORS AND MISSED ONE. The
     comment says the claim was "never actually true for the header" and then left the header's
     home link — the logo, the single most-tapped control on any page — measuring 40px on every
     page that wears the shared nav. overflow.mjs could not catch the miss: its tap-target check
     queries `button,[role=button],input[type=submit],select` and .pc-home is a plain <a>, so the
     one element still failing was the one element never measured. Measured at 375px and 320px:
     40px before, 44px after. */
  .pc-nav a, .pc-drop-trig, #pcSignOut, .pc-home { min-height: 44px; display: inline-flex; align-items: center; }
  /* Selects and anchors-as-buttons were never covered by the rule at all. */
  select, #pcViewSel { min-height: 44px; }
  a[role="button"] { min-height: 44px; display: inline-flex; align-items: center; }
  /* Breadcrumbs and bare disclosures — the two navigation patterns the enumeration above still
     missed. Measured at 375px: `.crumb a` was 15px on forum-section/forum-thread/marketplace-sell
     and 18.8px on marketplace-listing; a `<summary>` outside `.pc-disc` (which brings its own
     padding) was 18.8px on forum-profile. Both are how you move backwards through the app, so
     they are exactly the targets worth hitting on the first try.
     Deliberately NOT a blanket `a{min-height:44px}`: that would inflate every link inside a
     paragraph, which WCAG 2.5.8 exempts and which breaks the line box it sits in. */
  .crumb a { min-height: 44px; display: inline-flex; align-items: center; }
  summary { min-height: 44px; display: flex; align-items: center; }
  .pc-disc > summary { min-height: 0; }

  /* Tables: horizontal scroll is the right call on a phone, but make
     it obvious and smooth rather than clipping. */
  .pc-table, table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; white-space: nowrap; }

  /* KPI strips and grids collapse to a single readable column. */
  .pc-2, .pc-3, .pc-grid, [class*="grid-2"], [class*="grid-3"] { grid-template-columns: 1fr !important; }

  /* Sidebars never take horizontal room on mobile — the nav's hamburger
     covers navigation instead. */
  .pc-sidenav { display: none; }

  /* Segmented controls / pill rows wrap instead of overflowing. */
  .seg, .tabs, .times, .cal-nav, .pc-viewbar { flex-wrap: wrap; }
}

/* Very small (≤360px): trim padding a touch more so 320px devices fit. */
@media (max-width: 360px) {
  .pc-card { padding: 14px 12px; }
  .pc-main, .wrap, .pc-wrap { padding-left: 10px; padding-right: 10px; }
  body { font-size: 15px; }
}

/* Unread count on the Messages nav item. Rendered by payco-nav.js on every page that wears the
   shared header, so the count is not a property of the messages page you are not looking at. */
.pc-unread{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 5px;margin-left:7px;
  border-radius:999px;background:var(--blue,#1a4dd6);color:#fff;font-size:12px;font-weight:800;line-height:1}
.pc-drop-trig .pc-unread,.pc-nav a .pc-unread{vertical-align:middle}
/* The support-desk counter (0071) sits in the same nav, often two items from the messages
   one, and the two mean different things: unread MESSAGES against unanswered TICKETS. Same
   shape so it reads as the same kind of signal, warm rather than brand-blue so a glance can
   tell which one moved. --warn is the platform's "somebody is waiting" colour and already
   clears AA against white at these weights. */
.pc-unread-tk{background:var(--warn,#b3401f)}

/* ============================================================================
   Forum moderation UI (payco-mod.js). Lived inline in forum-section.html and
   forum-thread.html as two near-identical copies, which is why the moderator
   queue could not be opened from forum.html — the markup had nowhere to get its
   styling. One copy, on the shared sheet every forum page already loads.
   ==========================================================================*/
/* ---- moderation UI (payco-mod.js) ---- */
/* post command cluster — top-right of each post, clean pills (no amber) */
.pm-post{position:absolute;top:14px;right:16px;display:flex;gap:6px;z-index:2}
.pm-cmd{font:inherit;font-size:12px;font-weight:700;border:1px solid var(--line,#e3e7f0);border-radius:999px;
  padding:5px 11px;background:#fff;color:var(--ink,#0b1b3a);cursor:pointer;
  transition:border-color .13s,background .13s,color .13s}
.pm-cmd:hover{border-color:var(--electric,#1a4dd6);color:var(--electric,#1a4dd6);background:#f2f6ff}
.pm-cmd.danger{color:#b3401f}
.pm-cmd.danger:hover{border-color:#b3401f;background:#fdf0ec;color:#b3401f}
@media(max-width:760px){.pm-post{position:static;margin-bottom:10px;justify-content:flex-end}}
.pm-bar{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:9px 12px;margin-top:10px;
  background:#f7f9fd;border:1px solid var(--line,#e3e7f0);border-radius:11px}
.pm-bar i{font-style:normal;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--electric,#1a4dd6);margin-right:4px}
.pm-bar button{font:inherit;font-size:12px;font-weight:700;border:1px solid var(--line,#e3e7f0);border-radius:999px;
  padding:5px 11px;background:#fff;color:var(--ink,#0b1b3a);cursor:pointer;position:relative;
  transition:border-color .13s,background .13s,transform .08s;margin:0}
.pm-bar button:hover{border-color:var(--electric,#1a4dd6);background:#f2f6ff;color:var(--electric,#1a4dd6)}
.pm-bar button:active{transform:scale(.95)}
.pm-bar button.danger{color:#b3401f}
.pm-bar button.danger:hover{border-color:#b3401f;background:#fdf0ec}
.pm-badge{display:none;min-width:15px;height:15px;line-height:15px;padding:0 4px;border-radius:99px;
  background:#b3401f;color:#fff;font-size:12px;font-weight:800;margin-left:5px;text-align:center}
.pm-flag{font:inherit;font-size:12px;font-weight:700;border:1px solid transparent;border-radius:999px;
  padding:3px 9px;background:transparent;color:var(--mut,#5b6474);cursor:pointer;opacity:.55;transition:.13s}
.pm-flag:hover{opacity:1;border-color:var(--line,#e3e7f0);background:#fff}

.pm-wrap{position:fixed;inset:0;z-index:9999;display:grid;place-items:center;padding:20px}
.pm-back{position:absolute;inset:0;background:rgba(6,16,44,.55);backdrop-filter:blur(3px);animation:pmFade .16s}
.pm{position:relative;background:#fff;border-radius:18px;width:min(560px,100%);max-height:86vh;display:flex;
  flex-direction:column;box-shadow:0 30px 80px rgba(6,16,44,.4);animation:pmUp .22s cubic-bezier(.2,.8,.2,1)}
@keyframes pmFade{from{opacity:0}to{opacity:1}}
@keyframes pmUp{from{opacity:0;transform:translateY(12px) scale(.98)}to{opacity:1;transform:none}}
.pm-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px 18px;
  border-bottom:1px solid var(--line,#e3e7f0)}
.pm-head b{font-size:15px;letter-spacing:-.01em}
.pm-x{font:inherit;font-size:20px;line-height:1;border:0;background:transparent;color:var(--mut,#5b6474);
  cursor:pointer;padding:0 4px;margin:0}
.pm-x:hover{color:var(--ink,#0b1b3a)}
.pm-body{padding:16px 18px;overflow-y:auto}
.pm-foot{padding:13px 18px;border-top:1px solid var(--line,#e3e7f0);display:flex;justify-content:flex-end}
.pm-go{font:inherit;font-size:13px;font-weight:700;border:0;border-radius:999px;padding:10px 20px;
  background:linear-gradient(90deg,#0b2a8a,#1a4dd6);color:#fff;cursor:pointer;margin:0}
.pm-go:hover{transform:translateY(-1px)}
.pm-p{font-size:13px;color:var(--mut,#5b6474);line-height:1.55;margin:0 0 12px}
.pm-p code{background:#f2f5fb;padding:1px 6px;border-radius:5px;font-size:12px}
.pm-l{display:block;font-size:12px;font-weight:700;color:var(--mut,#5b6474);margin:12px 0 5px}
.pm-i{width:100%;font:inherit;font-size:13.5px;border:1px solid var(--line,#e3e7f0);border-radius:10px;
  padding:10px 12px;box-sizing:border-box;background:#fff;color:var(--ink,#0b1b3a)}
.pm-i:focus{outline:none;border-color:#3b82ff;box-shadow:0 0 0 3px rgba(59,130,255,.12)}
textarea.pm-i{resize:vertical;min-height:72px}
.pm-load{padding:24px;text-align:center;color:var(--mut,#5b6474);font-size:13px}

.pm-rep{border:1px solid var(--line,#e3e7f0);border-radius:12px;padding:12px;margin-bottom:9px}
.pm-rh{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.pm-rh b{font-size:13px}
.pm-rh i{font-style:normal;font-size:12px;color:var(--mut,#5b6474);margin-left:auto}
.pm-st{font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;padding:2px 8px;border-radius:99px}
.pm-st.s-open{background:var(--warn-bg);color:var(--warn)}
.pm-st.s-reviewing{background:var(--amber-bg);color:var(--amber)}
.pm-st.s-actioned{background:var(--ok-bg);color:var(--ok)}
.pm-st.s-dismissed{background:var(--paper-warm);color:var(--ink-mute)}
.pm-rd{font-size:12.5px;color:var(--ink-soft,#424551);line-height:1.5;margin:8px 0 0}
.pm-rn{font-size:12px;color:#0e7a4e;margin:8px 0 0;background:#f2fbf6;border-radius:8px;padding:7px 9px}
.pm-ra{display:flex;gap:6px;margin-top:10px}
.pm-ra button{font:inherit;font-size:12px;font-weight:700;border:1px solid var(--line,#e3e7f0);
  border-radius:999px;padding:5px 11px;background:#fff;cursor:pointer;margin:0}
.pm-ra button:hover{border-color:#3b82ff;background:#f5f8ff}
.pm-prior{background:#fdf8f0;border:1px solid #f0dfc0;border-radius:10px;padding:10px;margin-bottom:6px}
.pm-prior>b{display:block;font-size:12px;color:#9a6b00;margin-bottom:6px}
.pm-pr{font-size:12px;color:var(--ink-soft,#424551);padding:4px 0;line-height:1.45}
.pm-pr i{display:block;font-style:normal;font-size:12px;color:var(--mut,#5b6474)}
.pm-sev{font-size:12px;font-weight:800;text-transform:uppercase;padding:1px 7px;border-radius:99px;margin-right:6px}
.pm-sev.s-note{background:#eef1f7;color:#5b6474}
.pm-sev.s-warning{background:#fdf3e7;color:#9a6b00}
.pm-sev.s-final{background:#fdece8;color:#b3401f}

.pm-toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:10000;
  background:#0b1b3a;color:#fff;font-size:13px;font-weight:600;padding:11px 18px;border-radius:999px;
  box-shadow:0 12px 34px rgba(6,16,44,.35);animation:pmToast .22s cubic-bezier(.2,.8,.2,1)}
.pm-toast.bad{background:#b3401f}
.pm-toast.go{opacity:0;transform:translateX(-50%) translateY(8px);transition:.3s}
@keyframes pmToast{from{opacity:0;transform:translateX(-50%) translateY(10px)}to{opacity:1;transform:translateX(-50%)}}
.tlocked{font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;padding:2px 7px;
  border-radius:99px;background:#eef1f7;color:#5b6474;margin-left:7px}
.tpinned{background:#fdf3e7;color:#9a6b00}
.tdeleted{background:#fdece8;color:#b3401f}
@media(prefers-reduced-motion:reduce){.pm,.pm-back,.pm-toast{animation:none}}

/* Moderator queue (reports · mod log · warnings) and the removed-content markers. */
.pm-tabs{display:flex;gap:6px;margin-bottom:14px;flex-wrap:wrap}
.pm-tab{font:inherit;font-size:12.5px;font-weight:700;border:1px solid var(--line,#e3e7f0);border-radius:999px;
  padding:6px 13px;background:#fff;color:var(--mut,#5b6474);cursor:pointer;margin:0}
.pm-tab.on{background:var(--electric,#1a4dd6);border-color:var(--electric,#1a4dd6);color:#fff}
.pm-tab:hover:not(.on){border-color:var(--electric,#1a4dd6);color:var(--electric,#1a4dd6)}
.pm-qbody{min-height:120px}
.pm-logrow{border-left:2px solid var(--line,#e3e7f0);padding:8px 0 8px 12px;margin-bottom:8px}
.pm-logrow b{font-size:13px;text-transform:capitalize}
.pm-lt{font-size:12px;color:var(--mut,#5b6474);margin-left:7px}
.pm-lv{display:block;font-size:12px;color:var(--ink-soft,#424551);margin-top:3px}
.pm-logrow i{display:block;font-style:normal;font-size:12px;color:var(--mut,#5b6474);margin-top:4px}
.pm-removed{font-style:normal;font-size:12px;font-weight:700;color:#b3401f;margin-left:auto;
  background:#fdece8;border-radius:99px;padding:3px 10px}
/* A post a moderator can see but members cannot — never let the two look the same. */
.post.removed{background:repeating-linear-gradient(135deg,#fff,#fff 12px,#fdf6f4 12px,#fdf6f4 24px)}
.post.removed .pbody{opacity:.62}
/* Report affordance under a post — quiet until you look for it. */
.pfoot{display:flex;justify-content:flex-end;margin-top:12px}
/* Moderator strip on the forum index. */
.pm-strip{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:11px 16px;margin-bottom:14px;
  background:#f7f9fd;border:1px solid var(--line,#e3e7f0);border-radius:12px}
.pm-strip b{font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--electric,#1a4dd6)}
.pm-strip span{font-size:12.5px;color:var(--mut,#5b6474);margin-right:auto}


/* ============================================================================
   THE SCROLL REVEAL — .pc-reveal / .pc-in            (W9 LANE 7, ADDITIVE)
   ----------------------------------------------------------------------------
   WHAT WAS MISSING. The design law this wave asks every page to obey says
   "reveal on scroll, subtle, never janky, prefers-reduced-motion respected
   absolutely", and the platform had no shared way to do it. It had three
   private ones instead, none of which any console can reach:

     marketing-core.css:111    .rv / .rv.in        22px, .6s, its own easing
     marketing-pillar.css:99   .rv / .rv.in        the same three lines, copied
     index.html:2820           .reveal / .visible  a fourth spelling, JS-added

   A measured sweep of every root page (tests/designlaw.mjs --survey) found the
   reveal absent from every console page on the platform and present on the
   marketing pages only, in the two forks above.

   WHY THE MOTION IS ON A TOKEN CURVE AND THE FORKS ARE NOT. Both forks ease on
   cubic-bezier(.2,.7,.2,1), which neither payco.css nor this file declares
   anywhere — it is an invented curve, which is the first thing the law forbids.
   This uses cubic-bezier(0,0,.2,1), the entrance easing payco.css:38 and
   payco.css:73 already ship, so a section that reveals and a page that enters
   move on the same curve. .28s and 14px rather than .6s and 22px for the same
   reason payco.css:36 gives for the body cross-fade: long enough to read as
   motion, far too short to wait on.

   THE ARMING CLASS IS THE WHOLE SAFETY ARGUMENT. `.pc-reveal` on its own hides
   NOTHING. The hidden state is written only under `html.pc-reveal-armed`, and
   that class is added by the page's own script. A reader with JavaScript off, a
   crawler, a reader-mode extraction, or a page whose script threw before it ran
   gets the content at full opacity in its final position — because the rule that
   would have hidden it never matched. The forks above hide first and reveal
   later, so any of those four cases leaves the page blank.

   PREFERS-REDUCED-MOTION IS ABSOLUTE, and "absolutely" is doing real work in the
   law's sentence: it is not enough to remove the transition, because an element
   left at opacity:0 with no transition is not calm, it is GONE. Under reduce the
   hidden state itself is cancelled — opacity:1, transform:none — whether or not
   the observer ever fires. tests/designlaw.mjs E4 and E5 assert exactly that, in
   a real engine with the media feature emulated.

   NO TRANSFORM ON A PERSISTENT ELEMENT. `will-change` is deliberately NOT set
   here. payco.css:60-64 records why a transform in the entrance layer is
   dangerous — it makes the element the containing block for its position:fixed
   descendants and quietly breaks them — and .pc-reveal's transform is removed
   the moment the element lands, so it is transient by construction. A
   will-change:transform would make it permanent.

   MIRRORED IN css/payco-system.css AS ITS SECTION 35, byte-identical, exactly as
   this file's :root and that file's :root hold the identical numbers on purpose
   (css/payco-system.css:19-21). A page linking either sheet, or both, in either
   order, gets the same four rules.

   THE MARKUP AND THE OBSERVER are in css/payco-system.md and reproduced in
   docs/lane-reports/W9-LANE-7.md; design-law.html is the working copy. This is
   NOT a new global .js asset — a page pastes eight lines, exactly as sections
   28-31 are adopted.

   THIS BLOCK IS BELOW THE BUTTON BEHAVIOUR BLOCK and that is safe: it declares
   nothing on .pc-btn, .cta, .btn, .go or .pri, so payco.css:806-846 is still the
   last word on button behaviour and the two sheets still commute.

   ADDITIVE. Both class names were greped across every .html, .css, .js, .mjs and
   .md in the tree before they were chosen and neither existed anywhere, so no
   page on the platform changes by one pixel.
   ==========================================================================*/
.pc-reveal{transition:opacity .28s cubic-bezier(0,0,.2,1),transform .28s cubic-bezier(0,0,.2,1)}
html.pc-reveal-armed .pc-reveal{opacity:0;transform:translateY(14px)}
html.pc-reveal-armed .pc-reveal.pc-in{opacity:1;transform:none}
@media(prefers-reduced-motion:reduce){
  .pc-reveal{transition:none}
  html.pc-reveal-armed .pc-reveal,html.pc-reveal-armed .pc-reveal.pc-in{opacity:1;transform:none}
}

/* ==== end the scroll reveal ==== */


/* The access-denied panel's one sub-44px control (client-profile.html), the twelfth
   page of the tap-floor set. Same rule, same reasons, stated where that page can
   reach it — it links payco.css and not payco-marketing.css. See the block in
   payco-marketing.css for the measurement and for why the `:root ` prefix is there. */
@media (max-width: 767px) {
  :root .deny > p > a:only-child { display: inline-flex; align-items: center; min-height: 44px; }
}
