
  :root{
    --bg:#f6f4f1; --panel:#fff; --ink:#181012; --muted:#544b44; --line:#d8d2cb;
    --navy:#1c0d10; --indigo:#1c0d10; --green:#2f6b43; --amber:#9a6a1a; --red:#9a3b3b;
    --hover:#f4f2ef; --btn-bg:#1c0d10; --btn-fg:#fff;
    --purple:#7c3aed; --review:#b45309; --amber-fill:#d9730a; --err:#b42318; --ok:#067647; --rec:#1456c4;
    --shadow:0 1px 2px rgba(28,13,16,.03),0 12px 30px rgba(28,13,16,.05);
    --spring:cubic-bezier(.22,1,.36,1); color-scheme:light;
  }
  [data-theme="dark"]{
    --bg:#0f1012; --panel:#18191c; --ink:#f1f2f4; --muted:#9a9ea6; --line:#2c2e33;
    --navy:#d6d8dc; --indigo:#d6d8dc; --green:#5fd98c; --amber:#eeb152; --red:#ee8a7e;
    --hover:#1f2024; --btn-bg:#e9ebee; --btn-fg:#15161a;
    --purple:#b9a3ef; --review:#e8a55c; --amber-fill:#e8a55c; --err:#f0998d; --ok:#5fd98c; --rec:#7db0f6;
    --shadow:0 1px 2px rgba(0,0,0,.2),0 12px 30px rgba(0,0,0,.4); color-scheme:dark;
  }
  [data-theme="dark"] .b-review{background:#3a2c12;color:#f2b85f}
  [data-theme="dark"] .b-ready{background:#1e2e4a;color:#8bb4f2}
  [data-theme="dark"] .b-approved{background:#18402a;color:#6edb94}
  [data-theme="dark"] .b-approv-y{background:#3a3010;color:#f0d060}
  [data-theme="dark"] .b-missing{background:#3f2020;color:#f3978c}
  [data-theme="dark"] .b-proj{background:#2b2e35;color:#cdd0d7}
  [data-theme="dark"] .b-up{background:#3a3414;color:#e7d271}
  [data-theme="dark"] .up-row td{background:#1d1e22}
  [data-theme="dark"] .b-mem{background:#2a2140;color:#bcaeea}
  [data-theme="dark"] .pill.hi{background:#18402a;color:#6edb94}
  [data-theme="dark"] .pill.mid{background:#3a2c12;color:#f2b85f}
  [data-theme="dark"] .pill.lo{background:#3f2020;color:#f3978c}
  [data-theme="dark"] .soa{background:#2a2013;border-color:#4a3a1e;color:#e2ab52}
  [data-theme="dark"] .code.flag{background:#241f14;border-color:#4a3e22}
  [data-theme="dark"] .code.missing{background:#2b1a1a;border-color:#4d2b2b}
  [data-theme="dark"] .bar{background:#2a241e}
  [data-theme="dark"] .step.done{background:#13271a;border-color:#2b4a35}
  [data-theme="dark"] .reasons li{color:#d6a85a}
  [data-theme="dark"] .intake .addr{background:#251f2c;border-color:#3a3350;color:#c2b6e0}
  [data-theme="dark"] .sidebar img{filter:invert(1) brightness(1.6)}
  [data-theme="dark"] #bulkbar{background:#2a2c33 !important;color:#eceef1 !important}
  [data-theme="dark"] #bulkbar .bsel{background:#34373f}
  [data-theme="dark"] input,[data-theme="dark"] select{background-color:var(--panel);color:var(--ink)}
  /* Coding pills (job/account/entity codes + on-charge) -- defined here in the shared stylesheet so the register,
     approver portal and dashboard render them identically. Account/data-chip style: rounded-rect, hairline border,
     left-aligned, wraps long names. On-charge reuses that shape but keeps its own colour + a hue-matched border. */
  /* PILL RULE: by default a pill is a SINGLE-LINE identifier (a job/account/entity code) -- it CENTRES its
     icon + term (align-items:center) and never wraps, so a long unbroken code can't shatter one char per line;
     the table card scrolls horizontally instead. A pill that holds a descriptive NAME opts into wrapping with
     .wrap: it then TOP-aligns (align-items:flex-start) so the leading icon + trailing arrow stay on the first
     line while the text flows beneath, breaking only at word boundaries. Sized by content, no min-height. */
  .codepill{display:inline-flex;align-items:center;box-sizing:border-box;font-size:11.5px;font-weight:600;border-radius:7px;padding:3px 9px;line-height:1.35;background:color-mix(in srgb,var(--ink) 5%,var(--panel));border:1px solid color-mix(in srgb,var(--ink) 22%,transparent);color:var(--ink);vertical-align:middle;white-space:nowrap;overflow-wrap:normal;max-width:100%;text-align:left}
  .codepill.wrap{align-items:flex-start;white-space:normal;overflow-wrap:break-word;text-align:left}
  /* A pill wrapped in .jlink navigates somewhere on click -- show a small "opens a page" arrow so it isn't a surprise. */
  .jlink{text-decoration:none;color:inherit}
  .jlink .codepill{transition:border-color .14s ease}
  .jlink .codepill::after{content:"";display:inline-block;align-self:center;width:13px;height:1.35em;margin-left:5px;flex-shrink:0;background:currentColor;opacity:.5;-webkit-mask:url(/icons/IconArrowUpRight.svg) center/13px no-repeat;mask:url(/icons/IconArrowUpRight.svg) center/13px no-repeat;transition:opacity .14s ease,transform .2s cubic-bezier(.34,1.56,.64,1)}
  .jlink:hover .codepill{border-color:color-mix(in srgb,var(--btn-bg) 38%,var(--line))}
  .jlink:hover .codepill::after{opacity:.85;transform:translate(2.5px,-2.5px)}
  .ocp,.ocpill{display:inline-flex;align-items:flex-start;flex-wrap:wrap;box-sizing:border-box;font-size:10.5px;font-weight:700;border-radius:7px;padding:2px 7px;line-height:1.3;text-align:left;white-space:normal;overflow-wrap:anywhere;max-width:100%;border:1px solid color-mix(in srgb,currentColor 32%,transparent);vertical-align:middle}
  .ocp-yes,.ocpill{color:#0f766e;background:#ccfbf1}
  .ocp-no{color:var(--muted);background:var(--hover)}
  .ocp-rev{color:var(--review);background:color-mix(in srgb,#b45309 14%,transparent);cursor:help}
  .ocp-party{background:color-mix(in srgb,#f04438 14%,transparent);color:#b42318}
  [data-theme="dark"] .ocp-yes,[data-theme="dark"] .ocpill{background:#123b36;color:#62d6a6}
  [data-theme="dark"] .ocp-party{background:#3a1512;color:#f97066}
  .ocell{display:flex;flex-direction:column;align-items:flex-start;gap:6px;max-width:100%}
  *{box-sizing:border-box}
  /* overscroll-behavior-x:none kills the horizontal rubber-band on the document. overflow-x:clip
     already stops anything pushing the page wider, but a trackpad swipe that runs past the end of an
     inner scroller still bounced the whole layout sideways -- and on macOS the same gesture triggers
     swipe-to-go-back, which loses whatever the customer had filtered or typed. */
  html,body{margin:0;background:var(--bg);overflow-x:clip;overscroll-behavior-x:none;accent-color:var(--btn-bg);-webkit-tap-highlight-color:transparent}
  @font-face{font-family:'Inter-fallback';src:local('Arial');ascent-override:90.2%;descent-override:22.48%;line-gap-override:0%;size-adjust:107.1%}
  body{font-family:'Inter','Inter-fallback',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;letter-spacing:-0.005em;
    font-weight:500;color:var(--ink);-webkit-font-smoothing:antialiased;line-height:1.45;display:flex;align-items:stretch;min-height:100vh}
  button{font:inherit;cursor:pointer}
  .muted{color:var(--muted)}

  /* The CONTAINER never scrolls. Scrolling it moved the brand and the Dark / Sign out rail off with
     the nav, so on any window shorter than the full menu the bottom items sat past the fold with no
     visible affordance -- the sidebar read as detached from the page. The nav alone scrolls (below),
     which keeps the brand pinned at the top and the rail pinned at the bottom at every height. */
  .sidebar{width:268px;flex-shrink:0;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;gap:5px;padding:24px 18px calc(24px + env(safe-area-inset-bottom));position:sticky;top:0;height:100dvh;overflow:hidden}
  /* The sidebar sits in a flex row with the main content, so animating its WIDTH re-lays-out the whole
   (heavy) page every frame -- that was the collapse/expand jank. Snap the width instead (one reflow) and
   let the labels + logo crossfade (opacity/transform, composited) carry the motion, so it reads smooth
   and instant rather than struggling. */
body.nav-ready .sidebar{transition:none}
  .sidebar .brand{display:flex;justify-content:center;margin:2px 0 0}
  .sidebar .brand{position:relative;transition:transform .12s ease}
  .sidebar .brand:active{transform:scale(.93)}
  /* Pressed + hover for the menu toggle. These lived in pages.py's inline style, so /ap -- which
     renders this same sidebar straight from THIS file -- had no pressed state at all. Shared
     chrome belongs in the shared stylesheet. */
  .hamburger{transition:background .13s ease,border-color .13s ease,transform .09s ease}
  .hamburger:hover{background:var(--hover)}
  .hamburger:active{background:var(--hover);border-color:var(--ink);transform:scale(.92)}
  .sidebar .brand:hover{opacity:.72}
  @media(min-width:861px){
    /* Left-aligned, clearing the fixed toggle at x=14..46. Centring drifted it to x=77, away from
       the nav items at x=18. */
    body:not(.navmin) .sidebar .brand{justify-content:flex-start;padding-left:38px}
  }
  .sidebar .brand img{display:block}
  /* 34px, not 26. base.css is generated FROM this block, so this is the single source both
     renderers share -- pages.py's shell() and dashboard.html's own /ap render. An override
     added in pages.py alone fixed the shell pages and left every other path on 26px. */
    /* NO forced aspect-ratio. brand.svg is viewBox "0 0 190 32" -- natural aspect 5.938 -- and this
     rule pinned it to 77.9/21.9 = 3.557, squashing the mark horizontally by 40% wherever
     .logo-full is used. The mobile header uses .mlogo, which has no such rule, which is why the
     two never matched and why no height change ever made this look right. The ratio belonged to a
     previous logo file and outlived it. Let the SVG keep its own proportions. */
  .sidebar .brand .logo-full{height:32px;width:auto}
  .sidebar .brand .logo-c{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) scale(.8);height:36px;width:36px;opacity:0}
  body.nav-ready .sidebar .brand .logo-full{transition:opacity .12s ease .06s}
  body.nav-ready.navmin .sidebar .brand .logo-full{transition:opacity .08s ease}
  /* On EXPAND the rail's top padding snaps from 74px to 24px, which would yank the C up into the hamburger's
     row; killing its opacity instantly (0s) means it is already gone on that first frame -> never seen under it. */
  body.nav-ready .sidebar .brand .logo-c{transition:opacity 0s,transform .1s ease}
  body.nav-ready.navmin .sidebar .brand .logo-c{transition:opacity .14s ease .05s,transform .19s cubic-bezier(.34,1.56,.64,1) .05s}
  /* The separator is its OWN element (a sibling of the logo link), so the logo's press-scale and its box
     height can never distort it: it stays a clean, static, inset line that floats with spacing on each side. */
  .brand-sep{height:1px;background:var(--line);margin:14px 0 16px;flex-shrink:0}
  @keyframes brandIn{from{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}
  .dept{font-size:12px;font-weight:700;color:var(--muted);text-transform:none;letter-spacing:0;margin:6px 8px 18px}
  /* min-height:0 is the load-bearing part. A flex child will not shrink below its content height
     without it, so overflow-y:auto never engages and the list simply overflows the panel. */
  .snav{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-height:0;overflow-y:auto;
        scrollbar-width:thin;overscroll-behavior:contain}
  .snav::-webkit-scrollbar{width:6px}
  .snav::-webkit-scrollbar-thumb{background:var(--line);border-radius:99px}
  .snav::-webkit-scrollbar-thumb:hover{background:var(--muted)}
  .snav::-webkit-scrollbar-track{background:transparent}
  .navgrp{display:flex;flex-direction:column;gap:3px}
  .navgrp+.navgrp{margin-top:6px}
  .navgrp-h{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;font-family:inherit;font-size:13px;font-weight:700;color:var(--muted);letter-spacing:.01em;padding:9px 14px;white-space:nowrap;user-select:none;background:none;border:0;border-radius:10px;cursor:pointer;text-align:left;outline:none;transition:background .14s ease,color .12s ease,transform .09s ease}
  .navgrp-h:hover{background:var(--hover);color:var(--ink)}
  .navgrp-h:focus-visible{background:var(--hover);color:var(--ink)}
  .navgrp-h:active{transform:scale(.98)}
  .navgrp-cv{width:17px;height:17px;flex-shrink:0;background:currentColor;opacity:.7;-webkit-mask:url(/icons/IconChevronRightMedium.svg) center/contain no-repeat;mask:url(/icons/IconChevronRightMedium.svg) center/contain no-repeat;transform:rotate(90deg);transition:transform .24s cubic-bezier(.4,0,.2,1)}
  .navgrp.ncol .navgrp-cv{transform:rotate(0deg)}
  .navgrp-h .navgrp-ico{display:none}
  .navgrp-items{display:grid;grid-template-rows:1fr;transition:grid-template-rows .24s cubic-bezier(.4,0,.2,1)}
  .navgrp-inner{overflow:hidden;display:flex;flex-direction:column;gap:2px;min-height:0;opacity:1;transition:opacity .18s ease .04s}
  .navgrp.ncol .navgrp-items{grid-template-rows:0fr}
  .navgrp.ncol .navgrp-inner{opacity:0;transition:opacity .12s ease}
  @media(prefers-reduced-motion:reduce){.navgrp-items,.navgrp-inner,.navgrp-cv{transition:none}}
  /* While the rail switches between full and minimized, freeze the per-section collapse transitions so a
     collapsed section (e.g. Administration) snaps to its rail state instead of animating its rows open/shut
     under the width slide -- that double layout animation was the jerk. Section toggles still animate normally. */
  body.navswitch .navgrp-items,body.navswitch .navgrp-inner{transition:none}
/* During the collapse/expand toggle, freeze EVERY sidebar transition so the width, labels, logo and
   nav items all snap to the new state in one coordinated step -- otherwise the width changes while the
   logo's springy transform + the label fades run on their own timings, which reads as chaotic/glitchy.
   navswitch is removed two frames later, so normal hover transitions come straight back. */
body.navswitch .sidebar,body.navswitch .sidebar *{transition:none!important}
  .snav a{display:flex;align-items:center;gap:13px;padding:9px 14px;border-radius:10px;font-size:14px;font-weight:600;color:var(--muted);text-decoration:none;white-space:nowrap;transition:background .15s ease,color .15s ease}
  .snav a{transition:background .15s ease,color .15s ease,transform .09s ease}
  .snav a:hover{background:var(--hover);color:var(--ink)}
  .snav a:active{transform:scale(.97)}
  .snav a.on{background:var(--btn-bg);color:var(--btn-fg)}
  /* Labels fade in only once the rail has the width to hold them (delay), and fade out fast on collapse, so
     the half-clipped intermediate text never shows. Icons are untouched -> they appear/stay instantly. */
  .snav a .navlbl,.navgrp-lbl{transition:opacity .1s ease .08s}
  .ico{display:inline-block;width:21px;height:21px;flex-shrink:0;background:currentColor;-webkit-mask:var(--i) center/contain no-repeat;mask:var(--i) center/contain no-repeat}
  .navtip{position:fixed;transform:translateY(-50%) translateX(-4px);background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:9px;padding:7px 12px;font:600 13px/1.2 Inter,system-ui,sans-serif;white-space:nowrap;box-shadow:0 8px 24px rgba(0,0,0,.16);z-index:300;pointer-events:none;opacity:0;transition:opacity .12s ease,transform .12s ease}
  .navtip.on{opacity:1;transform:translateY(-50%) translateX(0)}
  /* Left-pointing caret on the tooltip. The tooltip is vertically centred on the icon (JS sets top to the
     icon's mid + translateY(-50%)), so a caret at top:50% points back at the icon's exact centre. */
  .navtip::before{content:"";position:absolute;left:-5px;top:50%;width:9px;height:9px;background:var(--panel);border-left:1px solid var(--line);border-bottom:1px solid var(--line);transform:translateY(-50%) rotate(45deg);border-bottom-left-radius:2px}
  .srail{margin:auto 0 0;display:flex;flex-direction:column;gap:10px;padding:18px 0 calc(2px + env(safe-area-inset-bottom));border-top:1px solid var(--line)}
  .srail .addr{font-size:12px;font-weight:600;color:var(--muted);line-height:1.55}
  .srail .addr b{color:var(--navy);font-weight:700;font-size:12.5px}
  /* "Delivered by <partner>" at the foot of the rail: whose logo is at the TOP is the customer's,
     this is who built it. Deliberately quiet -- it must never read as a second brand competing
     with theirs. */
  .dlvby{padding-top:2px}
  .dlvby-l{font-size:8.5px;font-weight:700;letter-spacing:1.1px;text-transform:uppercase;
           color:var(--muted);opacity:.72;margin-bottom:6px}
  .dlvby a{display:inline-block;line-height:0;opacity:.72;transition:opacity .15s ease}
  .dlvby a:hover{opacity:1}
  .dlvby-logo{height:15px;width:auto;display:block}
  /* The marks ship BLACK on transparent so one asset serves both themes, same as the customer's
     own logo; dark mode inverts them here rather than needing a second file. */
  [data-theme="dark"] .dlvby-logo{filter:invert(1) brightness(1.6)}
  .dlvby-name{font-size:12.5px;font-weight:700;letter-spacing:.02em;color:var(--ink)}
  /* Collapsed rail: hide it with .addr rather than letting a wordmark squash into a 64px column. */
  body.navmin .dlvby{display:none}
  .themebtn{background:none;border:1px solid var(--line);border-radius:8px;padding:8px 12px;font-size:13px;font-weight:600;color:var(--muted);cursor:pointer;width:fit-content;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:background .15s ease,color .15s ease,border-color .15s ease,transform .09s ease}
  .themebtn:hover{background:var(--hover);color:var(--ink);border-color:var(--btn-bg)}
  .themebtn:active{transform:scale(.96)}
  .signout{background:none;border:1px solid var(--line);border-radius:8px;padding:8px 14px;font-size:13px;font-weight:600;color:var(--muted);text-decoration:none;width:fit-content;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:background .15s ease,color .15s ease,border-color .15s ease,transform .09s ease}
  .signout:hover{background:var(--hover);color:var(--ink);border-color:var(--btn-bg)}
  .signout:active{transform:scale(.96)}
  .content{flex:1;min-width:0;display:flex;flex-direction:column}

/* MOBILE PLATFORM RULES, product-wide. These lived in pages.shell(), which meant /ap had none
   of them: it is read straight off this file rather than composed through the shell, so its
   buttons were 40px, its filter pills 33px, and its search box would zoom iOS Safari and never
   zoom back. Found by adding /ap to the design config -- it had never been swept, which is
   exactly why it drifted. They belong HERE because this file IS the shared stylesheet
   (_base_css serves it at /static/base.css), so both renderers get them.
   `.tab` added to the 44px list: /ap's filter pills, which no shell page has. */
  @media(max-width:820px){
    /* 44px, not 32. 32 was this project's own earlier guess; 44 is the platform minimum and
       what the mobile sweep enforces, and these are the controls that code and approve money. */
    .svtog,.mtab,.stab,.pchip,.btn,.badge,.lfsel,.addrowbtn,.rlbtn,.upzmin,.mxedit,.tab{min-height:44px;
      display:inline-flex;align-items:center;justify-content:center}
    /* iOS Safari ZOOMS the whole page when a field under 16px takes focus, and does not zoom
       back out. Every search box, every dropdown. 16px is the threshold, not a preference. */
    /* !important on purpose, and this is the case it exists for: a class rule like .suctry
       (0,1,0) out-specifies a bare element selector, so every styled field would keep its
       13px and keep zooming. This is a platform constraint, not a style preference --
       matching every field's own class instead would be a list that rots on the next one. */
    input,select,textarea{font-size:16px !important}
    .jlink{display:inline-block;padding:8px 0}
    /* 13x13 is too small to hit; 20 is the platform norm for the control itself. No hit-area
       padding beyond that: these sit inside a <label>, which already gives them a target far
       larger than the box, and a label forwards the tap to its control. Verified with
       elementFromPoint -- 8px outside the checkbox returns the LABEL, not the input. An earlier
       version added a transparent ::after to extend the hit area; the label sits above it, so it
       did nothing at all and has been removed rather than shipped as decoration. */
    input[type="checkbox"],input[type="radio"]{min-width:20px;min-height:20px}
    .upzmin{min-width:44px;min-height:44px}
  }


/* PAGE ENTRANCE. Every page is a full server render, so without this the entire screen -- title,
   stat cards, filter bar, a 400-row table -- paints in one frame with no order to it. Nothing was
   wrong on any single page; the problem was that everything arrived at once.

   An entrance already existed and almost never ran: _TRANSITION_JS faded `.content` only when
   sessionStorage `qe-fx` was set, and the only thing that set it was smoothReload(). So the fade
   played after an in-app approve/pay/save and NEVER on a normal navigation or a first load, which
   is every other way anybody arrives at a page. Exists-but-unreachable, in the UI rather than the
   API. That JS is gone; this replaces it for every arrival.

   nth-of-TYPE, not nth-child. `main`'s direct children include the per-page <style>, <script>,
   <link> and <datalist> tags -- /ledger has 66 children of which 59 are zero-height <link>s -- so
   nth-child would hand the real delays to invisible nodes and stagger the content into the far
   tail. nth-of-type counts only the divs, which is exactly the set of content blocks.

   OPACITY ONLY, no transform. A transform makes its element a containing block for any
   position:fixed descendant, and there are nine of them inside `main` across the product (the
   modals on /ledger, /setup, /inbox, /payments). They are display:none at load, so the breakage
   would be a 600ms window in which opening one anchors it to a mid-animation block instead of the
   viewport -- rare, self-correcting, and exactly the kind of thing that looks broken. A staged fade
   fixes the complaint without buying that risk.

   The delay rides on a custom property rather than the shorthand so that any element with an
   entrance of its own KEEPS it: `main>div` is specificity 0-0-2, so a class rule like .pbar's own
   animation wins, and --qe-enter-d is simply left unused.

   Timed against a measured filmstrip, not picked: 7 steps x 30ms + 280ms = 490ms end to end. The
   first draft ran 606ms and the /ledger table visibly lagged -- its two hidden modals sit at
   of-type slots 4 and 5, so the table inherits slot 6's delay and finishes last. Well inside the
   1400ms the sweeps settle for, so nothing is measured mid-animation.

   `backwards` fill is load-bearing: without it every block would paint at full opacity and THEN
   drop to zero to start its animation, which is worse than no entrance at all. */
@media(prefers-reduced-motion:no-preference){
  @keyframes qeEnter{from{opacity:0}to{opacity:1}}
  main>div{animation:qeEnter .28s cubic-bezier(.22,.61,.36,1) var(--qe-enter-d,0ms) backwards}
  main>div:nth-of-type(2){--qe-enter-d:30ms}
  main>div:nth-of-type(3){--qe-enter-d:60ms}
  main>div:nth-of-type(4){--qe-enter-d:90ms}
  main>div:nth-of-type(5){--qe-enter-d:120ms}
  main>div:nth-of-type(6){--qe-enter-d:150ms}
  main>div:nth-of-type(7){--qe-enter-d:180ms}
  /* Capped. /reports has ten blocks and /account nine; without a ceiling the last one would arrive
     a third of a second after the page looked finished, which reads as a stutter, not a stagger. */
  main>div:nth-of-type(n+8){--qe-enter-d:210ms}
}

/* SKELETON. /ap is the one page that does not arrive complete: it ships `<div id="stats"></div>`
   and `<div id="list"></div>` EMPTY and fills both from /api/invoices after load. Measured 03-08-2026:
   main's text goes from 82 characters to 623 and its node count from 15 to 69 after first paint --
   by far the largest async fill in the product, and every other page measured 0-3%. So until that
   fetch lands you get a header, two buttons and a blank shell, and then the entire screen arrives in
   one frame. Locally that is fast enough to miss. Prod is a t4g.nano.

   The bars are wrapped in the REAL .stat and .row markup rather than bespoke placeholder boxes, so
   the skeleton inherits the actual padding, gaps, borders and grid and cannot drift out of shape
   when those change. Height is 1em on each bar, which resolves against the parent it sits in --
   .stat .n is 25px and .stat .l is 12px -- so the card is the height it will be with real numbers
   in it and nothing jumps when the fetch lands.

   aria-hidden on both containers: a screen reader announcing five blank stats and four blank rows
   is worse than silence, and the a11y gate would be right to fail it. renderStats() and renderList()
   both assign innerHTML, so the skeleton removes itself with no teardown code to forget. */
@keyframes qeSkShim{from{background-position:-340px 0}to{background-position:340px 0}}
.skb{display:inline-block;height:1em;min-height:9px;width:var(--w,60px);border-radius:5px;vertical-align:middle;
     background:linear-gradient(90deg,var(--line) 0%,var(--hover) 50%,var(--line) 100%);
     background-size:340px 100%;animation:qeSkShim 1.15s linear infinite}
@media(prefers-reduced-motion:reduce){.skb{animation:none}}
/* Minimized rail is a DESKTOP-only concept. Below the drawer breakpoint the nav is the full slide-out
   (wordmark + labels), so navmin must not leak into the mobile drawer. */
@media(min-width:861px){
/* The nav list scrolls inside the rail (brand + theme/sign-out stay put); a stable gutter + a thin styled
   scrollbar means collapsing a section never shifts the layout or flashes the default browser scrollbar. */
.sidebar{overflow:hidden}
.sidebar .brand{flex-shrink:0}
.snav{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;scrollbar-gutter:stable;scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--ink) 22%,transparent) transparent}
.snav::-webkit-scrollbar{width:10px}
.snav::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--ink) 20%,transparent);border-radius:8px;border:3px solid transparent;background-clip:padding-box}
.snav:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--ink) 32%,transparent);background-clip:padding-box}
.snav::-webkit-scrollbar-track{background:transparent}
.srail{flex:0 0 auto}
body.navmin .snav{scrollbar-gutter:auto}
body.navmin .sidebar{width:64px;padding:74px 0 calc(22px + env(safe-area-inset-bottom));align-items:center;overflow:hidden;gap:2px}
body.navmin .sidebar .brand{display:flex;justify-content:center;width:100%;height:36px;margin:0 0 0}
body.navmin .brand-sep{width:26px;margin:10px auto 12px}
body.navmin .sidebar .brand .logo-full{opacity:0}
body.navmin .sidebar .brand .logo-c{opacity:1;transform:translate(-50%,-50%) scale(1)}
body.navmin .snav{width:100%;align-items:center;gap:7px}
body.navmin .navgrp{width:100%;align-items:center;gap:7px;padding:9px 0}
/* In the rail the section HEADER icon is dropped: each section's own item icons already carry icons, so showing
   a header icon on top just pushed single-item sections (e.g. Employee portal) into the top half. Items show
   centred between the separators instead. Sections are force-shown here since there's no header to toggle them. */
body.navmin .navgrp-h{display:none}
body.navmin .navgrp-lbl{font-size:0;opacity:0;transition:opacity .05s ease}
body.navmin .snav a .navlbl{opacity:0;transition:opacity .05s ease}
body.navmin .navgrp-cv{display:none}
body.navmin .navgrp-ico{display:block;width:21px;height:21px;opacity:.55}
body.navmin .navgrp-h:hover .navgrp-ico{opacity:.95}
body.navmin .navgrp-items{width:100%;grid-template-rows:1fr!important}
body.navmin .navgrp-inner{align-items:center;gap:7px;opacity:1!important}
body.navmin .navgrp+.navgrp{margin-top:0;border-top:1px solid var(--line)}
body.navmin .snav a{justify-content:center;font-size:0;gap:0;padding:11px 0;width:44px;margin:0 auto}
body.navmin .srail{width:100%;margin:auto 0 0;padding:14px 0 calc(2px + env(safe-area-inset-bottom));align-items:center}
body.navmin .srail .addr{display:none}
body.navmin .srail .themebtn,body.navmin .srail .signout{font-size:0;gap:0;padding:9px;width:auto;justify-content:center}
}
#navtoggle{position:fixed;left:14px;top:20px;z-index:200;width:32px;height:32px;border-radius:8px;border:1px solid var(--line);background:var(--panel);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:left .2s,transform .09s ease,background .13s ease}#navtoggle:hover{background:var(--hover)}#navtoggle:active{transform:scale(.85)}
#navtoggle::before{content:"";width:15px;height:1.6px;background:var(--ink);box-shadow:0 5px 0 var(--ink),0 -5px 0 var(--ink)}
body.navmin #navtoggle{left:16px}
@media(max-width:860px){#navtoggle{display:none}}
  .topbar{display:flex;justify-content:flex-end;align-items:center;gap:10px;padding:13px 24px;border-bottom:1px solid var(--line);background:var(--panel)}
  .fwd{background:var(--btn-bg);color:var(--btn-fg);border:1px solid var(--btn-bg);border-radius:10px;padding:9px 15px;font-size:14px;font-weight:600;transition:.2s var(--spring)}
  .fwd:hover{filter:brightness(.94)}
  .fwd:active{transform:scale(.96)}

  .stats{display:flex;gap:14px;padding:18px 24px 4px;flex-wrap:wrap}
  .stat{background:var(--panel);border-radius:12px;padding:12px 16px;min-width:130px;box-shadow:var(--shadow)}
  .stat .n{font-size:25px;font-weight:700;letter-spacing:-.02em}
  .stat .l{font-size:12px;color:var(--muted);margin-top:2px;font-weight:600;white-space:nowrap}
  .stat.rev .n{color:var(--amber)} .stat.app .n{color:var(--green)}

  /* SCOPED TO .apgrid ON PURPOSE. This is the two-pane body of THIS page (/ap) only, but this
     stylesheet is also served to every other page as /static/base.css. As a bare `main` selector it
     applied product-wide the moment shell() started emitting <main class="page"> for the skip-link
     landmark, and every one of the 17 nav routes silently split into two columns above 1160px --
     on /setup that left the panel text 60px wide, one word per line. A bare element selector in a
     shared stylesheet is a page-local rule waiting for somebody to use that element elsewhere. */
  main.apgrid{display:grid;grid-template-columns:minmax(400px,1fr) minmax(420px,1.05fr);gap:18px;padding:14px 24px 40px;align-items:start}
  @media(max-width:1160px){main.apgrid{grid-template-columns:1fr}}
  .hamburger{display:none;align-items:center;justify-content:center;width:34px;height:34px;border:1px solid var(--line);border-radius:9px;background:var(--panel);cursor:pointer;flex-shrink:0}
  .hamburger span{display:block;width:15px;height:2px;background:var(--ink);position:relative}
  .hamburger span::before,.hamburger span::after{content:"";position:absolute;left:0;width:15px;height:2px;background:var(--ink)}
  .hamburger span::before{top:-5px}.hamburger span::after{top:5px}
  .mtop{display:none}
  [data-theme="dark"] .mtop .mlogo{filter:invert(1) brightness(1.6)}
  @media(max-width:860px){.mtop{display:flex;align-items:center;gap:13px;padding:max(10px,env(safe-area-inset-top)) 16px 10px;border-bottom:1px solid var(--line);background:var(--panel);position:sticky;top:0;z-index:45}.mtop .mlogo{height:32px;display:block}.mtop .hamburger{margin:0}}
  .scrim{display:none}
  .navclose{display:none}


  /* SKIP LINK. Off-screen until focused, then it lands in the top-left. Without it a keyboard user
     tabs through the whole sidebar on every single page before reaching the content. */
  .skiplink{position:absolute;left:-9999px;top:0;z-index:10999;background:var(--btn-bg);
    color:var(--btn-fg);padding:11px 16px;border-radius:0 0 10px 0;font-weight:600;font-size:13.5px;
    text-decoration:none}
  .skiplink:focus{left:0}

  /* TOOLTIPS, product-wide. A native `title` takes a second to appear, cannot be styled, and
     never appears on touch at all -- so on a phone the hint simply does not exist. This replaces
     it everywhere: the script below converts every [title] to [data-tip] on load and renders it
     with these styles. It used to live in the queue page's own script, which is exactly why the
     queue had proper tooltips and the other twenty pages had OS ones. */
.cavtip{position:fixed;z-index:10600;background:var(--panel);color:var(--ink);border:1px solid var(--line);font-size:11.5px;font-weight:500;line-height:1.45;padding:7px 11px;border-radius:9px;box-shadow:0 12px 32px rgba(0,0,0,.17);pointer-events:none;max-width:300px;white-space:normal;opacity:0;transform:translateY(3px);transition:opacity .12s ease,transform .12s ease}.cavtip.on{opacity:1;transform:none}.cavtip::after{content:"";position:absolute;left:var(--ax,50%);top:100%;transform:translateX(-50%);width:0;height:0;border:7px solid transparent;border-bottom:0;border-top-color:var(--panel);filter:drop-shadow(0 1px 0 var(--line))}.cavtip.below::after{top:auto;bottom:100%;border-top:0;border-bottom:7px solid var(--panel);filter:drop-shadow(0 -1px 0 var(--line))}
/* The information circle. A REAL <button>, not a styled span: it has to be reachable by keyboard,
   and the tooltip layer now shows on focus as well as hover, so a keyboard user gets the same
   breakdown a mouse user does. Lives HERE, in the shared stylesheet, because it is used in table
   cells on several pages -- the same rule defined inside one page's <style> is how a component
   renders as an unstyled box everywhere else, which has happened three times in this codebase.
   Sized 15px with a 22px hit box: the glyph must not shove a table cell wider, but the target
   still has to be findable. */
.qeinfo{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;
  flex:none;padding:0;margin-left:4px;border:0;border-radius:50%;cursor:help;vertical-align:-2px;
  background:color-mix(in srgb,var(--ink) 13%,transparent);color:var(--muted);
  font:600 10px/1 ui-sans-serif,system-ui,sans-serif;font-style:italic;
  transition:background .12s ease,color .12s ease;position:relative}
.qeinfo::before{content:"";position:absolute;inset:-4px}   /* 23px target without 23px of layout */
.qeinfo:hover,.qeinfo:focus-visible{background:var(--btn-bg);color:var(--btn-fg)}
.qeinfo:focus-visible{outline:2px solid var(--btn-bg);outline-offset:2px}
/* The same words, rendered inline. Hidden on desktop, where the circle is the affordance. */
.qeinote{display:none}
/* Keeps the circle on the badge's line WHEN THERE IS ROOM. The first version forced it with
   white-space:nowrap, which stopped the wrap and started an overflow instead: the Status column is
   129px at 1440 and the pair wants 138px, so the gate correctly reported TRUNCATED where it had
   previously reported nothing. Wrapping is the honest behaviour at that width -- the circle drops
   under its badge rather than pushing the cell past its own edge. */
.qeinfo-w{display:inline-flex;align-items:center;flex-wrap:wrap;row-gap:2px}
/* ON A PHONE THERE IS NO CIRCLE AT ALL, and the text simply shows.
   A hover tooltip is a poor pattern on touch to begin with, and the honest ways to satisfy the
   44px minimum were to grow the button to 44px -- which shoves the status badge and the entity
   pill around on the narrowest screens -- or to expand the hit area with a pseudo-element, which
   the gate measures as still 18px and would have been gaming the check rather than passing it.
   The tables stack into per-row cards below 900px, so there is room to just say the thing. */
@media(max-width:900px){
  .qeinfo{display:none}
  /* The nowrap wrapper is a DESKTOP rule: it exists to keep a 15px circle on the badge's line.
     Left in place on a phone it pins the sentence beside the pill in a column a few words wide.
     Here the pair stacks, which is what the per-row card layout wants anyway. */
  .qeinfo-w{display:block;white-space:normal}
  .qeinote{display:block;font-size:11px;line-height:1.4;color:var(--muted);margin-top:3px;
    font-weight:500;white-space:normal}
}


  /* SHARED CARD COMPONENTS -- .addwrap / .addcard / .add-ico / .ingpill and the day-one upload
     zone. These lived inside the Inbox page's own <style>, so the moment a second screen used
     .addcard it rendered as an unstyled box: same markup, no rules. That is the mechanical reason
     two pages of the same product stopped looking like it. Anything reused by more than one page
     belongs here, in the stylesheet both of them already load. */
.addwrap{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:0 0 16px;align-items:stretch}.addwrap>.upz{margin:0;height:100%}.upz.hero{flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:11px;padding:30px 24px}.upz.hero .upz-ico-wrap{width:54px;height:54px}.upz.hero .upz-ico{width:27px;height:27px}.upz.hero .upz-tx{align-items:center}.upz.hero .upz-t{font-size:17px}.upz.hero .upz-s{font-size:13px}.upz.hero .upz-btns{margin:4px 0 0;justify-content:center}.upz.hero .upz-btns .btn{padding:9px 16px;font-size:13px}.upzphone{display:inline-flex;align-items:center;gap:7px;margin-top:4px;padding:7px 12px;border-radius:9px;background:var(--hover);color:var(--muted);font-size:12.5px;font-weight:500;line-height:1.45;text-align:left}.upzphone i{flex:none;opacity:.75}@media(max-width:860px){.upzphone{display:none}}.upz:not(.hero) .upzphone{display:none}.ing-t{font-size:15px;font-weight:600;display:flex;align-items:center;gap:9px}.ing-s{font-size:13px;color:var(--muted);font-weight:500;line-height:1.55}.ingpill{display:flex;align-items:center;gap:9px;padding:8px 9px 8px 12px;background:var(--hover);border-radius:9px;font-weight:600;font-size:13px;justify-content:space-between;width:100%;box-sizing:border-box}.ingpill .ingaddr{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.addcard{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:11px;padding:30px 24px;box-sizing:border-box;margin:0;text-align:center}.add-ico-wrap{width:54px;height:54px;flex:none;border-radius:50%;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--btn-bg) 10%,transparent);transition:background .15s}.addcard:hover .add-ico-wrap{background:color-mix(in srgb,var(--btn-bg) 18%,transparent)}.add-ico{width:27px;height:27px;background:var(--btn-bg);-webkit-mask:var(--i) center/contain no-repeat;mask:var(--i) center/contain no-repeat}.add-t{font-size:17px;font-weight:600}.add-s{font-size:13px;color:var(--muted);font-weight:500;line-height:1.55}.addbtn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:9px 16px;font-size:13px}


  /* TOUCH TARGETS. Six controls in the shared chrome measured 34-37px against the 44px minimum,
     and every one of them lives in the mobile drawer -- which is exactly where a thumb goes. The
     visual size is unchanged: the height is met with padding and a min-height, so the hamburger
     still LOOKS like a 34px square while being a 44px target. Scoped to <=900px because that is
     where these are touched; the desktop sidebar keeps its tighter rhythm. */
  @media(max-width:900px){
    .hamburger{width:44px;height:44px}
    .navclose{min-width:44px;min-height:44px}
    .navgrp-h{min-height:44px}
    .themebtn{min-height:44px;padding:10px 12px}
  }
  @media(max-width:860px){
    .sidebar{position:fixed;left:0;top:0;width:250px;height:100dvh;z-index:60;transform:translateX(-100%);transition:transform .25s var(--spring);padding:14px 18px calc(22px + env(safe-area-inset-bottom));overflow-y:auto}
    .sidebar .brand{justify-content:flex-start;padding-left:45px;margin:0 0 26px}
    /* The drawer shows the logo at EXACTLY the header's size and x, so opening the menu does not
       make it grow or shift. At the true 5.938 aspect a 32px logo is 190px wide, which needs the
       drawer at 260 rather than 250 to sit at x=63 with a margin. */
    .sidebar{width:260px}
    .sidebar .brand .logo-full{height:32px}
    .sidebar .brand{padding-left:45px;margin:-3px 0 22px}
    .sidebar.open{transform:translateX(0)}
    .navclose{display:flex;align-items:center;justify-content:center;position:absolute;top:14px;left:16px;width:34px;height:34px;border:1px solid var(--line);border-radius:9px;background:var(--bg);cursor:pointer;z-index:2;transition:transform .09s ease,background .13s ease}
    .navclose:hover{background:var(--hover)}
    .navclose:active{transform:scale(.9)}
    .navclose span{position:absolute;width:15px;height:2px;border-radius:2px;background:var(--ink);transition:transform .33s var(--spring)}
    .navclose span:first-child{transform:translateY(-4px)}
    .navclose span:last-child{transform:translateY(4px)}
    .sidebar.open .navclose span:first-child{transform:rotate(45deg)}
    .sidebar.open .navclose span:last-child{transform:rotate(-45deg)}
    .hamburger{display:inline-flex}
    .scrim{display:block;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:55;opacity:0;pointer-events:none;transition:opacity .25s}
    .scrim.on{opacity:1;pointer-events:auto}
    .phead{padding:14px 14px 0;flex-wrap:wrap} .quickbar{padding:10px 14px 0;justify-content:flex-start}
    .stats{padding:12px 14px 0;gap:8px}
    .stat{min-width:0;flex:1 1 28%}
    main.apgrid{padding:12px 14px 30px}
    .dhead{flex-wrap:wrap}
    .card{overflow-x:auto}
    /* In the slide-out drawer the theme + sign-out controls fill the width like the nav items above them,
       instead of sitting as small fit-content pills with dead space to their right. */
    .srail .themebtn,.srail .signout{width:100%;box-sizing:border-box;justify-content:flex-start}
  }
  .col{background:var(--panel);border-radius:14px;box-shadow:var(--shadow);overflow:hidden}
  #list{max-height:calc(100dvh - 250px);overflow-y:auto}
  .dtabs{display:flex;gap:6px;padding:6px 22px 2px}
  .dtab{background:none;border:0;border-bottom:2px solid transparent;padding:7px 2px;margin-right:16px;font-size:13px;font-weight:700;color:var(--muted);cursor:pointer;transition:color .15s ease,border-color .15s ease}
  .dtab:hover{color:var(--ink)}
  .dtab.on{color:var(--ink);border-bottom-color:var(--btn-bg)}
  .future{margin:0 22px 14px;background:#fff7ed;border:1px solid #fcdcb6;border-radius:10px;padding:11px 14px}
  [data-theme="dark"] .future{background:#2a2013;border-color:#4a3a1e}
  .future-h{font-size:12px;font-weight:700;letter-spacing:.02em;color:#b45309;display:flex;align-items:center;gap:6px;margin-bottom:5px}
  [data-theme="dark"] .future-h{color:#e2ab52}
  .future-h .bico{width:13px;height:13px;background:currentColor;-webkit-mask:var(--m) center/contain no-repeat;mask:var(--m) center/contain no-repeat;display:inline-block}
  .future ul{margin:0;padding-left:18px;font-size:13px;line-height:1.6;color:var(--ink)}

  .tabs{display:flex;gap:7px;padding:15px 16px;border-bottom:1px solid var(--line);flex-wrap:wrap;align-items:center;row-gap:9px}
  .tab{border:1px solid var(--line);background:var(--panel);border-radius:999px;padding:6px 13px;font-size:13px;font-weight:600;color:var(--muted);transition:.15s}
  .tab.on{background:var(--btn-bg);color:var(--btn-fg);border-color:var(--btn-bg)}
  .tab:hover:not(.on){border-color:var(--btn-bg);color:var(--navy)}

  .row{display:grid;grid-template-columns:1fr auto;gap:6px 12px;padding:14px 16px;border-bottom:1px solid var(--line);cursor:pointer;transition:background .15s}
  .row:hover{background:var(--hover)}
  .row.sel{background:var(--hover)}
  .row .sup{font-weight:700;font-size:15px}
  .row .sub{color:var(--muted);font-size:12px;margin-top:2px}
  .row .amt{font-weight:700;text-align:right;white-space:nowrap}
  .row .tags{grid-column:1/3;display:flex;gap:7px;flex-wrap:wrap;margin-top:3px}
  html{scrollbar-gutter:stable}
  .badge{font-size:12px;font-weight:700;border-radius:7px;padding:5px 9px;min-height:24px;box-sizing:border-box;white-space:nowrap;display:inline-flex;align-items:center;gap:5px;vertical-align:middle;border:1px solid color-mix(in srgb,currentColor 26%,transparent)}
  .b-review{background:#fef3e6;color:#a85a06} .b-ready{background:#eaf1fe;color:#1f4fa8}
  .b-approved{background:#e7f6ec;color:#137a37} .b-approv-y{background:#fdf3c3;color:#8a6400} .b-missing{background:#fde7e7;color:#b42318}
  .b-proj{background:#eef2f7;color:#3a4658} .b-mem{background:#efeaff;color:#5a40c6}
  .b-up{background:#fff3d6;color:#8a6418}
  .up-row td{background:#fffaf0}

  .phead{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;padding:26px 24px 0}
  .phead h1{font-size:23px;font-weight:700;letter-spacing:-.02em;margin:0 0 3px}
  .phead .lead{color:var(--muted);font-size:13px;font-weight:600;max-width:620px;margin:0}
  .lastupd{font-size:12px;white-space:nowrap;padding-top:7px}
  .quickbar{display:flex;gap:10px;padding:14px 24px 0;justify-content:flex-end;flex-wrap:wrap}
  .col{position:relative}
  .col.drag{box-shadow:0 0 0 2px var(--btn-bg) inset, var(--shadow)}
  .drophint{display:none;position:absolute;inset:0;z-index:40;flex-direction:column;align-items:center;justify-content:center;gap:14px;background:color-mix(in srgb,var(--panel) 92%,transparent);font-size:14px;font-weight:700;color:var(--ink);pointer-events:none;text-align:center;padding:24px}
  .col.drag .drophint{display:flex}
  .dropico{width:38px;height:38px;background:var(--btn-bg);-webkit-mask:url(/icons/folder-upload.svg) center/contain no-repeat;mask:url(/icons/folder-upload.svg) center/contain no-repeat}

  .empty{padding:60px 24px;text-align:center;color:var(--muted)}
  .detail{padding:0}
  .dhead{padding:20px 22px 8px;display:flex;justify-content:space-between;gap:16px;align-items:center}
  .dhead .sup{font-size:20px;font-weight:700;letter-spacing:-.01em}
  .dhead .meta{color:var(--muted);font-size:13px;margin-top:3px}
  .dhead .amt{font-size:24px;font-weight:700;text-align:right;white-space:nowrap}
  .dhead .amt small{display:block;font-size:12px;font-weight:600;color:var(--muted)}
  .soa{margin:6px 22px 0;background:#fff7ed;border:1px solid #fcdcb6;color:#b45309;border-radius:10px;padding:8px 12px;font-size:13px;font-weight:600}
  .sec{padding:14px 22px;border-top:1px solid var(--line);margin-top:12px}
  .sec h4,.cardh{margin:0 0 10px;font-size:13px;letter-spacing:.01em;color:var(--ink);font-weight:700}
  details.disc{border-top:1px solid var(--line);padding:13px 22px}
  details.disc>summary{list-style:none;cursor:pointer;font-size:13px;letter-spacing:.01em;color:var(--ink);font-weight:700;display:flex;justify-content:space-between;align-items:center}
  details.disc>summary::-webkit-details-marker{display:none}
  details.disc>summary::after{content:'';width:16px;height:16px;flex-shrink:0;background:var(--muted);-webkit-mask:url(/icons/chevron-right.svg) center/contain no-repeat;mask:url(/icons/chevron-right.svg) center/contain no-repeat;transition:transform .2s}
  details.disc[open]>summary::after{transform:rotate(90deg)}
  details.disc>div,details.disc>ul{margin-top:10px}
  .code{padding:9px 11px;border:1px solid var(--line);border-radius:10px;margin-bottom:7px}
  .code.flag{border-color:#f6dcae;background:#fffaf2}
  .code.missing{border-color:#f3c2c2;background:#fff5f5}
  .code .lab{font-size:12px;font-weight:700;color:var(--muted);display:flex;justify-content:space-between;align-items:center;gap:10px}
  .code .val{font-size:13.5px;font-weight:700;margin-top:3px;word-break:break-word;line-height:1.4}
  .pill{font-size:12px;font-weight:700;padding:2px 8px;border-radius:7px;display:inline-flex;align-items:center;flex-wrap:wrap;white-space:normal;overflow-wrap:anywhere;max-width:100%;border:1px solid color-mix(in srgb,currentColor 26%,transparent)}
  .pill.hi{background:#e7f6ec;color:#137a37}.pill.mid{background:#fdf0dc;color:#a85a06}.pill.lo{background:#fde7e7;color:#b42318}
  .bar{height:3px;border-radius:3px;background:#eef0f3;margin-top:6px;overflow:hidden}
  .bar i{display:block;height:100%;border-radius:4px;transition:width .6s var(--spring)}
  /* Slim pills at narrow widths — trim padding/font/line-height only, keep rounded shape + colours (desktop untouched). Placed after the base rules so source order wins. */
  @media(max-width:860px){
    .badge{font-size:11px;padding:1px 8px;line-height:1.45;gap:4px}
    .pill{font-size:11px;padding:1px 7px;line-height:1.45}
  }

  .resolve{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}
  select,.btn{border:1px solid var(--line);background:var(--panel);border-radius:9px;padding:9px 12px;font-size:14px;font-weight:600;color:var(--ink)}
  select{min-width:160px;-webkit-appearance:none;appearance:none;height:32px;padding:0 32px 0 11px;border:1px solid var(--line);border-radius:8px;background-color:var(--bg);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%239aa0a6' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 11px center;font:inherit;font-size:12px;font-weight:500;color:var(--ink);cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}
  select:hover{border-color:color-mix(in srgb,var(--ink) 28%,var(--line))}
  select:focus,select:focus-visible{outline:none;border-color:var(--btn-bg);box-shadow:0 0 0 3px color-mix(in srgb,var(--btn-bg) 13%,transparent)}
  select:focus{outline:none;border-color:var(--btn-bg);box-shadow:0 0 0 3px color-mix(in srgb,var(--btn-bg) 13%,transparent)}
  /* text-decoration AND display belong on the BASE rule, not per call site: .btn is used on both
     <button> and <a>, whose browser defaults differ in two ways that both bite. Without
     text-decoration every anchor-button carries a link underline its <button> siblings do not; and
     without inline-block an anchor stays `display:inline`, silently IGNORES vertical margin, and
     overlaps the line above it -- which is exactly how the Connect MYOB button ended up sitting on
     top of its own description. Both were being patched one inline style at a time. */
  /* Reserve the scrollbar gutter ALWAYS. Every modal in this app sets body{overflow:hidden} while it
     is open, which removes the scrollbar and shifts the entire page 15px sideways as it appears --
     a visible jolt on a screen whose whole job is to feel calm. Reserving the space means nothing
     moves whether the bar is there or not. */
  html{scrollbar-gutter:stable}
  /* Billing plan cards. Colour carries meaning only: the current plan is marked, the recommended one
     is outlined. Everything else stays monochrome so the price is what the eye lands on. */
  .bnote{border:1px solid var(--line);border-radius:11px;padding:12px 16px;font-size:13.5px;
         line-height:1.55;margin-bottom:16px;background:var(--panel)}
  .bnote-warn{border-color:var(--amber);background:color-mix(in srgb,var(--amber) 8%,var(--panel))}
  .bgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:14px;align-items:start}
  .bcard{border:1px solid var(--line);border-radius:15px;background:var(--panel);overflow:hidden;
         transition:border-color .16s ease,transform .16s var(--spring)}
  .bcard:hover{transform:translateY(-2px)}
  .bcard-in{padding:19px 20px}
  .bcard-cur{border-color:var(--green)}
  .bcard-rec{border-color:var(--ink)}
  .brec{background:var(--ink);color:var(--panel);font-size:11.5px;font-weight:700;letter-spacing:.01em;
        padding:7px 14px;text-align:center}
  .bname{font-size:15px;font-weight:700;letter-spacing:-.01em}
  .bpr{margin-top:8px;display:flex;align-items:baseline;gap:2px}
  .bpr-n{font-size:28px;font-weight:700;letter-spacing:-.03em}
  .bpr-u{font-size:13px}
  .bvol{font-size:13px;margin-top:5px}
  .bfeat{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:8px}
  .bfeat li{display:flex;gap:9px;align-items:flex-start;font-size:13px;line-height:1.45;color:var(--ink)}
  .bck{width:15px;height:15px;flex-shrink:0;margin-top:1px;border-radius:99px;background:var(--green);
       -webkit-mask:none;position:relative}
  .bck::after{content:"";position:absolute;inset:0;background:var(--panel);
              -webkit-mask:url(/icons/check.svg) center/9px 9px no-repeat;
              mask:url(/icons/check.svg) center/9px 9px no-repeat}
  .bgo{display:block;width:100%;text-align:center;margin-top:16px;padding:10px;border-radius:10px}
  .bcur{margin-top:16px;font-size:13px;font-weight:650;color:var(--green);display:flex;align-items:center;gap:7px}
  .bcur::before{content:"";width:15px;height:15px;border-radius:99px;background:var(--green)}
  /* Usage / quota card. The bar is SEGMENTED because an invoice allowance is a count of discrete
     things; a smooth fill implies a divisible quantity it is not. */
  .usgcard{border:1px solid var(--line);border-radius:14px;padding:17px 19px;background:var(--panel)}
  .usgttl{font-size:14.5px;font-weight:650;color:var(--ink)}
  .usgsub{font-size:13px;margin-top:3px;line-height:1.5}
  .usgbar{display:flex;gap:3px;margin:14px 0 9px;height:8px}
  .usgbar i{flex:1;border-radius:2px;transition:background .2s ease}
  .usgrow{display:flex;justify-content:space-between;align-items:baseline;gap:12px;font-size:13.5px;flex-wrap:wrap}
  /* Connect-your-ledger modal. Centred, generous, colour only from the brand marks -- the language of
     the dashboard-ui reference. */
  .cxmodal{position:fixed;inset:0;background:rgba(16,12,14,.46);backdrop-filter:blur(2px);z-index:11000;
           display:none;align-items:center;justify-content:center;padding:20px;opacity:0;
           transition:opacity .18s ease}
  .cxmodal.on{display:flex;opacity:1}
  .cxbox{background:var(--panel);border:1px solid var(--line);border-radius:20px;padding:30px 32px;
         width:100%;max-width:470px;max-height:calc(100dvh - 40px);overflow:auto;position:relative;
         box-shadow:0 28px 80px rgba(0,0,0,.34);transform:scale(.975);transition:transform .2s cubic-bezier(.33,1,.68,1)}
  .cxmodal.on .cxbox{transform:none}
  .cxx{position:absolute;top:14px;right:14px;width:32px;height:32px;border-radius:9px;border:0;
       background:var(--hover);color:var(--muted);font-size:20px;line-height:1;cursor:pointer;
       transition:background .14s ease,color .14s ease}
  .cxx:hover{background:var(--line);color:var(--ink)}
  .cxgo{display:block;width:100%;text-align:center;margin-top:18px;padding:12px;font-size:15px;border-radius:11px}
  @media(max-width:520px){.cxbox{padding:24px 20px;border-radius:16px}}
  .btn{transition:.18s var(--spring);text-decoration:none;display:inline-block}
  .btn:active{transform:scale(.95)}
  .btn:hover{filter:brightness(.94)}
  .btn.pri{background:var(--btn-bg);color:var(--btn-fg);border-color:var(--btn-bg)}
  .btn.navy{background:var(--btn-bg);color:var(--btn-fg);border-color:var(--btn-bg)}
  .btn.ghost{background:var(--panel)}
  /* outline-primary: the clear per-row action without a heavy black fill -- reserves solid .pri for one section/page primary, like the approver portal */
  .btn.prio{background:var(--panel);color:var(--btn-bg);border-color:var(--btn-bg);font-weight:700}
  .btn.prio:hover{background:var(--btn-bg);color:var(--btn-fg)}
  .btn:disabled{opacity:.5;cursor:default;transform:none;box-shadow:none}

  .reasons{margin:0;padding:0;counter-reset:rn}
  .reasons li{list-style:none;font-size:13px;color:#8a5a14;padding:5px 0 5px 22px;position:relative;counter-increment:rn}
  .reasons li:before{content:counter(rn) ".";position:absolute;left:0;color:var(--muted);font-weight:700;font-size:11.5px}

  .wf{display:flex;gap:10px;flex-wrap:wrap}
  .step{flex:1;min-width:120px;border:1px solid var(--line);border-radius:11px;padding:11px}
  .step .t{font-size:12px;font-weight:700;color:var(--muted)}
  .step .who{font-size:13px;font-weight:700;margin:4px 0 8px}
  .step.done{background:#f1f9f3;border-color:#cdeed8}
  .step.done .who{color:var(--green)}
  .step .btn{width:100%;padding:7px;font-size:13px}

  .rel{font-size:13px;padding:9px 11px;border:1px solid var(--line);border-radius:9px;margin-bottom:7px;display:flex;justify-content:space-between;cursor:pointer}
  .rel:hover{background:var(--hover)}
  .actions{display:flex;gap:9px;flex-wrap:wrap;padding:16px 22px 22px}
  .btn .bico{display:inline-block;width:14px;height:14px;background:currentColor;vertical-align:-2px;margin-right:6px;-webkit-mask:var(--m) center/contain no-repeat;mask:var(--m) center/contain no-repeat}
  .btn .bico.aft{margin:0 0 0 6px}

  .ov{position:fixed;inset:0;background:rgba(16,20,30,.5);display:none;align-items:center;justify-content:center;padding:24px;z-index:50}
  .ov.on{display:flex}
  .modal{background:var(--panel);border-radius:16px;max-width:620px;width:100%;max-height:84vh;overflow:auto;box-shadow:0 30px 80px rgba(0,0,0,.3)}
  .modal h3{margin:0;padding:18px 22px;border-bottom:1px solid var(--line);font-size:16px}
  .modal .body{padding:18px 22px}
  .mail{background:var(--hover);border:1px solid var(--line);border-radius:10px;padding:14px;font-size:13px;white-space:pre-wrap;font-family:ui-monospace,Menlo,monospace;line-height:1.5}
  .mail .h{font-family:inherit;color:var(--muted);font-size:12px}
  pre{background:#0f1320;color:#d6e2ff;border-radius:10px;padding:14px;overflow:auto;font-size:12px;line-height:1.5}
  .toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(20px);background:#161a1f;color:#fff;padding:11px 18px;border-radius:10px;font-size:14px;font-weight:600;opacity:0;transition:.3s var(--spring);z-index:60}
  .toast.on{opacity:1;transform:translateX(-50%) translateY(0)}
  .donut-in{transform-origin:center;animation:donutIn .5s cubic-bezier(.33,1,.68,1) both}
  @keyframes donutIn{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}
