/* ============================================================================
   payco-system.css — THE CANONICAL PAYCO DESIGN SYSTEM (Phase B, lane B-1)
   ============================================================================

   WHAT THIS IS. One self-contained stylesheet that carries the whole light
   blue/white Inter system the core consoles already wear: the token names, the
   chrome, the pill buttons, the hover and press behaviour, the focus ring, the
   card and KPI language, the header shell, the mobile floors, the breakpoints.
   A page adopts the entire system by linking this ONE file and using the
   classes below. Nothing else is required — no build step, no dependency, no
   @import, and specifically no @import of payco.css (that would make this sheet
   a wrapper rather than a standalone).

   WHAT THIS IS NOT. It is not a repaint. Every value here is lifted VERBATIM
   from payco.css and from the core pages' own markup, and every one carries the
   file:line it came from in the comment beside it. The operator's decision,
   recorded in docs/lane-reports/PB-LANE-1.md, is that the light palette IS the
   canonical aesthetic; PHASE-A-SPEC.md §1.5 offered a near-black repaint as an
   option and it was declined. This lane freezes NAMES and COMPONENTS. If a
   value ever changes, it changes in ONE place — here and in payco.css's :root,
   which hold the identical numbers on purpose.

   HOW IT COMPOSES WITH payco.css. A page may link both. Every shared selector
   in this file declares the same value payco.css declares, so neither load
   order changes a pixel. One ordering subtlety is handled deliberately: the
   button BEHAVIOUR block (transition / hover / active / disabled / focus) is
   the last block in this file exactly as it is the last block in payco.css
   (payco.css:806-834), so whichever sheet loads second, the final
   `.pc-btn{transition}` declaration is the same one. Do not move that block.

   LINK ORDER for a page adopting this sheet:
       <link href="vendor/inter/inter.css" rel="stylesheet">   <- the font files
       <link rel="stylesheet" href="/css/payco-system.css">
   The Inter face is NOT bundled here. This sheet names the family in --font and
   degrades to -apple-system / Segoe UI without it; a page that wants the real
   face links vendor/inter/inter.css itself, exactly as admin.html:6 does.

   The contract lanes B-2..B-7 follow — token table, class table, the nav-include
   requirement, the mobile rules — is css/payco-system.md. The extraction record
   with every citation is docs/lane-reports/PB-LANE-1.md.
   ==========================================================================*/


/* ============================================================================
   1. TOKENS — the frozen name set (PHASE-A-SPEC.md §2.1 / §2.2 / §2.3)
   ----------------------------------------------------------------------------
   These names are FROZEN. No page re-declares any of them under a new spelling.
   A page that writes its own --fn-ink / --lb-ink / --px-ink / --rf-ink opts out
   of every future contrast fix made here — that is precisely the divergence
   PHASE-A-SPEC.md §1.4(b) documents on the fifteen blue consoles.
   ==========================================================================*/
:root{
  /* ---- ink / paper ---- payco.css:80-82 ---- */
  --ink:#0a1628;                    /* payco.css:80  body text                */
  --ink-soft:#2a3a5c;               /* payco.css:80  secondary text           */
  --ink-mute:#5a6885;               /* payco.css:80  muted text (5.59:1 on white) */
  --paper:#fbfcfe;                  /* payco.css:81  near-white surface       */
  --paper-warm:#f4f6fb;             /* payco.css:81  recessed surface         */
  --white:#fff;                     /* payco.css:81  card surface             */
  --line:#dce3f0;                   /* payco.css:82  border                   */
  --line-soft:#eaeff7;              /* payco.css:82  hairline border          */

  /* ---- brand ---- payco.css:84-95 ---- */
  --blue:#1a4dd6;                   /* payco.css:84  accent, primary          */
  --blue-deep:#0b2a8a;              /* payco.css:84  accent, deep             */
  --blue-electric:#3b82ff;          /* payco.css:84  accent, electric — FOCUS  */
  --blue-cyan:#6dd5fa;              /* payco.css:84  accent, light end        */
  /* --gradient is a BACKGROUND ramp. It runs to #6dd5fa and measures 1.67:1 as
     text on white. If you are filling GLYPHS reach for --gradient-text, never
     this one. The rule is load-bearing and is quoted from payco.css:86-92. */
  --gradient:linear-gradient(135deg,#0b2a8a 0%,#1a4dd6 35%,#3b82ff 70%,#6dd5fa 100%);   /* payco.css:85 */
  --gradient-text:linear-gradient(135deg,#0b2a8a 0%,#1a4dd6 45%,#2a63d8 100%);          /* payco.css:93 */
  --gradient-text-flow:linear-gradient(90deg,#0b2a8a,#1a4dd6,#2158cf,#2a63d8,#2158cf,#1a4dd6,#0b2a8a); /* payco.css:94 */
  --gradient-soft:linear-gradient(135deg,rgba(11,42,138,.04) 0%,rgba(109,213,250,.06) 100%); /* payco.css:95 */

  /* ---- semantic ---- payco.css:97 / payco.css:101 ---- */
  --ok:#0e7a4e;      --ok-bg:#e7f5ee;      /* payco.css:97  success  */
  --warn:#b3401f;    --warn-bg:#fdece8;    /* payco.css:97  danger   */
  --amber:#8f6300;   --amber-bg:#fdf3e0;   /* payco.css:101 caution  */
  --info:#1a4dd6;    --info-bg:#eaf1ff;    /* payco.css:101 info     */

  /* ---- the RISK ramp — the only tokens in this sheet with no payco.css line ----
     ADDED AT THE PHASE-B CLOSEOUT, on PB-LANE-2 §8 P3's patch request, and the exception is
     deliberate rather than an oversight. Every other token above cites the payco.css line it was
     extracted from, because this sheet's job is to be the one copy of values that already exist.
     These three do not exist in payco.css: onboarding.html and checklist.html both declare them
     privately, and Phase-A §1.4 files that as the FORK-LIGHT defect — a value re-declared in a
     page cannot stop being a fork until the canonical sheet carries it. So the sheet carries it.

     WHAT THE ROLE IS, because a colour with no role is a repaint and the operator declined one:
     --warn is "this action is dangerous" and --amber is "look before you continue". Neither is
     what these paint. The risk ramp is the "what you are paying TODAY" side of a comparison — the
     incumbent processor's numbers, sitting beside ours — which is not a warning, not a caution and
     not an error. It is a second subject, and it needs a hue that is legibly not our blue and
     legibly not our red. THIS IS NOT A NEW HUE: it is the burnt orange those two pages already
     ship, lifted here byte-for-byte, so nothing on screen changes today.

     CONTRAST, measured rather than assumed: --risk-deep #8a3d12 on --white is 7.2:1 and on
     --paper #fbfcfe is 7.4:1 — AA at every size and AAA for body text, which is what the
     `.risk-banner b` rule that uses it needs. --risk #b8541a is 4.6:1 on white: AA for normal
     text, and it is used for borders, dots and 5%-alpha fills rather than for prose.
     Documented in payco-system.md §1 with the same reasoning. */
  --risk:#b8541a;                   /* the incumbent's side of a comparison — NOT a warning */
  --risk-deep:#8a3d12;              /* 7.2:1 on white — the readable weight for prose        */
  --risk-gradient:linear-gradient(135deg,#8a3d12 0%,#b8541a 50%,#e08544 100%);

  /* ---- elevation ---- payco.css:103-105 ---- */
  --shadow-sm:0 1px 2px rgba(10,22,40,.04),0 1px 1px rgba(10,22,40,.06);        /* payco.css:103 resting */
  --shadow-md:0 4px 24px rgba(11,42,138,.08),0 1px 2px rgba(10,22,40,.04);      /* payco.css:104 raised  */
  --shadow-lg:0 20px 60px rgba(11,42,138,.12),0 4px 12px rgba(10,22,40,.06);    /* payco.css:105 modal   */

  /* ---- geometry ---- payco.css:107 ---- */
  --radius-sm:10px;                 /* payco.css:107 */
  --radius:16px;                    /* payco.css:107 */
  --radius-lg:28px;                 /* payco.css:107 */
  --maxw:1240px;                    /* payco.css:107 content max width */

  /* ---- the sliver — the house signature ---- payco.css:109-112 ---- */
  --sliver:linear-gradient(90deg,#0b2a8a,#1a4dd6,#3b82ff,#6dd5fa);              /* payco.css:109 */
  --sliver-warn:linear-gradient(90deg,#6e150c,#9a2018,#b3401f,#e0663f);         /* payco.css:110 */
  --sliver-h:3px;                   /* payco.css:111 resting height */
  --sliver-h-hover:4px;             /* payco.css:111 hover height   */
  --sliver-ease:.18s cubic-bezier(.4,0,.2,1);                                   /* payco.css:112 */

  /* ---- type ---- payco.css:114-115 ---- */
  --font:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;        /* payco.css:114 */
  --mono:'SF Mono','JetBrains Mono',ui-monospace,monospace;                     /* payco.css:115 */

  /* ---- legacy aliases — payco.css:135-136 ----
     Kept because ~16 call sites across the tree still write var(--electric) and
     var(--mut). Aliased, never re-forked: a page that re-declares --mut in its
     own :root silently opts out of every future contrast fix. */
  --electric:var(--blue);           /* payco.css:135 */
  --navy:var(--blue-deep);          /* payco.css:135 */
  --cyan:var(--blue-cyan);          /* payco.css:135 */
  --mut:#5a6885;                    /* payco.css:136 aliases --ink-mute */
  --bg:#f5f7fb;                     /* payco.css:136 the APP background — not --paper */

  /* ---- button elevation ---- payco.css:816-817 ---- */
  --btn-shadow-hover:0 6px 18px rgba(26,77,214,.32);                            /* payco.css:816 */
  --btn-shadow-active:0 2px 8px rgba(26,77,214,.24);                            /* payco.css:817 */

  /* ---- SPACING RHYTHM — NEW NAMES, EXISTING VALUES ----
     PHASE-A-SPEC.md §2.3 records that there is no --space-* scale in payco.css:
     the rhythm is expressed as literals inside the chrome rules and it is
     already consistent. §2.3's RECOMMENDATION is to promote exactly these five
     numbers to names WITHOUT changing any value, so a reconciled page has
     something to import instead of re-typing the literal. That is what these
     are. A value-preserving rename cannot regress a screenshot. */
  --pad-card:20px;                  /* payco.css:507 .pc-card padding        */
  --gap-grid:16px;                  /* payco.css:510 .pc-grid gap            */
  --gap-kpi:12px;                   /* payco.css:514 .pc-kpis gap            */
  --header-h:74px;                  /* payco.css:175 .pc-header height       */
  --rail-w:246px;                   /* payco.css:362 .pc-sidenav width       */

  /* ---- SIXTH NEW NAME, EXISTING VALUE ----
     The three core consoles all raise a panel above --shadow-sm for their top-
     level band, and they disagree on the numbers: admin.html:81 and staff.html:89
     both ship `0 1px 2px rgba(11,27,58,.04),0 10px 30px rgba(11,27,58,.05)`,
     operations.html:24 ships `0 1px 2px rgba(11,27,58,.04),0 14px 38px
     rgba(11,27,58,.06)`. operations.html wins — it is the only one of the three
     that builds its band out of the canonical .pc-card/.pc-kpi classes rather
     than a private .card/.kpi fork, so it is the most-polished instance. The
     rejected admin/staff value is recorded in payco-system.md and in the lane
     report. --shadow-sm is UNCHANGED and remains the resting card elevation. */
  --shadow-deck:0 1px 2px rgba(11,27,58,.04),0 14px 38px rgba(11,27,58,.06);    /* operations.html:24 */

  /* ---- THE DIVIDER WEIGHT — ONE HAIR THICKER SITEWIDE (W7 LANE 1 §E) --------
     Every divider on the platform was 1px, and at 1px against --line on
     --paper the edge of a card is a suggestion rather than a boundary: on a
     high-DPI laptop it renders as a half-intensity grey line and on a scaled
     Windows display it disappears entirely on alternate rows. The wave brief
     asks for one hair more, everywhere.

     IT IS A TOKEN AND NOT A FIND-AND-REPLACE, and that is the whole reason it
     can be "sitewide": a page that wants the platform weight writes
     `var(--hair)` and never has to know the number, and retuning it is one edit
     here rather than forty across the tree.

     WHAT IT DOES NOT TOUCH. Control edges — buttons, inputs, selects, the
     identity chip — keep their own 1px. A divider separates two things; a
     control's border is the thing you have to find before you can click it, and
     tests/mobilewave2.mjs measures those against a contrast floor that a weight
     change would move underneath it. Two jobs, two weights, and only one of them
     is what the brief calls a divider.

     --hair-thin is for the places that must stay hairline no matter what --hair
     becomes. THE APP BAR IS THE IMPORTANT ONE, and it is not a matter of taste:
     .pc-header is 74px of BORDER box, so a 2px bottom border eats a second pixel
     of content height and re-centres the wordmark 1px up. The marketing pages
     draw the same bar from payco-marketing.css, which this file cannot reach, so
     thickening it here moves the logo on every console page and leaves index.html
     where it was — which is precisely the drift tests/headerparity.mjs exists to
     catch (it went 2850/0 -> 2782/68, all of them "logo top matches index").
     The bar is a SEAM between two stylesheets before it is a divider, so it keeps
     the hairline until both sides can move together; the lane report carries that
     as an interface request against payco-marketing.css. */
  /* ---- THE MOTION SCALE — NEW NAMES, THE VALUES THE TREE ALREADY WINS WITH (W5 POLISH) ----
     MEASURED FIRST. Across the fifty-one pages of the four main surfaces and every stylesheet
     they link, the platform ships THIRTY-EIGHT distinct transition/animation durations, and five
     stylesheets each declare a PRIVATE three-name scale of their own with a different answer:

         --pc-fast  .16s   --pc-slow  .34s   --pc-ease   cubic-bezier(.32,.72,0,1)   (hud)
         --ind-fast .18s   --ind-slow .42s   --ind-ease  cubic-bezier(.32,.72,0,1)   (industries)
         --pci-fast .18s   --pci-slow .42s   --pci-ease  cubic-bezier(.32,.72,0,1)   (industries)
         --inv-fast .16s   --inv-slow .26s   --inv-ease  cubic-bezier(.4,0,.2,1)     (invoice)
         --sliver-ease .18s cubic-bezier(.4,0,.2,1)                                  (FROZEN)

     That is the FORK-LIGHT defect PHASE-A §1.4 names, in the one axis nobody had promoted to a
     token here: this sheet had no motion scale at all, so every sheet that wanted one invented it.

     THE THREE VALUES ARE NOT NEW. .16s and .34s are --pc-fast and --pc-slow verbatim, the pair the
     HUD has shipped since W7 and the pair four suites' settle windows are written against
     (tests/hudmount.mjs:1232, integrationproof-w8:1425, navheader:232, statementlead:1112 all
     quote "--pc-slow is .34s"). .22s is the middle step the tree already reaches for most often
     after those two — .2s appears 98 times and .22s 50 across the surfaces — and it is what
     --inv-slow's .26s rounds to. The curve is cubic-bezier(.32,.72,0,1), which four of the six
     private scales already declare.

     EVERY RE-POINT BELOW MAKES MOTION EQUAL OR FASTER, NEVER SLOWER — .18 -> .16, .42 -> .34,
     .26 -> .22 — so a suite that waits out a transition still waits long enough, and the operator's
     "fast" is the direction of the whole change rather than a claim about it.
     --sliver-ease is UNTOUCHED: tests/designsystem.mjs:243 pins it at its exact string. */
  --dur-fast:.16s;                  /* --pc-fast, verbatim                     */
  --dur-base:.22s;                  /* the middle step, the one the tree uses  */
  --dur-slow:.34s;                  /* --pc-slow, verbatim                     */
  --ease-std:cubic-bezier(.32,.72,0,1);   /* four of the six private scales already say this */

  --hair:2px;                       /* the platform divider weight (was 1px)  */
  --hair-thin:1px;                  /* control edges + true hairlines          */
}


/* ============================================================================
   1b. DARK THEME — THE TOKEN RE-POINT (W8 LANE 15)
   ----------------------------------------------------------------------------
   WHAT WAS BROKEN. Until this block, this sheet had no dark theme at all. The
   HUD contract (css/payco-hud.css, lane 14) has shipped --pc-* tokens on :root
   and on [data-theme="dark"] since W7, and payco-hud.js:511 flips that attribute
   from the taskbar's theme widget on EVERY page. So the platform already had a
   working switch wired to a shell that could not follow it: the HUD went dark and
   the body, the cards, the tables, the rail and the header stayed hard white.
   partners.html:71-73 names that exact failure — "a half-themed page: HUD-aware
   panels on a hard-white body" — and settings.html:19-21 carries a whole private
   token block whose own comment calls itself "A STAND-IN FOR css/payco-hud.css …
   delete from here to the end marker" when the shared one lands. This is the
   shared one, on the sheet the consoles actually link.

   IT IS A VARIABLE OVERRIDE AND NOTHING ELSE, which is the HUD contract's own
   rule (css/payco-hud.css:24 and css/payco-hud-widgets.css:58: "[data-theme=
   'dark'] overrides VARIABLES, never rules"). Section 1b re-points names.
   Section 34 at the foot of this file is the short list of places that could not
   be fixed by a name because the rule paints a literal, and every one of those is
   itemised there with the reason.

   NO NEW COLOUR ENTERS THE SYSTEM HERE. Every value below is var() of a --pc-*
   token that lane 14 already defines under [data-theme="dark"]. The fallback
   after each comma is that token's own value copied from css/payco-hud.css:80-99,
   written in the hsl() notation that file uses deliberately so a grep for `#`
   cannot mistake it for a new hex (css/payco-hud.css:11-14). The fallback is
   load-bearing rather than defensive: the HUD sheet is injected by payco-nav.js
   and is NOT in PUBLIC_ASSETS, so an anonymous reader on a marketing or industry
   page gets a 401 for it and would otherwise land here with every token empty.

   NOTHING IS DELETED. tests/crmui.mjs:152 builds its "colours this platform
   already knows" set by grepping THIS FILE plus payco.css, so removing any hex
   above shrinks that set and turns other lanes' pages red for a colour they did
   not change. Every light value above is left byte-for-byte where it was; this
   block only adds a second column.

   WHY THE ROLES MOVE THE WAY THEY DO — this is a role map, not a lightness flip:

     --bg          the app backdrop. Takes --pc-surface, the DARKEST step, because
                   in dark the backdrop is the floor and elevation reads lighter.
                   In light --bg is the recessed one, so the role is preserved and
                   only the direction of "recessed" changes.
     --white       the card. One step up from the floor (--pc-surface-2), so a
                   panel separates from the page by its own surface and not only
                   by its border. This is why --white is NOT --pc-surface: the HUD
                   rail is chrome and is allowed to sit darker than the content.
     --paper       the near-white surface — same step as the card.
     --paper-warm  the recess INSIDE a card (table head, row band, hover, ghost
                   button). One step up again, --pc-surface-3.
     --blue-deep   inverts to the BRIGHTEST accent. "Deep" means "more emphasis",
                   and on a dark field more emphasis is lighter, not darker. Left
                   pointing at --pc-accent-deep it measures 1.87:1 on --paper-warm
                   and the ghost button's label disappears; --pc-accent-hi
                   measures 7.7:1 there. settings.html:70 makes the same inversion.
     --info/-bg    the accent tint. --pc-accent-soft is a 16%-alpha accent that
                   sits OVER whatever surface it lands on, so a selected table row
                   tints correctly on the card and on the page.
     --shadow-*    dark shadows are invisible on dark surfaces; lane 14 already
                   ships a re-tuned pair and this reads them.

   --ok / --amber  the caution and success ramps, and W8 MOVES THEM. The previous
                   wave left them frozen on the reasoning that a status chip is a
                   SELF-CONTAINED pair and that moving the ink without the fill is
                   how you get salmon on pink. That reasoning was right about the
                   chips and wrong about the total: measured on the dark card,
                   --ok #0e7a4e is 3.06:1 and --amber #8f6300 is 3.09:1, and the
                   ramps are read as bare ink far more often than they are read as
                   a chip — 265 call sites across 30 pages. Both numbers are under
                   AA for text, so the frozen position was not "legible, not a
                   regression"; it was a floor failure everywhere the token is used
                   as ink rather than as a chip.

                   The pin is moved the way --warn's was: THE INK AND THE FILL MOVE
                   TOGETHER. -bg goes to the same hue at 14% alpha, exactly the
                   --pc-danger-wash shape, so .pc-pill.ok and .pc-pill.amber stay a
                   matched pair (5.6:1 and 5.9:1 measured) instead of becoming
                   lifted ink on a light fill. Lane 14 still publishes no --pc-ok
                   and no --pc-amber, so each name is consumed as
                   var(--pc-ok, <hsl>) with a fallback that is correct on its own:
                   this is the 401 path every public page takes, and it means the
                   theme is right today and defers to lane 14's column the moment
                   that sheet ships. IR-3 stays open for that; it is no longer a
                   blocker.

                   THE INVERSION THIS CREATES IS REPAIRED, NOT SHIPPED. Lifting a
                   ramp turns every rule that puts light text ON it into the mirror
                   of the bug being fixed — white on the lifted --ok measures
                   2.26:1 where it was 5.37:1. Section 34.10 repairs each such rule
                   against --pc-ink-invert (8.05:1 and 8.49:1 measured), the same
                   move section 34.7 already makes for .pc-btn.danger. The sites
                   are enumerated there from a browser sweep, not from a grep.
   ==========================================================================*/
[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 ----
     The gradients are NOT re-pointed. They run through the deep end of the brand
     blue and they are used as FILLS behind white text (.pc-btn, .pc-tag, the
     header hairline, the panel sliver), so they carry their own contrast with
     them and read the same on either theme. That is the one thing the HUD
     contract asks to be preserved across themes (css/payco-hud.css:25-26). */
  --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 ----
     THE ONE SEMANTIC ROLE THAT MOVES, because it is the one lane 14 published a
     dark column for. Its own comment (css/payco-hud-widgets.css:59-60) is the
     measurement that forced it: "the reds lift because the light red measures
     under 3:1 on a near-black surface, and a warning nobody can read is not a
     warning". #b3401f on the dark card measures 1.61:1 here.

     THE INK AND THE FILL MOVE TOGETHER, which is the whole reason this role can
     move and --ok / --amber cannot: --pc-danger-wash is the same red at 10-14%
     alpha, so .pc-btn.warn and .pc-pill.warn keep a matched pair instead of
     becoming lifted salmon on light pink. The two rules that invert the pair —
     .pc-btn.danger and .pc-pill.bad, which put #fff ON --warn — are repaired in
     section 34.7 against --pc-danger-ink, lane 14's name for that exact job.

     NO CYCLE, and it is worth saying why because the shape looks like one:
     css/payco-hud-widgets.css:51 declares --pc-danger:var(--warn,…) on :root, so
     in LIGHT the reference runs this way and --warn is the literal at line 78. In
     DARK that file's own [data-theme="dark"] block re-declares --pc-danger as a
     plain rgb(), so the chain terminates one step in. Verified by reading the
     resolved value back out of the cascade in Chromium, both with that sheet
     loaded and with it absent — tests/darktheme.mjs section 3 keeps that true. */
  --warn:var(--pc-danger,rgb(255 138 110));
  --warn-bg:var(--pc-danger-wash,rgb(255 138 110 / .14));

  /* ---- the success and caution ramps ----
     Moved in W8. The light literals measure 3.06:1 (--ok) and 3.09:1 (--amber) on
     the dark card, both under the 4.5 AA floor for text, and both are read as bare
     ink far more often than as a chip. The lifted values are tuned to sit at the
     same weight as the danger ramp above — 7.27:1 and 7.67:1 on --pc-surface-2
     against its 7.13:1 — so the three status colours read as one family and no one
     of them shouts louder than the others. The fill follows the ink at 14% alpha,
     which is what keeps .pc-pill.ok / .pc-pill.amber a matched pair.
     --pc-ok / --pc-amber are lane 14's names and do not exist yet; the fallback is
     the value that ships today and the var() is the seam that hands the role over
     the moment lane 14 publishes a dark column. See section 34.10 for the rules
     that put light text ON these fills and are repaired because of this move. */
  --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));
  --shadow-deck:var(--pc-shadow,0 1px 2px hsl(220 30% 2% / .5),0 14px 38px 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 line 136-137, not
     aliases, so they have to be re-pointed by hand or the ~16 call sites that
     write var(--mut) keep the light grey. */
  --mut:var(--pc-ink-mute,hsl(216 11% 64%));
  --bg:var(--pc-surface,hsl(220 18% 9%));
}


/* ============================================================================
   2. BASE — payco.css:138-151, payco.css:919-920
   ==========================================================================*/
*{box-sizing:border-box}                                    /* payco.css:138 */
html{-webkit-text-size-adjust:100%;background:var(--paper,#fbfcfe)}   /* payco.css:139 / payco.css:26 */
/* The app background is --bg (#f5f7fb), NOT --paper. --paper stays the
   near-white for surfaces that sit ON this background — cards, drops, the
   header. payco.css:140-146 records that losing that distinction shipped three
   different backgrounds across sibling consoles. */
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}  /* payco.css:147-148 */
/* 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}                   /* payco.css:149 */
h1,h2,h3,h4{margin:0;font-weight:700;letter-spacing:-.02em;color:var(--ink)}  /* payco.css:150 */
::selection{background:rgba(59,130,255,.18)}                /* payco.css:151 */

/* The horizontal-overflow guard. TWO declarations of overflow-x, deliberately.
   `clip` is the correct one (it does not create a scroll container, so
   position:sticky still works inside), but Safari <=15 / iOS 15 discard it as
   invalid and would be left with NO guard at all. Delete neither, do not
   reorder. Quoted verbatim from payco.css:912-920. */
html, body { max-width: 100%; overflow-x: hidden; overflow-x: clip; }   /* payco.css:919 */
img, svg, video, canvas, table { max-width: 100%; }                     /* payco.css:920 */


/* ============================================================================
   3. ENTRANCE MOTION — payco.css:35-76
   ----------------------------------------------------------------------------
   Two layers. The view-transition layer is Chrome-and-friends only and is
   invisible when unsupported, so "smooth" cannot be built on it; the pcSoftIn
   fallback is what actually does the work, in every browser, with no JS.
   Opacity only — a transform here would make these elements the containing
   block for their position:fixed descendants and quietly break them.
   ==========================================================================*/
@media(prefers-reduced-motion:no-preference){
  .pc-header{view-transition-name:pc-header}                /* payco.css:31 */
  .pc-viewbar{view-transition-name:pc-viewbar}              /* payco.css:32 */
}
::view-transition-old(root){animation:pcFadeOut .16s cubic-bezier(.4,0,1,1) both}   /* payco.css:37 */
::view-transition-new(root){animation:pcFadeIn .22s cubic-bezier(0,0,.2,1) both}    /* payco.css:38 */
@keyframes pcFadeOut{to{opacity:0;transform:translateY(-3px)}}                       /* payco.css:39 */
@keyframes pcFadeIn{from{opacity:0;transform:translateY(6px)}}                       /* payco.css:40 */
/* The header holds still while the body changes underneath it. payco.css:42-45 */
::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}  /* payco.css:48 */
}
/* will-change:opacity promotes the incoming subtree to ONE compositor layer so
   the card background and its 3px sliver land in the same frame — without it
   they read as a "white then blue" flicker on the KPI cards. payco.css:62-69 */
@keyframes pcSoftIn{from{opacity:0}to{opacity:1}}           /* payco.css:70 */
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}  /* payco.css:71-74 */
@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}  /* payco.css:75 */
}


/* ============================================================================
   4. SKIP LINK — payco.css:159-165
   ----------------------------------------------------------------------------
   Every page here is a sticky header with a full nav in front of the content.
   Off-screen until focused, so it costs nothing visually; z-index clears the
   header's 50. payco-nav.js injects it on the pages that load it; a page that
   does not load the nav writes <a class="pc-skip" href="#pc-content"> directly,
   exactly as operations.html:204 does.
   ==========================================================================*/
.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)}               /* payco.css:159-162 */
.pc-skip:focus{top:0}                                       /* payco.css:163 */
@media(prefers-reduced-motion:reduce){.pc-skip{transition:none}}   /* payco.css:165 */


/* ============================================================================
   5. APP HEADER — payco.css:173-231
   ----------------------------------------------------------------------------
   THE MARKUP IS PART OF THE CONTRACT. payco-nav.js does
   `const nav = document.querySelector('header nav'); if (!nav) return;`
   (payco-nav.js:1060-1061) and builds the home link by wrapping .pc-logo in an
   a.pc-home (payco-nav.js:1069-1078). A page therefore needs BOTH
   <script src="payco-nav.js"></script> AND a header.pc-header containing a
   <nav> and a .pc-logo, or it gets no nav and no way home — which is exactly
   the "header-only nav, no home link" of PHASE-A-SPEC.md §1.4(c). The exact
   markup to copy is in css/payco-system.md.

   The horizontal inset and the gap are payco-marketing.css's .mnav numbers
   VERBATIM (clamp(18px,3vw,40px) and 20px), not near-misses, so the wordmark
   does not move on a marketing -> app click. payco.css:167-172.
   ==========================================================================*/
.pc-header{background:rgba(251,252,254,.86);backdrop-filter:blur(5px);
  -webkit-backdrop-filter:blur(5px);border-bottom:var(--hair-thin) solid var(--line);
  padding:0 clamp(18px,3vw,40px);height:var(--header-h);display:flex;align-items:center;gap:20px;
  position:sticky;top:0;z-index:50}                         /* payco.css:173-176, height 74px = --header-h */
/* the index signature — a STATIC gradient hairline under the bar. No animation
   loop: a forever-looping sweep burns CPU for a decoration nobody asked to
   keep. No overflow:hidden either — it clipped the nav dropdowns. payco.css:177-182 */
.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}                           /* payco.css:178-180 */
/* aspect-ratio reserves the logo's box before the image decodes; width:auto is
   load-bearing, not tidying — a width ATTRIBUTE is a presentational hint for
   the width PROPERTY, and without width:auto the 38 tags carrying width="163"
   painted the wordmark at a 9% horizontal squash. 640/157 is logo.png's real
   intrinsic size; if logo.png is re-exported, this pair moves with it.
   payco.css:183-205. */
.pc-logo{height:44px;width:auto;aspect-ratio:640/157;display:block;position:relative;z-index:1}  /* payco.css:206 */
@media(max-width:600px){.pc-logo{height:34px}}              /* payco.css:212 */
/* Logo -> home. The glow paints only on hover, so idle CPU is zero.
   min-height:44px is stated HERE and not only in the <=560px layer: the image
   is 34px from 600px down, so between 600 and 561 the most-tapped control on
   any app page was a 34px target with nothing holding it open. payco.css:213-217 */
.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}   /* payco.css:218-220 */
.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))}  /* payco.css:221-222 */
.pc-home:active{transform:translateY(0)}                    /* payco.css:223 */
/* The page title in the bar. 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. payco.css:224-227 */
.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}                                                 /* payco.css:228-230 */
.pc-nav{flex:1;margin-left:0;display:flex;align-items:center;justify-content:flex-end;position:static;z-index:50}  /* payco.css:231 */
.pc-sep{width:var(--hair);height:22px;background:var(--line);margin:0 3px}   /* payco.css:344 */


/* ============================================================================
   6. NAV MENUS — the type sizes, at their ORIGINAL values
   ----------------------------------------------------------------------------
   PHASE-A-SPEC.md §3.1 is the restore order and this sheet ships the RESTORED
   numbers, not the shrunken ones currently shipping.

     .pc-burger-sec a   14px    payco.css:331 — unchanged since commit 0597237
                                ("Payco - initial launch"). payco-nav.js:2170
                                shadows it to 13.5px and payco-nav.js:2181
                                shadows it again to 13px on any plane carrying
                                more than 20 destinations. Staff carries 32
                                (payco-nav.js:255-270), so staff ALWAYS render
                                at the 13px floor today. 14px is the original.
     .pc-burger-h       12px    payco.css:329. payco-nav.js:2148 shadows the
                                mega caption to 11.5px. 12px is the original.
     .pc-drop-menu a    13.5px  payco.css:285. This one was NEVER shrunk and
                                must NOT be raised — it is the bar dropdown, and
                                raising it makes the bar and the panel disagree.

   Deleting the payco-nav.js overrides is lane B-2's job (§3.1 items 1-5); this
   sheet only states what the correct sizes are, so nothing downstream has to go
   looking for them.
   ==========================================================================*/
.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}          /* payco.css:285-287 */
/* Leading accent bar slides in from the left on hover — a considered touch that
   costs only a transform, no repaint of a gradient. The fallback names the
   token this sheet OWNS: var(--electric) alone was undefined on 14 pages and
   the whole declaration was invalid at computed-value time, so the bar never
   painted. payco.css:288-296. */
.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}         /* payco.css:297-298 */
.pc-drop-menu a:hover{background:var(--paper-warm);color:var(--ink)}   /* payco.css:299 */
.pc-drop-menu a:hover::before{transform:translateX(0);opacity:1}       /* payco.css:300 */
.pc-drop-menu a.on{background:var(--gradient);color:#fff}              /* payco.css:301 */
.pc-drop-menu a.on::before{opacity:0}                                  /* payco.css:302 */

.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}    /* payco.css:329-330 — THE RESTORE TARGET */
.pc-burger-sec{margin-top:12px}                             /* payco.css:327 */
.pc-burger-sec:first-of-type{margin-top:0}                  /* payco.css:328 */
.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}          /* payco.css:331-333 — THE RESTORE TARGET */
.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}         /* payco.css:335-336 */
.pc-burger-sec a:hover{background:var(--paper-warm);color:var(--ink)}   /* payco.css:337 */
.pc-burger-sec a:hover::before{transform:translateX(0);opacity:1}       /* payco.css:338 */
.pc-burger-sec a.on{background:var(--gradient);color:#fff}              /* payco.css:339 */
.pc-burger-sec a.on::before{opacity:0}                                  /* payco.css:340 */
@keyframes pcMenuIn{from{opacity:0;transform:translateY(-6px) scale(.97)}to{opacity:1;transform:none}}  /* payco.css:284 */
@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}}  /* payco.css:341-343 */


/* ============================================================================
   7. LAYOUT — payco.css:443-455, payco.css:885
   ----------------------------------------------------------------------------
   THE RESOLVED INCONSISTENCY. Three core consoles, three content widths:
     operations.html:207   <div class="pc-wrap wide">  -> 1560px (payco.css:885)  CHOSEN
     admin.html:21         .wrap{max-width:1520px}                                REJECTED
     staff.html:19         .wrap{max-width:1280px}                                REJECTED
   operations.html wins because it is the only one of the three that uses the
   SHARED class and the shared token rather than a private .wrap fork with a
   hand-typed number — 1520 and 1280 are near-misses of 1560 and of --maxw's
   1240 respectively, and neither has a reason recorded anywhere. Default is
   .pc-wrap at --maxw (1240px); a genuinely wide console adds .wide (1560px).
   ==========================================================================*/
.pc-wrap{max-width:var(--maxw);margin:0 auto;padding:clamp(16px,2.4vw,28px) clamp(16px,2.5vw,30px)}  /* payco.css:444 */
.pc-wrap.wide{max-width:1560px}                             /* payco.css:885 */
.pc-shell{display:block}                                    /* payco.css:452 */
.pc-main{max-width:var(--maxw);margin:0 auto;padding:clamp(16px,2.4vw,28px) clamp(16px,2.5vw,30px)}  /* payco.css:453 */
.pc-head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--gap-grid);flex-wrap:wrap;margin-bottom:18px}  /* payco.css:454, gap 16px = --gap-grid */
.pc-head h1{font-size:clamp(19px,2.4vw,25px)}               /* payco.css:455 */
.pc-head .sub{color:var(--ink-mute);font-size:13px;margin-top:3px}   /* payco.css:456 */


/* ============================================================================
   8. THE SLIVER — payco.css:457-503
   ----------------------------------------------------------------------------
   The blue bar across the top of a panel. 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.
   background-clip:border-box (the default, left alone) — the bar is trimmed to
     the panel's OWN corner curve, so its ends taper the way the corners do. No
     overflow:hidden, no pseudo-element, no hand-authored radius to get wrong.

   The hover is 3px -> 4px over 180ms, triggered by raising --sliver-h rather
   than by re-declaring background-size. That indirection is load-bearing: a
   panel's background is declared in its own page's <style>, which loads after
   this file, so a background-size hover would silently lose on specificity.
   Nobody competes to SET --sliver-h — pages only read it.

   @property makes it inherits:false, which stops a hovered panel from
   thickening the slivers of panels nested inside it. The :root value above is
   the safety net for engines without @property.
   ==========================================================================*/
@property --sliver-h{syntax:'<length>';inherits:false;initial-value:3px}   /* payco.css:491 */
.pc-sliver{background:var(--sliver) top center/100% var(--sliver-h) no-repeat,var(--white);
  background-origin:border-box}                             /* payco.css:492-493 */
.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}   /* payco.css:497-499 */
.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)}   /* payco.css:500-501 */
@media(prefers-reduced-motion:reduce){
  .pc-sliver,.pc-card,.card,.panel,.deck,.detailband,.qadd,.cc-kpi,.kpis,.pc-kpi{transition:none}}   /* payco.css:502-503 */


/* ============================================================================
   9. CARDS / PANELS — payco.css:506-511 (PHASE-A-SPEC.md §2.4)
   ----------------------------------------------------------------------------
   THE RESOLVED INCONSISTENCY, card heading. Three treatments shipped:
     payco.css:508     .pc-card>h2,h3  14px, flex, gap 8px, margin-bottom 14px  CHOSEN
     admin.html:82-83  .card h3        13.5px/800 + a 7px dot ::before          REJECTED
     staff.html:90     .card h2,h3     13px/800/uppercase/.06em/var(--navy)     REJECTED
   The shared sheet's 14px wins: it is the value PHASE-A-SPEC.md §2.2 names as
   canonical, it is the only one of the three that is not page-private, and the
   two page forks disagree with EACH OTHER (13.5px sentence case with a dot vs
   13px uppercase navy), so neither can be "the" house style. A page that wants
   the eyebrow treatment uses .pc-stage below, which is a section divider rather
   than a card title.

   THE RESOLVED INCONSISTENCY, panel class. operations.html builds its band out
   of .pc-card / .pc-kpi / .pc-kpis (operations.html:28-30,57) and reads --white
   and --sliver as tokens; admin.html:21-25 and staff.html:19-20 fork private
   .card / .kpi / .kpis rules with literal #fff and a hand-typed 14px/16px
   radius. operations.html wins — same reason as the width above.
   ==========================================================================*/
.pc-card{background:var(--white);border:var(--hair) solid var(--line);border-radius:var(--radius);
  padding:var(--pad-card);box-shadow:var(--shadow-sm);position:relative}   /* payco.css:506-507, padding 20px = --pad-card */
.pc-card>h2,.pc-card>h3{font-size:14px;margin-bottom:14px;display:flex;align-items:center;gap:8px}  /* payco.css:508 */
.pc-card.pad0{padding:0;overflow:hidden}                    /* payco.css:509 */
.pc-grid{display:grid;gap:var(--gap-grid)}                  /* payco.css:510, gap 16px = --gap-grid */
@media(min-width:900px){.pc-2{grid-template-columns:1fr 1fr}.pc-3{grid-template-columns:repeat(3,1fr)}}  /* payco.css:511 */

/* .pc-deck — NEW CLASS NAME, VALUES VERBATIM FROM operations.html:22-25.
   The master horizontal band the three consoles all reach for and none of them
   had a shared name for. Chosen instance: operations.html:22-25 (18px radius,
   18px 22px 0 padding, --shadow-deck, the sliver as a background layer).
   Rejected: admin.html:79-81 and staff.html:87-89, which paint the same idea at
   16px radius with the 10px/30px shadow and no top padding rhythm. .hr-mode
   swaps the ramp to --sliver-warn, exactly as operations.html:26 does. */
.pc-deck{position:relative;background:var(--sliver) top center/100% var(--sliver-h) no-repeat,var(--white);
  background-origin:border-box;border:var(--hair) solid var(--line);border-radius:18px;
  padding:18px 22px 0;margin-top:4px;box-shadow:var(--shadow-deck)}   /* operations.html:22-25 */
body.hr-mode .pc-deck{background:var(--sliver-warn) top center/100% var(--sliver-h) no-repeat,var(--white);
  background-origin:border-box}                             /* operations.html:26 */

/* .pc-stage — NEW CLASS NAME, VALUES VERBATIM FROM admin.html:75-77.
   The section eyebrow between panel groups. Two treatments shipped and neither
   is in payco.css: admin.html:75-77 is a hairline-flanked uppercase eyebrow
   (12.5px/800/.1em, --navy, ::before and ::after rules); operations.html:142 is
   a pill on --info-bg (12px/800/.08em, --blue-deep). admin.html wins — a
   full-width divider carries a group of panels, which is what the fifteen
   consoles being reconciled actually need, whereas the pill reads as a single
   inline label. The rejected pill is recorded in payco-system.md. */
.pc-stage{display:flex;align-items:center;gap:12px;background:none;padding:0;margin:26px 0 12px;border:0;
  font-size:12.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--navy,#0b2a8a)}  /* admin.html:75-76 */
.pc-stage::before,.pc-stage::after{content:"";height:var(--hair);background:var(--line);flex:1}   /* admin.html:77 */
.pc-stage::before{max-width:26px}                           /* admin.html:77 */
.pc-stage:first-of-type{margin-top:0}                       /* admin.html:66 */


/* ============================================================================
   10. KPIs — payco.css:514-528
   ----------------------------------------------------------------------------
   The KPI's top-edge sliver is THE canonical accent application: a 3px gradient
   hairline painted as a BACKGROUND LAYER, growing to 4px on hover via
   --sliver-h-hover. admin.html:14 names it "the canonical sliver (payco.css)".
   ==========================================================================*/
.pc-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--gap-kpi)}  /* payco.css:514, gap 12px = --gap-kpi */
.pc-kpi{background:var(--sliver) top center/100% var(--sliver-h) no-repeat,var(--white);
  border:var(--hair) solid var(--line);border-radius:var(--radius);
  padding:16px 18px;box-shadow:var(--shadow-sm);position:relative;overflow:hidden;cursor:default}  /* payco.css:515-517 */
.pc-kpi.click{cursor:pointer}                               /* payco.css:518 */
.pc-kpi.click:hover{transform:translateY(-2px);box-shadow:var(--shadow-md)}   /* payco.css:519 */
.pc-kpi b{font-size:27px;font-weight:800;display:block;line-height:1;letter-spacing:-.03em}   /* payco.css:520 */
.pc-kpi span{color:var(--ink-mute);font-size:12px;font-weight:600;margin-top:5px;display:block}   /* payco.css:521 */
.pc-kpi.alert{--sliver:linear-gradient(90deg,var(--warn),var(--warn))}   /* payco.css:522 */
.pc-kpi.alert b{color:var(--warn)}                          /* payco.css:523 */
/* Legacy: the bar used to be this child element carrying a lozenge radius. The
   KPI paints its own sliver now, so the element is inert — kept selectable
   because three call sites still emit it. payco.css:524-528 */
.pc-kpi>.accent{display:none}                               /* payco.css:528 */


/* ============================================================================
   11. PILL BUTTONS — payco.css:531-540 (shape) + section 20 (behaviour)
   ----------------------------------------------------------------------------
   THE VARIANT MAP. PHASE-A-SPEC.md's brief asks for primary / secondary /
   ghost. payco.css ships four variants and one size modifier, and "secondary"
   is spelled .ghost — there is no .pc-btn.secondary anywhere in the tree, so
   this sheet does NOT invent one:

     primary    .pc-btn              --gradient fill, white text  payco.css:531-533
     secondary  .pc-btn.ghost        --paper-warm fill, --blue-deep text, no shadow  payco.css:536
     ghost      .pc-btn.ghost        the same class — one quiet variant, not two
     caution    .pc-btn.warn         --warn-bg fill, --warn text  payco.css:538
     danger     .pc-btn.danger       solid --warn fill, white text  payco.css:789
     small      .pc-btn.sm           6px 13px / 12px  payco.css:539

   SIZES. Default is 10px 18px at 13px/700 (payco.css:531). .sm is 6px 13px at
   12px (payco.css:539). At <=560px BOTH grow to min-height:44px / 12px 18px
   (payco.css:938) — the Apple HIG tap floor, and it is a real measured floor,
   not a grep: tests/overflow.mjs measures it in a browser.

   THE RESOLVED INCONSISTENCY. admin.html:38-39 and staff.html:24-25 both style
   the BARE <button> element at font-weight:600 / padding:11px 20px instead of
   using .pc-btn (700 / 10px 18px). .pc-btn wins: it is the shared class, it is
   the one the hover/press/disabled/focus system at payco.css:806-834 is written
   against, and the two page forks would otherwise put a second button shape on
   every console that has a form.

   Radius and padding are deliberately NOT reset across the app — see the note
   at payco.css:806-812. What is shared is how a button RESPONDS: one lift, one
   shadow ramp, one press, one focus ring, one disabled state. That is section 20.
   ==========================================================================*/
.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)}   /* payco.css:531-533 */
.pc-btn:hover{transform:translateY(-1px);box-shadow:0 6px 18px rgba(26,77,214,.32)}   /* payco.css:534 */
.pc-btn:active{transform:translateY(0)}                     /* payco.css:535 */
.pc-btn.ghost{background:var(--paper-warm);color:var(--blue-deep);box-shadow:none}   /* payco.css:536 */
.pc-btn.ghost:hover{background:var(--line-soft)}            /* payco.css:537 */
.pc-btn.warn{background:var(--warn-bg);color:var(--warn);box-shadow:none}   /* payco.css:538 */
.pc-btn.sm{padding:6px 13px;font-size:12px}                 /* payco.css:539 */
.pc-btn:disabled{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none}   /* payco.css:540 */
.pc-btn.danger{background:var(--warn);color:#fff;border:1px solid var(--warn);box-shadow:none}   /* payco.css:789 */
.pc-btn.danger:hover{background:#8f3319;border-color:#8f3319}   /* payco.css:790 */
/* .cta is the marketing-side pill, kept here so a reconciled page that already
   writes .cta keeps the same shape rather than needing a markup edit. payco.css:798-800 */
.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)}   /* payco.css:798-799 */


/* ============================================================================
   12. STATUS PILLS — payco.css:543-550, payco.css:791-792
   ==========================================================================*/
.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}   /* payco.css:543-544 */
.pc-pill.ok{background:var(--ok-bg);color:var(--ok)}        /* payco.css:545 */
.pc-pill.warn{background:var(--warn-bg);color:var(--warn)}  /* payco.css:546 */
.pc-pill.amber{background:var(--amber-bg);color:var(--amber)}   /* payco.css:547 */
.pc-pill.info{background:var(--info-bg);color:var(--info)}  /* payco.css:548 */
.pc-pill.mute{background:var(--paper-warm);color:var(--ink-mute)}   /* payco.css:549 */
.pc-pill.bad{background:var(--warn);color:#fff}             /* payco.css:791 */
.pc-pill.cy{background:var(--info-bg);color:var(--blue)}    /* payco.css:792 */
.pc-pill .dot{width:6px;height:6px;border-radius:50%;background:currentColor}   /* payco.css:550 */


/* ============================================================================
   13. TABLES — payco.css:553-559, payco.css:575-579
   ==========================================================================*/
.pc-table{width:100%;border-collapse:collapse}              /* payco.css:553 */
.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:var(--hair) solid var(--line);
  background:var(--paper-warm)}                             /* payco.css:554-555 */
.pc-table td{padding:12px 14px;border-bottom:var(--hair) solid var(--line-soft);font-size:13px}   /* payco.css:556 */
.pc-table tbody tr{transition:background .12s;cursor:pointer}   /* payco.css:557 */
.pc-table tbody tr:hover{background:var(--paper-warm)}      /* payco.css:558 */
.pc-table tbody tr.sel{background:var(--info-bg)}           /* payco.css:559 */
.pc-table tbody tr:last-child td{border-bottom:0}           /* payco.css:560 */
/* .pc-compact is applied by payco-nav.js from the settings.html "Compact tables"
   pref. Density ONLY — no colour, and no size change that would break the type
   scale. payco.css:568-574 */
.pc-compact .pc-table th{padding:7px 10px}                  /* payco.css:575 */
.pc-compact .pc-table td{padding:6px 10px;font-size:12.5px} /* payco.css:576 */
.pc-compact table th{padding:7px 10px}                      /* payco.css:577 */
.pc-compact table td{padding:6px 10px}                      /* payco.css:578 */
.pc-compact .pc-card{padding:14px}                          /* payco.css:579 */


/* ============================================================================
   14. FORMS — payco.css:582-587
   ----------------------------------------------------------------------------
   Field focus is DELIBERATELY separate from the outline ring in section 20:
   payco.css:586 sets outline:none and moves to border-color + a 3px glow,
   because stacking an outline on top of that reads as an error state rather
   than a focus one. PHASE-A-SPEC.md §2.6 records the same split.
   ==========================================================================*/
label{display:block;font-size:12px;font-weight:600;color:var(--ink-soft);margin:12px 0 5px}   /* payco.css:582 */
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}             /* payco.css:583-585 */
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)}                /* payco.css:586-587 */


/* ============================================================================
   15. DISCLOSURE — payco.css:590-598
   ==========================================================================*/
.pc-disc{border:var(--hair) solid var(--line);border-radius:var(--radius-sm);overflow:hidden;margin-bottom:10px;background:var(--white)}  /* payco.css:590 */
.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}   /* payco.css:591-592 */
.pc-disc>summary::-webkit-details-marker{display:none}      /* payco.css:593 */
.pc-disc>summary:hover{background:var(--paper-warm)}        /* payco.css:594 */
.pc-disc>summary .chev{margin-left:auto;transition:transform .2s;color:var(--ink-mute)}   /* payco.css:595 */
.pc-disc[open]>summary .chev{transform:rotate(90deg)}       /* payco.css:596 */
.pc-disc[open]>summary{border-bottom:var(--hair) solid var(--line-soft)}   /* payco.css:597 */
.pc-disc .body{padding:15px}                                /* payco.css:598 */


/* ============================================================================
   16. ADMIN TASKBAR (left rail) — payco.css:362-384
   ----------------------------------------------------------------------------
   Built by payco-nav.js:1562-1573. The rail is position:fixed at top:74px and
   body.pc-hasside releases the content margin. IT HIDES AT <=1100px
   (payco.css:384) — that breakpoint is asserted by the existing suites and by
   PHASE-A-SPEC.md §2.7, so a page must not re-declare it.
   The scroll shadow is a background pinned to the CONTENT via
   background-attachment:local, not a ::after fade: an overlay would tint the
   bottom item even when the nav fits. payco.css:355-361.
   ==========================================================================*/
.pc-sidenav{position:fixed;left:0;top:var(--header-h);bottom:0;width:var(--rail-w);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) solid var(--line);padding:20px 14px 26px}   /* payco.css:362-369, top 74px = --header-h, width 246px = --rail-w */
.pc-sidenav h4{margin:2px 12px 12px;font-size:12px;text-transform:uppercase;letter-spacing:1.4px;
  font-weight:800;color:var(--ink-mute)}                    /* payco.css:370-371 */
.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}   /* payco.css:372-374 */
.pc-sidenav a:hover{background:var(--paper-warm,#f3f6fd);border-color:var(--line)}   /* payco.css:375 */
.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)}   /* payco.css:376-377 */
.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}          /* payco.css:378-379 */
.pc-sidenav .ico svg{width:20px;height:20px}                /* payco.css:380 */
.pc-sidenav a.on .ico{background:var(--gradient);color:#fff;box-shadow:0 4px 12px rgba(26,77,214,.3)}   /* payco.css:381 */
/* THE CONSOLE LANE USED TO BE STATED HERE TOO, AND IT IS DELETED (docs/A-NOTES.md S-1, landed by
   the launch merge lane). It read:

     body.pc-hasside .wrap,body.pc-hasside main,body.pc-hasside .pc-wrap{
       margin-left:274px;margin-right:28px;max-width:min(1240px,calc(100vw - 302px));width:auto}
     @media(max-width:1100px){ .pc-sidenav{display:none}
       body.pc-hasside .wrap,...{margin:0 auto;max-width:var(--maxw,1180px)}}

   at (0,2,0), in a sheet that loads AFTER payco.css. For as long as it existed the forty-four
   consoles that link this file obeyed a hardcode and the thirty-three that do not obeyed the
   Viewport Law — one selector, two geometries, decided by which <link> tags a page happened to
   carry. payco.css:835-884 now states the Law at (0,3,0) and wins everywhere, so this copy
   contradicted nothing and was purely a trap for the next reader; it also released the lane at
   1100px where the Law releases it at 1280.
   DELETED ON A MEASUREMENT, NOT ON A SPECIFICITY ARGUMENT. Six consoles (admin, crm, operations,
   support, calendar, staff) x six widths chosen to straddle both release points (1100, 1180, 1279,
   1280, 1440, 1920), reading the computed x, width, margin-left, margin-right and max-width of
   .wrap / main / .pc-wrap plus body.scrollWidth, with this block present and with it removed:
   36 measurements, 36 identical, 0 different. The rule was dead in every state it could reach.
   `.pc-sidenav` display:none below 1100px went with it and is likewise stated in payco.css. */


/* ============================================================================
   17. VIEW BAR + IDENTITY CHIP — payco.css:393-411
   ==========================================================================*/
.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}                          /* payco.css:393-396 */
.pc-viewbar b{color:var(--ink);font-weight:800}             /* payco.css:397 */
.pc-viewbar > b:first-child{font-size:14.5px;letter-spacing:-.2px;color:var(--blue-deep)}   /* payco.css:398 */
.pc-viewbar > span{color:var(--ink-mute)}                   /* payco.css:399 */
.pc-viewbar input,.pc-viewbar select{width:auto;font-size:12.5px;padding:7px 11px;
  min-width:150px;max-width:340px}                          /* payco.css:400-401 */
.pc-viewbar.active{background:var(--info-bg);border-bottom-color:rgba(26,77,214,.22)}   /* payco.css:402 */
.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}   /* payco.css:405-408 */
.adminbox em{font-style:normal}                             /* payco.css:409 */
.adminbox:hover{background:#12305e;transform:translateY(-1px);box-shadow:0 6px 18px rgba(11,27,58,.28)}   /* payco.css:410 */
.adminbox:active{transform:translateY(0)}                   /* payco.css:411 */
@media(max-width:700px){.pc-viewbar{font-size:12px}.pc-viewbar input,.pc-viewbar select{max-width:150px}}   /* payco.css:412 */


/* ============================================================================
   18. UNREAD COUNTERS + SR-ONLY — payco.css:566-567, payco.css:988-996
   ----------------------------------------------------------------------------
   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. .sr-only is how that meaning gets said out loud. payco.css:562-565.
   ==========================================================================*/
.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}   /* payco.css:566-567 */
.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}  /* payco.css:988-989 */
.pc-drop-trig .pc-unread,.pc-nav a .pc-unread{vertical-align:middle}   /* payco.css:990 */
/* Same shape so it reads as the same kind of signal, warm rather than
   brand-blue so a glance can tell which one moved: unread MESSAGES against
   unanswered TICKETS. payco.css:991-995 */
.pc-unread-tk{background:var(--warn,#b3401f)}               /* payco.css:996 */


/* ============================================================================
   19. BREAKPOINTS — PHASE-A-SPEC.md §2.7
   ----------------------------------------------------------------------------
   The full ladder, and where each one lives:

     min 1200 / max 1560   header pill cluster tightens   payco.css:734
     max 1120              header pill cluster folds      payco.css:703
     max 1100              TASKBAR HIDES                  payco.css:384  (section 16)
     min 900               .pc-2 / .pc-3 columns engage   payco.css:511  (section 9)
     max 900               header stacks, .pc-nav wraps   payco.css:744
     max 700               .pc-viewbar compresses         payco.css:412  (section 17)
     max 600               logo 44px -> 34px              payco.css:212  (section 5)
     max 560               the phone layer                payco.css:922
     max 360               the 320px-device layer         payco.css:980
     prefers-reduced-motion  five separate kill layers    payco.css:48,75,165,341,502,846

   THE MOBILE BAR. tests/overflow.mjs owns "zero horizontal overflow at 375px
   AND 320px"; tests/mobilewave2.mjs owns the wider bar — 12px font floor, 44px
   tap targets, a visible focus ring on everything focusable, a real label on
   every input. Both MEASURE in a real browser; neither greps. The rules below
   are what makes a page pass them, and the overflow guard at section 2
   (html,body{overflow-x:hidden;overflow-x:clip}) is the other half.
   ==========================================================================*/
@media(max-width:900px){
  /* Vertical padding only. The horizontal inset stays the shared clamp — a flat
     14px here was the last place the wordmark's left edge disagreed with the
     marketing bar. payco.css:752-755 */
  .pc-header{height:auto;padding:10px clamp(18px,3vw,40px);flex-wrap:wrap;row-gap:8px}   /* payco.css:756 */
  .pc-nav{width:100%;margin-left:0;justify-content:flex-end;flex-wrap:wrap;gap:8px}      /* payco.css:757 */
}
@media (max-width: 560px) {
  /* Type: bump base so body text is comfortably legible. iOS will not zoom
     inputs at >=16px, which is why that one is !important. payco.css:923-926 */
  body { font-size: 15.5px; -webkit-text-size-adjust: 100%; }               /* payco.css:925 */
  input, select, textarea, button { font-size: 16px !important; }           /* payco.css:926 */
  /* Cards breathe, edge-to-edge but with inner padding. payco.css:928-930 */
  .pc-card { padding: 16px 16px; border-radius: 14px; }                     /* payco.css:929 */
  .pc-main, .wrap, .pc-wrap { padding-left: 14px; padding-right: 14px; }    /* payco.css:930 */
  /* Headings scale down but stay heavy. payco.css:932-935 */
  h1 { font-size: clamp(20px, 6vw, 26px); line-height: 1.15; }              /* payco.css:933 */
  h2 { font-size: clamp(17px, 5vw, 21px); line-height: 1.2; }               /* payco.css:934 */
  h3 { font-size: 15.5px; }                                                 /* payco.css:935 */
  /* Tap targets: everything clickable >=44px tall (Apple HIG). The history
     here is instructive — this rule ENUMERATES SELECTORS, and every time it
     missed one the miss shipped: .pc-home sat at 40px on every page wearing
     the shared nav, and overflow.mjs could not catch it because its tap-target
     query never looked at a plain <a>. Add to the list, do not replace it with
     a blanket a{min-height:44px}: WCAG 2.5.8 exempts links inside a paragraph
     and inflating them breaks the line box. payco.css:937-957 */
  .pc-btn, button, .btn, a.btn { min-height: 44px; padding: 12px 18px; }    /* payco.css:938 */
  .pc-nav a, .pc-drop-trig, #pcSignOut, .pc-home { min-height: 44px; display: inline-flex; align-items: center; }  /* payco.css:949 */
  select, #pcViewSel { min-height: 44px; }                                  /* payco.css:951 */
  a[role="button"] { min-height: 44px; display: inline-flex; align-items: center; }   /* payco.css:952 */
  .crumb a { min-height: 44px; display: inline-flex; align-items: center; } /* payco.css:960 */
  summary { min-height: 44px; display: flex; align-items: center; }         /* payco.css:961 */
  .pc-disc > summary { min-height: 0; }                                     /* payco.css:962 */
  /* Tables: horizontal scroll is the right call on a phone, but make it
     obvious and smooth rather than clipping. payco.css:961-963 */
  .pc-table, table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; white-space: nowrap; }   /* payco.css:966 */
  /* KPI strips and grids collapse to a single readable column. payco.css:966 */
  .pc-2, .pc-3, .pc-grid, [class*="grid-2"], [class*="grid-3"] { grid-template-columns: 1fr !important; }   /* payco.css:969 */
  /* Sidebars never take horizontal room on mobile — the hamburger covers
     navigation instead. payco.css:968-971 */
  .pc-sidenav { display: none; }                                            /* payco.css:973 */
  /* Segmented controls / pill rows wrap instead of overflowing. payco.css:974-975 */
  .seg, .tabs, .times, .cal-nav, .pc-viewbar { flex-wrap: wrap; }           /* payco.css:976 */
}
/* Very small (<=360px): trim padding a touch more so 320px devices fit. payco.css:978-983 */
@media (max-width: 360px) {
  .pc-card { padding: 14px 12px; }                                          /* payco.css:981 */
  .pc-main, .wrap, .pc-wrap { padding-left: 10px; padding-right: 10px; }    /* payco.css:982 */
  body { font-size: 15px; }                                                 /* payco.css:983 */
}


/* ============================================================================
   20. BUTTON BEHAVIOUR + FOCUS RING — payco.css:806-846
   ----------------------------------------------------------------------------
   THIS BLOCK IS LAST ON PURPOSE. payco.css:813-814 puts the identical block
   last in that file "so it settles payco.css's own earlier button rules", and
   this sheet mirrors the position so that a page linking BOTH sheets ends up
   with the same final .pc-btn{transition} declaration in either load order.
   Do not move it, and do not move section 11 below it.

   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".
   ==========================================================================*/
.pc-btn,.cta,.btn,.go,.pri{
  transition:transform .12s cubic-bezier(.4,0,.2,1),box-shadow .18s ease,background-color .18s ease}   /* payco.css:819-820 */
.pc-btn:hover,.cta:hover,.btn:hover,.go:hover,.pri:hover{
  transform:translateY(-1px);box-shadow:var(--btn-shadow-hover)}   /* payco.css:821-822 */
.pc-btn:active,.cta:active,.btn:active,.go:active,.pri:active{
  transform:translateY(0);box-shadow:var(--btn-shadow-active)}     /* payco.css:823-824 */
/* 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}   /* payco.css:826 */
.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}   /* payco.css:827-829 */

/* THE FOCUS RING. :where() is kept deliberately: it contributes ZERO
   specificity, so this is a DEFAULT that any page's own :focus treatment
   overrides without needing !important. That is also its limit — a page rule
   doing `outline:0` with no replacement still wins, which is exactly what the
   fifteen divergent consoles do (PHASE-A-SPEC.md §2.6 cites disputes.html:167).
   The fix is to delete the page's private ring, not to out-specify it here.
   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 covered by the ring in
   section 14 instead — stacking an outline on their 3px glow reads as an error
   state. payco.css:830-843. */
:where(a,button,summary,input,select,textarea,[role=button],[tabindex]):focus-visible{
  outline:2px solid var(--blue-electric);outline-offset:2px}   /* payco.css:844-845 */
@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}}   /* payco.css:846-848 */

/* ============================================================================
   RYAN-Q — THE FIVE THINGS THIS LANE WOULD NOT DECIDE FOR YOU (W5 POLISH)
   ----------------------------------------------------------------------------
   Each is a BRAND or DENSITY decision, not a correctness one, so this lane built
   the defensible default and marked it rather than choosing. Greppable:
   `grep -rn "RYAN-Q" css/`. The measurement behind each is in
   docs/lane-reports/wave5/POLISH.md.

   RYAN-Q: Are the marketing site and the console ONE product or TWO?
   ASSUMED: Two, held apart on purpose. The public bar is drawn from
     payco-marketing.css and the console bar from this sheet; --hair is 2px here
     and the app bar keeps --hair-thin 1px precisely so the two can differ
     (section 1's note, and tests/headerparity.mjs went 2850/0 -> 2782/68 the one
     time they were moved together).
   CHANGES IF: you want one product. Then --hair-thin goes and payco-marketing.css
     takes this sheet's tokens, which is a ~40-file change and a real repaint.

   RYAN-Q: The type scale. MEASURED across the 51 pages of the four main surfaces:
     133 distinct font-size values, 117 of them declared in the PAGES' own <style>
     blocks rather than in any stylesheet, and 49 used exactly once. The body range
     alone runs 12 / 12.5 / 13 / 13.5 / 14 / 14.5 / 15 / 15.5 / 16 - nine steps
     inside four pixels.
   ASSUMED: the half-pixel steps are DELIBERATE (12.5px alone has 281 uses) and
     were left alone. Only the two single-use oddities were collapsed (13.2 -> 13,
     12.4 -> 12.5) and the last sub-12px declaration in the tree was raised.
   CHANGES IF: you want one scale. Collapsing to 12/13/14/16/18/20/24/30/38 is a
     REPAINT of every page, and PB-LANE-1 records that you declined a repaint once
     already. It is a page-by-page job, not a stylesheet one.

   RYAN-Q: Spacing density. MEASURED: 107 distinct padding/margin/gap values -
     effectively every integer from 1px to 30px. There is no rhythm to converge
     onto; there is only the decision of what the rhythm should be.
   ASSUMED: unchanged. A spacing change moves every box on every page and this
     platform is days from live merchants.
   CHANGES IF: you name a grid. --pad-card / --gap-grid / --gap-kpi already exist
     here to carry it and the sweep is then mechanical.

   RYAN-Q: Motion feel. Six private easing curves existed; this lane converged them
     onto ONE, --ease-std cubic-bezier(.32,.72,0,1), which four of the six already
     declared.
   ASSUMED: one curve, and the chat panel's cubic-bezier(.22,1,.36,1) overshoot
     goes with it - the one place the change is FELT rather than measured.
   CHANGES IF: you want the chat bubble to keep its spring. Give it a second named
     token here (--ease-spring) rather than a literal, so it is a decision and not
     a fork.

   RYAN-Q: The lozenge is spelled three ways - 999px (3104 uses), 99px (344) and
     980px (4) all mean "fully round".
   ASSUMED: 999px is the spelling. Verified already uniform inside css/ (grep: zero
     hits for 99px or 980px as a radius). The other two live in payco.css and in
     pages' own <style>, which this lane does not own.
   CHANGES IF: nothing - this one is free, and is filed as a request in the lane
     report because the remaining sites are outside css/.
   ==========================================================================*/

/* ==== end payco-system.css ==== */


/* ============================================================================
   SECTIONS 21-27 — THE LAYOUT PRIMITIVES (Wave 4, lane 2)
   ----------------------------------------------------------------------------
   WHY THESE SIT BELOW SECTION 20, WHICH CALLS ITSELF THE LAST BLOCK. Section 20's
   rule is not "be at the end of the file"; it is "the FINAL `.pc-btn{transition}`
   declaration must be the same one in either load order" (see its own header, and
   payco.css:813-814). Nothing in sections 21-27 declares a single property on
   .pc-btn, .cta, .btn, .go or .pri, and none of these selectors exists in
   payco.css at all, so section 20 is still the last word on button behaviour and
   the two sheets still commute. tests/designsystem.mjs asserts the property that
   actually matters (shape block before behaviour block) and stays green.

   EVERYTHING HERE IS ADDITIVE. Not one existing selector, token or value above
   this line was edited, renamed or removed. Two rules made that possible and both
   are worth keeping:

     * A NEW COMPONENT GETS A NEW NAME. .pc-empty already existed (payco.css:673,
       53 call sites) as a one-line muted note; the full empty state with an icon
       and an action is .pc-emptystate, a different thing with a different name.
       The alternative — redefining .pc-empty — is exactly the defect
       tests/forumui.mjs recorded when a page got "a full empty state where it
       meant a quiet note".
     * NO NEW COLOURS, NO NEW FONTS, NO NEW BUTTONS. Every fill, ink, border,
       radius, shadow and family below is a var() of a token defined in section 1.
       The only literals are lengths, and each one either already appears in this
       sheet or is derived from a token in a comment beside it.

   The contract for all seven — markup, options, and the harness that measures
   them — is css/payco-system.md sections 10-17.
   ==========================================================================*/


/* ============================================================================
   21. THE BALANCED GRID — .pc-grid-fit
   ----------------------------------------------------------------------------
   THE PROBLEM. `repeat(auto-fit,minmax(260px,1fr))` is the grid every console
   reaches for, and it orphans: seven cards in a three-up grid draw 3 / 3 / 1, and
   that last card sits alone at a third of the width with two card-widths of white
   space beside it. The eye reads the hole, not the card. Nine cards are fine,
   seven are not, and which one a page has depends on data nobody controls.

   THE FIX, AND WHY IT IS TWELVE TRACKS. The grid is always twelve equal tracks.
   A "column" is a span: 2-up is span 6, 3-up is span 4, 4-up is span 3. Twelve is
   the smallest count divisible by 2, 3, 4 and 6, so whatever the last row holds,
   it divides the row EXACTLY — one leftover card takes all twelve, two leftovers
   take six each, three take four each. No fractional tracks, no `calc`, no
   hand-tuned widths, and the gap arithmetic comes out right on its own: a card of
   6 tracks carries 5 internal gaps, so two of them plus one gap is 12 tracks and
   11 gaps, which is the container. Same for 4 and 3.

   THE ONE RULE. Regrouping only ever applies to a LAST ROW THAT HAS A ROW ABOVE
   IT. A grid whose children all fit on one line is left alone — two cards in a
   3-up grid stay two thirds-wide cards, because there is no hole above them to
   answer for and silently promoting them to half-width is a layout change the
   author did not ask for. That is what every `:not(:first-child)` /
   `:not(:nth-child(2))` / `:not(:nth-child(3))` guard below is doing.

   WHY EACH COLUMN COUNT GETS AN EXCLUSIVE WIDTH RANGE rather than a `min-width`
   ladder: an orphan rule is necessarily more specific than the base span rule it
   overrides (it carries three or four extra pseudo-classes), so with overlapping
   `min-width` queries the 2-up orphan rule would still be winning at 3-up widths
   and a five-card grid would draw 3 / 1-spanning-everything instead of 3 / 2. The
   ranges below never overlap, so specificity never has to be reasoned about.

   THE BREAKPOINTS ARE THE SHEET'S OWN (section 19), not new numbers:
     900   the width .pc-2 / .pc-3 already engage at        (payco.css:511)
     1200  the header pill cluster's own tighten point      (payco.css:734)
     1560  the .pc-wrap.wide console width                  (payco.css:885)

   VARIANTS. .pc-fit-2 is 2-up from 900 and stays there. Bare .pc-grid-fit and
   .pc-fit-3 are the same thing: 2-up at 900, 3-up at 1200. .pc-fit-4 adds a
   fourth column at 1560. Below 900 everything is one column, which is what the
   phone layer at section 19 forces anyway.

   align-items is left at the grid default (stretch) ON PURPOSE: equal-height
   cards in a row are the cheapest possible defence against the panel-height rule
   in tests/layoutbalance.mjs. Do not set `align-items:start` on this grid without
   reading section 22 first.
   ==========================================================================*/
.pc-grid-fit{display:grid;gap:var(--gap-grid);grid-template-columns:repeat(12,minmax(0,1fr))}
.pc-grid-fit>*{grid-column:span 12;min-width:0}   /* min-width:0 — a grid item's default min-width:auto is what lets a long table push a track wider than its share */
.pc-grid-fit.pc-gap-tight{gap:var(--gap-kpi)}     /* 12px — the KPI-strip rhythm */

/* ---- 2-UP: .pc-fit-2, from 900px and forever ---- */
@media(min-width:900px){
  .pc-grid-fit.pc-fit-2>*{grid-column:span 6}
  .pc-grid-fit.pc-fit-2>:last-child:nth-child(odd):not(:first-child){grid-column:span 12}
}
/* ---- 2-UP: everything else, 900-1199 ---- */
@media(min-width:900px) and (max-width:1199px){
  .pc-grid-fit:not(.pc-fit-2):not(.pc-fit-4)>*,
  .pc-grid-fit.pc-fit-4>*{grid-column:span 6}
  .pc-grid-fit:not(.pc-fit-2):not(.pc-fit-4)>:last-child:nth-child(odd):not(:first-child),
  .pc-grid-fit.pc-fit-4>:last-child:nth-child(odd):not(:first-child){grid-column:span 12}
}
/* ---- 3-UP: bare .pc-grid-fit and .pc-fit-3, from 1200px ---- */
@media(min-width:1200px){
  .pc-grid-fit:not(.pc-fit-2):not(.pc-fit-4)>*{grid-column:span 4}
  /* n mod 3 == 1 — one card alone on the last row takes the whole row */
  .pc-grid-fit:not(.pc-fit-2):not(.pc-fit-4)>:last-child:nth-child(3n+1):not(:first-child){grid-column:span 12}
  /* n mod 3 == 2 — the last two regroup to half a row each */
  .pc-grid-fit:not(.pc-fit-2):not(.pc-fit-4)>:last-child:nth-child(3n+2):not(:nth-child(2)),
  .pc-grid-fit:not(.pc-fit-2):not(.pc-fit-4)>:nth-last-child(2):nth-child(3n+1):not(:first-child){grid-column:span 6}
}
/* ---- 3-UP: .pc-fit-4 is 3-up between 1200 and 1559 ---- */
@media(min-width:1200px) and (max-width:1559px){
  .pc-grid-fit.pc-fit-4>*{grid-column:span 4}
  .pc-grid-fit.pc-fit-4>:last-child:nth-child(3n+1):not(:first-child){grid-column:span 12}
  .pc-grid-fit.pc-fit-4>:last-child:nth-child(3n+2):not(:nth-child(2)),
  .pc-grid-fit.pc-fit-4>:nth-last-child(2):nth-child(3n+1):not(:first-child){grid-column:span 6}
}
/* ---- 4-UP: .pc-fit-4, from 1560px ---- */
@media(min-width:1560px){
  .pc-grid-fit.pc-fit-4>*{grid-column:span 3}
  /* n mod 4 == 1 — one card alone takes the whole row */
  .pc-grid-fit.pc-fit-4>:last-child:nth-child(4n+1):not(:first-child){grid-column:span 12}
  /* n mod 4 == 2 — the last two take half a row each */
  .pc-grid-fit.pc-fit-4>:last-child:nth-child(4n+2):not(:nth-child(2)),
  .pc-grid-fit.pc-fit-4>:nth-last-child(2):nth-child(4n+1):not(:first-child){grid-column:span 6}
  /* n mod 4 == 3 — the last three take a third of a row each */
  .pc-grid-fit.pc-fit-4>:last-child:nth-child(4n+3):not(:nth-child(3)),
  .pc-grid-fit.pc-fit-4>:nth-last-child(2):nth-child(4n+2):not(:nth-child(2)),
  .pc-grid-fit.pc-fit-4>:nth-last-child(3):nth-child(4n+1):not(:first-child){grid-column:span 4}
}


/* ============================================================================
   22. PANEL SIZE VARIANTS — .pc-size-compact / .pc-size-standard / .pc-size-tall
   ----------------------------------------------------------------------------
   Three sizes for one panel, applied ALONGSIDE .pc-card so the border, radius,
   surface, shadow and sliver behaviour are untouched. None of these redefines
   .pc-card; each sets padding and, for tall, a height contract.

     .pc-size-compact   14px padding   the density payco.css:579 already ships
                                       for .pc-compact .pc-card, promoted to a
                                       per-panel opt-in rather than a page-wide
                                       preference. Same number, chosen instance.
     .pc-size-standard  20px padding   --pad-card. States the default out loud so
                                       a page can put all three in one grid and
                                       read which is which from the markup.
     .pc-size-tall      capped height  max-height plus an INTERNAL scroll region.

   WHY TALL IS CAPPED AT 520px. A panel is "too tall" when it drags a grid row's
   height past what the other cards in that row need, and tests/layoutbalance.mjs
   draws that line at 2.5x the row's neighbours. A standard console card measures
   roughly 180-260px, so the failure band starts somewhere around 450-650px. 520px
   sits inside it deliberately: a tall panel is allowed to be conspicuously the
   tallest thing in its row and is still bounded before it becomes the row. It is
   also comfortably inside one 900px-tall laptop screen once the 74px header and
   the wrapper's 28px of padding are taken out. Override per instance with
   --pc-tall-h; the value is a plain length and nothing else reads it.

   THE LONG LIST GOES IN .pc-scroll AND THIS IS NOT OPTIONAL. .pc-size-tall is a
   column flexbox with overflow:hidden, so a list dropped straight into it is
   CLIPPED rather than scrollable — silently losing rows, which is worse than an
   ugly tall card. Two things guard that: .pc-scroll is one class on one element,
   and tests/layoutbalance.mjs fails a .pc-size-tall that contains no scroll
   region at all. min-height:0 on .pc-scroll is load-bearing in the same way — a
   flex item's default min-height:auto refuses to shrink below its content, so
   without it the panel grows past its own max-height and nothing ever scrolls.

   The fade/shadow pair is payco.css:362-369's technique verbatim, values and all:
   the fades are background-attachment:local so they travel with the CONTENT and
   vanish at each end, and the shadows are `scroll` so they stay pinned to the
   frame. An ::after overlay instead would tint the bottom row even when the list
   fits, which is the bug that technique exists to avoid.
   ==========================================================================*/
.pc-size-compact{padding:14px}                          /* payco.css:579 */
.pc-size-compact>h2,.pc-size-compact>h3{margin-bottom:10px}
.pc-size-standard{padding:var(--pad-card)}              /* 20px */
.pc-size-tall{display:flex;flex-direction:column;overflow:hidden;max-height:var(--pc-tall-h,520px)}
.pc-size-tall>h2,.pc-size-tall>h3,.pc-size-tall>.pc-sec-head{flex:none}
.pc-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-gutter:stable;
  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)}                                       /* payco.css:362-369 verbatim */
/* A scroll region inside a .pad0 card runs to the card's own edge, so it needs the
   inset back. Kept as a modifier rather than baked in: a .pc-scroll inside a
   padded card must NOT re-add padding or the list indents twice. */
.pc-scroll.pc-scroll-inset{padding:0 var(--pad-card)}


/* ============================================================================
   23. SECTION HEADER + SUBHEADER — .pc-sec-head
   ----------------------------------------------------------------------------
   The sheet already has the two ends of this scale and nothing in the middle:
   .pc-head is the PAGE title row (h1 at clamp(19px,2.4vw,25px), payco.css:454-456)
   and .pc-card>h2 is a PANEL title (14px, payco.css:508). A console that stacks
   four groups of panels under one page title has no way to say "this group is
   Settlement and this group is Disputes" except .pc-stage, which is an eyebrow
   divider and carries no room for a sentence of explanation.

   .pc-sec-head is that middle step, and it is deliberately built as .pc-head's
   sibling: same flex shape, same flex-end alignment, same wrap, same --gap-grid,
   same 13px muted subtitle in --ink-mute. The ONE new number is the 16px title,
   which is the single step between the panel title's 14px and the page title's
   19px floor. Weight, letter-spacing, colour and family all come from the base
   h1..h4 rule at section 2 — this adds a size, not a type treatment.

   .pc-sec-sub is capped at 72ch because tests/designfix.mjs enforces a 65ch-ish
   prose measure across the tree and an uncapped subtitle on a 1560px console runs
   to about 190 characters a line.
   ==========================================================================*/
.pc-sec-head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--gap-grid);
  flex-wrap:wrap;margin-bottom:14px}
.pc-sec-head>div{min-width:0}
.pc-sec-head h2,.pc-sec-head h3,.pc-sec-title{font-size:16px}
.pc-sec-sub{color:var(--ink-mute);font-size:13px;margin-top:3px;line-height:1.45;max-width:72ch}
.pc-sec-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex:none}


/* ============================================================================
   24. EMPTY STATE — .pc-empty (the quiet note) / .pc-emptystate (the component)
   ----------------------------------------------------------------------------
   TWO THINGS, TWO NAMES, AND THE DISTINCTION IS THE POINT. tests/forumui.mjs
   records what happens when they are conflated: a page that meant "nothing here
   yet" got a full centred empty state and the panel read as broken.

     .pc-empty       ONE muted line inside a list that happens to be empty.
                     Lifted VERBATIM from payco.css:673 — 53 call sites across the
                     tree write this class and this sheet did not carry it, so a
                     page linking only payco-system.css got no styling at all.
                     Byte-identical to payco.css, so no page moves a pixel.
     .pc-emptystate  the component: icon slot, headline, one line of explanation,
                     optional action. For a PANEL or a PAGE with nothing in it,
                     where the reader needs to be told what would put something
                     there.

   The icon slot is 44px on an 11px radius over --paper-warm with a 20px glyph —
   .pc-sidenav .ico's own geometry (payco.css:378-380) at the 44px tap floor the
   phone layer already uses, so a slot that becomes a button later does not resize.
   The action is whatever button the page already has; this component does not
   define one. Everything else is --ink / --ink-mute / --paper-warm.
   ==========================================================================*/
.pc-empty{text-align:center;color:var(--ink-mute);padding:30px 18px;font-size:13px}   /* payco.css:673 VERBATIM */
.pc-emptystate{display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;gap:10px;padding:34px 22px;color:var(--ink-mute)}
.pc-emptystate .pc-empty-ico{width:44px;height:44px;border-radius:11px;background:var(--paper-warm);
  color:var(--blue-deep);display:grid;place-items:center;flex:none;margin-bottom:2px}   /* payco.css:378 */
.pc-emptystate .pc-empty-ico svg{width:20px;height:20px}                               /* payco.css:380 */
.pc-emptystate h2,.pc-emptystate h3,.pc-emptystate .pc-empty-head{font-size:14px;color:var(--ink);
  display:block;margin:0}
.pc-emptystate p,.pc-emptystate .pc-empty-note{font-size:13px;color:var(--ink-mute);margin:0;
  line-height:1.5;max-width:52ch}
.pc-emptystate .pc-empty-act{margin-top:4px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  justify-content:center}
/* An empty state that IS the panel body fills it, so a 3-up row of cards does not
   go ragged the moment one of them has no data. */
.pc-card>.pc-emptystate:only-child,.pc-size-tall>.pc-scroll>.pc-emptystate:only-child{flex:1 1 auto}


/* ============================================================================
   25. DENSE TABLE — .pc-dense
   ----------------------------------------------------------------------------
   .pc-table (section 13) is the comfortable reading table. .pc-dense is the one
   for a settlement run or a residual ledger: hundreds of rows, a sticky header, a
   fixed scroll frame, and numbers that line up. It is a SEPARATE class, not an
   edit of .pc-table, and it stands alone — `class="pc-dense"` is enough;
   `class="pc-table pc-dense"` also works and .pc-dense wins on source order.

   STICKY MEANS A CONTAINER THAT ACTUALLY SCROLLS. tests/dashboardui.mjs exists in
   part because nine tables on integrations.html declared position:sticky inside a
   bare `overflow-x:auto` div with NO HEIGHT — a scroll container that never
   scrolls, so the header never stuck on any of them. .pc-table-scroll is that
   container and it carries a max-height for exactly that reason. A sticky header
   with no bounded frame around it is decoration.

   border-collapse:separate is not a style choice. With `collapse`, the borders
   belong to the TABLE rather than to the cells, so a sticky `th` scrolls away from
   its own bottom border and the header ends up floating over the rows with a
   hairline gap under it. `separate` + border-spacing:0 draws identically and keeps
   the border attached to the cell.

   ZONAL STRIPING, NOT ZEBRA. Alternating every other row is noisy at this density
   and gives the eye nothing to count. Bands of five (rows 6-10 of every ten) tint
   to --paper-warm, so a reader tracking a value across a 12-column row has a
   landmark every five rows. That takes --paper-warm, so hover moves down to
   --line-soft and the selected row keeps --info-bg: three states, three existing
   tokens, all distinguishable, and hover is declared after the band so equal
   specificity resolves the right way.

   RIGHT-ALIGNED NUMERICS with tabular figures — the decimal points line up in a
   column of money, which is the whole reason anyone reads a table like this.
   Mark a cell with .pc-num (or .num, scoped inside .pc-dense so it cannot collide
   with anything global) or [data-num].

   ROW RHYTHM. 10px/14px cells, sitting between .pc-table's comfortable 12px/14px
   (payco.css:556) and .pc-compact's cramped 6px/10px (payco.css:576). Dense in
   information, not in air: line-height stays at 1.45 so a wrapped cell is still
   readable. .pc-compact still tightens it further if the operator asks for that.

   Rows are cursor:default here, because a ledger is usually read rather than
   clicked; add .pc-rows-click when they navigate.

   ON PHONES the shared rule at section 19 turns any `table` into a horizontally
   scrolling block, which is right for a 12-column ledger and does mean the sticky
   header is not guaranteed below 560px. That trade is deliberate.
   ==========================================================================*/
.pc-table-scroll{overflow:auto;max-height:var(--pc-table-h,520px);background:var(--white);
  border:var(--hair) solid var(--line);border-radius:var(--radius-sm);scrollbar-gutter:stable}
.pc-card.pad0>.pc-table-scroll{border:0;border-radius:0}
.pc-dense{width:100%;border-collapse:separate;border-spacing:0;font-size:13px}
.pc-dense th{text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-mute);font-weight:700;padding:9px 14px;background:var(--paper-warm);
  border-bottom:var(--hair) solid var(--line);position:sticky;top:0;z-index:2}
.pc-dense td{padding:10px 14px;border-bottom:var(--hair) solid var(--line-soft);font-size:13px;line-height:1.45}
.pc-dense tbody tr:last-child td{border-bottom:0}
.pc-dense tbody tr{transition:background .12s;cursor:default}
.pc-dense.pc-rows-click tbody tr{cursor:pointer}
.pc-dense tbody tr:nth-child(10n+6),.pc-dense tbody tr:nth-child(10n+7),
.pc-dense tbody tr:nth-child(10n+8),.pc-dense tbody tr:nth-child(10n+9),
.pc-dense tbody tr:nth-child(10n+10){background:var(--paper-warm)}
.pc-dense tbody tr:hover{background:var(--line-soft)}
.pc-dense tbody tr.sel{background:var(--info-bg)}
.pc-dense th.pc-num,.pc-dense td.pc-num,.pc-dense th.num,.pc-dense td.num,
.pc-dense [data-num]{text-align:right;font-variant-numeric:tabular-nums}
@media(prefers-reduced-motion:reduce){.pc-dense tbody tr{transition:none}}


/* ============================================================================
   26. TABS — .pc-tabs (ARIA) and .pc-tabset (no JavaScript at all)
   ----------------------------------------------------------------------------
   TWO IMPLEMENTATIONS, ONE APPEARANCE, because "keyboard support" is a behaviour
   and a stylesheet cannot ship behaviour. Both wear .pc-tab and look identical.

     .pc-tabs    the WAI-ARIA tablist: role=tablist / button.pc-tab[role=tab] /
                 aria-selected. The arrow-key handler is TEN LINES and belongs to
                 the page; css/payco-system.md section 15 has it verbatim to paste.
                 Use this one when the panels' content is fetched or the selection
                 has to be readable from script.
     .pc-tabset  radio inputs and labels. Arrow keys, Home/End, roving focus and
                 the selected state are the browser's native radio-group behaviour
                 — no script anywhere, and nothing to forget to wire up. Panels
                 switch on :checked. Use this one for static panels, which is most
                 of them. Ships up to six panes; a seventh needs a rule here.

   THE FOCUS RING IS EXPLICIT ON BOTH and that is not redundancy. Section 20's
   :where() ring reaches a `button.pc-tab` on its own, but .pc-tabset's real
   focusable element is a clipped 1px radio that nobody can see, so the ring has
   to be forwarded to the label with `:focus-visible + .pc-tab`. The radio is
   clipped rather than display:none for the same reason .sr-only is: a
   display:none radio is not focusable and the arrow keys stop working entirely.

   THE STRIP RULE. .pc-tabs draws its own border-bottom. .pc-tabset cannot — its
   panes are siblings of its labels — so the rule is drawn per label and the ::after
   filler carries it across the leftover width. The selected tab's 2px gradient
   underline sits at bottom:-1px so it covers that hairline rather than stacking
   on it.
   ==========================================================================*/
.pc-tabs{display:flex;align-items:flex-end;gap:6px;flex-wrap:wrap;
  border-bottom:var(--hair) solid var(--line);margin-bottom:16px}
.pc-tab{position:relative;display:inline-flex;align-items:center;gap:7px;font:inherit;font-size:13px;
  font-weight:700;color:var(--ink-mute);background:none;border:0;padding:10px 14px;
  border-radius:10px 10px 0 0;cursor:pointer;white-space:nowrap;text-decoration:none;
  transition:color .16s ease,background .16s ease}
.pc-tab:hover{color:var(--ink);background:var(--paper-warm)}
.pc-tab::after{content:"";position:absolute;left:10px;right:10px;bottom:calc(var(--hair) * -1);height:var(--hair);border-radius:2px;
  background:var(--gradient);opacity:0;transform:scaleX(.4);
  transition:opacity .18s ease,transform .18s ease}
.pc-tab[aria-selected=true],.pc-tab-in:checked+.pc-tab{color:var(--blue-deep)}
.pc-tab[aria-selected=true]::after,.pc-tab-in:checked+.pc-tab::after{opacity:1;transform:none}
.pc-tab:focus-visible{outline:2px solid var(--blue-electric);outline-offset:2px}
.pc-tab-in{position:absolute;width:1px;height:1px;margin:0;padding:0;border:0;opacity:0;
  clip-path:inset(50%);pointer-events:none}
.pc-tab-in:focus-visible+.pc-tab{outline:2px solid var(--blue-electric);outline-offset:2px}
.pc-tabpanel[hidden],[role=tabpanel][hidden]{display:none}
/* the no-JavaScript variant: inputs and labels first, then one <section> per pane */
.pc-tabset{display:flex;flex-wrap:wrap;align-items:flex-end}
.pc-tabset>.pc-tab{order:1;margin-right:6px;border-bottom:var(--hair) solid var(--line)}
.pc-tabset::after{content:"";order:1;flex:1 1 auto;min-width:16px;align-self:stretch;
  border-bottom:var(--hair) solid var(--line)}
.pc-tabset>section{order:2;width:100%;display:none;padding-top:16px}
.pc-tabset>input:nth-of-type(1):checked~section:nth-of-type(1),
.pc-tabset>input:nth-of-type(2):checked~section:nth-of-type(2),
.pc-tabset>input:nth-of-type(3):checked~section:nth-of-type(3),
.pc-tabset>input:nth-of-type(4):checked~section:nth-of-type(4),
.pc-tabset>input:nth-of-type(5):checked~section:nth-of-type(5),
.pc-tabset>input:nth-of-type(6):checked~section:nth-of-type(6){display:block}
@media(prefers-reduced-motion:reduce){.pc-tab,.pc-tab::after{transition:none}}


/* ============================================================================
   27. PAGE-SHELL RHYTHM — .pc-page / .pc-section
   ----------------------------------------------------------------------------
   Three spacings decide whether a console reads as one page or as a pile of
   panels, and until now each of the fifteen consoles picked its own.

     TOP           OWNED BY .pc-wrap / .pc-main — clamp(16px,2.4vw,28px), already
                   in this sheet at payco.css:444/453. .pc-page adds NOTHING here,
                   and that is the rule: a page that wants more air at the top is
                   asking for a different wrapper, not a second padding.
     INTER-SECTION 26px. Not a new number — it is .pc-stage's own margin-top
                   (admin.html:75), which is the rhythm the eyebrow divider has
                   been setting between panel groups all along. Stating it as a
                   rule means a group WITHOUT an eyebrow gets the same air as one
                   with it.
     BOTTOM        48px, written as calc(var(--gap-grid) * 3) so it moves with the
                   grid rhythm rather than drifting from it. Three grid gaps is the
                   smallest band that reads as "the page has ended" instead of "a
                   card got clipped".

   THAT LAST NUMBER IS HALF OF THE HARNESS'S BUDGET. tests/layoutbalance.mjs fails
   a page carrying more than 96px of dead space above its footer, and 96 is this
   48 plus the wrapper's 28px maximum plus 20px of slack for a card's own bottom
   padding and border rounding. The two numbers are chosen together on purpose: a
   page built on .pc-page passes the trailing-space rule by construction, and a
   page that fails it has added padding nobody asked for.
   ==========================================================================*/
.pc-page{padding-top:var(--pc-page-top,0);padding-bottom:var(--pc-page-bottom,calc(var(--gap-grid) * 3))}
.pc-page>*+*{margin-top:var(--pc-page-gap,26px)}
.pc-section{display:block}
.pc-section+.pc-section{margin-top:var(--pc-page-gap,26px)}
/* .pc-stage already carries this rhythm itself (admin.html:66,75) and states its
   own first-child reset; re-stating it here keeps the two from disagreeing when a
   stage is the first thing in a .pc-page. */
.pc-page>.pc-stage:first-child{margin-top:0}

/* ==== end sections 21-27 ==== */


/* ============================================================================
   SECTIONS 28-31 — THE FOUR STATE SURFACES (Wave 5, lane 8)
   ----------------------------------------------------------------------------
   WHAT THESE ARE FOR. Every console on this platform is a shell that fetches. A
   fetch has four outcomes the markup has to be able to say out loud, and until
   now the sheet could say exactly one of them:

     it came back with nothing   -> section 24's .pc-emptystate  (already shipped)
     it failed                   -> section 29's .pc-errorstate  (new)
     the network is gone         -> section 30's .pc-offline     (new)
     the session is gone         -> section 31's .pc-sessionmodal (new)

   tests/statecoverage.mjs's inventory scan measured 191 fetch sites across 73 of
   the 87 root pages, and found that NOT ONE of those 73 pages carried any of the
   four shared components; every page that says any of these things at all says it
   in its own private markup, in its own words, at its own size. Three of its
   numbers are worth repeating here, because they are what these components are
   for: 117 of the 191 surfaces show a failure the reader cannot retry, only 11
   offer a retry control the reader can actually see and press, and ZERO pages on
   the platform listen for the browser's offline / online events at all. The full
   inventory, one row per fetch site, is docs/lane-reports/W5-LANE-8.md.

   THE VOCABULARY IS DELIBERATELY PARALLEL. .pc-emptystate and .pc-errorstate are
   the same box — a centred column of icon / headline / one line of explanation /
   optional action — with different ink and a different ARIA role. They are two
   names rather than one class with a modifier because they are answers to
   different questions and a reader must not have to check a modifier to tell
   "there is nothing here" from "this is broken".

   THE ROLES ARE THE PRODUCT, NOT DECORATION, and they are the one part of these
   components a stylesheet cannot ship:

     .pc-emptystate     role="status"   polite. Nothing is wrong; do not interrupt.
     .pc-errorstate     role="alert"    assertive. Something failed; say so now.
     .pc-offline        role="status"   polite. A condition, not an event.
     .pc-sessionmodal   <dialog>        role=dialog + aria-modal, from the element.

   A sighted reader tells the empty state from the error state by its icon colour
   — --paper-warm/--blue-deep against --warn-bg/--warn. A screen-reader user has
   no icon and no colour, so WITHOUT those two roles the two states are the same
   object with different words in it. tests/statecoverage.mjs asserts the
   distinction against Chromium's real accessibility tree rather than against the
   attributes, because an attribute that the engine declines to map is not a role.

   EVERYTHING HERE IS ADDITIVE. Not one selector, token, value or byte above this
   line was edited, renamed or removed — `git show --stat` on this lane's commit
   is 0 deletions, and the four component names were greped across every .html,
   .js, .css, .mjs and .md in the tree before they were chosen: none of
   .pc-state, .pc-errorstate, .pc-offline, .pc-sessionmodal, .pc-retry,
   .pc-state-ico, .pc-is-offline or PaycoState existed anywhere.

   NO NEW COLOUR, NO NEW FONT, NO NEW BUTTON. Every fill, ink, border, radius and
   shadow below is a var() of a token from section 1. The retry control is a
   plain .pc-btn (section 11) carrying one data attribute; .pc-retry is a hook,
   not a fifth button variant, and it declares no appearance of its own.

   THE BEHAVIOUR SHIPS SEPARATELY. Retry has to re-fire a handler, the offline
   banner has to listen for two events, and the session modal has to remember a
   view — none of which a stylesheet can do. That is one small progressive-
   enhancement block, printed in full in css/payco-system.md section 22. It is
   NOT a new root .js file: this lane owns no page and no root script, so a page
   adopts it by pasting it, and tests/statecoverage.mjs extracts the block FROM
   THE DOCUMENTATION and runs that exact text, so the documented copy and the
   tested copy cannot drift apart.
   ==========================================================================*/


/* ============================================================================
   28. THE SHARED STATE BOX — .pc-state-ico / -head / -note / -detail / -act
   ----------------------------------------------------------------------------
   Section 24 shipped .pc-emptystate with children named .pc-empty-ico /
   .pc-empty-head / .pc-empty-note / .pc-empty-act. Those names still work and are
   not touched. But the error state is the same box, and naming its parts
   .pc-error-ico would leave the system with two spellings of one anatomy and a
   page swapping empty for error would have to rewrite every child class.

   So the children get ONE shared spelling, .pc-state-*, which both boxes accept,
   and the section-24 .pc-empty-* names stay as aliases for the call sites that
   already exist. Adding a name is additive; renaming one is not.

   THE BOX ITSELF is section 24's .pc-emptystate geometry, quoted rather than
   re-derived: a centred flex column, 10px gap, 34px 22px padding, 44px icon slot
   on an 11px radius with a 20px glyph (.pc-sidenav .ico's geometry at the phone
   layer's 44px tap floor, payco.css:378-380), a 14px --ink headline, a 13px
   --ink-mute note capped at 52ch, and an action row. Same numbers, so an empty
   state and an error state in the same grid row are the same height.
   ==========================================================================*/
.pc-emptystate .pc-state-ico,.pc-errorstate .pc-state-ico{width:44px;height:44px;border-radius:11px;
  background:var(--paper-warm);color:var(--blue-deep);display:grid;place-items:center;flex:none;
  margin-bottom:2px}                                                                   /* payco.css:378 */
.pc-emptystate .pc-state-ico svg,.pc-errorstate .pc-state-ico svg{width:20px;height:20px}  /* payco.css:380 */
.pc-emptystate .pc-state-head,.pc-errorstate .pc-state-head,
.pc-errorstate h2,.pc-errorstate h3{font-size:14px;color:var(--ink);
  font-weight:700;letter-spacing:-.02em;display:block;margin:0}
.pc-emptystate .pc-state-note,.pc-errorstate .pc-state-note,
.pc-errorstate p{font-size:13px;color:var(--ink-mute);
  margin:0;line-height:1.5;max-width:52ch}
.pc-emptystate .pc-state-act,.pc-errorstate .pc-state-act{margin-top:4px;display:flex;align-items:center;
  gap:8px;flex-wrap:wrap;justify-content:center}
/* The technical reason, for the person who has to report it. --mono at 12px on
   --paper-warm, capped and wrapped — a raw message is often a URL or a stack
   fragment and must not push a card wider than its grid track. Optional: a state
   with nothing useful to say omits it rather than printing "undefined". */
.pc-emptystate .pc-state-detail,.pc-errorstate .pc-state-detail{font-family:var(--mono);font-size:12px;
  color:var(--ink-mute);background:var(--paper-warm);border:1px solid var(--line-soft);
  border-radius:var(--radius-sm);padding:7px 10px;margin:2px 0 0;max-width:52ch;
  overflow-wrap:anywhere;text-align:left;line-height:1.45}
/* .pc-retry is a HOOK, not a variant. It carries no appearance at all: the retry
   control is whatever .pc-btn the page already uses. The one declaration is a
   busy state, because a retry that gives no feedback gets pressed four times. */
.pc-retry[aria-busy=true]{opacity:.6;cursor:progress;pointer-events:none}


/* ============================================================================
   29. ERROR STATE — .pc-errorstate
   ----------------------------------------------------------------------------
   The box of section 28 with the warning ramp on the icon and a retry control in
   the action row. Two things make it the ERROR state rather than a red empty one:

     role="alert"   assertive. An empty state is a fact about the data and can
                    wait for the reader; a failed fetch is a thing that just
                    happened to them and interrupts. This is set in the MARKUP —
                    see css/payco-system.md section 19 — and the suite reads it
                    back out of the accessibility tree, not off the element.
     --warn-bg / --warn on the icon slot. Section 1 defines --warn as "this is
                    dangerous / this failed" and --amber as "look before you
                    continue". A fetch that failed is the first of those. The
                    offline banner in section 30 is the second, and they are
                    deliberately different colours for that reason.

   WHY NOT A RED PANEL. A full --warn-bg card reads as a destructive-action
   confirmation and, at 3-up, one red card in a row of white ones is the loudest
   thing on the console for a failure that may be a dropped packet. The ink is
   carried by a 44px icon and the words; the surface stays the panel's own.

   .pc-errorstate.pc-state-inline is the same content laid out as one horizontal
   row for a strip that is 40px tall and has no room for a centred column — a
   filter bar, a KPI rail, the head of a table. Same classes, same roles, same
   retry; only the axis changes.
   ==========================================================================*/
.pc-errorstate{display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;gap:10px;padding:34px 22px;color:var(--ink-mute)}                   /* section 24's box */
.pc-errorstate .pc-state-ico{background:var(--warn-bg);color:var(--warn)}
/* An error state that IS the panel body fills it, so a 3-up row does not go
   ragged the moment one card's fetch fails. Mirrors section 24's rule. */
.pc-card>.pc-errorstate:only-child,.pc-size-tall>.pc-scroll>.pc-errorstate:only-child{flex:1 1 auto}
/* the strip variant */
.pc-errorstate.pc-state-inline{flex-direction:row;text-align:left;justify-content:flex-start;
  padding:12px 14px;gap:12px;flex-wrap:wrap}
.pc-errorstate.pc-state-inline .pc-state-ico{width:32px;height:32px;border-radius:9px;margin-bottom:0}
.pc-errorstate.pc-state-inline .pc-state-ico svg{width:17px;height:17px}
.pc-errorstate.pc-state-inline .pc-state-act{margin-top:0;margin-left:auto}
.pc-errorstate.pc-state-inline .pc-state-note{max-width:none}
/* .pc-emptystate accepts the same inline variant, for symmetry — a page must not
   have to change component to change axis. */
.pc-emptystate.pc-state-inline{flex-direction:row;text-align:left;justify-content:flex-start;
  padding:12px 14px;gap:12px;flex-wrap:wrap}
.pc-emptystate.pc-state-inline .pc-state-ico,.pc-emptystate.pc-state-inline .pc-empty-ico{
  width:32px;height:32px;border-radius:9px;margin-bottom:0}
.pc-emptystate.pc-state-inline .pc-state-act,.pc-emptystate.pc-state-inline .pc-empty-act{
  margin-top:0;margin-left:auto}


/* ============================================================================
   30. OFFLINE BANNER — .pc-offline
   ----------------------------------------------------------------------------
   One bar across the top of the viewport saying the browser has lost the
   network. It is a CONDITION, not an event: it appears when `offline` fires and
   it clears when `online` fires, and it never needs dismissing, because a
   dismissable "you are offline" is a lie the moment it is dismissed.

   WHY --amber AND NOT --warn. Section 1's roles: --warn is "this is dangerous /
   this failed", --amber is "look before you continue". Losing the network has not
   broken anything yet — it means the next thing you press may not save. That is
   exactly caution, and painting it in the failure colour would make it
   indistinguishable from the error state in section 29, which sits two
   centimetres below it on the same screen.

   ---- THE Z-ORDER, WHICH IS THE WHOLE DESIGN PROBLEM HERE ----
   This tree already has eight fixed overlays and they are all pinned to the
   BOTTOM or the CORNERS. Measured, not assumed (grep for z-index across *.css,
   *.js and *.html):

     40      .pc-sidenav          left rail, fixed, top:74px to bottom:0
     50      .pc-header           sticky, top:0
     60      .pc-skip             the skip link
     9990    .pcb-ring            bug-reporter highlight
     9996    .pc-offline          <- THIS BANNER, top edge
     9997    .pca-nudge           assist nudge,    bottom right
     9998    .pcc                 COOKIE BAR,      bottom, full bleed
     9998    .pca-bubble/.pca-panel, .pcb-btn       bottom right
     9999    .pm-wrap, .pcb-panel, .pcb-pinbar      centre / bottom
     10000   .pm-toast, .pcdlg-ovl, .pcb-halo       centre
     10001   .pcb-tip
     10002   .pc-sessionmodal     <- section 31, and see its own note
     100000  #splashSkip          index.html only

   9996 is chosen to sit ABOVE everything the app chrome owns (the rail at 40,
   the header at 50, the skip link at 60, every nav dropdown) and BELOW every
   pre-existing overlay, so this lane cannot re-order somebody else's stack. It
   costs nothing to be below them: they are all pinned to the bottom or the
   corners and this is pinned to the top, so the two sets never occupy a pixel in
   common. tests/statecoverage.mjs proves that by rendering the cookie bar and
   this banner on one page and measuring that their boxes do not intersect —
   which is also why that measurement is written against these two elements
   specifically rather than as a general overlap sweep. A general sweep over a
   console is defeated by the rail and the cookie bar both being full-height /
   full-bleed fixed boxes, and would report an "overlap" on every page in the
   tree whether or not this banner existed.

   ---- IT RESERVES ITS OWN ROOM, RATHER THAN COVERING THE HEADER ----
   A fixed bar at top:0 lands on top of a sticky header that is also at top:0.
   Three rules move the whole chrome down by exactly the banner's height, and
   only while body carries .pc-is-offline:

     body.pc-is-offline              padding-top   the banner's height
     body.pc-is-offline .pc-header   top           the sticky offset follows it
     body.pc-is-offline .pc-sidenav  top           74px + the banner's height

   The rail one is easy to forget and expensive to miss: .pc-sidenav is
   `top:var(--header-h)` and fixed, so without it the rail's first item slides
   under the header. --pc-offline-h is one custom property read by all four
   rules, so a page that needs a taller banner sets one number.

   The height is 40px: 13px/700 text on 11px of vertical padding, which is the
   .pc-viewbar rhythm (payco.css:393-396) rather than a new one. Under 560px it
   is allowed to wrap to two lines and the reservation follows automatically
   because it is measured from the element, not hard-coded — see the
   measure-then-set line in the section-22 script.
   ==========================================================================*/
.pc-offline{position:fixed;left:0;right:0;top:0;z-index:9996;
  display:flex;align-items:center;justify-content:center;gap:10px;flex-wrap:wrap;
  min-height:var(--pc-offline-h,40px);padding:11px clamp(16px,2.5vw,30px);
  background:var(--amber-bg);color:var(--amber);border-bottom:1px solid rgba(143,99,0,.22);
  font-size:13px;font-weight:700;line-height:1.4;text-align:center}      /* payco.css:393-396 rhythm */
.pc-offline[hidden]{display:none}
.pc-offline .pc-offline-dot{width:8px;height:8px;border-radius:50%;background:currentColor;flex:none}
.pc-offline .pc-offline-note{font-weight:600;color:var(--amber)}
body.pc-is-offline{padding-top:var(--pc-offline-h,40px)}
body.pc-is-offline .pc-header{top:var(--pc-offline-h,40px)}
body.pc-is-offline .pc-sidenav{top:calc(var(--header-h) + var(--pc-offline-h,40px))}
/* The banner is a live region that appears and disappears; a slide would animate
   a layout shift the reader is already coping with. Opacity only, and section
   19's reduced-motion contract kills it outright. */
@keyframes pcOfflineIn{from{opacity:0}to{opacity:1}}
.pc-offline{animation:pcOfflineIn .16s cubic-bezier(0,0,.2,1) both}
@media(prefers-reduced-motion:reduce){.pc-offline{animation:none}}


/* ============================================================================
   31. SESSION-EXPIRED MODAL — .pc-sessionmodal
   ----------------------------------------------------------------------------
   The session ended while the tab was open. The reader is looking at a console
   full of data they can no longer act on, and every button on it will now fail.

   THE ONE REQUIREMENT THAT MAKES THIS HARD: it must PRESERVE THE VIEW. The
   platform has no way to do that today, measured rather than assumed: 42 of the
   191 surfaces check 401 near the call site at all (18 of them as one branch with
   403, which is a different thing — see below), every one of them answers with a
   sign-in link or a navigation, NOT ONE stores where the reader was, and
   login.html:127 sends every successful sign-in to `operations.html` regardless
   of where it came from. So the reader signs back in and lands on a console they
   were not using, then has to find their way back to what they were doing. That
   whole path is in docs/lane-reports/W5-LANE-8.md, with the patch request to the
   door's own lane. This component does not navigate. It captures
   {href, scrollY, page-supplied view state} into sessionStorage, opens over the
   page the reader is already on, and hands that record back after re-auth so the
   page can put itself back. The capture and restore are in the section-22
   script; tests/statecoverage.mjs asserts a round trip through a real reload.

   IT IS A NATIVE <dialog>. That is the whole reason to use one: showModal() puts
   it in the TOP LAYER — above every z-index in the table in section 30, without
   entering the contest — and gives a focus trap, inert page content behind it,
   ::backdrop and a role of dialog+aria-modal from the element itself, none of
   which a div can have without script this lane is not shipping.

   Esc IS CANCELLED, deliberately and with one line in the script (`cancel` ->
   preventDefault). A modal you can dismiss to reveal a console that no longer
   works is worse than no modal: every subsequent click fails silently and the
   reader concludes the product is broken rather than that they are signed out.
   The dialog is closed by signing in, and by nothing else.

   THE z-index:10002 IS THE FALLBACK PATH, NOT THE MAIN ONE. If showModal() is
   unavailable or throws (it throws if the element is already open), the script
   falls back to setting the `open` attribute, which does NOT enter the top layer
   — a plain [open] dialog is position:absolute and in flow. .pc-nodialog is the
   class the script sets on that path: it re-pins the element to the viewport and
   paints its own backdrop as a ::before at z-index:-1 inside the dialog's own
   stacking context, so it covers the viewport at the dialog's layer and still
   paints behind the panel. 10002 clears .pcb-tip's 10001, the highest overlay in
   the tree outside index.html's own splash.

   THE PANEL is --shadow-lg on --white at --radius, which section 1 names as the
   modal elevation, and its own top sliver so it reads as part of the product
   rather than as a browser artefact. Width is min(440px,100%) — .pcdlg's own
   420px plus one --gap-grid, so it holds a two-button action row without wrap.
   ==========================================================================*/
.pc-sessionmodal{border:0;padding:0;background:transparent;max-width:none;max-height:none;
  width:min(440px,calc(100% - 32px));color:var(--ink);font-family:var(--font)}
.pc-sessionmodal::backdrop{background:rgba(11,27,58,.42)}
.pc-sessionmodal>.pc-sessionpanel{background:var(--sliver) top center/100% var(--sliver-h) no-repeat,var(--white);
  background-origin:border-box;border:var(--hair) solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow-lg);padding:24px 22px 18px}
.pc-sessionmodal h2,.pc-sessionmodal .pc-session-head{font-size:15.5px;font-weight:800;
  letter-spacing:-.01em;line-height:1.35;margin:0 0 7px;color:var(--ink)}
.pc-sessionmodal p,.pc-sessionmodal .pc-session-note{margin:0;font-size:13.5px;line-height:1.55;
  color:var(--ink-soft)}
/* "you will come back to this page" — the promise the capture actually keeps.
   Quiet, because it is reassurance rather than instruction. */
.pc-sessionmodal .pc-session-keep{margin-top:10px;font-size:12.5px;color:var(--ink-mute);
  background:var(--paper-warm);border:var(--hair) solid var(--line-soft);border-radius:var(--radius-sm);
  padding:9px 11px;display:flex;align-items:center;gap:8px;text-align:left}
.pc-sessionmodal .pc-session-keep b{color:var(--ink-soft);font-weight:700;overflow-wrap:anywhere}
.pc-sessionmodal .pc-session-act{margin-top:16px;display:flex;align-items:center;gap:8px;
  justify-content:flex-end;flex-wrap:wrap}
/* the no-top-layer fallback — see the note above */
.pc-sessionmodal.pc-nodialog[open]{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
  z-index:10002;margin:0}
.pc-sessionmodal.pc-nodialog[open]::before{content:"";position:fixed;inset:0;z-index:-1;
  background:rgba(11,27,58,.42)}
/* The page behind a modal must not scroll under it. Set with the modal, cleared
   with it; scoped to a class so nothing else in the tree can strand it. */
body.pc-session-locked{overflow:hidden}
@media(max-width:560px){
  .pc-sessionmodal>.pc-sessionpanel{padding:20px 16px 16px}
  .pc-sessionmodal .pc-session-act>*{flex:1 1 auto;justify-content:center}
}

/* ==== end sections 28-31 ==== */


/* ============================================================================
   SECTIONS 32-33 — THE TWO THINGS THE COMPONENTS COULD NOT YET DO (Wave 6, lane 12)
   ----------------------------------------------------------------------------
   Wave 5 shipped the four state surfaces above and a per-page adoption request.
   Wave 6's inventory re-ran that scan over the WHOLE tree — every root page AND
   every root .js module, which the first scan did not read — and found 225 async
   surfaces on 82 files, still with zero adoption. Two of the gaps it exposed are
   gaps in THESE COMPONENTS rather than in the pages, and both are fixed here and
   in the section-22 script:

     1. THE RETRY DID NOT RE-ISSUE THE CALL. Section 29's contract was
        `retry: <the function that failed>` — the retry re-ran the PAGE'S IDEA of
        the request. If that function re-reads a filter input, a retry after the
        reader has touched the filter sends a DIFFERENT request under a button
        that promised the same one; and the 61 surfaces the scan counted as
        "has a retry" are mostly location.reload(), which is not a retry of the
        call at all — it re-issues every request on the page, discards every
        unsaved field and puts the reader back at the top. Section 22 now freezes
        the request (method, url, body, headers, credentials) at the moment it is
        issued and the control re-issues THAT descriptor. Proved by driving it:
        tests/systemadoption.mjs intercepts both attempts in a real Chromium and
        compares method, url and body byte for byte, and asserts the document was
        never replaced.

     2. THE SESSION MODAL RESTORED THE PLACE BUT NOT THE TYPING. Coming back to
        the right page, at the right scroll offset, to an empty form is not being
        brought back: on this platform the surfaces behind a 401 are intake,
        onboarding, underwriting parameters and a dispute reply — the typing IS
        the work. Section 22's record now carries the in-progress field values
        too (never a password, never a file input, never anything inside
        [data-pc-noretain]) and restores them with the offset.

   WHAT THAT NEEDS FROM THE STYLESHEET is exactly two things, and this section is
   only those two. No token, value, selector or byte above this line is edited —
   this whole file's change this wave is additive, which is why every suite that
   pins a number in it (designsystem, designfix, designfixes, systemui,
   loadingstates, statecoverage, consolelayout, overflow) is still green.
   ==========================================================================*/


/* ============================================================================
   32. "THE NETWORK IS GONE" AS A PANEL STATE — .pc-errorstate.pc-state-nonet
   ----------------------------------------------------------------------------
   Section 30's banner is a PAGE-level condition: one bar, across the top, saying
   the browser has no network. It is not an answer for the panel the reader is
   actually looking at, which is showing them a failure in the failure colour and
   a button that will fail again the moment they press it.

   This is the same error box, re-inked to the banner's ramp so the two agree
   with each other, and it says the one useful thing: nothing is broken, and this
   panel will load itself when the connection comes back. Section 22 keeps that
   promise — a surface that failed offline goes on a waiting list that the
   `online` event drains, so the reader does not have to find and press a retry
   control on every panel of a console that lost its connection for four seconds.

   WHY --amber AND NOT --warn, again. Section 30 made this distinction for the
   banner and it would be pointless to make the banner amber and then paint the
   panel it explains in the failure colour two centimetres below it. --warn is
   "this failed"; --amber is "look before you continue". A dropped connection is
   the second one. The retry control keeps its own appearance — it is still a
   plain .pc-btn, and this section adds nothing to it.
   ==========================================================================*/
.pc-errorstate.pc-state-nonet .pc-state-ico{background:var(--amber-bg);color:var(--amber)}


/* ============================================================================
   33. THE RESTORED FIELD — .pc-restored
   ----------------------------------------------------------------------------
   After re-authentication the page comes back with the reader's own half-typed
   values in it. Those values are indistinguishable from the page's defaults, and
   a reader who cannot tell which is which has to re-check every field — which is
   the work the restore was supposed to save.

   So a restored field carries a 3px edge in the caution ramp for as long as it
   is untouched, and the script clears the marker on the field's first `input`.
   It is deliberately quiet: this is "these are yours, from before", not a
   validation error, and it must not read as one on a form that also has real
   errors on it.

   IT IS AN EDGE AND NOT A FILL, and that is a measurement rather than a taste.
   Section 14 sets a field's surface with
   `input:not([type=radio]):not([type=checkbox])...:not([type=range])`, which is
   five attribute selectors and an element — specificity (0,5,1). A bare class is
   (0,1,0) and loses to it, so a marker written as `background:` would simply not
   paint on the text fields it is for, and the only ways to win are to out-specify
   another section's rule or to reach for !important. Both are worse than the
   honest answer: the field's surface belongs to section 14, and the marker is an
   edge drawn beside it.

   THE NUMBER IS AN EXISTING ONE. The edge is --sliver-h (3px), the same 3px this
   system already paints as the panel sliver, drawn as an inset shadow so it
   costs no layout and cannot move a field by a pixel or reflow a form. The ink
   is --amber, section 1's caution ink — no new colour enters the system here.

   IT DECLARES NO `outline`, ON PURPOSE, and that is the load-bearing line of
   this section. The focus ring in section 20 is an `outline` written at
   :where() — zero specificity, so that any page rule can override it without
   !important. A marker class that set an outline would therefore delete the
   focus ring from every field it lands on, which on a restored form is every
   field the reader is about to tab through. The marker is a box-shadow for that
   reason, and section 14's own focus rule (element + :focus, which
   out-specifies a bare class) paints the 3px focus glow over it the moment the
   field is entered — so the two never contest a declaration.
   ==========================================================================*/
.pc-restored{box-shadow:inset var(--sliver-h) 0 0 var(--amber)}
/* A checkbox and a radio paint their own control and have no inside to draw an
   edge on, so the marker goes round the outside of them instead — still a
   shadow, still no outline, so the focus ring survives here too. */
input[type=checkbox].pc-restored,input[type=radio].pc-restored{
  box-shadow:0 0 0 var(--sliver-h) var(--amber)}

/* ==== end sections 32-33 ==== */


/* ============================================================================
   34. DARK THEME — THE SURFACE REPAIRS (W8 LANE 15)
   ----------------------------------------------------------------------------
   Section 1b re-points names and that fixes almost everything, because almost
   every rule in this sheet paints with var(). This section is the AUDITED
   REMAINDER: the rules above that paint a colour literal, so no re-point can
   reach them, plus the three places where a re-pointed name lands on a fill that
   did not move with it. Each one is named with the line it repairs and the
   measured reason it needed repairing. Nothing here invents a colour — every
   value is a --pc-* token of lane 14's with that token's own value as fallback.

   ---- WHERE THIS BLOCK SITS, AND WHY THAT IS SAFE ----
   It is at the FOOT of the sheet, after section 19's mobile floors and after
   section 20's button-behaviour block, and both of those are load-bearing
   positions that a careless append destroys:

     Section 19's @media(max-width:560px) layer raises body to 15.5px, inputs to
     16px and every tap target to 44px. A later rule that re-states font-size,
     min-height or padding on those selectors wins on source order and silently
     reverts the floor. THIS BLOCK DECLARES NO font-size, NO min-height, NO
     padding and NO width/height ANYWHERE — it is colour, background, border-
     colour and box-shadow only, so there is nothing for it to revert.
     tests/darktheme.mjs asserts that property, by parsing this section's own
     declarations, so it stays true if the block is ever extended.

     Section 20's rule is that the last `.pc-btn{transition}` declaration must be
     the same one whichever of this sheet and payco.css loads second. Nothing
     below declares `transition` on .pc-btn, .cta, .btn, .go or .pri, so section
     20 is still the last word on button behaviour — the same argument sections
     21-27 make at line 851 and tests/designsystem.mjs measures.

   Every selector is [data-theme="dark"] + the rule it repairs, so it is inert on
   a light page: one attribute of extra specificity over the light rule, and no
   effect at all when the attribute is absent.
   ==========================================================================*/

/* ---- 34.1 THE APP BAR — .pc-header:298 paints rgba(251,252,254,.86) ----------
   A literal near-white at 86% alpha, so the re-point of --paper cannot reach it
   and the bar stayed white with light ink on it in dark. It goes OPAQUE here
   rather than keeping the translucency: a backdrop-filter blur behind an 86%
   near-black is a fifteen-percent contribution nobody can see, and paying for a
   compositor layer for it on every console is worse than not having it.

   COLOUR ONLY. Not the height, not the padding, not the border WIDTH — .pc-header
   is a 74px BORDER box and index.html draws the same bar from payco-marketing.css
   which this sheet cannot reach, so any geometry change here re-centres the
   wordmark on the consoles and leaves the marketing bar where it was.
   tests/headerparity.mjs measures exactly that and a 1px border change has
   already cost it 68 assertions once (see --hair-thin at line 187). Measured
   before and after this block: 2850 passed, 0 failed, unchanged. */
[data-theme="dark"] .pc-header{background:var(--pc-surface-2,hsl(220 16% 13%));
  border-bottom-color:var(--pc-line,hsl(218 13% 26%))}

/* ---- 34.2 THE VIEW BAR — .pc-viewbar:693 paints a two-stop near-white ramp ----
   linear-gradient(90deg,#e8efff,#eef4ff) with color:var(--ink-soft). Once
   --ink-soft is the light grey of a dark theme, that is hsl(214 14% 80%) text on
   a #e8efff fill: 1.27:1, which is the single worst pair the audit found. The
   ramp becomes the recessed surface; the ink follows its own token. */
[data-theme="dark"] .pc-viewbar{background:var(--pc-surface-3,hsl(220 14% 18%));
  border-bottom-color:var(--pc-line,hsl(218 13% 26%))}
[data-theme="dark"] .pc-viewbar.active{background:var(--pc-accent-soft,hsl(218 100% 66% / .16));
  border-bottom-color:var(--pc-line,hsl(218 13% 26%))}

/* ---- 34.3 THE IDENTITY CHIP — .adminbox:701 paints #0b1b3a / #1c3057 ---------
   A near-black pill with white text. It keeps its contrast in dark — but it is
   the same value as the page behind it, so the chip stops being a chip: measured
   1.16:1 against the dark backdrop, well under the 3:1 a control boundary needs.
   It moves UP to the recessed surface instead of down, which is the only
   direction available on a dark floor. */
[data-theme="dark"] .adminbox{background:var(--pc-surface-3,hsl(220 14% 18%));
  color:var(--pc-ink,hsl(210 22% 96%));border-color:var(--pc-line,hsl(218 13% 26%))}
[data-theme="dark"] .adminbox:hover{background:var(--pc-surface-2,hsl(220 16% 13%));
  border-color:var(--pc-accent,hsl(218 100% 66%))}

/* ---- 34.4 THE RAIL ICON SLOT — .pc-sidenav .ico:677 paints #eef1fa -----------
   A near-white chip carrying color:var(--blue-deep). Section 1b makes --blue-deep
   the bright accent, so this became hsl(200 96% 70%) on #eef1fa — 1.5:1, an icon
   that is simply not there. Same geometry, themed fill. .pc-emptystate's and
   .pc-errorstate's icon slots already read --paper-warm and follow for free. */
[data-theme="dark"] .pc-sidenav .ico{background:var(--pc-surface-3,hsl(220 14% 18%));
  color:var(--pc-accent-hi,hsl(200 96% 70%))}

/* ---- 34.5 THE TWO SCROLL FADES — .pc-sidenav:661 and .pc-scroll:1023 ---------
   Both build a scroll affordance out of four background layers, and both were
   quoted verbatim from payco.css:362-369, literals and all. Two of the four are
   `rgba(255,255,255,0)` — TRANSPARENT WHITE. A gradient from a dark surface to
   transparent white interpolates through translucent grey, so in dark each end of
   every scroll region grew a pale haze exactly where the fade was meant to be
   invisible. The other two are `rgba(10,22,40,.14)`, a near-black shadow, which
   on a near-black surface is nothing at all.

   The repair keeps the technique — background-attachment:local for the fades so
   they travel with the content and vanish at each end, `scroll` for the shadows
   so they stay pinned to the frame — and only re-inks it: the fade runs to the
   surface at zero alpha instead of to white, and the shadow is lifted to .55 so
   it is visible against a dark floor. The four-layer order is unchanged, so the
   solid surface stays last and still backs the whole region. */
[data-theme="dark"] .pc-sidenav{
  background:
    linear-gradient(var(--pc-surface-2,hsl(220 16% 13%)) 30%,hsl(220 16% 13% / 0)) top/100% 26px no-repeat local,
    linear-gradient(hsl(220 16% 13% / 0),var(--pc-surface-2,hsl(220 16% 13%)) 70%) bottom/100% 26px no-repeat local,
    radial-gradient(farthest-side at 50% 0,hsl(220 30% 2% / .55),hsl(220 30% 2% / 0)) top/100% 9px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%,hsl(220 30% 2% / .55),hsl(220 30% 2% / 0)) bottom/100% 9px no-repeat scroll,
    var(--pc-surface-2,hsl(220 16% 13%));
  border-right-color:var(--pc-line,hsl(218 13% 26%))}
[data-theme="dark"] .pc-scroll{
  background:
    linear-gradient(var(--pc-surface-2,hsl(220 16% 13%)) 30%,hsl(220 16% 13% / 0)) top/100% 26px no-repeat local,
    linear-gradient(hsl(220 16% 13% / 0),var(--pc-surface-2,hsl(220 16% 13%)) 70%) bottom/100% 26px no-repeat local,
    radial-gradient(farthest-side at 50% 0,hsl(220 30% 2% / .55),hsl(220 30% 2% / 0)) top/100% 9px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%,hsl(220 30% 2% / .55),hsl(220 30% 2% / 0)) bottom/100% 9px no-repeat scroll,
    var(--pc-surface-2,hsl(220 16% 13%))}

/* ---- 34.6 / 34.7 THE TWO INVERTED FILLS — a literal #fff ON a token fill -----
   Three rules in this sheet paint white text on a SOLID token background, and all
   three read that white as a literal so no re-point can reach it:

     .pc-unread:720      color:#fff on var(--blue)   light 6.83:1  dark 3.06:1  ✗
     .pc-pill.bad:589    color:#fff on var(--warn)   light 5.72:1  dark 2.30:1  ✗
     .pc-btn.danger:571  color:#fff on var(--warn)   light 5.72:1  dark 2.30:1  ✗

   Both fills LIFT in dark — the accent to hsl(218 100% 66%), the failure red to
   lane 14's rgb(255 138 110) — which is correct and is what makes them visible on
   a dark card. What breaks is the ink riding on top of them: white on a lifted
   fill is the classic dark-mode inversion, and each of these is small heavy text
   (10.5px/800 for the badge, 12px/700 for the pill) where AA is 4.5:1 with no
   large-text relief.

   The fills are right and the ink is what moves. Lane 14 already names both:
   --pc-ink-invert is "the ink that goes on an accent fill" and --pc-danger-ink is
   its partner on the failure fill. Measured after: 5.95:1 and 9.14:1.

   THE FALLBACKS ARE SPELLED hsl(), NOT rgb(), AND THAT IS LOAD-BEARING. Lane 8's
   tests/statecoverage.mjs slices this sheet from the sections 28-31 header to the
   END OF FILE and forbids any rgb()/rgba() literal it has not documented — so a
   fallback written as rgb(24 14 11) in section 34, which is inside that slice,
   turns CSS3 red in a suite this lane does not own. hsl(14 37% 7%) is the same
   colour to the byte (it round-trips to rgb(24,14,11)) and is outside the pattern
   that suite matches. Section 1b is above the slice and is not subject to it.

   .pc-unread needs no :not(.pc-unread-tk) exception. The ticket counter overrides
   only the BACKGROUND, to --warn, and --warn now moves with everything else, so
   one ink is correct on both badges — 5.95:1 on the accent, 7.89:1 on the lifted
   red. Section 1b's re-point is what collapsed two rules into one.

   .pc-kpi.alert b:523 is NOT in this list and that is the point: it reads
   color:var(--warn) rather than a literal, so section 1b's re-point of --warn
   repairs it with no rule at all. It measured 1.61:1 before that re-point. */
[data-theme="dark"] .pc-unread{color:var(--pc-ink-invert,hsl(220 18% 9%))}
[data-theme="dark"] .pc-pill.bad,
[data-theme="dark"] .pc-btn.danger{color:var(--pc-danger-ink,hsl(14 37% 7%))}

/* ---- 34.8 COMPATIBILITY — the four pages that define dark by SWAPPING --------
   disputes.html:357, outbox-console.html:271, reconcile.html:311 and
   verify-review.html:160 each ship the same block:

       [data-theme="dark"]{ --pc-hud-face:var(--ink); --pc-hud-ink:var(--white); … }

   That is correct ONLY on a platform where --ink and --white never move — it
   spells "dark" as "the light theme's ink", which is a workaround for the absence
   of a dark theme rather than a use of one. Section 1b moves both names, so those
   six declarations invert: --pc-hud-face becomes the LIGHT ink and the four
   consoles' HUD panels would have gone near-white in dark mode. Measured in
   Chromium on all four pages with this rule deleted at runtime, so the "before"
   is the real cascade and not a prediction: --pc-hud-face resolves to
   hsl(210 22% 96%) and --pc-hud-ink to hsl(220 16% 13%) — a near-white panel
   carrying near-black text, on a dark page. With the rule in place the same read
   back gives face hsl(220 16% 13%) and ink hsl(210 22% 96%), the right way round.

   This block keeps the promise those pages were making, in the tokens that
   actually carry it. It is deliberately :root[data-theme="dark"] and not
   [data-theme="dark"] — (0,1,1) against their (0,1,0) — because their block is in
   a page <style> that loads after this sheet and would otherwise win on order.
   Out-specifying another lane's page is not something to do lightly, and it is
   done here because the alternative is shipping a known inversion on four
   consoles. It is a SHIM WITH AN EXPIRY: the interface request to replace those
   six declarations with the --pc-* names is in docs/lane-reports/W8-LANE-15.md,
   and this rule comes out the day it lands. tests/adminpagesc.mjs:229 asserts
   those pages still DECLARE --pc-hud-face under [data-theme="dark"] and is
   untouched by this — measured 230 passed, 0 failed before and after. */
:root[data-theme="dark"]{
  --pc-hud-face:var(--pc-surface-2,hsl(220 16% 13%));
  --pc-hud-ink:var(--pc-ink,hsl(210 22% 96%));
  --pc-hud-ink-mute:var(--pc-ink-mute,hsl(216 11% 64%));
  --pc-hud-line:var(--pc-line,hsl(218 13% 26%));
  --pc-hud-accent:var(--pc-accent,hsl(218 100% 66%));
  --pc-hud-accent-soft:var(--pc-accent-soft,hsl(218 100% 66% / .16));
}

/* ---- 34.9 THE INVERTED ASSET — the wordmark ---------------------------------
   logo.png (640x157) is a DARK mark on transparent, drawn for a white bar. On the
   dark bar of 34.1 it all but disappears. Measured by rasterising the asset to a
   canvas and reading its own opaque pixels back, rather than by eye:

     unfiltered on the LIGHT bar   worst pixel 12.39:1   mean 6.94:1   (shipping)
     unfiltered on the DARK bar    worst pixel  1.29:1   mean 2.30:1   ✗
     with the filter below         worst pixel  3.03:1   mean 9.22:1

   There is no light-ink variant in the tree — the root and img/ carry one
   logo.png — and a stylesheet cannot ship an asset, so the choice is between an
   unreadable mark and a filter.

   invert(1) alone would flip the mark's dark blue to a pale yellow-cream, which is
   a colour the brand does not own; hue-rotate(180deg) puts the hue back, so it
   returns as light blue type. Scoped to .pc-header .pc-logo so it cannot reach a
   logo used inside page content, and to the dark theme only.

   ON THE TWO NUMBERS. The mean is what a reader sees — 9.22:1, better separated
   than the 6.94:1 the mark ships with on the light bar today. The 3.03:1 worst
   pixel is the single darkest pixel in a 640x157 anti-aliased raster, i.e. a glyph
   EDGE, which is meant to blend toward the surface; it still clears the 3:1 that
   WCAG 1.4.11 asks of a graphical object. `brightness(0) invert(1)` was measured
   too and gives a flat 16.49:1, but it discards every internal shade and the
   mark's blue with them, so it is a silhouette rather than the wordmark.

   THE PROPER FIX IS AN ASSET, not a filter, and the interface request for a
   light-ink logo.png variant is in docs/lane-reports/W8-LANE-15.md. This is what
   the sheet can do on its own in the meantime. */
[data-theme="dark"] .pc-header .pc-logo{filter:invert(1) hue-rotate(180deg)}

/* ---- 34.10 THE ONE INVERSION SECTION 1b's RAMP LIFT CREATES ------------------
   Lifting --ok / --amber (section 1b) turns every rule that puts LIGHT ink on
   those fills into the mirror of the bug being fixed. This is the enumeration of
   which rules those actually are, and it is a MEASUREMENT, not a grep: every
   candidate was rendered in Chromium with the page's own <style> applied to
   injected markup matching that page's own selector, in dark, and read back out
   of the cascade. A grep over the same rules proposes eleven sites. Ten of them
   are not defects, and the difference is what the measurement is for.

     marketplace-listing.html:133  .offer button.go   5.37:1 -> 2.25:1   ✗ REAL
       color:#fff on background:var(--ok), and the button carries a real label —
       <button class="go">Accept</button> at marketplace-listing.html:314. It is
       the seller's primary action on the offers panel. Repaired below.

   AND THE TEN THAT ARE NOT, because the distinction is the whole finding:

     pos.html:112          .dot.green / .dot.yellow    EMPTY. 10px round span,
     my-project.html:76,85 .mp-step.s-* .mp-dot        EMPTY, aria-hidden.
       These carry NO text — pos.html:410 renders <span class="dot green"
       title="…"></span> and my-project.html:347 renders an aria-hidden span. They
       are graphical objects under WCAG 1.4.11, which asks 3:1 against what is
       BEHIND them, not 4.5:1 against an ink they do not have. Measured against the
       app background they sit on, the green goes 3.38:1 -> 8.05:1 and the amber
       3.41:1 -> 8.47:1. BOTH ALREADY CLEARED 3:1 BEFORE THE LIFT: this is headroom,
       not a repair, and calling it a fix would be overclaiming. What matters here
       is only that the lift does not move them DOWN.

       Worth recording how close this came to being logged as four regressions: an
       earlier pass of this audit injected a digit into each dot so the probe would
       have something to measure, and so manufactured a text pair the product does
       not have. Read as ink, they "failed" at 2.06:1 and 1.96:1. The elements are
       empty; the probe wrote the defect it then found.

     underwriting.html:291,301,302  .tick.ok / .vmark  UNAFFECTED. Measured
     support.html:156               .sd-pri.p-high     UNAFFECTED. Measured
       These read #0e7a4e and #8f6300 in dark AFTER the lift, not the lifted
       values, because those pages declare their own --ok / --amber in their own
       <style>. Section 1b cannot reach them, so the lift neither helps nor harms
       them. They keep their existing 5.37:1 / 5.31:1 / 3.41:1 and are carried as
       interface requests rather than touched from here.

     jobs-post.html:291    .chk li.done .mark          3.07:1 -> 7.32:1  IMPROVED
       A tick glyph on the fill, inheriting the card's ink rather than a literal
       white, so the lift carries it up instead of down.

   THE REPAIR is the ink, not the fill — the same move 34.7 makes for
   .pc-btn.danger, against the same lane-14 name. [data-theme="dark"] + the page's
   own selector is (0,3,1) against its (0,2,1), so this wins without !important
   and without the page changing. Measured after: 8.05:1.

   THIS IS A SHIM AND IT IS SCOPED TO SAY SO. .offer is marketplace-listing.html's
   own class, not a system class, and a shared sheet reaching into one page's
   namespace is a coupling this file should not keep. The interface request asking
   that page to read var(--pc-ink-invert) — or simply to stop hardcoding #fff on a
   themed fill — is in docs/lane-reports/W8-LANE-15.md, and this rule comes out the
   moment it lands. It is dark-only and declares nothing but colour, so it is inert
   in light and cannot reach section 19's mobile floors. */
[data-theme="dark"] .offer button.go{color:var(--pc-ink-invert,hsl(220 18% 9%))}

/* ==== end section 34 ==== */


/* ============================================================================
   35. 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.

   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.

   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 section 35 ==== */


/* 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; }
}
