/* brand.css — the admin's one stylesheet.
   ===========================================================================
   Every styling decision the app makes is in this file: the palette and the
   type scale, the shell, every page's rules, and (at the end) the utilities
   and components that replaced the styles once written inline in the script.
   The page links it from <head>; nothing else styles anything. When a rule
   needs a reason, the reason is written beside it, here — that is the point
   of one sheet. Lived inside public/index.html's <style> until 2026-09-07.

   Served by the ASSETS binding beside index.html. The two deploy together,
   so a class the script names is always the class this file defines.
   =========================================================================== */
  /* ===== palette ===========================================================
     Every colour on the page is a token here, and the whole of dark mode is
     the second copy of this list further down. Two rules keep that workable:

     · A token is named for the JOB it does (--panel, --dim, --amb-bd), not for
       what it looks like, because the two themes disagree about the latter.
     · The light values are the exact hexes the stylesheet grew up with, so
       light mode is unchanged; a handful of washes that differed by a
       channel or two were folded into one token on the way.

     Which theme is on is decided by the html element: data-theme="dark" or
     "light" is a choice the user made, and no attribute at all means follow
     the device — see the pre-paint script under the stylesheet, and setTheme.
     Accent FILLS (--red, --done, --prog…) hold their colour across both
     themes: white text on the app's red is the same button either way. Red as
     INK is a different job — on a dark ground it has to lift to stay legible —
     so text uses --red-ink, which is the brand red only in light mode. */
  /* The one webfont the admin loads: Archivo, self-hosted beside this sheet
     under /brand/ (like the printable-menu faces). Used ONLY for hero figures
     (the big stat / ledger numbers) via --disp — body and labels stay the
     system sans. A single variable woff2, so it is one small cached asset,
     shared with the POS till. See the brand guide §04. */
  @font-face{font-family:"Archivo";src:url(/brand/Archivo.woff2) format("woff2");
    font-weight:400 900;font-style:normal;font-display:swap}
  :root{
    color-scheme:light;
    /* type scale — one source of truth for every font-size in the app chrome.
       Named by role, not by pixels, so a control reaches for its rung of the
       hierarchy rather than a hand-tuned literal. Theme-independent (the dark
       block below only re-states colours), so these hold in both themes. The
       printable menu sheet keeps its own scale and does not draw from these. */
    --fs-3xs:9px;   /* micro: superscripts, tiny badges */
    --fs-2xs:10px;  /* caption: gstat, footnotes, the quietest labels */
    --fs-xs:11px;   /* label: secondary text, dimmed meta */
    --fs-sm:12px;   /* body: the app's default reading size */
    --fs-md:13px;   /* control: filter chips, the emphasised row */
    --fs-lg:14px;   /* title: section heads, prominent labels */
    --fs-xl:16px;   /* the mobile search field's floor (iOS won't zoom at 16) */
    --fs-2xl:18px;  /* small display */
    --fs-3xl:22px;  /* display */
    --fs-4xl:26px;  /* hero number */
    --fs-5xl:32px;  /* largest hero */
    /* display face — hero figures only (stat/ledger numbers); see @font-face above */
    --disp:"Archivo",system-ui,sans-serif;
    --red:#E10600; --red-ink:#e10600; --red-hover:#f76e6a;
    /* the five status colours, as FILLS: roadmap bars, board card edges, the
       status dots and the legend swatches. Same split as --red / --red-ink —
       a fill is read against the page, ink is read on it, and on a dark ground
       those want opposite treatments, so the two text colours have their own
       tokens below. In light mode every one of these is its original value. */
    --todo:#c9ced6; --prog:#e9a80c; --block:#d9483b; --done:#2fae66; --group:#b7bdc8;
    --block-ink:#d9483b; --done-ink:#2fae66;
    /* accents the script asks for by name — the six milestone colours, the four
       segments of the sales-recon money ladder, the proposal banner. Tokens
       rather than hexes in the script, so this sheet is the one place a colour
       is chosen. They hold across both themes, like the status fills above. */
    --ms-1:#7c3aed; --ms-2:#0891b2; --ms-3:#c2410c; --ms-4:#0d9488; --ms-5:#be185d; --ms-6:#4f46e5;
    --seg-cash:#94a3b8; --seg-unsettled:#c9b06a; --seg-fees:#d0a24a; --seg-refunds:#c98a6a;
    --proposal:#6b3fd4;
    --lblw:360px;
    /* surfaces, lightest first: the page, the cards on it, and the tints that
       separate a hovered row, a note box or a grouping band from either */
    --bg:#fbfbfc; --panel:#ffffff; --panel-2:#fafafa;
    --soft:#f7f8fb; --soft-2:#f5f6f8; --hover:#f4f4f4; --rowhover:#f6f8ff;
    /* floating discs (phone back-to-top + details) — a touch darker than --bg
       so they read as raised over the page; a touch lighter than --bg in dark */
    --float-face:#eceef2;
    --band:#faf7f2; --band-h:#f5f0e5; --info-bg:#f4f6f8; --tagbg:#f1f2f5;
    --track:#eef0f3; --chip:#f4f1ea; --chip-h:#ece7dc; --chipline:#f1ede4;
    --pill-bg:#eef1f6; --pill-bd:#d8dde6; --ink-blue:#334;
    --line:#e4e4e4; --line-2:#ccc; --grid:#f0f0f0; --wk:#d8d8d8; --dashline:#cfd4dc;
    --raw-bg:#20242c; --raw-tx:#dfe4ec;
    /* the pill divider and the Drive half's hover: dark ink on a light pill */
    --pill-div:rgba(0,0,0,.14); --pill-dh:rgba(0,0,0,.05);
    --wkend:rgba(60,70,90,.07);
    /* text, strongest first */
    --ink:#1a1a1a; --ink-strong:#111; --ink-2:#222; --ink-3:#333; --ink-4:#444;
    --text:#555; --text-2:#666; --text-3:#777;
    --muted:#888; --muted-2:#999; --muted-3:#aaa; --muted-4:#bbb;
    --dim:#8a8f98; --na-tx:#71767f; --info-tx:#46506a; --link:#2f6fed;
    /* red — a problem, or money overdue */
    --red-tx:#b42318; --red-tx-2:#d1242f; --red-tx-3:#b00000; --red-bar:#cf3a33;
    --red-wash:#fff5f5; --red-bg:#fef3f2; --red-bg-h:#feecec; --red-chip:#f7d9d5;
    --red-bd:#f2c9c4;
    /* amber — still to sort out; money owed is this, not red */
    --amb-tx:#b54708; --amb-tx-2:#c77400; --amb-tx-3:#8a5b00; --amb-bar:#e8a33d;
    --amb-bg:#fff8ee; --amb-bg-2:#fdf3e3; --amb-bd:#f0dcba; --amb-bd-2:#e8c79a;
    --amb-line:#f0e6d2;
    /* green — settled, paid, checked */
    --grn-tx:#1a7f37; --grn-tx-2:#19713a; --grn-tx-3:#1c8a4e;
    --grn-bg:#eaf6ee; --grn-bg-2:#eef8f1; --grn-bd:#a8d5b5; --grn-bd-2:#b9dfc3;
    /* blue — informational, "going stale" (a warning that is not yet a problem) */
    /* --blu-dot is the info-blue dot/highlight; it is the SAME colour as the
       text-link (--link), so alias it rather than re-type the literal — that
       keeps "a link" and "an info highlight" one value but two names, and lets
       non-link blue (a drop target, a hint) point at --blu-dot, never --link. */
    --blu-tx:#1d4ed8; --blu-bg:#eef4ff; --blu-bd:#c2d4f7; --blu-dot:var(--link);
    --shadow:rgba(20,22,26,.14);
    /* elevation shadows, one per role (brand-guide §08). --shadow-pop (menus,
       popovers) and --shadow-drawer (the right slide-over) ride --shadow, so
       they adapt to dark on their own — which is why the old per-surface dark
       shadow-rim rule is gone. A modal sits higher, so it carries a touch more
       and restates its colour in the dark block below. */
    --shadow-pop:0 8px 24px var(--shadow);
    --shadow-drawer:-10px 0 32px var(--shadow);
    --shadow-modal:0 20px 52px rgba(20,22,26,.24);
    /* chart series. Validated for colour-blind separation against the surface
       they sit on: current-period navy vs prior-period grey at dE 21.7, and
       income teal vs expense red at dE 11.9 — a conventional green against that
       red measures 3.8 under deuteranopia, which is no separation at all. The
       accent red is NOT reused as a series: it means "overdue" everywhere else. */
    --c-now:#245C9E; --c-prev:#948A82; --c-grid:#ECE7E1;
    --c-inc:#0F766E; --c-exp:#B42318; --c-net:#1A1614;
  }
  /* Dark mode. The tints invert their relationship to the page — a "lighter"
     surface is one nearer the viewer, so --panel sits ABOVE --bg here where in
     light it sits below — and every status colour is restated at a lightness
     that carries on a dark ground instead of sinking into it.
     There is one selector rather than a second copy under a media query,
     because the attribute is always the RESOLVED theme: "follow the device" is
     a stored preference that the script below turns into light or dark before
     first paint, and re-resolves when the device setting changes. */
  html[data-theme="dark"]{
    color-scheme:dark;
    --red-ink:#ff6b64;
    /* The status fills are the one part of the palette that is not simply
       inverted: a bar keeps its hue, so a blocked task is still red and work
       in progress still amber, but at the saturation and lightness those
       colours had against WHITE they glare against near-black — and the
       roadmap and the board are nothing but these, tiled across the page.
       Each is taken down to roughly a third of its light luminance, which
       also lifts the white label on a bar from unreadable (2:1 on the light
       amber) to 4:1 or better on every one of them.
       Their ink counterparts go the other way, as all ink does here. */
    --todo:#6e7684; --prog:#a5741a; --block:#a8392f; --done:#21804a; --group:#5b6270;
    --block-ink:#ff8a7e; --done-ink:#5cd68d;
    --bg:#101216; --panel:#1a1d23; --panel-2:#20242b;
    --soft:#21252d; --soft-2:#262a33; --hover:#2a2f38; --rowhover:#232b3a;
    --float-face:#2b3038;
    --band:#23252b; --band-h:#2b2e35; --info-bg:#1e2732; --tagbg:#2b303a;
    --track:#2b303a; --chip:#2b2924; --chip-h:#35322b; --chipline:#2e2c26;
    --pill-bg:#272d38; --pill-bd:#3a4250; --ink-blue:#ccd4e2;
    --line:#333944; --line-2:#3d434e; --grid:#262a31; --wk:#3a404b; --dashline:#454c58;
    --raw-bg:#0c0e12; --raw-tx:#dfe4ec;
    /* light ink on a dark pill, so the divider and the Drive half read the
       same way round as they do in light */
    --pill-div:rgba(255,255,255,.16); --pill-dh:rgba(255,255,255,.07);
    --wkend:rgba(150,170,200,.07);
    --ink:#e6e9ef; --ink-strong:#f5f7fa; --ink-2:#e1e5ec; --ink-3:#d4d9e2; --ink-4:#c4cad4;
    --text:#b3bac5; --text-2:#a4abb7; --text-3:#969da9;
    --muted:#8b929e; --muted-2:#848b97; --muted-3:#7c838f; --muted-4:#737a86;
    --dim:#949ba7; --na-tx:#9aa1ad; --info-tx:#a8b6c8; --link:#6fa3ff;
    --red-tx:#ff8f84; --red-tx-2:#ff8078; --red-tx-3:#ff7a72; --red-bar:#cf3a33;
    --red-wash:#241a1c; --red-bg:#2d1e1f; --red-bg-h:#3a2325; --red-chip:#4a2a2b;
    --red-bd:#5d3230;
    --amb-tx:#f0a86a; --amb-tx-2:#e8a44b; --amb-tx-3:#e0b063; --amb-bar:#e8a33d;
    --amb-bg:#2c2418; --amb-bg-2:#33291a; --amb-bd:#58462a; --amb-bd-2:#63502e;
    --amb-line:#453a26;
    --grn-tx:#5cd68d; --grn-tx-2:#52ce85; --grn-tx-3:#58d492;
    --grn-bg:#17281d; --grn-bg-2:#182a1f; --grn-bd:#2d5a3c; --grn-bd-2:#326244;
    --blu-tx:#6fa3ff; --blu-bg:#1a2436; --blu-bd:#33507d; --blu-dot:var(--link);
    --shadow:rgba(0,0,0,.5);
    --shadow-modal:0 20px 52px rgba(0,0,0,.55);
    /* the series again, re-picked for the dark ground rather than flipped:
       navy and teal both go light so they clear 3:1 on the panel behind them */
    --c-now:#8AA6FF; --c-prev:#8A8078; --c-grid:#2A2F36;
    --c-inc:#4FBFAE; --c-exp:#F4796C; --c-net:#F2EDE8;
  }
  /* the two toolbar pickers draw their own chevron as a background image, and
     an ink-coloured one disappears on a dark field */
  html[data-theme="dark"] .topbar select#prodExportSel{
    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23949ba7' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>")}
  *{box-sizing:border-box}
  html,body{margin:0;height:100%;font-family:Arial,Helvetica,sans-serif;color:var(--ink);background:var(--bg)}
  /* the base link: a reference you follow is --link, no underline at rest,
     underline on hover. Scoped to bare anchors so a CLASSED <a> — the entity
     pills (.mpill/.ipill), the go-links — keeps its own control styling and
     its own hover (it reddens; it does not underline). The rule in one line:
     links underline, controls redden. See brand-guide §06. */
  a{color:var(--link);text-decoration:none}
  @media (hover:hover){ a:not([class]):hover{text-decoration:underline} }
  #app{display:flex;height:100vh;height:100dvh}
  #workspace{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}
  /* the LIST is what scrolls, not the whole column: the two things under it —
     the appearance switch and who Access let in — belong to the foot of the
     menu, and a column that scrolls as one piece drops them off the bottom of
     any window shorter than the nav is tall (most of them) */
  #sidebar{width:220px;flex:0 0 220px;background:var(--panel);border-right:1px solid var(--line);padding:18px 12px;overflow:hidden;display:flex;flex-direction:column}
  #sidebar nav{flex:1 1 auto;overflow:auto;min-height:0}
  /* who Cloudflare Access let in — the left of the footer's signed-in row */
  #whoami{display:none;min-width:0;font-size:var(--fs-xs);color:var(--dim);overflow-wrap:anywhere;line-height:1.4}
  #whoami .who{color:var(--text);font-weight:bold}
  /* the appearance switch, shrunk to one icon on the right of the signed-in row:
     it shows the current choice (sun / moon / auto) and cycles on click, where
     three full-width buttons used to spend a whole row */
  .themebtn{flex:0 0 auto;width:30px;height:30px;display:flex;align-items:center;justify-content:center;
    border:1px solid var(--line);border-radius:7px;background:var(--panel);color:var(--ink-3);cursor:pointer}
  .themebtn svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  @media (hover:hover){ .themebtn:hover{background:var(--hover);color:var(--red-ink);border-color:var(--red-bd)} }
  /* the foot's icon buttons cluster on the right of the signed-in row: the
     company-details bank button sits beside the appearance switch, both the same
     square. The wrapper is what keeps them together against #whoami's left. */
  .footbtns{flex:0 0 auto;display:flex;align-items:center;gap:8px}
  .footbtn{flex:0 0 auto;width:30px;height:30px;display:flex;align-items:center;justify-content:center;
    border:1px solid var(--line);border-radius:7px;background:var(--panel);color:var(--ink-3);cursor:pointer}
  .footbtn svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  .footbtn[aria-expanded="true"]{background:var(--hover);color:var(--red-ink);border-color:var(--red-bd)}
  @media (hover:hover){ .footbtn:hover{background:var(--hover);color:var(--red-ink);border-color:var(--red-bd)} }
  .brandrow{display:flex;align-items:center;justify-content:space-between;padding:0 8px 16px}
  /* the top-left identity, the shape the POS shares: a mark, the tenant name,
     and the app label under it ("Admin" here, "POS" there). The mark is themed
     (var(--red) is the tenant's primary — Taco red, Telos moss), not an emoji,
     so it is right for every tenant; drop in a real logo when there is one. */
  .brandid{display:flex;align-items:center;gap:9px;min-width:0;font:inherit;text-align:left;color:inherit;
    background:transparent;border:0;border-radius:8px;padding:4px 6px;margin:-4px 0 -4px -6px;cursor:pointer}
  .brandid .bchev{width:12px;height:12px;flex:0 0 12px;fill:none;stroke:var(--muted);stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round;align-self:flex-end;margin-bottom:5px}
  .brandid[aria-expanded="true"]{background:var(--soft-2)}
  @media (hover:hover){ .brandid:not(:disabled):hover{background:var(--soft-2)} }
  .brandid:disabled{cursor:default}
  .brandid:disabled .bchev{display:none}
  /* the app switch itself: a body-level popover like #createMenu, the POS's
     .appswitch drawn with this sheet's tokens — a filled mark for the till, a
     panel one for this app (worn by the identity itself, .brandmark). */
  #appSwitchScrim{display:none;position:fixed;inset:0;z-index:90;background:transparent}
  #appSwitchScrim.open{display:block}
  #appSwitch{display:none;position:fixed;z-index:91;width:220px;background:var(--panel);
    border:1px solid var(--line);border-radius:10px;padding:6px;flex-direction:column;box-shadow:0 8px 24px var(--shadow)}
  #appSwitch.open{display:flex}
  .asw-it{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:8px;font-size:var(--fs-md);font-weight:600;color:var(--ink-3);text-decoration:none}
  @media (hover:hover){ a.asw-it:hover{background:var(--soft-2)} }
  .asw-mk{width:26px;height:26px;border-radius:7px;flex:0 0 26px;display:inline-flex;align-items:center;justify-content:center}
  .asw-mk svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  .asw-pos{background:var(--red);color:#fff}
  .asw-adm{background:var(--soft);border:1px solid var(--line);color:var(--ink-3)}
  .asw-ext{margin-left:auto;color:var(--muted)}
  .asw-hint{font-size:var(--fs-2xs);color:var(--muted);padding:6px 10px 4px;border-top:1px solid var(--grid);margin-top:4px}
  .brandmark{flex:none}
  .brandnm{display:flex;flex-direction:column;line-height:1.05;min-width:0}
  .brandapp{font-size:var(--fs-2xs);letter-spacing:.14em;text-transform:uppercase;color:var(--muted);font-weight:bold;margin-top:1px}
  .brand{font-weight:bold;color:var(--red-ink);font-size:var(--fs-xl)}
  /* the sidebar's own collapse control, top right; a folder-tab « brings it back */
  #navCollapse{font:inherit;font-size:var(--fs-lg);line-height:1;padding:3px 7px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--text-2);cursor:pointer}
  @media (hover:hover){ #navCollapse:hover{color:var(--red-ink);border-color:var(--red)} }
  #navReopen{display:none;position:fixed;left:0;top:10px;z-index:85;font:inherit;font-size:var(--fs-lg);line-height:1;padding:8px 9px 8px 7px;
    border:1px solid var(--line);border-left:0;border-radius:0 8px 8px 0;background:var(--panel);color:var(--text-2);cursor:pointer;box-shadow:2px 2px 8px var(--shadow)}
  @media (hover:hover){ #navReopen:hover{color:var(--red-ink);border-color:var(--red)} }
  /* ---- the global Create -------------------------------------------------
     The desktop counterpart of the phone's speed-dial: one front door for
     "new …", at the right end of the top band beside the sync chip (the phone
     keeps its speed-dial, so the pill folds away there). It wears a solid red
     (a fill, not the wash the active nav row uses) so it reads as a button, not
     a location. Hidden for a seat with nothing it may add (syncCreate). */
  #tbRight{margin-left:auto;display:flex;align-items:center;gap:8px}
  #createBtn{display:flex;align-items:center;gap:7px;font:inherit;
    font-size:var(--fs-sm);font-weight:bold;color:#fff;background:var(--red);border:1px solid var(--red);
    border-radius:7px;padding:6px 10px;cursor:pointer;box-shadow:0 1px 2px var(--shadow);white-space:nowrap}
  #createBtn .cplus{width:14px;height:14px;flex:0 0 14px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round}
  #createBtn .ccv{width:11px;height:11px;flex:0 0 11px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
  @media (hover:hover){ #createBtn:hover{box-shadow:0 3px 10px -2px var(--shadow)} }
  @media (max-width:700px){ #createBtn{display:none} }
  /* the menu itself: a body-level popover, positioned by createMenuOpen. Two
     groups — the section's own adds, then Task and a document — each row a red
     disc and a label, matching the phone dial's items. */
  #createScrim{display:none;position:fixed;inset:0;z-index:90;background:transparent}
  #createScrim.open{display:block}
  #createMenu{display:none;position:fixed;z-index:91;width:244px;background:var(--panel);
    border:1px solid var(--line);border-radius:8px;padding:6px;flex-direction:column;gap:1px;
    box-shadow:var(--shadow-pop)}
  #createMenu.open{display:flex}
  .create-grp{font-size:var(--fs-2xs);font-weight:bold;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);padding:8px 10px 5px}
  .create-mi{display:flex;align-items:center;gap:11px;width:100%;flex:0 0 auto;font:inherit;font-size:var(--fs-md);
    text-align:left;padding:8px 10px;border:0;border-radius:8px;background:transparent;cursor:pointer;color:var(--ink-3)}
  @media (hover:hover){ .create-mi:hover{background:var(--soft-2)} }
  .create-mi .cmico{width:26px;height:26px;flex:0 0 26px;border-radius:50%;background:var(--red);color:#fff;display:inline-flex;align-items:center;justify-content:center}
  .create-mi .cmico svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  .create-sep{height:1px;background:var(--grid);margin:5px 8px}
  /* One flat column, rows tight. No subject captions space the groups apart any
     more: the list is a single rhythm top to bottom, and a row that holds more
     than one page opens its pages as a submenu underneath itself. */
  #sidebar nav{display:flex;flex-direction:column;gap:2px}
  .navitem{display:flex;flex-direction:column}
  /* the pinned foot: the signed-in row with the theme switch on its right —
     tight to the bottom, apart from the working menu above. Status is a plain
     menu row now, so it rides in #sidebar nav with the rest. */
  #sidefoot{flex:0 0 auto;margin-top:12px;padding-top:10px;border-top:1px solid var(--grid);display:flex;flex-direction:column;gap:10px}
  .footrow{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 8px}
  #sidebar .viewbtn{display:flex;align-items:center;gap:9px;font:inherit;font-size:var(--fs-sm);text-align:left;padding:5px 8px;border:1px solid transparent;background:transparent;border-radius:6px;cursor:pointer;color:var(--ink-3)}
  /* One glyph per destination, drawn rather than lettered: at a glance the shape
     is what you find the row by, and it costs no height — 15px inside a 25px
     row. Tabler outline, stroked in currentColor so a row's icon takes the same
     red as its label the moment it goes active. */
  .ni{width:15px;height:15px;flex:0 0 15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  #sidebar .viewbtn.active{background:var(--red-wash);color:var(--red-ink);border-color:var(--red-bd);font-weight:bold}
  @media (hover:hover){ #sidebar .viewbtn:hover:not(.active){background:var(--soft-2)} }
  /* A row that holds a submenu carries a chevron at its end; .lbl takes the
     middle so the chevron sits hard right, and the chevron flips when open. */
  /* the nav label reuses the .lbl class the timeline grid rows use, which carries
     a fixed width, a sticky seat and a border-right (the divider between a grid's
     frozen name column and its cells). None of that belongs on a menu row, so
     reset it here — the border-right is the stray vertical line that showed up
     beside the chevrons. */
  #sidebar .viewbtn .lbl{flex:1 1 auto;min-width:0;width:auto;overflow:hidden;text-overflow:ellipsis;position:static;background:none;border-right:0;padding:0}
  .chev{width:13px;height:13px;flex:0 0 13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;color:var(--dim);transition:transform .18s ease}
  .navitem.open>.viewbtn .chev{transform:rotate(180deg)}
  /* The submenu, animated open with the grid-rows 0fr→1fr trick, its pages
     indented under a hairline guide. A child is a .subbtn, not a .viewbtn — so
     applyAccess (which gates .viewbtn rows by seat) leaves the children alone;
     renderSideKids already omits the ones the seat cannot open. */
  .side-kids{display:grid;grid-template-rows:0fr;transition:grid-template-rows .18s ease}
  .navitem.open>.side-kids{grid-template-rows:1fr}
  .side-kids-in{overflow:hidden}
  .sidekidlist{display:flex;flex-direction:column;gap:2px;margin:2px 0 4px 15px;padding-left:9px;border-left:1px solid var(--grid)}
  #sidebar .subbtn{display:block;width:100%;font:inherit;font-size:var(--fs-sm);text-align:left;padding:5px 8px;border:1px solid transparent;background:transparent;border-radius:6px;cursor:pointer;color:var(--text-2)}
  #sidebar .subbtn.active{background:var(--red-wash);color:var(--red-ink);border-color:var(--red-bd);font-weight:bold}
  @media (hover:hover){ #sidebar .subbtn:hover:not(.active){background:var(--soft-2);color:var(--ink-3)} }
  @media (prefers-reduced-motion:reduce){ .side-kids,.chev{transition:none} }
  /* The top bar is now exclusively for actions within the current page. */
  /* Two bands, in that order: where you are, then what you can do here. The
     first says the page — as a title where the page stands alone, and as the
     row of its siblings where it does not, which is the same statement either
     way. The second carries the page's own buttons and is dropped entirely on
     the pages that have none, rather than left as an empty stripe. */
  .topbar{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--panel);border-bottom:1px solid var(--line);flex:0 0 auto;min-height:49px}
  /* the top band's children run its full height so a selected tab's underline
     can sit on the band's own bottom edge */
  header.topbar{align-items:stretch;padding:0 12px}
  /* ---- the sync indicator: one chip, pinned to the right of the top band, that
     stands for whichever feeds the current page depends on. Quiet green check at
     rest (no banner — the check IS the all-clear); amber when a detection feed has
     drift to review; red when a feed has failed or gone silent. margin-left:auto
     pushes it clear of the title/subnav; it hides itself on pages with no feed. */
  #syncCell{display:flex;align-items:center;position:relative}
  #syncCell[hidden]{display:none}
  .syncchip{display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-xs);color:var(--dim);
    padding:5px 9px;border-radius:7px;border:1px solid transparent;background:transparent;cursor:pointer;
    white-space:nowrap;line-height:1;font-family:inherit}
  .syncchip .sci{width:14px;height:14px;flex:0 0 14px;display:block;fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
  .syncchip .scwhen{color:var(--dim)}
  .syncchip .sclbl{font-weight:500}
  @media (hover:hover){.syncchip:hover{background:var(--soft);border-color:var(--line)}}
  .syncchip.ok .sci{stroke:var(--grn-tx)}
  .syncchip.busy{color:var(--blu-tx)} .syncchip.busy .sclbl{color:var(--blu-tx);font-weight:600}
  .scspin{width:13px;height:13px;flex:0 0 13px;border:2px solid var(--blu-bd);border-top-color:var(--blu-dot);border-radius:50%;animation:scspin .7s linear infinite}
  @keyframes scspin{to{transform:rotate(360deg)}}
  @media (prefers-reduced-motion:reduce){.scspin{animation:none}}
  .syncchip.warn{background:var(--amb-bg);border-color:var(--amb-bd);color:var(--amb-tx)}
  .syncchip.warn .sci{stroke:var(--amb-tx)} .syncchip.warn .sclbl{color:var(--amb-tx);font-weight:700}
  .syncchip.bad{background:var(--red-bg);border-color:var(--red-bd);color:var(--red-tx)}
  .syncchip.bad .sci{stroke:var(--red-tx)} .syncchip.bad .sclbl{color:var(--red-tx);font-weight:700}
  .syncchip.bad .scwhen{color:var(--red-tx);opacity:.8}
  .screv{font-size:var(--fs-2xs);font-weight:700;color:var(--amb-tx);background:var(--amb-bg-2);
    border:1px solid var(--amb-bd-2);border-radius:5px;padding:2px 7px;margin-left:2px}
  /* on a phone the chip is just the icon + a short relative time; the label folds
     away except on amber, where the count is the whole point of speaking up */
  @media (max-width:700px){
    .syncchip .sclbl{display:none}
    .syncchip.warn .sclbl{display:inline}
  }
  /* the sync centre: the cross-feed popover, hung under the chip */
  .scpop{position:absolute;top:calc(100% + 6px);right:0;width:334px;max-width:calc(100vw - 24px);
    background:var(--panel);border:1px solid var(--line);border-radius:8px;
    box-shadow:var(--shadow-pop);padding:6px;z-index:60;text-align:left;cursor:default}
  .scpop-h{display:flex;align-items:baseline;justify-content:space-between;padding:8px 8px 4px}
  .scpop-h .t{font-size:var(--fs-sm);font-weight:700;color:var(--ink-2)}
  .scpop-h .s{font-size:var(--fs-2xs);color:var(--dim)}
  .scgrp{font-size:var(--fs-3xs);letter-spacing:.1em;text-transform:uppercase;color:var(--muted-2);padding:9px 8px 3px}
  .scrow{display:flex;align-items:center;gap:9px;padding:7px 8px;border-radius:7px}
  .scrow.here{background:var(--rowhover)}
  @media (hover:hover){.scrow:hover{background:var(--soft)}}
  .scdot{width:7px;height:7px;border-radius:50%;flex:0 0 7px}
  .scdot.g{background:var(--grn-tx)} .scdot.a{background:var(--amb-tx)} .scdot.r{background:var(--red-tx)} .scdot.b{background:var(--blu-dot)}
  .scrow .nm{font-size:var(--fs-sm);color:var(--ink-3);flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* the per-row action cluster: last-synced time, then the feed's own Review (only
     when it has changes waiting), then a Sync that runs just this one feed. Every
     row can be synced on demand; only a detection/publish feed with drift reviews. */
  .scact{display:flex;align-items:center;gap:6px;flex:0 0 auto}
  .scrow .tm{font-size:var(--fs-2xs);color:var(--dim);font-variant-numeric:tabular-nums;white-space:nowrap}
  .scrow .tm.a{color:var(--amb-tx);font-weight:600} .scrow .tm.r{color:var(--red-tx);font-weight:600}
  .scsync{display:inline-flex;align-items:center;gap:5px;font-family:inherit;font-size:var(--fs-2xs);
    font-weight:600;color:var(--dim);background:none;border:1px solid transparent;border-radius:6px;
    padding:3px 8px 3px 6px;cursor:pointer;white-space:nowrap}
  .scsync svg{width:12px;height:12px;flex:0 0 12px;fill:none;stroke:currentColor;stroke-width:2.2;
    stroke-linecap:round;stroke-linejoin:round}
  @media (hover:hover){.scsync:hover{background:var(--soft-2);border-color:var(--line);color:var(--ink-3)}}
  .scpop-f{display:flex;align-items:center;justify-content:flex-end;gap:8px;margin-top:4px;padding:8px;border-top:1px solid var(--line)}
  .scpop-f .allx{display:inline-flex;align-items:center;gap:7px;font-size:var(--fs-sm);font-weight:600;color:#fff;
    background:var(--red);border:1px solid var(--red);border-radius:6px;padding:7px 14px;cursor:pointer;
    white-space:nowrap;font-family:inherit}
  .scpop-f .allx svg{width:13px;height:13px;fill:none;stroke:#fff;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
  .scpop-f .allx:disabled{opacity:.5;cursor:default}
  #pageTitle{font-size:var(--fs-lg);font-weight:bold;color:var(--ink-3);display:flex;align-items:center}
  #navToggle{display:none;align-self:center}
  /* desktop: « in the sidebar collapses the column entirely; the choice is
     remembered, and the fixed folder-tab reopener takes the sidebar's place —
     the topbar shifts right so the page title stays clear of it */
  @media (min-width:701px){
    html.navhidden #sidebar{display:none}
    html.navhidden #navReopen{display:block}
    html.navhidden header.topbar{padding-left:42px}
  }
  .topbar button{font:inherit;font-size:var(--fs-sm);padding:6px 10px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
  /* the products Export picker sits with the toolbar buttons and dresses like one.
     Native chrome is dropped: the browser sizes a select to its widest option and
     draws its own arrow, leaving a slab of dead space after short labels — instead
     JS sizes the box to the current choice (fitSelect) and the chevron is ours,
     inset from the right edge by the same 10px the text gets on the left */
  .topbar select#prodExportSel{font:inherit;font-size:var(--fs-sm);padding:6px 28px 6px 10px;border:1px solid var(--line);border-radius:6px;cursor:pointer;appearance:none;-webkit-appearance:none;background:var(--panel) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23666' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>") no-repeat right 10px center}
  /* every :hover in this file is gated to hover-capable devices: on touch the
     browser keeps an element's hover state after a tap, leaving buttons stuck
     in their "pressed" look until the next tap lands somewhere else */
  @media (hover:hover){ .topbar button:hover{background:var(--hover)} }
  .topbar button.primary{background:var(--red);color:#fff;border-color:var(--red)}
  .topbar button:disabled{opacity:.4;cursor:default}
  /* Live / Preview switch. Whether the app is writing what you do is the kind of
     state that has to be readable at a glance rather than worked out from a
     button's wording, so it sits in one of two positions and wears two colours:
     Live is the quiet default, Preview is amber — the same amber the unsaved bar
     uses, because it means the same thing, work the sheet hasn't got yet.
     The sw- prefix on its parts is not decoration: this button's label started
     out as .lbl, which is the roadmap's sticky row-label column — 360px wide,
     sticky, bordered — and the switch inherited all of it. */
  .topbar button.modesw{display:flex;align-items:center;gap:7px;padding:5px 10px 5px 7px}
  .modesw .swtrack{width:26px;height:15px;border-radius:999px;background:var(--line);border:1px solid var(--line);
    position:relative;flex:0 0 auto;transition:background .12s,border-color .12s}
  .modesw .swknob{position:absolute;top:1px;left:1px;width:11px;height:11px;border-radius:50%;background:var(--panel);
    box-shadow:0 1px 2px rgba(0,0,0,.28);transition:transform .12s}
  .modesw .swlbl{font-weight:600}
  .topbar button.modesw.on{background:var(--amb-bg);border-color:var(--amb-bd-2);color:var(--amb-tx)}
  .modesw.on .swtrack{background:var(--amb-bar);border-color:var(--amb-bar)}
  .modesw.on .swknob{transform:translateX(11px)}
  @media (hover:hover){ .topbar button.modesw.on:hover{background:var(--amb-bg-2)} }
  @media (prefers-reduced-motion:reduce){ .modesw .swtrack,.modesw .swknob{transition:none} }
  /* The page's siblings, in the top band where the page title would otherwise
     be. It carries the depth the sidebar used to: eight Accounting rows in the
     menu became Statements + Documents + Transactions + Budgets + Settings, and
     what those rows named lives here instead.

     The selected tab is #pageTitle exactly — same size, same weight, same ink —
     because it IS the title; the red underline is the only thing added, and all
     it says is that this page has siblings. So "Home", which has none, and
     "Roadmap", which sits under Tasks, read at the same size and in the same
     place. The unselected ones drop to normal weight and a quieter grey.

     Deliberately not the red pill chips every page uses for its own filters:
     Statements and Rules draw their chip row a band below, and two rows of the
     same pill would read as one control that had been split in half. Tabs are a
     level up — they change the page, where a chip narrows it. It scrolls
     sideways rather than wrapping: a second line here would push the content
     down by a whole band on the windows that can least afford it. */
  #subnav{display:none;gap:4px;align-items:stretch;flex:0 1 auto;overflow-x:auto;white-space:nowrap;
    scrollbar-width:none}
  #subnav::-webkit-scrollbar{display:none}
  #subnav.on{display:flex}
  /* radius:0 is load-bearing — .topbar button rounds every corner 6px, and a
     rounded corner on a border that exists on one side only curves the
     underline up at both ends */
  #subnav button{font:inherit;font-size:var(--fs-lg);padding:0 8px;border:0;border-bottom:2px solid transparent;
    border-radius:0;background:transparent;color:var(--dim);cursor:pointer;flex:0 0 auto}
  #subnav button.on{color:var(--ink-3);border-bottom-color:var(--red);font-weight:bold}
  @media (hover:hover){ #subnav button:not(.on):hover{color:var(--ink-3)} }
  /* The second band, holding both kinds of page control at opposite ends: what
     you can do on the left, which slice you are reading on the right. They used
     to be a band each — the buttons in the toolbar and the pills over the
     content — which made three horizontal rules above every table. Two kinds of
     control do not need two bands; they need two ends of one.

     The bar scrolls sideways when the pair outgrows it. margin-left:auto on the
     pills does the right thing under that: it collapses to nothing once there
     is no slack, so a wide page loses the gap between the groups rather than
     pushing the pills off where nobody would look for them. */
  #actionsBar{display:none;overflow-x:auto;scrollbar-width:none}
  #actionsBar::-webkit-scrollbar{display:none}
  #actionsBar.on{display:flex}
  .actionsrow{display:flex;align-items:center;gap:10px;margin-left:0;flex:0 0 auto}
  #filterRow{display:flex;align-items:center;gap:8px;margin-left:auto;flex:0 0 auto}
  #filterRow:empty{display:none}
  /* pills, not buttons: .topbar button above has already given them their border
     and ground, so this is only the shape and the red that says "this one".
     The height is stated rather than left to fall out of the type, because the
     search box beside them states one too and the two have to agree — a chip
     sized by its line box lands a pixel or two off whatever the box does, and
     the row reads as a set of controls that were not drawn together. 28px and
     13px is a notch up from where this row started: the phone's box carries
     16px type (anything under that and iOS zooms the page when the field takes
     focus, so it cannot come down to meet this), and 12px in a 26px pill was
     reading as a different, daintier design rather than the same one. */
  /* One size for every sub-menu control, wherever it is drawn — the chips AND
     the pickers (the ledger's month, Documents' merchant) that ride the same
     rows, across the filter row, the phone active-filters strip (.txactive) and
     the phone filter sheet. Everything in those rows lines up because its size
     is set here once and nowhere else; each container's own rule below keeps
     only what genuinely differs between them (height, padding, radius). Each
     selector is compound on purpose: it has to out-rank the base rule its
     container imposes (.topbar button / the selects' own reset here, the
     sheet's reset there). The rule editor's chips are the one exception and set
     their own size; see #rulesview .txfilter. */
  #filterRow .txfilter,.txactive .txfilter,.sheet .txfilter,#sheetSlot #filterRow .txfilter,#filterRow select.stmonth,.sheet select.stmonth,#sheetSlot #filterRow select.stmonth{font-size:var(--fs-md)}
  #filterRow .txfilter{height:28px;padding:0 11px;border-radius:14px;
    display:inline-flex;align-items:center;gap:5px}
  #filterRow .txfilter.on{background:var(--red);color:#fff;border-color:var(--red)}
  @media (hover:hover){ #filterRow .txfilter.on:hover{background:var(--red-hover);border-color:var(--red-hover)} }
  #filterRow .filtersep{align-self:stretch;width:1px;background:var(--line);margin:2px 3px;flex:0 0 auto}
  /* the month picker rides in the same row as the chips: chip metrics, chip
     radius, and the chevron the topbar's selects use, so it reads as one of the
     narrowings rather than a form control that wandered in. It has no "all" to
     be narrowed from, so it never carries the lit fill a chip does. */
  #filterRow select.stmonth{font-family:inherit;height:28px;padding:0 26px 0 11px;border:1px solid var(--line);border-radius:14px;
    background:var(--panel) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23666' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>") no-repeat right 9px center;
    color:var(--text);cursor:pointer;appearance:none;-webkit-appearance:none;max-width:260px}
  .topbar label{font-size:var(--fs-sm);color:var(--text)}
  .legend{display:flex;gap:10px;font-size:var(--fs-xs);color:var(--text);align-items:center;white-space:nowrap;
    position:absolute;bottom:10px;right:12px;z-index:4;background:var(--panel);border:1px solid var(--line);
    border-radius:6px;padding:6px 10px;box-shadow:0 1px 3px rgba(0,0,0,.08);transition:right .15s}
  .legend i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:3px;vertical-align:middle}
  #main{flex:1 1 auto;display:flex;min-height:0;position:relative}
  #scroll{flex:1 1 auto;overflow:auto;position:relative}
  #grid{position:relative}
  /* rows */
  .row{display:flex;align-items:stretch;height:34px;border-bottom:1px solid var(--grid)}
  .lbl{position:sticky;left:0;z-index:3;width:var(--lblw);min-width:var(--lblw);background:var(--panel);border-right:1px solid var(--line);
       display:flex;align-items:center;gap:6px;padding:0 8px;font-size:var(--fs-sm);overflow:hidden;white-space:nowrap}
  .row.phase,.row.area{cursor:pointer}
  .row.phase .lbl{font-weight:bold;background:var(--red-wash)}
  .row.area  .lbl{font-weight:bold;color:var(--ink-4);background:var(--panel-2);padding-left:22px}
  .row.action .lbl{padding-left:40px;color:var(--ink-2);cursor:pointer}
  @media (hover:hover){ .row.action .lbl:hover{background:var(--rowhover)} }
  .caret{width:14px;text-align:center;cursor:pointer;color:var(--muted);flex:0 0 14px;user-select:none}
  .lbltext{overflow:hidden;text-overflow:ellipsis}
  .gstat{font-weight:normal;color:var(--muted-2);font-size:var(--fs-2xs);margin-left:6px;white-space:nowrap;flex:0 0 auto}
  .gstat.sshort{display:none}
  .pill{width:8px;height:8px;border-radius:50%;flex:0 0 8px}
  .addbtn{margin-left:auto;flex:0 0 18px;width:18px;height:18px;line-height:16px;text-align:center;
    border:1px solid var(--line);border-radius:4px;background:var(--panel);color:var(--text-2);font-size:var(--fs-md);cursor:pointer}
  /* a group row's own controls, at the right end of its label: where it reads
     against its siblings, what it is called, and what goes in it */
  .lblops{margin-left:auto;flex:0 0 auto;display:flex;align-items:center;gap:3px}
  .lblops .addbtn{margin-left:0}
  .lblops .addbtn.tiny{font-size:var(--fs-2xs);line-height:16px}
  /* a disabled arrow keeps its box (the row would jump if it vanished) but
     stops being something to press. Colour, not opacity: the hover rules below
     set opacity on the whole set and would paint it back in. */
  .lblops .addbtn.off{color:var(--muted-3);cursor:default}
  /* hover-revealed on desktop only. On iOS a tap on a row whose :hover reveals
     content just applies the hover state and eats the click, so the collapse
     toggle needed a double tap — on touch the button is simply always shown. */
  @media (hover:hover){ .row.phase .addbtn,.row.area .addbtn{opacity:0}
    .row.phase:hover .addbtn,.row.area:hover .addbtn{opacity:1} }
  @media (hover:hover){ .addbtn:not(.off):hover{color:var(--red-ink);border-color:var(--red)} }
  /* the row you type a new phase/area/task name into, in the label column where
     the name will end up — so naming one is not a trip through the side panel */
  .newin{flex:1 1 auto;min-width:0;font:inherit;font-size:var(--fs-sm);padding:2px 6px;
    border:1px solid var(--red);border-radius:4px;background:var(--panel);color:var(--ink-2)}
  .row.newrow .lbl{padding-left:40px}
  .row.newrow.newarea .lbl{padding-left:22px}
  .idtag{color:var(--muted-3);font-size:var(--fs-2xs);flex:0 0 24px;text-align:right}
  .tl{position:relative;flex:1 1 auto}
  /* corner + header */
  #head{position:sticky;top:0;z-index:5;display:flex;height:40px;background:var(--panel);border-bottom:1px solid var(--line)}
  #head .corner{position:sticky;left:0;z-index:6;width:var(--lblw);min-width:var(--lblw);background:var(--panel);border-right:1px solid var(--line);
                display:flex;align-items:center;padding:0 10px;font-size:var(--fs-sm);color:var(--muted)}
  #head .corner .cornertext{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  #lblToggle{flex:0 0 auto;font:inherit;font-size:var(--fs-sm);line-height:1;padding:4px 6px;margin-left:6px;
    border:1px solid var(--line);border-radius:5px;background:var(--panel);color:var(--text-2);cursor:pointer}
  @media (hover:hover){ #lblToggle:hover{color:var(--red-ink);border-color:var(--red)} }
  /* sidebar hidden: the label column collapses to a thin rail; only the corner toggle stays */
  html.nolbl{--lblw:30px}
  html.nolbl .lbl>*{display:none}
  /* the rail must be exactly --lblw: the timeline starts where the label column
     ends, while the week ticks, weekend shading, today line and milestone lines
     are all placed at LBL_W. The indents that step phase → area → action rows
     (up to 40px) outlast the hidden text and, being wider than the 30px rail,
     floor the label at 49px — pushing every bar ~19px (most of a day) right of
     the dates in the header. Specificity has to clear .row.action .lbl. */
  html.nolbl .row .lbl{padding:0}
  html.nolbl #head .corner{padding:0;justify-content:center}
  html.nolbl #head .corner .cornertext{display:none}
  html.nolbl #lblToggle{margin-left:0}
  #head .scale{position:relative;flex:1 1 auto}
  .wtick{position:absolute;top:0;bottom:0;border-left:1px solid var(--wk);font-size:var(--fs-2xs);color:var(--muted-2);padding:2px 3px;white-space:nowrap}
  .today{position:absolute;top:0;bottom:0;width:0;border-left:2px dotted rgba(225,6,0,.3);z-index:4}
  .wkend{position:absolute;top:0;bottom:0;background:var(--wkend);pointer-events:none}
  /* bars */
  .bar{position:absolute;height:20px;top:6px;border-radius:4px;cursor:grab;display:flex;align-items:center;
       padding:0 6px;color:#fff;font-size:var(--fs-2xs);white-space:nowrap;overflow:hidden;box-shadow:0 1px 2px rgba(0,0,0,.15);user-select:none}
  .bar.dragging{cursor:grabbing;opacity:.9;box-shadow:0 3px 8px rgba(0,0,0,.25)}
  .bar .rh{position:absolute;right:0;top:0;bottom:0;width:12px;cursor:ew-resize;border-radius:0 4px 4px 0}
  .bar.sel{outline:2px solid var(--red);outline-offset:1px}
  /* major-milestone action: rounder, ringed bar so it stands out from ordinary work */
  .bar.milestone{border-radius:11px;font-weight:bold;box-shadow:0 0 0 2px var(--panel),0 1px 3px rgba(0,0,0,.3)}
  .mstar{color:#eab308;flex:0 0 auto;font-size:var(--fs-xs)}
  .row.action .lbl.mlbl .lbltext{font-weight:bold}
  /* full-height vertical guide line at a milestone action's projected finish */
  .mstone-line{position:absolute;top:0;bottom:0;width:0;border-left:2px dashed;z-index:1;pointer-events:none;opacity:.55}
  .mstone-line.met{border-left-style:solid;opacity:.85}
  /* labeled flag pinned to that line, in the sticky header scale */
  .mflag{position:absolute;bottom:2px;font-size:var(--fs-3xs);font-weight:bold;white-space:nowrap;padding:1px 6px;
    border:1px solid;border-radius:9px;background:var(--panel);z-index:2;max-width:160px;overflow:hidden;
    text-overflow:ellipsis;line-height:1.35;pointer-events:none;box-shadow:0 1px 2px rgba(0,0,0,.12)}
  .gbar{position:absolute;height:8px;top:12px;border-radius:3px;background:var(--group)}
  .linkdot{position:absolute;top:12px;width:8px;height:8px;border-radius:50%;background:var(--panel);
    border:2px solid var(--red);cursor:crosshair;z-index:3;transform-origin:center center;
    transition:opacity .12s,transform .1s,background .1s}
  /* same iOS tap-eating trap as .addbtn: hover-revealed on desktop, dimmed-but-present on touch */
  @media (hover:hover){ .linkdot{opacity:0}
    .tl:hover .linkdot{opacity:1} }
  @media (hover:none){ .linkdot{opacity:.45} }
  body.linking .linkdot{opacity:1}
  svg.arrows{position:absolute;top:0;left:0;pointer-events:none;z-index:2;overflow:visible}
  /* the "about to link" preview line lives in a viewport-fixed overlay, NOT inside
     #grid / the recon body — those get rebuilt on any re-render, which would wipe a
     line drawn between the two clicks. Fixed + pointer-events:none, so it floats over
     the bars and never eats a click. */
  #linkoverlay{position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:200;overflow:visible}
  /* the bar (and its dot) the pointer is over while a link is being drawn — it pops
     so you can see which task/order the line is about to join. Scoped to both
     surfaces so it out-specifies #reconview's own .linkdot / .bar rules. */
  #grid .bar.linktarget,#reconview .bar.linktarget{box-shadow:0 0 0 2px var(--red)!important;filter:brightness(1.04)}
  #grid .linkdot.linktarget,#reconview .linkdot.linktarget{opacity:1;transform:scale(1.9);background:var(--red);border-color:var(--red-ink)}
  /* the task detail panel. It used to be a column docked beside the grid,
     squeezing the timeline (and the Budgets table) sideways every time it
     opened; it is now the same drawer every other panel is, sitting OVER the
     page — see .invoice-panel, which carries its box, and #panelhost, which
     carries the stack it opens in. What is left here is what is particular to
     this one: its form fields and its pinned header. */
  #detail .dhead{position:sticky;top:0;z-index:5;background:var(--panel);margin:-20px -20px 10px;padding:14px 20px 8px;border-bottom:1px solid var(--line)}
  #detail h3{margin:0 0 4px;font-size:var(--fs-lg);color:var(--red-ink)}
  #detail .sub{font-size:var(--fs-xs);color:var(--muted)}
  #detail label{display:block;font-size:var(--fs-xs);color:var(--text-2);margin:10px 0 3px;font-weight:bold}
  #detail input,#detail select,#detail textarea{width:100%;font:inherit;font-size:var(--fs-sm);padding:6px;border:1px solid var(--line);border-radius:6px}
  #detail textarea{min-height:80px;resize:vertical}
  #detail .dhead .close{float:right;font:inherit;font-size:var(--fs-xl);line-height:1;background:none;border:0;padding:0 2px;cursor:pointer;color:var(--muted-2)}
  /* related-action lists in the detail panel, one block per relation type */
  .rellist{border:1px solid var(--line);border-radius:6px;overflow:hidden}
  .relitem{display:flex;align-items:center;gap:6px;padding:5px 6px;font-size:var(--fs-sm);cursor:pointer;border-bottom:1px solid var(--grid)}
  .relitem:last-child{border-bottom:0}
  @media (hover:hover){ .relitem:hover{background:var(--rowhover)} }
  .relitem .idtag{flex:0 0 24px}
  .reltext{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .reltext.doneblocker{color:var(--muted-2);text-decoration:line-through}
  .reldel{font:inherit;font-size:var(--fs-lg);line-height:1;padding:0 5px;border:1px solid var(--line);border-radius:4px;
    background:var(--panel);color:var(--muted-4);cursor:pointer;flex:0 0 auto}
  @media (hover:hover){ .reldel:hover{color:var(--red-ink);border-color:var(--red)} }
  .relnone{font-size:var(--fs-sm);color:var(--muted-3);padding:2px 0}
  #detail input.reladd{margin-top:4px}
  /* review-changes modal shown before exporting */
  /* z-index 75 = the shared modal rung (dialogs, wise, order-link): #review sat
     at 50, BELOW the panel drawer (56) and its backdrop (55), so opening a review
     over an open panel rendered it behind the drawer. */
  #review{position:fixed;inset:0;background:rgba(0,0,0,.4);display:none;z-index:75;align-items:center;justify-content:center}
  #review.open{display:flex}
  #review .card{background:var(--panel);border:1px solid var(--line);border-radius:12px;width:90%;max-width:640px;max-height:80vh;display:flex;flex-direction:column;box-shadow:var(--shadow-modal)}
  #review .rhead{padding:14px 16px;border-bottom:1px solid var(--line);font-size:var(--fs-lg);font-weight:bold;color:var(--red-ink)}
  #review .rbody{padding:12px 16px;overflow:auto;font-size:var(--fs-sm)}
  #review .rfoot{padding:12px 16px;border-top:1px solid var(--line);display:flex;gap:8px;justify-content:flex-end}
  #review .rfoot button{font:inherit;font-size:var(--fs-sm);padding:6px 12px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
  #review .rfoot button.primary{background:var(--red);color:#fff;border-color:var(--red)}
  #review .chg{margin:0 0 12px;padding-bottom:8px;border-bottom:1px dashed var(--line)}
  #review .chg .name{font-weight:bold;margin-bottom:4px}
  #review .chg .tag{font-size:var(--fs-2xs);font-weight:bold;padding:1px 5px;border-radius:4px;margin-left:6px;vertical-align:middle}
  #review .chg .tag.new{background:var(--done);color:#fff}
  #review .fld{color:var(--text);margin:2px 0 2px 12px}
  #review .fld b{color:var(--ink-3)}
  #review del{color:var(--red-tx-3);text-decoration:line-through}
  #review ins{color:var(--grn-tx-3);text-decoration:none;font-weight:bold}
  #review .none{color:var(--muted)}
  /* phase multi-select popover for the board */
  /* z-index above the phone filter sheet (76): the board's Workstreams / Sort /
     View open these over that sheet now, so they must sit above it, like the
     column-filter menus do. */
  #phaseMenu,#sortMenu,#rangeMenu{position:fixed;display:none;z-index:120;background:var(--panel);border:1px solid var(--line);border-radius:8px;
    box-shadow:var(--shadow-pop);padding:6px;min-width:210px;max-height:60vh;overflow:auto}
  #phaseMenu.open,#sortMenu.open,#rangeMenu.open{display:block}
  #phaseMenu label,#sortMenu label,#rangeMenu label{display:flex;align-items:center;gap:8px;font-size:var(--fs-sm);color:var(--ink);
    padding:5px 8px;border-radius:5px;cursor:pointer;white-space:nowrap}
  @media (hover:hover){ #phaseMenu label:hover,#sortMenu label:hover,#rangeMenu label:hover{background:var(--rowhover)} }
  #phaseMenu .ddall{border-bottom:1px solid var(--grid);border-radius:0;margin-bottom:4px;padding-bottom:7px;font-weight:bold}
  #phaseBtn.filtered{background:var(--prog);color:#fff;border-color:var(--prog)}
  @media (hover:hover){ #phaseBtn.filtered:hover{background:#f2bd37;border-color:#f2bd37} }
  /* score-explainer popover: hover the badge on desktop, tap the (i) on touch */
  #scorePop{position:fixed;display:none;z-index:70;background:var(--panel);border:1px solid var(--line);border-radius:8px;
    box-shadow:var(--shadow-pop);padding:10px 12px;width:272px;font-size:var(--fs-sm);line-height:1.45}
  #scorePop.open{display:block}
  #scorePop b{font-size:var(--fs-sm)}
  #scorePop table{width:100%;border-collapse:collapse;margin:6px 0 4px}
  #scorePop td{padding:2px 0}
  #scorePop td:last-child{text-align:right;font-weight:bold;white-space:nowrap;padding-left:10px}
  #scorePop tr.off{color:var(--muted-4)}
  #scorePop tr.off td:last-child{font-weight:normal}
  #scorePop tr.tot td{border-top:1px solid var(--line);padding-top:4px;font-weight:bold}
  #scorePop .sp-note{color:var(--dim);font-size:var(--fs-2xs)}
  .score-link{color:var(--link);cursor:pointer;white-space:nowrap}
  @media (hover:hover){ .score-link:hover{text-decoration:underline} }
  .viewbtn.active{background:var(--red);color:#fff;border-color:var(--red)}
  @media (hover:hover){ .viewbtn.active:hover,.topbar button.primary:hover:not(:disabled){background:var(--red-hover);border-color:var(--red-hover)} }
  /* ===== the page shell ====================================================
     Every page in #main is a .view, and this is everything a page gets for
     being one: the shell that makes it scroll inside itself, and the button it
     lends its content. Both used to be copied per view — eleven copies of the
     shell, and the button written out five times at one size and six at
     another, which is how #merchview and #catsview came to have no :disabled
     rule and the ten views nobody copied it to came to have none of it at all.

     It is a CLASS, and that is the load-bearing part. Styling a page through
     its id (#txview button) outranks any component class, so every shared
     component had to be written to beat it. A page that genuinely differs can
     still say so through its id — that now reads as the exception it is,
     rather than as the way pages are built. */
  .view{display:none;flex:1 1 auto;overflow:auto;padding:14px 18px}
  /* The button a page lends its content sits in a LAYER, and an unlayered rule
     beats a layered one whatever either one's specificity is. That is the whole
     mechanism: a component dresses its own button with a plain class — .cinvst,
     .txfilter, .pill-l — and wins, without knowing which page it is on and
     without the page having to be told about it. What used to happen instead is
     written in the two rules this replaces: .pill2 named ten view ids to get its
     label back, and .cinvst named two to keep its shape. Neither has to now. */
  @layer pagebutton {
    .view button{font:inherit;font-size:var(--fs-sm);padding:6px 10px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
    @media (hover:hover){ .view button:hover:not(:disabled){background:var(--hover)} }
    .view button:disabled{opacity:.4;cursor:default}
  /* The dense pages — the ledger, the two settings lists, the rules, the
     statements — put buttons INSIDE table rows, where the 12px toolbar size is
     taller than the row it sits in. One opt-in class instead of the same three
     declarations under five ids. */
    .view.dense button{font-size:var(--fs-xs);padding:3px 8px;white-space:nowrap}
  }
  /* the phone's padding, once. 700px is where this app becomes a phone —
     .mobonly turns on there and thirteen other blocks agree; the four that
     said 760px were copies that drifted, and the band between the two only
     ever changed the padding on four of the eighteen pages. */
  @media (max-width:700px){ .view{padding:12px;overflow-x:hidden} }

  /* transactions view (read-mostly: rows write straight to the sheet, no staged sync) */
  #txview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:16px 0 8px}
  #txview h3:first-child{margin-top:0}
  /* ===== desktop table shell ==============================================
     One treatment for every wide table (.txtable and .ctable alike), taken
     from the phone cards: a rounded white card (.tblwrap) holding rows split
     by horizontal lines only — no cell grid — under the cards' own quiet
     uppercase column header. The wrapper owns the border, the background and
     the horizontal scroll, so the table itself draws nothing but row lines. */
  /* Every data table reads at the app's body size; the .txtable/.ctable/.sttable
     families state it themselves, and the tblwrap tables (counterparties,
     invoices, categories, costs) take it from here — html/body set no size, so
     without this their plain cells fall back to the UA 16px and tower over every
     other table. The compact .sum/.invs variants override this with their own. */
  .tblwrap{width:fit-content;max-width:100%;background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow-x:auto;font-size:var(--fs-sm)}
  .tblwrap tr:last-child td{border-bottom:0}
  /* the table that sizes itself by width, not content, sizes its card instead */
  .cattable.tblwrap{width:100%;max-width:760px}
  .txtable{border-collapse:collapse;font-size:var(--fs-sm);min-width:760px}
  .txtable th,.txtable td,.ctable th,.ctable td{border:0;border-bottom:1px solid var(--grid);padding:7px 10px;text-align:left;vertical-align:middle}
  /* One header treatment for every data table — the transactions/budgets look
     (small, uppercase, dim, 600) applied to the standalone tables too, so the
     statements, counterparties, invoices, categories and costs headers stop
     rendering as browser-default bold. These tables use <th> only in <thead>
     (row labels are <td>), so this reaches column headers alone. */
  .txtable th,.ctable th,.tblwrap th,.sttable th{font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--dim);font-weight:600;white-space:nowrap;border-bottom-color:var(--line)}
  .txtable td.num,.txtable th.num{text-align:right;white-space:nowrap}
  .txtable td.num,.ctable td.crem,.ctable td.cmon{font-variant-numeric:tabular-nums}
  /* ---- nested rows, for any .txtable that holds a parent→child hierarchy ----
     One treatment shared by the Owners table (a holding company over the
     parties that hold it) and the Categories table (a parent account over the
     subaccounts it rolls up), so the two nested tables read as one pattern
     rather than each inventing its own. A parent that has children heads a
     BLOCK: its own banded row with a left rail, marking it as the heading it is
     — a bare indent read as "sub-item of the row above" rather than "the row
     above rolls these up". A child sits under it, indented by its depth (--d)
     with an elbow that says it hangs off the block rather than merely following
     it. Two levels in practice; --d still steps a deeper chain in rather than
     flattening it. */
  .txtable tr.nest-head td{background:var(--band);border-bottom-color:var(--line)}
  /* the band carries its own background, which by specificity would swallow the
     shared row-hover — so it hovers in its own darker tint, keeping the feedback
     every other clickable row gives */
  @media (hover:hover){ .txtable tr.nest-head:hover td{background:var(--band-h)} }
  /* the rail is deliberately NOT a status colour: amber/red down the first cell
     mean "this row needs doing"; a band is a heading, not a flag */
  .txtable tr.nest-head td:first-child{box-shadow:inset 3px 0 0 var(--line-2)}
  .txtable td.nest-sub{position:relative;padding-left:calc(14px + var(--d,1) * 14px)}
  .txtable td.nest-sub .nest-line{position:absolute;left:calc(var(--d,1) * 14px);top:-8px;bottom:0;
    width:7px;height:17px;margin:auto 0;border-left:1px solid var(--line-2);border-bottom:1px solid var(--line-2)}
  /* ---- the same nesting, in the phone-card lists ----------------------
     Owners, Categories and a split payment each roll a parent over its
     children on a phone too. They used to nest three different ways — an
     inset rail, a ↳ glyph, a bare indent — so the three lists read as three
     patterns. Now they share this, the card echo of .nest-head / .nest-sub
     / .nest-line above: the parent heads a banded card with the same grey
     left rail (.nest-head-card), and each child is a card stepped in under
     the same └ elbow the wide tables draw (.nest-kid + .nest-line).
     The elbow is ~17px tall and vertically centred, so it sits inside the
     child's own box — .txrow's overflow:hidden never clips it — and the
     card only has to be a positioning context for it to hang from.
     Scoped to .pcard/.txrow so the base .pcard{background} (declared later
     in the file, same specificity) cannot win the band back. */
  .pcard.nest-head-card,.txrow.nest-head-card{position:relative;background:var(--band)}
  @media (hover:hover){ .pcard.nest-head-card:hover,.txrow.nest-head-card:hover{background:var(--band-h)} }
  .pcard.nest-head-card:before,.txrow.nest-head-card:before{content:"";position:absolute;
    left:0;top:0;bottom:0;width:3px;background:var(--line-2)}
  .pcard.nest-kid,.txrow.nest-kid{position:relative;padding-left:calc(14px + var(--d,1) * 14px)}
  .pcard.nest-kid .nest-line,.txrow.nest-kid .nest-line{position:absolute;left:calc(var(--d,1) * 14px);
    top:-8px;bottom:0;width:7px;height:17px;margin:auto 0;border-left:1px solid var(--line-2);
    border-bottom:1px solid var(--line-2);pointer-events:none}
  /* ---- Rules ----------------------------------------------------------
     Column widths and the condition editor's grid. Everything else is the
     app's own: .txtable in a .tblwrap, .txfilter chips, .invoice-empty for a
     paragraph above a list, .cbtype for the account type beside a category
     name — the same one the ledger's category button uses. */
  .rulestbl th.rsel,.rulestbl td.rsel{width:34px;padding-right:0}
  /* The status column, which is the Products page's status column: same select,
     same two words, so the same width — which means letting the control size
     itself rather than pinning the column to 56px and squeezing "Inactive"
     into something narrower than the identical control two pages over. */
  .rulestbl th.ron,.rulestbl td.ron{width:1%;white-space:nowrap}
  .rulestbl tr.rsel-on td{background:var(--rowhover)}
  .txtable th.rbtncol,.txtable td.rbtncol{width:150px;text-align:right}
  .rdim{color:var(--dim)}
  .rerr{color:var(--red-tx-2)}
  .rout{margin-top:10px;font-size:var(--fs-sm);line-height:1.6}
  .rlines{margin-top:6px;color:var(--dim);font-size:var(--fs-sm)}
  /* What the ledger would support, above the rules it is offering to become.
     Quiet, not amber: this is an offer, and the page's amber banner (.txreview)
     is what a page uses to say something is WRONG. It empties itself as each
     suggestion is taken or ruled out, so on most days there is nothing here. */
  .rsugg{background:var(--soft-2);border:1px solid var(--line);border-radius:10px;
    padding:10px 14px;margin-bottom:16px}
  .rsugg h3{margin:0 0 2px;font-size:var(--fs-lg)}
  /* What a rule filled in, on the transaction it filled. See ruleFills. */
  .rfill{font-size:var(--fs-sm);line-height:1.55}
  .rfill .rdim{font-size:var(--fs-sm)}
  /* one condition per line: field, operator, value, and the cross that drops it */
  .rconds,.racts{display:flex;flex-direction:column;gap:6px;width:100%}
  .rcond{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
  .rcond .mrdesc{flex:0 1 auto}
  .rcond .full{flex:1 1 120px}
  .rcond .rnum{width:96px}
  .rcond .phint{flex:1 1 120px;margin:0}
  .rapast{display:flex;align-items:center;gap:7px;font-size:var(--fs-sm);color:var(--ink-2);cursor:pointer;margin-bottom:8px}
  .rapast input{margin:0}
  /* a txchip is a cell's own content — a receipt-name alias, or a document-cell
     state ("Document missing" / "no document needed" / "link pending"). It sits
     at the same 12px level as the document pill or the cells beside it, not a
     smaller one. (Only the colored payment-status chips, .cinvst, and the
     sub-hints stay 10px.) */
  .txchip{display:inline-block;font-size:var(--fs-sm);background:var(--chip);border:1px solid var(--line);border-radius:6px;padding:2px 7px;margin:1px 3px 1px 0}
  /* the document on a payment — and on a cost bundle: the same .ipill as
     everywhere else, led by a dot for how far it is paid — green settled,
     orange still owed.
     still owed is the amber every warning on these pages is written in (var(--amb-tx-2)),
     not a red: money outstanding is something to get to, not something wrong. */
  .txipill .idot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:5px;vertical-align:middle;background:var(--amb-tx-2)}
  .txipill .idot.paid{background:var(--grn-tx)}
  /* archived: the dot goes grey, because the amber one means "still owed" and an
     archived document is owed by nobody */
  .txipill .idot.archived{background:var(--muted-2)}
  #txview details{margin-top:6px}
  #txview summary{cursor:pointer;font-size:var(--fs-sm);color:var(--text)}
  /* "back to top" on the transactions list: absolute inside #main (which is
     position:relative), so it sits over the bottom-right of the list without
     taking width from it. Shown only once the list is scrolled far enough that
     the filter chips are out of reach. */
  .totop{display:none;position:absolute;z-index:6;right:18px;bottom:calc(18px + env(safe-area-inset-bottom));
    width:38px;height:38px;align-items:center;justify-content:center;
    font:inherit;font-size:var(--fs-xl);line-height:1;color:var(--text);
    background:var(--float-face);border:1px solid var(--line);border-radius:50%;cursor:pointer;
    box-shadow:0 2px 8px var(--shadow)}
  .totop.show{display:flex}
  @media (hover:hover){ .totop:hover{color:var(--red-ink);border-color:var(--red)} }
  /* a panel covers this corner — the button would sit on its backdrop */
  html:has(.invoice-panel) .totop{display:none}
  /* on a phone the corner belongs to the upload button below, so back-to-top
     steps above it rather than the two stacking on the same spot */
  @media (max-width:700px){ .totop{right:23px;bottom:calc(84px + env(safe-area-inset-bottom))} }
  /* ---- the phone's add: a speed-dial --------------------------------------
     One "+" at the thumb that fans out the adds this page offers (fabMenuItems):
     the page's own "new …" on top, then the two you capture from anywhere — a
     Task, and a document for an invoice or receipt. The document press still
     opens the phone's own picker (Photo Library · Take Photo · Choose Files),
     the popup coming up behind it already uploading, so a receipt at the
     counter is two presses, not the old five through the menu.

     Closed, the "+" sits at z-index 35 — over the page (.totop at 6, the
     selection bar at 40) and under every overlay (panels 55, dialogs 75). Open,
     the whole dial rises to 60 so its menu clears the panels, over a scrim at
     59; an add it launches opens its dialog at 75, above both, once the menu
     has closed. A desktop keeps its labelled toolbar buttons — the dial is a
     phone affordance and is hidden outright above the breakpoint. */
  #fabDial{display:none;position:fixed;right:14px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:35}
  #fabScrim{display:none;position:fixed;inset:0;z-index:59;background:rgba(0,0,0,.45)}
  #fabScrim.open{display:block}
  #fabDial.open{z-index:60}
  #fabMain{display:flex;align-items:center;justify-content:center;width:56px;height:56px;margin-left:auto;
    border-radius:50%;border:1px solid var(--red);background:var(--red);color:#fff;cursor:pointer;
    box-shadow:0 4px 14px rgba(0,0,0,.28);transition:transform .18s ease}
  #fabMain svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}
  #fabDial.open #fabMain{transform:rotate(45deg)}   /* the + turns into an × */
  #fabMenu{display:none;flex-direction:column;align-items:flex-end;gap:12px;margin-bottom:14px}
  #fabDial.open #fabMenu{display:flex}
  .fabitem{display:inline-flex;align-items:center;gap:10px;height:44px;padding:0 16px 0 8px;font:inherit;
    font-size:var(--fs-lg);font-weight:600;color:var(--ink-2);background:var(--panel);border:1px solid var(--line);
    border-radius:22px;cursor:pointer;box-shadow:0 3px 12px rgba(0,0,0,.18);white-space:nowrap}
  .fabitem .fabico{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;flex:0 0 auto;
    border-radius:50%;background:var(--red);color:#fff}
  .fabitem .fabico svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  @media (max-width:700px){ #fabDial.has{display:block} }
  @media (min-width:701px){ #fabDial,#fabScrim{display:none!important} }
  /* ---- company details: the quick-copy card and its two triggers -----------
     A smaller second circle beside the phone's "+" (panel-filled, so it reads as
     reference rather than a second add), and on desktop a square in the sidebar
     foot beside the appearance switch. Both open one body-level popover — its
     rows are copy buttons the app's single [data-copy] handler serves. */
  #coFab{position:fixed;right:80px;bottom:calc(27px + env(safe-area-inset-bottom));z-index:35;
    width:38px;height:38px;align-items:center;justify-content:center;border-radius:50%;
    border:1px solid var(--line);background:var(--float-face);color:var(--ink-2);cursor:pointer;
    box-shadow:0 4px 14px rgba(0,0,0,.22)}
  #coFab svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  @media (min-width:701px){ #coFab{display:none!important} }
  #coScrim{display:none;position:fixed;inset:0;z-index:90;background:transparent}
  #coScrim.open{display:block}
  #coDetails{display:none;position:fixed;z-index:91;width:min(320px,calc(100vw - 28px));
    background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:6px;
    flex-direction:column;box-shadow:var(--shadow-pop)}
  #coDetails.open{display:flex}
  #coDetails .co-h{font-size:var(--fs-2xs);font-weight:bold;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);padding:8px 10px 6px}
  .co-row{display:flex;flex-direction:column;gap:2px;width:100%;font:inherit;text-align:left;
    padding:7px 10px;border:0;border-radius:8px;background:transparent;cursor:pointer}
  .co-row .co-lbl{font-size:var(--fs-2xs);color:var(--muted)}
  .co-row .co-val{display:flex;align-items:flex-start;gap:8px;font-size:var(--fs-md);font-weight:600;color:var(--ink-2);overflow-wrap:anywhere;word-break:break-word}
  .co-row .co-val .txref-c{flex:0 0 auto;width:14px;height:14px;margin-top:2px;fill:none;stroke:var(--dim);stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  @media (hover:hover){ .co-row:hover{background:var(--soft-2)} .co-row:hover .txref-c{stroke:var(--red-ink)} }
  /* both are opened from inside a detail panel ("+ Connect document" and the
     category menu's "Add new"), so they have to outrank the panels — 55/56 for
     the invoice panels, 60 for the transaction panel and the roadmap sheet.
     At 60 they tied with the transaction panel and lost on DOM order, opening
     behind it with only their keyboard showing. */
  /* .dlg is the dialog: a backdrop with a .box in it, shown by .open, opened
     and dismissed by openDialog/closeDialog in the page. The Wise dialogs
     (#wrecipModal, #wtdistModal) are .dlg too and restyle their own box below. */
  .dlg{display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:75}
  .dlg.open{display:flex;align-items:center;justify-content:center}
  .dlg .box{background:var(--panel);border:1px solid var(--line);border-radius:12px;max-width:560px;width:calc(100% - 32px);max-height:70vh;display:flex;flex-direction:column;padding:14px;box-shadow:var(--shadow-modal)}
  .dlg .box>b,#empnew .empnote b,#docnew .box b{color:var(--red-ink)}   /* the title; the team dialog's note and the upload widget bold the same way */
  #docnew .box{max-width:520px;max-height:85vh}
  /* ---- split dialog. Four columns per part — how much, what it is, what
     evidences it, and anything worth saying about it — under one header row, so
     a part reads across rather than being four labelled fields deep. Every band
     in the dialog is laid on the SAME grid, which is what puts the running total
     directly under the amounts it is counting and the add button directly under
     the crosses that undo it. The rows scroll and the two totals do not: the
     amount being accounted for and the amount still unaccounted for are what
     must stay on screen while you type into a long list. */
  #txsplit .box{max-width:720px;max-height:86vh;--tspcols:98px 1.1fr 1.25fr 1fr 24px}
  #txsplit .tsp-total{font-size:var(--fs-sm);color:var(--text-3);margin:2px 0 10px}
  #txsplit .tsp-total b{color:var(--ink-3);font-size:var(--fs-lg)}
  #txsplit .tsp-hd,#txsplit .tsp-row,#txsplit .tsp-addrow,#txsplit .tsp-rem{
    display:grid;grid-template-columns:var(--tspcols);gap:7px;align-items:center}
  #txsplit .tsp-hd{font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--muted-2);margin-bottom:5px}
  #txsplit #tspRows{overflow:auto;min-height:0;display:flex;flex-direction:column;gap:6px}
  #txsplit .tsp-row input,#txsplit .tsp-row select{font:inherit;font-size:var(--fs-sm);padding:5px 6px;border:1px solid var(--line);
    border-radius:6px;background:var(--panel);color:var(--text);width:100%;min-width:0}
  #txsplit .tsp-row .tsp-amt{text-align:right}
  #txsplit .tsp-row input:focus,#txsplit .tsp-row select:focus{border-color:var(--red);outline:none}
  /* The first two parts carry no remove button — a split of one is not a split —
     so rather than leaving a hole at the end of those rows, their last field
     takes the width the cross would have had. */
  #txsplit .tsp-row.full .tsp-note{grid-column:4 / span 2}
  #txsplit .tsp-x{font:inherit;font-size:var(--fs-lg);line-height:1;padding:0 5px;border:1px solid var(--line);border-radius:4px;
    background:var(--panel);color:var(--muted-2);cursor:pointer}
  @media (hover:hover){ #txsplit .tsp-x:hover{color:var(--red-ink);border-color:var(--red)} }
  /* the add button sits in the crosses' own column, directly under them: the
     two controls that change how many parts there are read as one column */
  #txsplit .tsp-addrow{margin-top:8px}
  #txsplit #tspAdd{grid-column:5;font:inherit;font-size:var(--fs-lg);line-height:1;padding:2px 5px;border:1px solid var(--line);
    border-radius:4px;background:var(--panel);color:var(--text-3);cursor:pointer}
  @media (hover:hover){ #txsplit #tspAdd:hover{color:var(--ink-blue);border-color:var(--ink-blue)} }
  /* What is left to account for, under the column it is the sum of. It decides
     whether Split is available at all, so it is stated in words beside the
     figure as well as coloured. */
  #txsplit .tsp-rem{margin-top:12px;padding-top:9px;border-top:1px solid var(--line);font-size:var(--fs-sm);color:var(--text-3)}
  #txsplit .tsp-rem b{font-size:var(--fs-lg);color:var(--amb-tx-2);text-align:right}
  #txsplit .tsp-rem span{grid-column:2 / span 4}
  #txsplit .tsp-rem.ok b{color:var(--grn-tx)}
  #txsplit #tspSave[disabled]{opacity:.45;cursor:not-allowed}
  /* the Document cell holds one of three things — a select to pick one, or (when
     editing an existing split) the document already on the part as its own pill
     with a ✕ to unlink it, or the "no document needed" note. All three sit in the
     one grid column, so the cell wraps them and its select fills the width. */
  #txsplit .tsp-doc-cell{min-width:0;display:flex;align-items:center;gap:5px;overflow:hidden}
  #txsplit .tsp-doc-cell.col{flex-direction:column;align-items:stretch;gap:3px}
  #txsplit .tsp-doc-cell select{flex:1;min-width:0}
  #txsplit .tsp-doc-cell .ipill,#txsplit .tsp-doc-cell .txipill{min-width:0;max-width:100%}
  #txsplit .tsp-docnote{font-size:var(--fs-sm);color:var(--dim)}
  /* why a document went off a part: the amount it evidenced no longer matches */
  #txsplit .tsp-dochint{font-size:var(--fs-2xs);color:var(--amb-tx-2)}
  #txsplit .tsp-docx{flex:none;font:inherit;font-size:var(--fs-md);line-height:1;padding:0 5px;border:1px solid var(--line);
    border-radius:4px;background:var(--panel);color:var(--muted-2);cursor:pointer}
  @media (hover:hover){ #txsplit .tsp-docx:hover{color:var(--red-ink);border-color:var(--red)} }
  #docnew .box>div:last-child button{font:inherit;font-size:var(--fs-sm);padding:6px 12px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
  #docnewHost{overflow:auto;min-height:0}

  /* ---- document upload (the widget inside #docnew) ------------------------
     One drop zone, three ways in: browse files, the photo album, the camera.
     Everything lands in "0. To be sorted"; the queue below shows each file's
     progress and its Drive link once it's up. */
  .upzone{border:2px dashed var(--dashline);border-radius:12px;background:var(--panel);padding:26px 16px;text-align:center;cursor:pointer;transition:border-color .12s,background .12s}
  .upzone.drag{border-color:var(--red);background:var(--red-wash)}
  .upzone-icon{font-size:var(--fs-5xl);line-height:1;margin-bottom:8px}
  .upzone-main{font-size:var(--fs-lg);font-weight:bold;color:var(--ink-3)}
  .upzone-sub{font-size:var(--fs-sm);color:var(--text-3);margin-top:4px}
  .upbtns{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:14px}
  .upbtn{font:inherit;font-size:var(--fs-sm);padding:8px 14px;border:1px solid var(--line);background:var(--panel);border-radius:8px;cursor:pointer;color:var(--ink-3)}
  @media (hover:hover){ .upbtn:hover{background:var(--soft-2)} }
  /* the widget's camera button is for a device with one: capture is ignored on a
     desktop, where "Take a photo" would open a plain file dialog. Coarse pointer
     only. */
  .upcam{display:none}
  @media (pointer:coarse){ .upcam{display:inline-block} }
  /* the camera input is visually hidden but LEFT IN THE LAYOUT, not display:none:
     iOS honours `capture` (straight to the camera) only on a rendered input, and
     drops to the generic file sheet on a display:none one. */
  .upcam-in{position:absolute;width:1px;height:1px;padding:0;margin:0;border:0;overflow:hidden;clip:rect(0 0 0 0);opacity:0;pointer-events:none}
  /* a thumb needs more than 12.5px of button */
  @media (pointer:coarse){ .upbtn{font-size:var(--fs-lg);padding:10px 16px} }
  .upnote{font-size:var(--fs-sm);color:var(--text);background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:8px 10px;margin-top:10px;line-height:1.5}
  .upnote b{color:var(--ink-3)}
  .upqueue{margin-top:12px;display:flex;flex-direction:column;gap:6px}
  .uprow{display:flex;align-items:center;gap:10px;background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:var(--fs-sm)}
  .uprow .upname{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink-3)}
  .uprow .upsize{flex:0 0 auto;color:var(--muted-2);font-size:var(--fs-xs)}
  .uprow .upstate{flex:0 0 auto;font-size:var(--fs-sm);color:var(--text-3);white-space:nowrap}
  .uprow .upstate a{color:var(--link);text-decoration:none}
  .uprow.err{border-color:var(--red-bd);background:var(--red-wash)}
  .uprow.err .upstate{color:var(--red-tx-2)}
  .uprow.ok .upstate{color:var(--done-ink)}
  .uprow.ok .upbar i{background:var(--done)}
  .uprow .upretry{font:inherit;font-size:var(--fs-xs);padding:3px 8px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
  .upbar{flex:0 0 90px;height:5px;border-radius:3px;background:var(--track);overflow:hidden}
  .upbar i{display:block;height:100%;width:0;background:var(--red);border-radius:3px;transition:width .15s}
  /* the phone document scanner (see initCamScan): a full-screen live camera. It
     is a viewfinder, not a page, so it is always dark and does not take the
     theme — the colours are literal here on purpose, not tokens. It rides on
     .dlg (id-styled, so the class stays exactly "dlg") for the shared
     open/close/Escape, over the top of everything. */
  #camscan{z-index:200}
  .camscan-body{position:absolute;inset:0;display:flex;flex-direction:column;background:#000;color:#fff}
  .camscan-video{flex:1 1 auto;min-height:0;width:100%;object-fit:cover;background:#000}
  .camscan-review{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#000;z-index:1}
  .camscan-flash{position:absolute;inset:0;background:#fff;opacity:0;pointer-events:none;transition:opacity .2s;z-index:1}
  #camscan.flash .camscan-flash{opacity:.75;transition:none}
  .camscan-top{position:absolute;top:0;left:0;right:0;display:flex;justify-content:flex-start;
    padding:calc(env(safe-area-inset-top) + 10px) 14px 10px;z-index:3}
  .camscan-x{width:40px;height:40px;border-radius:50%;border:0;background:rgba(0,0,0,.5);color:#fff;font-size:22px;line-height:1;cursor:pointer}
  /* the tray of shots taken, uploading in the background */
  .camscan-tray{position:absolute;left:0;right:0;bottom:calc(env(safe-area-inset-bottom) + 100px);display:flex;gap:8px;padding:0 16px;overflow-x:auto;z-index:3}
  .camscan-thumb{position:relative;flex:0 0 auto;width:52px;height:52px;border-radius:8px;overflow:hidden;background:#222;border:2px solid rgba(255,255,255,.5)}
  .camscan-thumb img{width:100%;height:100%;object-fit:cover}
  .camscan-thumb-doc{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:var(--fs-xs);font-weight:bold;color:#fff}
  .camscan-thumb-mark{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:15px;font-weight:bold}
  .camscan-thumb.is-up .camscan-thumb-mark{background:rgba(0,0,0,.45)}
  .camscan-thumb.is-up .camscan-thumb-mark::after{content:"";width:16px;height:16px;border-radius:50%;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;animation:camspin .7s linear infinite}
  .camscan-thumb.is-ok{border-color:#3ad07f}
  .camscan-thumb.is-ok .camscan-thumb-mark::after{content:"\2713";color:#3ad07f}
  .camscan-thumb.is-err{border-color:#ff5a5a}
  .camscan-thumb.is-err .camscan-thumb-mark{background:rgba(0,0,0,.5)}
  .camscan-thumb.is-err .camscan-thumb-mark::after{content:"\21bb";color:#ff5a5a;font-size:19px}
  @keyframes camspin{to{transform:rotate(360deg)}}
  .camscan-bar{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:space-around;
    padding:18px 24px calc(env(safe-area-inset-bottom) + 22px);background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.65));z-index:3}
  .camscan-tab{background:none;border:0;color:#fff;font:inherit;font-size:var(--fs-md);letter-spacing:.05em;text-transform:uppercase;opacity:.9;padding:10px;cursor:pointer}
  .camscan-shot{width:74px;height:74px;border-radius:50%;border:5px solid rgba(255,255,255,.9);background:#fff;cursor:pointer}
  .camscan-shot:active{transform:scale(.94)}
  /* review: the frozen shot, kept automatically as the ring fills (3s), or now */
  .camscan-review-bar{position:absolute;left:0;right:0;bottom:0;display:none;align-items:center;justify-content:center;gap:56px;
    padding:18px 24px calc(env(safe-area-inset-bottom) + 22px);z-index:3}
  #camscan.reviewing .camscan-review-bar{display:flex}
  #camscan.reviewing .camscan-bar,#camscan.reviewing .camscan-tray{display:none}
  .camscan-del{width:56px;height:56px;border-radius:50%;border:0;background:rgba(0,0,0,.55);color:#fff;font-size:26px;line-height:1;cursor:pointer}
  .camscan-accept{position:relative;width:74px;height:74px;border:0;background:none;padding:0;cursor:pointer}
  .camscan-ring{position:absolute;inset:0;width:100%;height:100%;transform:rotate(-90deg)}
  .camscan-ring-bg{fill:#fff;stroke:rgba(0,0,0,.12);stroke-width:3}
  .camscan-ring-fg{fill:none;stroke:#3ad07f;stroke-width:4;stroke-linecap:round;stroke-dasharray:132;stroke-dashoffset:132}
  #camscan.reviewing .camscan-ring-fg{animation:camring 3s linear forwards}
  @keyframes camring{to{stroke-dashoffset:0}}
  .camscan-tick{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#111;font-size:30px;font-weight:bold}
  #txpick input,#catnew input,#cpnew input,#prodnew input{font:inherit;font-size:var(--fs-md);margin:10px 0;padding:7px 9px;border:1px solid var(--line);border-radius:6px}
  /* new team member: two columns of labelled fields, scrolling inside the box */
  #empnew .box,#cashnew .box,#docadd .box{max-width:620px}
  #empnew .empgrid,#cashnew .empgrid,#docadd .empgrid{display:grid;grid-template-columns:1fr 1fr;gap:8px 12px;margin-top:12px;overflow:auto}
  #empnew .empgrid label,#cashnew .empgrid label,#docadd .empgrid label{display:flex;flex-direction:column;gap:3px;font-size:var(--fs-xs);font-weight:bold;
    letter-spacing:.04em;text-transform:uppercase;color:var(--dim)}
  #empnew .empgrid label.wide,#cashnew .empgrid label.wide,#docadd .empgrid label.wide{grid-column:1 / -1}
  /* one explicit height on both, so a <select> (which sizes itself) lines up
     with an <input> (which sizes to its text) rather than sitting a pixel or two
     shorter beside it */
  #empnew .empgrid input,#empnew .empgrid select,#cashnew .empgrid input,#cashnew .empgrid select,#docadd .empgrid input,#docadd .empgrid select{font:inherit;font-size:var(--fs-md);font-weight:normal;text-transform:none;
    letter-spacing:0;color:var(--ink);height:34px;line-height:1.3;padding:0 9px;border:1px solid var(--line);border-radius:6px;background:var(--panel);width:100%;box-sizing:border-box}
  #empnew .empgrid input:focus,#empnew .empgrid select:focus,#cashnew .empgrid input:focus,#cashnew .empgrid select:focus,#docadd .empgrid input:focus,#docadd .empgrid select:focus{border-color:var(--red);outline:none}
  #empnew .empnote,#cashnew .empnote,#docadd .empnote{font-size:var(--fs-sm);color:var(--text);background:var(--soft);border:1px solid var(--line);
    border-radius:8px;padding:8px 11px;margin-top:12px}
  #empnew .empnote b,#cashnew .empnote b,#docadd .empnote b{color:var(--ink-3)}
  #empnew .emperr,#cashnew .emperr,#docadd .emperr{font-size:var(--fs-sm);color:var(--red-tx-2);margin-top:8px}
  #empnew .emperr:empty,#cashnew .emperr:empty,#docadd .emperr:empty{display:none}
  #empnew button,#cashnew button,#docadd button{font:inherit;font-size:var(--fs-sm);padding:6px 12px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
  #empnew #empSave,#docadd #daSave{background:var(--red);color:#fff;border-color:var(--red)}
  #empnew button:disabled,#docadd button:disabled{opacity:.5;cursor:default}
  @media (max-width:560px){ #empnew .empgrid,#docadd .empgrid{grid-template-columns:1fr} }
  /* 16px stops iOS zooming the page when a field takes focus */
  @media (pointer:coarse){ #empnew .empgrid input,#empnew .empgrid select,#docadd .empgrid input,#docadd .empgrid select{font-size:var(--fs-xl)} }
  /* the accountant-packages window (#txexport): a Drive pill for the root, then
     the months still to export as action pills, then the ones already built as
     Drive pills with a Re-export beside each. Roomier than a table: this is a
     short list someone reads once a month, not a dense grid. */
  #txexport .txexp{overflow:auto;margin-top:16px;font-size:var(--fs-sm)}
  #txexport .txexp-root{margin-bottom:2px}
  #txexport .txexp-sec{font-size:var(--fs-3xs);letter-spacing:.12em;text-transform:uppercase;color:var(--muted-2);margin:22px 0 11px}
  #txexport .txexp-empty{color:var(--muted);padding:1px 0 3px}
  #txexport .txexp-pending{display:flex;flex-wrap:wrap;gap:10px}
  #txexport .txexp-do{font:inherit;font-size:var(--fs-sm);padding:8px 16px;border:1px solid var(--red);background:var(--red);color:#fff;border-radius:999px;cursor:pointer}
  @media (hover:hover){ #txexport .txexp-do:hover{filter:brightness(1.08)} }
  #txexport .txexp-list{display:flex;flex-direction:column}
  #txexport .txexp-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 2px;border-bottom:1px solid var(--line)}
  #txexport .txexp-row:last-child{border-bottom:0}
  #txexport .txexp-re{font:inherit;font-size:var(--fs-xs);padding:5px 13px;border:1px solid var(--line);background:var(--panel);color:var(--ink-3);border-radius:999px;cursor:pointer}
  @media (hover:hover){ #txexport .txexp-re:hover{border-color:var(--red);color:var(--red-ink)} }
  /* the month pills read a little larger here than in a dense transactions row */
  #txexport .txexp .mpill{font-size:var(--fs-sm)}
  #txexport .txexp .mpill.pill2>.pill-d svg{width:13px;height:13px}
  #txexport button:disabled{opacity:.5;cursor:default}
  #txpick .list{overflow:auto;display:flex;flex-direction:column;gap:4px}
  #txpick .list button{text-align:left;font-size:var(--fs-sm);padding:7px 9px;white-space:normal}
  /* every picker lists the merchant's own and nothing else, so the head line
     names whose list this is rather than offering a way out of it. Sticky: it
     says what you're looking at, however far you scroll. */
  #txpick .pickonly{position:sticky;top:0;background:var(--panel);padding-bottom:6px;z-index:1;font-size:var(--fs-xs);color:var(--dim)}
  #txpick .pickonly span{color:var(--text);font-weight:600}
  #txpick .picknone{font-size:var(--fs-sm);color:var(--text);line-height:1.45}
  /* the settled ones, folded away behind their own count. A rule rather than a
     button's box: it is the line between the two groups as much as the switch
     that opens the second. */
  #txpick .list button.pickmore{border:none;border-top:1px solid var(--line);border-radius:0;
    padding:9px 2px 5px;margin-top:4px;font-size:var(--fs-xs);color:var(--dim)}
  /* Multi-select. A pick gathers rather than commits, so every row carries a box
     and keeps its place in the list — what you've collected stays readable while
     you go on searching for the next one, and the footer button is the commit. */
  #txpick .list.multi button[data-pick]{display:flex;align-items:flex-start;gap:8px}
  #txpick .list.multi button[data-pick]::before{content:"";flex:0 0 auto;width:13px;height:13px;margin-top:1px;
    border:1px solid var(--line-2);border-radius:3px;background:var(--panel)}
  #txpick .list.multi button[data-pick].picked{border-color:var(--red);color:var(--red-ink)}
  #txpick .list.multi button[data-pick].picked::before{border-color:var(--red);background:var(--red);
    box-shadow:inset 0 0 0 2px var(--panel)}
  #txpick .pickfoot{margin-top:10px;display:flex;align-items:center;justify-content:flex-end;gap:8px}
  #txpick .pickfoot button.pickdone{background:var(--red);color:#fff;border-color:var(--red)}
  #txpick .pickfoot button.pickdone:disabled{opacity:.45;cursor:default}
  /* the "upload a new one" affordance sits at the far left of the footer; its
     status span holds the auto-margin so it and the button group left while
     Cancel/Connect stay right, even while the button is hidden for the pickers
     that don't offer upload */
  #txpick .pickfoot button.pickup{margin-right:0}
  #txpick .pickfoot button.pickup:disabled{opacity:.45;cursor:default}
  #txpick .pickfoot .pickupstate{margin-right:auto;font-size:var(--fs-xs);color:var(--dim)}
  #txpick .pickfoot .pickupstate.err{color:var(--red-ink)}
  #txpick .box button,#catnew .box button,#cpnew .box button,#prodnew .box button,#taskdel .box button,#paywise .box button{font:inherit;font-size:var(--fs-sm);padding:6px 10px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
  /* the delete warning: one line per thing that goes with the task, so what is
     about to happen to the money and the graph is read, not remembered */
  #taskdel .box{max-width:440px}
  #taskdel .tdname{font-size:var(--fs-sm);color:var(--ink-3);margin:8px 0 2px}
  #taskdel .tdrow{display:flex;gap:8px;font-size:var(--fs-sm);line-height:1.5;color:var(--text);padding:6px 0;border-top:1px solid var(--line)}
  #taskdel .tdk{flex:0 0 76px;color:var(--muted)}
  #taskdel .tdv b{color:inherit}
  #taskdel .tdnote{font-size:var(--fs-xs);color:var(--muted-3);line-height:1.45;margin-top:10px}
  #taskdel .box button.go{color:var(--red-tx-3);border-color:var(--red-tx-3)}
  #catnew .box,#cpnew .box,#prodnew .box{max-width:400px}
  #catnew label,#cpnew label,#prodnew label{font-size:var(--fs-sm);color:var(--text);margin-top:8px}
  #catnew input,#cpnew input,#prodnew input{margin:4px 0}
  #catnew select,#cpnew select,#prodnew select{font:inherit;font-size:var(--fs-md);margin:4px 0;padding:7px 9px;border:1px solid var(--line);border-radius:6px;background:var(--panel)}
  /* Aliases: the till name is one of the names this product answers to, so it
     reads as the first line of the list rather than a note above it — inside
     the same field, but not editable, because it follows the name and section.
     The box is the field: it takes the border and the field colours (Field /
     FieldText, so it matches the plain inputs in either theme), and the
     textarea sits inside it with neither. */
  /* what this creates depends on the type picked — the note keeps that visible */
  #cpnew .cpnnote{font-size:var(--fs-sm);color:var(--text);background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:8px 11px;margin-top:10px}
  /* Add-a-user dialog: the fields borrow the counterparty dialog's look, and the
     On record / New person toggle borrows the segmented .ordstep idiom. */
  #usernew label{display:block;font-size:var(--fs-sm);color:var(--text);margin-top:10px}
  #usernew input,#usernew select{width:100%;font:inherit;font-size:var(--fs-md);margin:4px 0;
    padding:7px 9px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--ink-2)}
  #usernew .unseg{margin-bottom:6px}
  #usernew .unseg>.txfilter.on{background:var(--red);border-color:var(--red);color:#fff}
  #usernew .cpnnote{font-size:var(--fs-sm);color:var(--text);background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:8px 11px;margin-top:10px}
  #usernew .cpnnote:empty{display:none}
  @media (pointer:coarse){ #usernew input,#usernew select{font-size:var(--fs-xl)} }
  /* Touch: iOS Safari zooms the page whenever you focus a field under 16px, and
     the zoom drags the fixed overlay off the right of the screen. Sizing the
     fields at 16px avoids it without locking scale in the viewport meta (which
     would also block deliberate pinch-zoom). */
  @media (pointer:coarse){ #catnew input,#catnew select,#cpnew input,#cpnew select,#prodnew input,#prodnew select,#txpick input{font-size:var(--fs-xl)} }
  /* Phones: a vertically centred dialog ends up behind the on-screen keyboard,
     which doesn't shrink the viewport on iOS. Anchored near the top, all three
     fields and the buttons stay above it. The height clause catches a phone in
     landscape, too wide for the width clause but the shape that needs this most
     — there the box scrolls rather than centring itself under the keyboard. */
  @media (max-width:640px),(max-height:560px){
    #catnew.open,#cpnew.open,#prodnew.open{align-items:flex-start;padding:max(14px,env(safe-area-inset-top)) 0 14px}
    #catnew .box,#cpnew .box,#prodnew .box{width:calc(100% - 24px);max-height:calc(100dvh - 28px);overflow:auto}
  }
  #txflash{position:fixed;bottom:18px;left:50%;transform:translateX(-50%);background:var(--grn-tx);color:#fff;font-size:var(--fs-sm);padding:7px 12px;border-radius:8px;opacity:0;pointer-events:none;transition:opacity .2s;z-index:70}
  #txflash.show{opacity:1}
  #txflash.bad{background:var(--red-tx-2)}
  /* HOLD flash — carries a link and a close ×, so it must take clicks and not
     time out. Laid out inline so the link and × sit on the message. */
  #txflash.hold{pointer-events:auto;display:inline-flex;align-items:center;gap:8px;padding-right:8px}
  #txflash.hold a{color:#fff;text-decoration:underline;font-weight:600;white-space:nowrap}
  #txflash .txflash-x{font:inherit;font-size:var(--fs-lg);line-height:1;color:#fff;background:none;border:0;padding:0 2px;cursor:pointer;opacity:.85}
  #txflash .txflash-x:hover{opacity:1}
  .txref{display:inline-flex;align-items:center;gap:5px;max-width:100%;font-family:ui-monospace,Menlo,monospace;font-size:var(--fs-xs);
    background:var(--chip);border:1px solid var(--line);border-radius:5px;padding:1px 5px;cursor:pointer;white-space:nowrap;vertical-align:middle}
  .txref .txref-t{overflow:hidden;text-overflow:ellipsis}
  .txref .txref-c{flex:0 0 auto;width:11px;height:11px;fill:none;stroke:var(--dim);stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  @media (hover:hover){ .txref:hover{background:var(--chip-h)} .txref:hover .txref-c{stroke:var(--ink-blue)} }
  /* The chips these two pages slice with are dressed by #filterRow now — the
     band they hang in, not the view they belong to. Documents' "Open payables"
     and "Open receivables" are slices of the open list rather than lists of
     their own: they look like every other chip, and the .filtersep after them
     closes the group off from Paid / All. */
  /* inline notes: looks like text until you interact with it */
  .txnotein{font:inherit;font-size:var(--fs-sm);color:var(--text);width:200px;padding:3px 6px;border:1px solid transparent;border-radius:5px;background:transparent}
  .txnotein:hover{border-color:var(--line);background:var(--panel)}
  .txnotein:focus{border-color:var(--red);background:var(--panel);outline:none;color:var(--ink-strong)}
  /* a read-only seat reads a note in place of the input: it may fill the column
     but wraps rather than running off the edge, so a long comment stays inside
     the cell instead of overrunning the table */
  .txnote-ro{display:inline-block;max-width:340px;color:var(--dim);white-space:normal;overflow-wrap:anywhere}
  .txneg{color:var(--red-tx-2)}
  #txview select,.txpanel select{font:inherit;font-size:var(--fs-xs);max-width:210px;padding:2px 3px;border:1px solid var(--line);border-radius:5px;background:var(--panel)}
  /* 16px or iOS zooms the page in when the review banner's pickers take focus */
  @media (pointer:coarse){ #txview select{font-size:var(--fs-xl);padding:4px 5px} }
  .txpanel b{color:var(--red-ink);font-size:var(--fs-lg)}
  /* where a panel's title is a pill — the counterparty on a transaction, the
     invoice number on an invoice — it takes the title's size. A pill can't wrap
     the way a plain heading does, so min-width:0 down the flex chain lets a long
     name ellipsize inside the pill rather than push the amount and the close ×
     off the row. */
  .invoice-panel-head>div:first-child,.invoice-panel-head h2{min-width:0}
  .invoice-panel-head h2 .mpill,.invoice-panel-head h2 .ipill{font-size:var(--fs-lg);padding:3px 11px;margin:0;max-width:100%}
  .invoice-panel-head h2 .mpill.pill2,.invoice-panel-head h2 .ipill.pill2{padding:0}
  .invoice-panel-head h2 .pill2>.pill-l{padding:3px 4px 3px 11px}
  .txpanel h4{color:var(--red-ink);font-size:var(--fs-sm);margin:16px 0 6px;text-transform:uppercase;letter-spacing:.04em}
  .txpanel textarea,.txpanel input[type=text]{font:inherit;font-size:var(--fs-sm);width:100%;box-sizing:border-box;padding:5px 7px;border:1px solid var(--line);border-radius:6px}
  .txpanel button{font:inherit;font-size:var(--fs-xs);padding:4px 9px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
  @media (hover:hover){ .txpanel button:hover:not(:disabled){background:var(--hover)} }
  /* no 16px field-floor — the panel is 12/10 on every viewport; we take the iOS
     zoom-on-focus over 16px fields beside 12px labels (see .invoice-panel above) */
  /* phones: the panel is nearly the whole screen, so the label column gives way
     to the values and the last row clears the home indicator */
  /* Transactions on phones: the nine-column table gives way to a list grouped by
     DAY. A day heading names the day and states what it came to, and that day's
     payments sit under it in one panel divided by hairlines — so the border, the
     radius and the gap a card used to pay per payment are paid once per day
     instead. A chevron opens the detail panel, which holds the reference, the
     editors and the raw Wise data.

     This is not the density trade it looks like: a day heading costs about as
     much as the chrome the grouping saves across four to six payments, which is
     what a trading day here actually holds. What it buys is the day nets — the
     in / out / net this page already computes for the table foot, where nobody
     scrolls to — and days as landmarks when you scroll back through a month.

     The other four card pages (Documents, Counterparties, Budgets, Costs) still
     use the .pcard shell above, and so do the payment lists inside panels: a
     panel lists a handful of payments spanning months, where grouping by day
     would be one heading per row. When those pages group, this is the shape. */
  .txcards{display:none;--tx-amt:96px;--tx-gap:8px}
  /* one grid for the header and every card, so the amounts line up down the list
     and the counterparty is what gives way */
  .txcard,.txcards-foot{display:grid;grid-template-columns:minmax(0,1fr) var(--tx-amt) 10px;gap:var(--tx-gap);align-items:center}
  /* still to sort out — an unknown counterparty, no category, no document filed,
     or any mix: amber border, and a bar down the edge that reads at a glance
     while scrolling. Absolute, so it costs the content no width. Whichever part
     of the card is amber inside is the part that needs the work. */
  /* Still to sort out — an unknown counterparty, no category, no document filed,
     or any mix. The bar is absolute, so it costs the content no width, and it is
     positioned against the row's PADDING box: the hairline dividing one row from
     the next crosses it, so a run of flagged rows still reads as a run of rows
     rather than one long stripe. */
  .txcard{position:relative;overflow:hidden}
  .txcard.needs:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--amb-bar)}
  /* a counterparty the Merchants tab doesn't know yet — the one the review banner
     above is asking about. Here the missing thing IS the name, so the pill carries
     the flag rather than a line under it. Same amber as the lines; the base pill's
     dashed edge stays, saying there's no folder behind it. The .mpill in the
     selector outranks the .nolink dimming that edge comes with. */
  .pcard .mpill.txcard-unknown{color:var(--amb-tx-2);border-color:var(--amb-bd-2);background:var(--amb-bg-2);opacity:1}
  @media (max-width:700px){
    #txview .tblwrap{display:none}
    #txview .txcards{display:block}
    /* the review banner stacks: the raw name and its total head the block, then
       the picker and the save button each take the full row — a fat tap target
       apiece. The ID in these selectors is load-bearing: without it the base
       "#txview select" cap of 210px outranks the width here and the picker
       renders as a squeezed strip beside the button. */
    .txreview{padding:10px 12px}
    #txview .txrev-row b,#prodview .txrev-row b,#invoicesview .txrev-row b{min-width:0;overflow-wrap:anywhere}
    #txview .txrev-row select,#prodview .txrev-row select,#invoicesview .txrev-row select{width:100%;max-width:100%;padding:8px 6px}
    #txview .txrev-row button[data-revsave],
    #prodview .txrev-row button[data-alsave],
    #invoicesview .txrev-row button[data-drevsave]{flex:1 1 100%;font-size:var(--fs-md);padding:9px 12px}
    /* the order chips stay inline — they are part of the row's meta line, not a
       control of it — but a finger has to be able to land on one */
    #prodview .txrev-row .txrev-ord{font-size:var(--fs-sm);padding:4px 10px}
  }
  /* ---- the day band and its panel ----
     The label and, at the far end, what the day came to. Both are quiet: the
     heading is a landmark you scroll past, not a figure competing with the
     payments under it. */
  /* The heading takes the ROW's grid, not a flex box pushed to both edges, so
     the day's net lands on the amount column and reads down the list with the
     payments it is the sum of. Same trick .pcards-head uses over the cards: the
     13px inset is the row's own 12px of padding plus the panel's 1px border, and
     the empty third column is the chevron the rows end with — without it the net
     would sit 18px further right than every figure under it. */
  .txday{display:grid;grid-template-columns:minmax(0,1fr) var(--tx-amt) 10px;gap:var(--tx-gap);
    align-items:baseline;padding:15px 13px 6px;font-size:var(--fs-sm)}
  .txday:first-child{padding-top:2px}
  .txday-l{font-weight:bold;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;
    white-space:nowrap}
  .txday-n{font-size:var(--fs-sm);color:var(--dim);font-variant-numeric:tabular-nums;white-space:nowrap;
    text-align:right}
  .txgrp{background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden}
  .txrow{position:relative;overflow:hidden}
  .txrow.needs:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--amb-bar)}
  /* A row is a div with role="button", NOT a <button> — the same shape .pcard
     takes, and for two reasons that both bit once.
     One: #txview button above dresses every button in this view at 11px with
     3px of padding and a 6px radius, and an id beats a class — so a <button>
     row came out a third shorter than it should be, with rounded corners on
     every row inside a panel that is meant to round only at its two ends.
     Two: the delegated [data-txopen] handler treats a click landing on a
     button as a control inside the row rather than the row itself, which is
     what makes the counterparty pill and the reference their own click — so a
     row that WAS a button could never open the detail panel.
     Enter and Space are handled by the same delegated keydown every other card
     list on this page relies on. */
  .txrow{display:grid;grid-template-columns:minmax(0,1fr) var(--tx-amt) 10px;gap:var(--tx-gap);
    align-items:center;width:100%;padding:10px 12px;border-top:1px solid var(--grid);
    line-height:1.35;cursor:pointer}
  .txrow:first-child{border-top:0}
  .txrow:focus-visible{outline:2px solid var(--red);outline-offset:-2px}
  @media (hover:hover){ .txrow:hover{background:var(--rowhover)} }
  /* a split's original payment heads its parts, and each part steps in under
     the elbow: the same nesting Owners and Categories now use on a phone,
     so the head takes .nest-head-card (band + rail) and the part .nest-kid
     (indent + └ elbow) from the shared block up in the table shell. Nothing
     split-specific is left to style here. */
  .txrow>div{min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:2px}
  .txrow>div.pcard-r{align-items:flex-end;text-align:right}
  /* Same ellipsis contract the .pcard shell has: every line clips to its column,
     and a span marking up part of a line stays in the flow of it.
     The :not() is load-bearing. .txrow-nm below is a flex row (a dot, then a
     name), and a bare .txrow>div>span selector outranks a single class — so
     without this the name line is blockified, its dot is caught by the nested
     "display:inline" rule underneath, and an inline span takes no width or
     height: the mark for "this one needs work" renders as nothing at all. */
  .txrow>div>span:not(.txrow-nm){display:block;max-width:100%;overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap}
  .txrow>div>span:not(.txrow-nm) span{display:inline}
  /* The name. The mark for "this one still needs work" is the amber edge bar
     every other list on this app uses — it does survive a shared panel, because
     the hairline between two rows crosses it. This list carried a dot for a
     while on the belief that it could not. */
  .txrow-nm{display:flex;align-items:center;gap:6px;font-size:var(--fs-sm);color:var(--ink-2);
    max-width:100%;line-height:1.35}
  .txrow-nm.unknown{color:var(--amb-tx-2)}
  .txrow-nm .nmt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* ---- the phone header: search, and one button for everything that narrows ----
     The band above the list used to be #actionsBar, which carries a page's
     actions and its filter pills in ONE sideways-scrolling lane — so the two
     syncs and Export accountant package… took the visible width and the
     chips, which are the work list, started past the right edge where nothing
     said they were.

     So the two are separated by kind rather than by position in one lane: what
     you search with is the default state of the header, what narrows lives in a
     sheet behind one button, and the three page actions — two of them monthly
     jobs — go behind a ⋯. It sticks to the top of the list, because a search box
     you have to scroll back up to find is one you stop using. */
  .txq{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:8px;height:38px;padding:0 13px;
    border:1px solid var(--line);border-radius:19px;background:var(--panel)}
  .txq:focus-within{border-color:var(--red)}
  .txq svg{flex:0 0 auto;width:14px;height:14px;fill:none;stroke:var(--dim);stroke-width:1.7;
    stroke-linecap:round}
  /* 16px or iOS zooms the page in when the box takes focus, the same guard every
     other phone field on this page carries */
  .txq input{flex:1 1 auto;min-width:0;font:inherit;font-size:var(--fs-xl);padding:0;border:0;outline:none;
    background:transparent;color:var(--ink-2);-webkit-appearance:none;appearance:none}
  .txq input::-webkit-search-cancel-button{-webkit-appearance:none}
  /* Scoped to .topbar, which is where both of these live, because that is what
     it takes to WIN. `.topbar button` is (0,1,1) and an unscoped `.txiconbtn` is
     (0,1,0), so the toolbar's own 6px radius and 6px 10px padding were taking
     this rule's place: a border-box button 26px wide with 20px of side padding
     has a 4px content box, and the icon — a flex child with nothing pinning its
     width — was shrinking into it. The glyph rendered as a 4px sliver on the
     desktop form of this button, which is what made it read as broken beside a
     phone that had room to spare. `flex:0 0 auto` is the belt to that braces:
     the icon is a fixed size and never a shrinkable one. */
  .topbar .txiconbtn{position:relative;flex:0 0 auto;width:38px;height:38px;padding:0;
    border-radius:50%;border:1px solid var(--line);background:var(--panel);color:var(--ink-3);
    cursor:pointer;display:flex;align-items:center;justify-content:center}
  .topbar .txiconbtn svg{flex:0 0 auto;width:16px;height:16px;fill:none;stroke:currentColor;
    stroke-width:1.8;stroke-linecap:round}
  .topbar .txiconbtn.on{background:var(--red);border-color:var(--red);color:#fff}
  /* The round action button turns its arrows while a sync runs — the phone's
     answer to the desktop button reading "Syncing…". Only ever the sync glyph
     spins; the ⋯ and the "+" do not (see beginSync / mobSyncFilterHome). */
  #actionsBar #mobActsBtn.spin svg{transform-origin:50% 50%;animation:cl-spin .9s linear infinite}
  /* how many narrowings are on, on the button that holds them. It is a count of
     filters, not of outstanding work — the strip below says which ones, and the
     sheet says what each one counts.
     It overlaps the button's own edge rather than floating clear of it: a badge
     hung fully outside a 26px circle is half again its width away from the thing
     it counts, and reads as a stray pill sitting in the band. */
  .topbar .txiconbtn .fbadge{position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;
    padding:0 4px;border-radius:8px;background:var(--amb-bar);color:#1a1a1a;font-size:var(--fs-2xs);
    font-weight:bold;line-height:1;display:flex;align-items:center;justify-content:center;
    box-shadow:0 0 0 2px var(--panel)}
  /* The narrowings that are actually ON, and nothing else. Putting the chips in
     a sheet is what buys the header its height back, but a filter you cannot see
     is a list lying about what it holds — and the homepage's issue rows link
     straight into a narrowed list, so arriving already filtered is the common
     case, not the rare one. This strip is empty (and gone) until something is
     narrowed; each chip clears the one it names. */
  .txactive{display:flex;gap:6px;margin-top:9px;overflow-x:auto;scrollbar-width:none}
  .txactive::-webkit-scrollbar{display:none}
  .txactive:empty{display:none}
  /* Outlined by default, red only when it is the one in force — the same
     "this is selected" the rest of the app reads. This strip used to hold only
     narrowings that were ON, so every chip in it was selected and this set red
     outright; it now also carries the work-queue slices that are merely on offer
     (Missing document, category, counterparty), and those have to read as
     unselected until you pick one. .topbar button above has already given every
     chip its border and ground, so the default needs to say nothing. */
  .txactive .txfilter{flex:0 0 auto;padding:5px 10px;border-radius:14px}
  .txactive .txfilter.on{background:var(--red);border-color:var(--red);color:#fff}
  @media (hover:hover){ .txactive .txfilter.on:hover{background:var(--red-hover);border-color:var(--red-hover)} }
  /* ---- the sheet: everything that narrows this list, in one place ---- */
  #mobsheet,#mobacts{display:none}
  #mobsheet.open,#mobacts.open{display:block;position:fixed;inset:0;z-index:76}
  .sheetbd{position:absolute;inset:0;background:rgba(0,0,0,.42)}
  .sheet{position:absolute;left:0;right:0;bottom:0;max-height:84vh;overflow:auto;
    background:var(--panel);border-top:1px solid var(--line);border-radius:16px 16px 0 0;
    padding:10px 16px calc(20px + env(safe-area-inset-bottom))}
  html[data-theme="dark"] .sheet{box-shadow:0 -8px 28px rgba(0,0,0,.55)}
  .sheet .grab{width:38px;height:4px;border-radius:2px;background:var(--line-2);margin:0 auto 14px}
  .sheet h4{color:var(--red-ink);font-size:var(--fs-xs);margin:16px 0 8px;text-transform:uppercase;
    letter-spacing:.04em}
  /* :first-of-type was scoped per PARENT, so #sheetExtra's first heading claimed
     it too and "Sort" landed hard against the chips wrapping above it. Only the
     sheet's own first heading loses its top margin. */
  #sheetSlotH{margin-top:0}
  .sheetchips{display:flex;flex-wrap:wrap;gap:7px}
  /* the sheet's own chips wrap rather than scroll: it has the height to show them
     all, which is the whole reason the counts moved in here */
  /* Inside the sheet the chips wrap instead of scrolling — it has the height to
     show them all, which is the whole reason the counts moved in here — and they
     take a fatter tap target than they do in the band on a desktop.
     The two that dress #filterRow's own children are scoped through #sheetSlot,
     not left on `.sheet` alone. `.sheet .txfilter` is (0,2,0) and the band's
     `#filterRow .txfilter` is (1,1,0), and an id beats any number of classes —
     so the fatter tap target this comment promises was never once applied: every
     chip in the sheet was rendering at the band's own metrics. */
  #sheetSlot #filterRow{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;overflow:visible}
  /* `.sheet .filtersep{display:none}` below cannot reach this one — it is (0,2,0)
     against the band's (1,1,0) `#filterRow .filtersep`, and an id outranks any
     number of classes — so a divider meant for a single scrolling lane was
     surviving into the wrapping set as a stray hairline. */
  #sheetSlot #filterRow .filtersep{display:none}
  /* The .sheet form stays as the rule for everything the sheet draws itself —
     #sheetExtra's sort buttons are .txfilter too, and they live nowhere near
     #filterRow — and is then restated through #sheetSlot for the row that IS
     moved in, which is the only part the band's id-scoped rule can reach. */
  .sheet .txfilter{font-family:inherit;padding:7px 12px;border-radius:16px;
    border:1px solid var(--line);background:var(--panel);color:var(--ink-2);cursor:pointer}
  .sheet .txfilter.on{background:var(--red);border-color:var(--red);color:#fff}
  .sheet select.stmonth{padding:7px 28px 7px 12px;
    border-radius:16px;max-width:100%}
  #sheetSlot #filterRow .txfilter{font-family:inherit;height:auto;padding:7px 12px;
    border-radius:16px;border:1px solid var(--line);background:var(--panel);color:var(--ink-2);
    cursor:pointer}
  #sheetSlot #filterRow .txfilter.on{background:var(--red);border-color:var(--red);color:#fff}
  #sheetSlot #filterRow select.stmonth{
    height:auto;padding:7px 28px 7px 12px;border-radius:16px;max-width:100%}
  .sheet button[data-colf],.sheet button[data-ordcolf],.sheet button[data-invcolf],.sheet button[data-prodcolf],.sheet button[data-clcolf],.sheet button[data-kbf],.sheet button[data-rmf]{font:inherit;font-size:var(--fs-md);
    padding:7px 12px;border-radius:16px;
    border:1px solid var(--line);background:var(--panel);color:var(--ink-2);cursor:pointer;
    display:inline-flex;align-items:center;gap:6px}
  .sheet button[data-colf].on,.sheet button[data-ordcolf].on,.sheet button[data-invcolf].on,.sheet button[data-prodcolf].on,.sheet button[data-clcolf].on,.sheet button[data-kbf].on,.sheet button[data-rmf].on{background:var(--red);
    border-color:var(--red);color:#fff}
  .sheet .filtersep{display:none} /* a divider inside a wrapping set reads as a stray chip */
  .sheet .cficon{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;
    stroke-linecap:round}
  .sheetdone{margin-top:20px}
  .sheetdone button{font:inherit;width:100%;font-size:var(--fs-lg);padding:11px 12px;border-radius:10px;
    background:var(--red);border:1px solid var(--red);color:#fff;cursor:pointer}
  /* the overflow: what the page can DO, which are jobs rather than narrowings */
  #mobacts .sheet{padding-top:10px}
  .sheet button[data-txact]{font:inherit;display:block;width:100%;text-align:left;font-size:var(--fs-lg);
    padding:12px 14px;border-radius:10px;margin-bottom:8px;border:1px solid var(--line);
    background:var(--panel);color:var(--ink-2);cursor:pointer}
  .sheet button[data-txact]:disabled{opacity:.4;cursor:default}
  /* A picker cannot be forwarded by a click, so the real <select> is moved into
     the sheet — and it arrives wearing the toolbar's own metrics, a narrow strip
     beside two full-width buttons. Here it is one of them. */
  .sheet .sheetsel{margin-bottom:8px}
  .sheet .sheetsel select{font:inherit;display:block;width:100%;font-size:var(--fs-lg);
    padding:12px 34px 12px 14px;border-radius:10px;border:1px solid var(--line);
    background-color:var(--panel);color:var(--ink-2);background-position:right 14px center}
  /* THE BAND, ON A DESKTOP — the same order as the phone's.
     It used to read actions-first, narrowings-last; the phone reads the other
     way round (search, filter, then what the page can do), and having the two
     mirror each other meant the same three groups sat at opposite ends
     depending on the window. They read left to right the same way now: what
     narrows the list, then a gap, then what acts on it.

     The search box crosses over from the phone header unconditionally. The two
     round buttons are a phone thing while the chips they stand in for are on
     screen here — a button that opens a sheet to show you what is already in
     front of you is a tap for nothing. The filter one earns its place on this
     side the moment that stops being true: see .chipsheet below, and
     mobSyncFilterHome, which decides at which width it does. */
  .mobonly{display:none}
  @media (min-width:701px){
    /* the same 8px the phone bar uses: once .chipsheet brings the round filter
       button out, it stands beside the search box rather than against it. The
       gap costs nothing while the button is hidden — a display:none child is
       not a flex item, so there is nothing for it to sit between. */
    #actionsBar #mobBar{display:flex;align-items:center;gap:8px;order:1;flex:0 0 auto}
    #actionsBar #mobBar .txiconbtn{display:none}
    /* Sized to the chips beside it, not to a thumb. The box is phone-first —
       38px tall with 16px text, which is what stops iOS zooming the page when
       the field takes focus — and at this width that was half again the height
       of every chip in the row with type a third larger.
       28px/13px, matching #filterRow .txfilter exactly. The two sizes are set in
       two places and have to agree; the chip rule is the one that says why.
       276px wide, not 260: Orders has the longest placeholder there is ("Search
       items, order, method, amount") and at 13px it measures 261px inside the
       icon, the gap and the padding — a box sized to the old type clipped its
       last letter. Sized to the longest one so no page is the odd one out. */
    #actionsBar #mobSearch{width:276px;height:28px;padding:0 12px;border-radius:14px}
    #actionsBar #mobSearch svg{width:13px;height:13px}
    #actionsBar #mobSearch input{font-size:var(--fs-md)}
    /* a page with nothing to search by gives the row back rather than showing an
       empty box: Budgets, and the statements */
    #actionsBar.nosearch #mobBar{display:none}
    #actionsBar #mobActive{display:none} /* the chips themselves are on screen */
    /* ---- the narrowings a page keeps in its own table header ----
       Transactions sorts and narrows by column from its header row, and Orders
       picks its one slice from its. Both still BUILD the sheet's version of
       those controls (#sheetExtra), because the phone hides the one table and
       scrolls the other sideways, and neither leaves a header a thumb can
       reach. This is the desktop half of that bargain: one control per
       narrowing, in the column it is about, and no second copy of it here. */
    #sheetExtra{display:none}
    #actionsBar #filterRow{order:2;margin-left:0}
    #actionsBar .actionsrow{order:3;margin-left:auto}
    /* ---- …and the same band once the chips have outgrown it ----------------
       mobSyncFilterHome moves them into the sheet at the width they stop
       fitting, and hangs .chipsheet here to say so. The round button comes out
       to open it — sized to the chips it stands in for rather than to a thumb —
       and what is narrowed is still said out loud beside it, because a filter
       you cannot see is a list lying about what it holds and the sheet is shut.
       ---- */
    #actionsBar.chipsheet #mobBar #mobFilterBtn{display:flex}
    /* Round, and the height of the search box beside it — a square sat between a
       pill of a search box and a strip of pill-shaped chips is the odd one out.
       The circle comes from `.topbar .txiconbtn` now rather than from here: this
       rule only has to say how big it is on this side of the breakpoint. */
    #actionsBar.chipsheet #mobFilterBtn{width:28px;height:28px}
    #actionsBar.chipsheet #mobFilterBtn svg{width:14px;height:14px}
    #actionsBar.chipsheet #mobFilterBtn .fbadge{min-width:15px;height:15px;padding:0 3px;
      font-size:var(--fs-3xs);top:-5px;right:-5px}
    /* it shrinks and scrolls within itself rather than widening the band, which
       would be the crowding this exists to end */
    #actionsBar.chipsheet #mobActive{display:flex;order:2;margin-top:0;flex:0 1 auto;min-width:0}
    /* a page with nothing to search still needs the button, so the bar it rides
       in stays — just without the box */
    #actionsBar.chipsheet.nosearch #mobBar{display:flex}
    #actionsBar.chipsheet.nosearch #mobSearch{display:none}
    /* ---- …and the same again for the actions, one fold later ---------------
       The chips going into the sheet buys the band roughly their width back.
       Where that is still not enough — Transactions carries a search box, its
       narrowings and four buttons, two of which name a whole month's job — the
       band went on overflowing, and #actionsBar scrolls sideways with its
       scrollbar hidden: the last action sat past the right edge with nothing
       saying it was there. That is the same disappearance the chips were moved
       out of, so it has the same answer — the ⋯ that already holds these
       buttons on a phone, on this side of the breakpoint too.

       First, and before the chips: an action wearing its own words in the band
       beats one behind a glyph, but a chip is telling you something without
       being asked and an action is only waiting to be asked. See
       mobSyncFilterHome, which measures the two folds in that order.

       It also takes the place the buttons had, at the right-hand end, rather
       than the place the ⋯ has on a phone. #mobBar groups the search box with
       the filter button because a thumb wants them together; on this side of
       the breakpoint the band reads left to right as what narrows the list and
       then what acts on it, and a ⋯ parked between the search box and the chips
       is a page action standing in the middle of the narrowings — worse still
       beside the filter button, where two identical circles would mean two
       different kinds of thing. So mobSyncFilterHome lifts it out of #mobBar
       and into the band itself while this is on, and puts it back after. */
    #actionsBar.actsheet .actionsrow{display:none}
    #actionsBar.actsheet>#mobActsBtn{display:flex;order:4;margin-left:auto;
      width:28px;height:28px;flex:0 0 auto}
    #actionsBar.actsheet>#mobActsBtn svg{width:14px;height:14px}
    /* the "+" is a character rather than an icon, so it is sized here too */
    #actionsBar.actsheet>#mobActsBtn.addbtn{font-size:var(--fs-2xl);padding-bottom:1px}
    /* The sheet, opened from a desktop: a panel beside the button that opened
       it, not a drawer drawn across two thousand pixels of monitor. Both of
       them — placeSheet puts each under whichever button summoned it. */
    #mobsheet .sheet,#mobacts .sheet{left:14px;right:auto;top:64px;bottom:auto;
      width:min(560px,calc(100vw - 28px));
      max-height:72vh;border:1px solid var(--line);border-radius:14px;padding-bottom:20px}
    #mobacts .sheet{width:min(320px,calc(100vw - 28px))}
    #mobsheet .grab,#mobacts .grab{display:none}
  }
  /* A wide touch screen — an iPad in landscape — is over the breakpoint above
     but still zooms on a sub-16px field. It keeps the desktop row, with a box
     tall enough to hold phone-sized type. Nothing else about the row changes,
     which is why this is its own block and not a second copy of the one above. */
  @media (min-width:701px) and (pointer:coarse){
    #actionsBar #mobSearch{height:32px;border-radius:16px}
    #actionsBar #mobSearch input{font-size:var(--fs-xl)}
  }
  /* A page with exactly one action does not need a menu to hold it: Users,
     Counterparties and Categories each have one "+ New …" and nothing else, and
     a ⋯ that opens a sheet with a single item in it is a tap spent on nothing.
     The button becomes the action, wearing that action's own glyph — a "+" for
     an add (this rule; it is a character, not an icon, so it needs sizing), and
     for anything else the icon the button names in data-mobicon, which draws
     itself through `.topbar .txiconbtn svg` like the other two. See soloAct. */
  .txiconbtn.addbtn{font-size:var(--fs-3xl);line-height:1;font-weight:300;padding-bottom:2px}
  /* A group of chips inside the sheet — Documents slices by payment state, then
     by everything else, then by merchant, and those are three questions rather
     than one row of eleven. The label only exists inside the sheet: in the band
     on a desktop the row is wide enough to read as groups on its own, and a
     heading per group there would be three headings in a toolbar. */
  .filtergrp{display:none}
  .sheet .filtergrp{display:block;width:100%;color:var(--red-ink);font-size:var(--fs-xs);
    margin:14px 0 2px;text-transform:uppercase;letter-spacing:.04em}
  .sheet .filtergrp:first-child{margin-top:0}
  /* the narrowest phones: give the counterparty column back a few pixels */
  @media (max-width:360px){ .txcards{--tx-amt:86px;--tx-gap:6px} }
  /* merchants view (same conventions as #txview) */
  #merchview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:0 0 10px}
  #merchview .mpill{font-size:var(--fs-sm)}
  #merchview select{font:inherit;font-size:var(--fs-xs);padding:2px 3px;border:1px solid var(--line);border-radius:5px;background:var(--panel)}
  /* The Type picker's closed options. The native popup greys a disabled option
     and shows its title on hover, which is the whole explanation on a desktop;
     the panel writes the same sentences out under the picker because a phone
     cannot hover. Unscoped: the picker is on the row AND in the panel, and a
     #merchview rule reaches only the row. */
  select[data-mrtype] option:disabled{color:var(--dim)}
  .prow .cptypewhy{flex-basis:100%;margin:2px 0 0;line-height:1.5}
  /* Description fields follow the same quiet, text-first treatment as transaction notes. */
  #merchview .mrdesc,#catsview .mrdesc,#teamview .mrdesc{font:inherit;font-size:var(--fs-sm);color:var(--text);width:260px;padding:3px 6px;border:1px solid transparent;border-radius:5px;background:transparent}
  #merchview .mrdesc:hover,#catsview .mrdesc:hover,#teamview .mrdesc:hover{border-color:var(--line);background:var(--panel)}
  #merchview .mrdesc:focus,#catsview .mrdesc:focus,#teamview .mrdesc:focus{border-color:var(--red);background:var(--panel);outline:none;color:var(--ink-strong)}
  /* ===== phone cards ======================================================
     The Documents and Counterparties tabs both drop their wide table on a phone
     and list cards instead. This is the shell they share — the card box, the
     column header above the list, how a cell stacks its lines, and how anything
     too long for its column ends in an ellipsis. Each tab adds only its own grid
     columns and its own accents.
     A cell is a column flex box, so ALIGNMENT, not text-align, is what places
     its contents: without align-items a pill stretches to the full column width
     however short its text, and max-width:100% is what makes a long one clip at
     the column edge instead of widening the card. Both tabs got that wrong once;
     the rules live here so there is one answer to fix. */
  .pcards-head{padding:0 13px 6px 13px;/* 12px card padding + 1px border: labels sit over their column */
    font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--dim)}
  .pcards-head span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* A card is a ROW IN A PANEL, not a box of its own. Every list on this app
     used to spend a border, a 10px radius and an 8px gap per item, so ten items
     paid that ten times and a list read as ten separate claims rather than one
     list of ten. The panel (.pgrp) pays it once: it carries the border and the
     radius, its rows are divided by hairlines, and only its own two ends are
     round. The Transactions day-list was the first to take this shape; this is
     that shape moved into the shell all the card pages already share, so
     Documents, Counterparties, Categories, Budgets, Team and Users take it
     without each having to be re-drawn. */
  .pgrp{background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden}
  .pgrp+.pcards-head{margin-top:18px}
  .pcard{width:100%;text-align:left;background:transparent;border:0;
    border-top:1px solid var(--grid);border-radius:0;padding:10px 12px;font:inherit;
    line-height:1.35;cursor:pointer}
  .pcard:first-child{border-top:0}
  .pcard:focus-visible{outline:2px solid var(--red);outline-offset:-2px}
  @media (hover:hover){ .pcard:hover{background:var(--rowhover)} }
  .pcard>div{min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:2px}
  /* right-hand columns: flex-end right-aligns a shrink-wrapped line, which
     text-align alone cannot do once the line is only as wide as its text */
  .pcard>div.pcard-r,.pcards-head span.pcard-r{text-align:right;align-items:flex-end}
  /* Everything that isn't a pill clips to its column. A split pill's innards —
     its Drive link and its state dot — are pill, not card line, so they are
     excluded here rather than fought over with a later rule. */
  .pcard span:not(.ipill):not(.mpill):not(.idot),
  .pcard a:not(.ipill):not(.mpill):not(.pill-d){display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* that block rule is for the card's own lines; a span marking up part of one —
     the amber half of "8 Aug · No category", the "is" in "merchant is Talho
     Central" — stays in the flow of its line.
     All THREE :not()s, not two. This was written to outrank the rule above and
     did not: that one carries four classes (.pcard and its three :not()s) against
     this one's three, so a nested span was blockified everywhere and any card
     line built from more than one span stacked a word per line. The Rules cards
     are where it finally showed. */
  /* The :not()s repeat on the INNER span too. A pill is a span, and a split pill
     (name half + Drive half) is inline-flex — catching it here turned it back to
     plain inline, which takes the halves out of the flex row and collapses the
     name to an ellipsis inside a stretched empty capsule. That is what the Rules
     list showed for every merchant whose name is long. The pills are excluded on
     both ends now, so this only ever reaches the plain spans it is for. */
  .pcard>div>span:not(.ipill):not(.mpill):not(.idot)
    span:not(.ipill):not(.mpill):not(.idot):not(.pill-l):not(.pill-d){display:inline}
  .pcard>div>span{max-width:100%}
  /* pills take their own text's width, up to the column, then ellipsize. Size is
     each tab's business: the counterparty name pill takes #merchview's 11.5px,
     the invoice number sets its own below, and a merchant pill keeps the base. */
  .pcard .ipill,.pcard .mpill{max-width:100%;margin:0}
  /* A pill's two halves are its own layout. The card and table rules above
     blockify and ellipsize a bare span or link, and a split pill has both
     inside it — the Drive link and the state dot — so they are called out by
     name rather than left to be caught by shape. */
  .pcard .pill2>.pill-l{max-width:100%}
  .pcard-sub{font-size:var(--fs-xs);color:var(--dim)}
  /* the sub line in amber: what this card is missing. Where it sits is each tab's
     business — under the counterparty it belongs to, or under the amount it is
     the missing proof of — but "needs attention" reads in one colour everywhere. */
  .pcard-miss{font-size:var(--fs-sm);color:var(--amb-tx-2)}
  /* an uncleared payment's status, inline beside its date — amber for the same
     "not settled yet" reason as pcard-miss, and lighter than the date it trails */
  .pcard-pending{font-weight:600;color:var(--amb-tx-2)}
  .pcard-n{font-size:var(--fs-md);font-weight:600;color:var(--ink-2)}
  .pcard-n.settled{color:var(--grn-tx)}
  .pcard-more{font-size:var(--fs-xl);color:var(--muted-4);line-height:1;overflow:visible!important}
  /* Counterparties on phones: the name over its type and description, the count
     and value on one line the way the table reads them, and a chevron into the
     detail panel that holds the rest (Drive folder, Wise names, the editors).
     A counterparty with invoices missing gets an amber line under its numbers
     and an amber edge, so the ones needing attention are the taller, marked
     cards. */
  .cpcards{display:none;--cp-gap:8px}
  /* the numbers column takes exactly what it needs and the name gives way; it is
     right-aligned against a fixed chevron, so the totals still line up down the
     list however wide each one runs */
  .cpcard,.cpcards-head{display:grid;grid-template-columns:minmax(0,1fr) auto 10px;gap:var(--cp-gap);align-items:center}
  /* the edge bar is absolute, so it costs the content no width */
  .cpcard{position:relative;overflow:hidden}
  .cpcard.needs:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--amb-bar)}
  /* the type line follows the name pill rather than a text line: a pixel more air */
  .cpcard-meta{margin-top:1px}
  /* detail panel: same drawer as the invoice one, with the sheet's editors inside.
     The Categories cards open the same drawer, so its rules are shared. */
  /* Panel internals. These used to be written as "#merchview .invoice-panel …",
     one prefix per view, because that was where each panel was rendered; they
     all live in #panelhost now, so the panel itself is the scope. */
  .invoice-panel h3{color:var(--red-ink);font-size:var(--fs-sm);margin:16px 0 6px;text-transform:uppercase;letter-spacing:.04em}
  /* the list price a figure came down from, struck beside it — the same idiom the
     Gross column carries in the table, but inline here (the panel has the width),
     so a reversed-to-zero gross reads "€0.00  €22.00" rather than stacking */
  .invoice-panel .was{margin-left:8px;font-size:var(--fs-2xs);color:var(--dim);
    font-variant-numeric:tabular-nums;text-decoration:line-through;font-weight:400}
  .invoice-panel .cpfield{font-size:var(--fs-sm);color:var(--text);margin:0 0 10px}
  /* the quiet line under a section: what the section does NOT let you edit, and
     where that is done instead */
  .invoice-panel .pnote{font-size:var(--fs-sm);color:var(--dim);line-height:1.45;margin:0 0 10px}
  .invoice-panel select:not(.cbtxt){font-size:var(--fs-sm);padding:5px 6px;max-width:100%}
  .invoice-panel .cpfield select{width:100%;box-sizing:border-box}
  .invoice-panel .mrdesc{font-size:var(--fs-sm);width:100%;box-sizing:border-box;border-color:var(--line);background:var(--panel);padding:5px 7px}
  /* condition and action rows lay their controls out ALONG the row, not stacked:
     the panel default above forces every control full width — right for a lone
     field, but it turns "Amount is 0" into three lines. Field and operator size
     to their own text; the value takes the rest. Still wraps on a narrow panel. */
  .invoice-panel .rcond .mrdesc{width:auto;min-width:0}
  .invoice-panel .rcond [data-rcval],.invoice-panel .rcond [data-raval]{flex:1 1 120px}
  /* NO 16px field-floor on a panel. The panel keeps its 12/10 type scale on
     every viewport — a phone included — rather than pinning fields to 16px to
     stop iOS zooming on focus. That guard traded one annoyance (a brief zoom
     you pinch back) for a worse one (16px fields stranded beside 12px labels,
     a panel that read as five sizes). We take the zoom. So do NOT reintroduce
     an `@media (pointer:coarse){ .invoice-panel …{font-size:var(--fs-xl)} }`
     rule here — 12/10 holds on mobile. See brand guide §08. Instead, size the
     fields to the 12px body explicitly (a bare input would otherwise sit at the
     ~13.3px UA default, a hair off the labels beside it): */
  .invoice-panel select,.invoice-panel input:not([type=checkbox]):not([type=radio]),.invoice-panel textarea{font-size:var(--fs-sm)}
  @media (max-width:700px){
    #merchview .mtable{display:none}
    #merchview .cpcards{display:block}
  }
  /* the narrowest phones: give the name column back a few pixels */
  @media (max-width:360px){ .cpcards{--cp-tx:82px;--cp-gap:6px} }
  /* categories view (same conventions as #merchview) */
  /* Rules had no view rule at all: no padding, so its tables ran into the edge
     of the window, and — because every .txfilter and button rule on this
     stylesheet is ID-scoped to its view — its tabs and buttons fell back to the
     browser's own grey chrome. Nothing here is particular to Rules; it is the
     same four lines every other view has, which is the point. */
  /* Rules' four slices hang in #filterRow with everyone else's. What is left in
     the view under this class is the "+ Add condition" button in the rule
     editor and the .rtgl toggles on a rule row — chip-shaped, but not slices. */
  #rulesview .txfilter{font-size:var(--fs-sm);padding:5px 10px;border-radius:14px}
  /* Rules on a phone. This page had no phone form at all: it fell back to the
     five-column table, which does not fit — Then and Fired started past the
     right edge, and the merchant pill in the When column was cut in half by it.
     The card is the rule read as the sentence it is ("merchant is Talho Central
     → Food"), which is the same sentence its drawer is titled with. The tick and
     the status picker stay on the row, because selecting a run of rules and
     switching them on is the whole job of this page; everything else — the
     conditions, the actions, what it has fired on — is behind the chevron. */
  /* The audit log on a phone. Five columns, one of them 420px of payload, so on
     a phone it ran off the side twice over — the log of who changed what was the
     one page you could not read on the device you were most likely to check it
     from. What each entry IS is the act and who did it; when it happened and
     whether it worked qualify that, and the payload is what you open it for. */
  .auditcards{display:none;--aud-gap:8px}
  .auditcard,.auditcards-head{display:grid;grid-template-columns:minmax(0,1fr) auto;
    gap:var(--aud-gap);align-items:baseline}
  .auditcard .aud-w{font-size:var(--fs-sm);color:var(--ink-2)}
  .auditcard .aud-who{font-size:var(--fs-xs);color:var(--dim)}
  .auditcard .aud-when{font-size:var(--fs-xs);color:var(--dim);white-space:nowrap}
  .auditcard .aud-ok{font-size:var(--fs-xs);font-weight:bold}
  .auditcard .aud-ok.bad{color:var(--red-tx-2)}
  .auditcard .aud-ok.good{color:var(--grn-tx-3)}
  /* the payload, once you have opened the entry it belongs to */
  .auditcard pre{grid-column:1/-1;margin:8px 0 0;padding:8px;background:var(--soft-2);
    border-radius:6px;font-size:var(--fs-xs);white-space:pre-wrap;word-break:break-all;
    max-height:50vh;overflow:auto}
  .rulecards{display:none;--rule-gap:8px}
  .rulecard,.rulecards-head{display:grid;grid-template-columns:22px minmax(0,1fr) auto 10px;
    gap:var(--rule-gap);align-items:center}
  .rulecards-head{padding-left:13px}
  .rulecard .rsel{display:flex;align-items:center;justify-content:center}
  .rulecard input[type=checkbox]{width:17px;height:17px;margin:0}
  /* the sentence: the When on top in the page's own markup (merchant pills and
     all), the Then under it — one line each, ellipsized at the column */
  /* The one card line on this app that WRAPS. The shell keeps a card line to
     one nowrap row that ellipsizes at the column, which is right for a name and
     a date — but this line is a sentence ending in a merchant pill, and under
     that rule the pill is the part that gets squeezed: the flex row inside it
     shrinks the name to nothing and a rule about "MBJD - Construções e
     Remodelações Lda" reads "merchant is …". Two lines and the whole name is
     better than one line and none of it. The ID beats the shell's .pcard rule. */
  #rulesview .rulecard .rwhen{font-size:var(--fs-sm);color:var(--ink-2);
    white-space:normal;overflow:visible;text-overflow:clip;line-height:1.5}
  #rulesview .rulecard .rwhen .mpill{max-width:100%;vertical-align:-2px}
  .rulecard .rthen{font-size:var(--fs-sm);color:var(--dim)}
  .rulecard .rthen b{color:var(--ink-3);font-weight:600}
  /* the status picker keeps the green/red it has in the table, at a tap size */
  #rulesview .rulecard select.pstat{font-size:var(--fs-sm);padding:5px 20px 5px 8px;max-width:104px}
  .rulecard.rsel-on{background:var(--rowhover)}
  @media (max-width:700px){
    #rulesview .tblwrap{display:none}
    #rulesview .rulecards{display:block}
    /* the Edits table is now all this page holds — the Syncs table that used to
       sit above it is on the Status page, with the rest of the checks */
    #auditview .tblwrap{display:none}
    #auditview .auditcards{display:block}
    /* the bulk bar floats over the list, so the end of the list needs somewhere
       to go — otherwise the last rules sit under it and cannot be reached */
    #rulesview.hasbulk{padding-bottom:96px}
    /* it already clears the unsaved-changes bar; on a phone it only needed to stop
       being one unbreakable line wider than the screen */
    .cselbar{max-width:calc(100vw - 24px);white-space:normal;justify-content:center;
      flex-wrap:wrap;gap:8px}
  }
  @media (max-width:360px){ .rulecards{--rule-gap:6px} }
  /* ===== statements ========================================================
     Four statements over one read model. Every figure is a money column, so
     the whole table is tabular-nums and right-aligned: a column of euros that
     does not line up digit for digit is where a misread starts.
     Months are columns and the row label is sticky, because the label is what
     tells you which line you are on once the table scrolls sideways. */
  #statementsview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:18px 0 8px}
  #statementsview h3:first-child{margin-top:0}
  #statementsview .sthead{font-size:var(--fs-sm);color:var(--text);margin:0 0 12px;max-width:70ch}
  /* the same button as every other page's — without it the tabs below fall back
     to the browser's own grey chrome, which is the one control on the app that
     doesn't look like the app */
  #statementsview a{color:var(--link);text-decoration:none}
  @media (hover:hover){ #statementsview a:hover{text-decoration:underline} }
  /* content-width, not 100%: a percentage-width table hands every spare pixel
     to the label column, which leaves a hand's width of empty cell between a
     line and its first figure. Same convention as .txtable everywhere else —
     as are the row rhythm and the quiet uppercase header, so a statement sits
     in the same card as every other table on the app.
     The label column is .stlbl and NOT .lbl: that class belongs to the
     roadmap, where it is a 360px flex box with a rule down its right edge,
     and a table cell that picks it up gets all three — an over-wide label
     column, a vertical rule no other table draws, and a colspan that stops
     working because the cell is no longer a table cell. */
  #statementsview .sttable{border-collapse:collapse;font-size:var(--fs-sm)}
  #statementsview .sttable td,#statementsview .sttable th{padding:7px 10px;border-bottom:1px solid var(--grid);text-align:right;
    font-variant-numeric:tabular-nums;white-space:nowrap}
  #statementsview .sttable td.stlbl,#statementsview .sttable th.stlbl{text-align:left;position:sticky;left:0;z-index:1;
    background:var(--panel);min-width:200px;font-variant-numeric:normal}
  #statementsview .sttable thead th{color:var(--dim);font-weight:600;font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;
    border-bottom-color:var(--line)}
  /* a child line is a subaccount of the line above it, and the indent is the
     only thing that says so once the numbers are the same size */
  #statementsview .sttable tr.kid td.stlbl{padding-left:26px;color:var(--text)}
  /* the weights, lightest to heaviest: a line, a group that sums other lines,
     a block total, the result. Only three of the four are bold, and .lone —
     a top-level line with nothing under it — is deliberately not one of them
     (see stExpand), because a page where most rows are bold has no emphasis
     left for the rows that are actually adding something up. */
  #statementsview .sttable tr.lone td{font-weight:400}
  #statementsview .sttable tr.grp td{font-weight:600}
  #statementsview .sttable tr.tot td{font-weight:700;border-top:1px solid var(--line-2);border-bottom:none}
  #statementsview .sttable tr.net td{font-weight:700;border-top:2px solid var(--line-2);border-bottom:none;font-size:var(--fs-md)}
  #statementsview .sttable tr.sub td{color:var(--dim)}
  #statementsview .stneg{color:var(--red-tx-2)}
  #statementsview .stnil{color:var(--dim)}
  /* the reconciliation strip: what the statement proves about itself. Green is
     not decoration here — it is the difference between a figure that has been
     checked and one that merely looks plausible. */
  #statementsview .stchecks{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px}
  #statementsview .stchk{font-size:var(--fs-xs);padding:4px 9px;border-radius:999px;border:1px solid var(--grn-bd);
    background:var(--grn-bg);color:var(--grn-tx)}
  #statementsview .stchk.bad{border-color:var(--red-bd);background:var(--red-bg);color:var(--red-tx-2)}
  #statementsview .stnote{border:1px solid var(--line);border-radius:10px;background:var(--panel);padding:12px 14px;margin:18px 0 0}
  #statementsview .stnote h4{margin:0 0 8px;font-size:var(--fs-sm);color:var(--text-2)}
  #statementsview .stnote ul{margin:0;padding-left:18px;font-size:var(--fs-sm);color:var(--text)}
  #statementsview .stnote li{margin:0 0 7px;line-height:1.45}
  #statementsview .stnote li b{font-variant-numeric:tabular-nums}
  #statementsview .stnote li.warn b{color:var(--red-tx-2)}
  #statementsview .stnote li.memo b{color:var(--dim)}
  #statementsview .stledger td.stlbl{min-width:0;position:static}
  #statementsview .stledger td.l{text-align:left;font-variant-numeric:normal;white-space:normal}
  #statementsview .stref{font-size:var(--fs-xs);color:var(--dim)}
  /* a Wise reference is a 90-character base64 blob and, printed in full, it is
     wider than every other column put together. Clipped to something
     recognisable, with the whole of it on the tooltip and on the Transactions
     page, which is where you go to act on one. */
  #statementsview .stref.ref{display:inline-block;max-width:20ch;overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}
  @media (max-width:700px){
    #statementsview{padding:12px}
    #statementsview .sttable td.stlbl,#statementsview .sttable th.stlbl{min-width:150px}
  }
  #usersview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:22px 0 10px}
  #usersview h3:first-child{margin-top:0}
  #usersview .umail{display:block;font-size:var(--fs-xs);color:var(--text)}
  /* nobody has told the app which address is theirs — the same amber the rest of
     the app uses for "this record is incomplete", said once for both layouts */
  #usersview .umail-none{color:var(--amb-tx-2)}
  #usersview select{font:inherit;font-size:var(--fs-sm);padding:4px 6px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--text)}
  #usersview .uview{font-size:var(--fs-2xs);padding:2px 7px;margin-left:8px;vertical-align:middle}
  /* the "viewing as" strip: a bar across the whole window, ABOVE the sidebar
     and the workspace, never inside a page. It sits on the body, which turns
     into a column so #app takes what is left rather than its 100vh. */
  body.actas{display:flex;flex-direction:column}
  body.actas #app{flex:1 1 auto;min-height:0;height:auto}
  #actas{flex:0 0 auto;display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap;
    padding:8px 18px;background:var(--amb-bg);border-bottom:1px solid var(--amb-bd);color:var(--text);font-size:var(--fs-sm);position:relative;z-index:90}
  #actas button{white-space:nowrap}
  #usersview .urtable td{vertical-align:top;font-size:var(--fs-xs)}
  #usersview .urtable .urpage{font-weight:600;white-space:nowrap;color:var(--text);border-right:1px solid var(--line)}
  #usersview .urtable .urwhat{color:var(--text);line-height:1.35;max-width:520px}
  #usersview .urtable .urseat,#usersview .urtable .uron,#usersview .urtable .uroff{text-align:center;white-space:nowrap}
  #usersview .urtable .uron{color:var(--red-ink);font-weight:700}
  #usersview .urtable .uroff{color:var(--dim)}
  #usersview .urtable tr.urfirst td{border-top:2px solid var(--line)}
  @media (min-width:701px) and (max-width:720px){ #usersview .urtable{display:table} #usersview .urtblwrap{display:block;overflow-x:auto} }
  /* the rights matrix as cards, for a phone: hidden until the breakpoint takes over */
  #usersview .urcards{display:none}
  #usersview .urcard{border:1px solid var(--line);border-radius:8px;background:var(--panel);margin:0 0 10px;overflow:hidden}
  #usersview .urcard-page{font-size:var(--fs-xs);font-weight:700;text-transform:uppercase;letter-spacing:.04em;
    color:var(--red-ink);background:var(--soft);padding:7px 11px;border-bottom:1px solid var(--line)}
  #usersview .urcard-fn{padding:8px 11px}
  #usersview .urcard-fn+.urcard-fn{border-top:1px solid var(--line)}
  #usersview .urcard-what{font-size:var(--fs-sm);color:var(--ink-2);line-height:1.35;margin-bottom:6px}
  #usersview .urcard-seats{display:flex;flex-wrap:wrap;gap:5px}
  #usersview .urcard-seats span{font-size:var(--fs-2xs);padding:2px 8px;border-radius:11px;border:1px solid var(--line);
    color:var(--muted);background:var(--panel)}
  #usersview .urcard-seats span.on{background:var(--red);border-color:var(--red);color:#fff;font-weight:600}
  #lockedview{padding:40px 18px;max-width:560px;margin:0 auto;text-align:center}
  #lockedview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:0 0 12px}
  #lockedview p{font-size:var(--fs-sm);color:var(--text);margin:0 0 10px}
  #lockedview .dim{color:var(--dim)}
  /* Users on phones: the three columns are a name, a stack of addresses and one
     word, and on a 390px screen the table pushed the addresses — the only column
     worth reading here — off the right edge. As cards: the person and their
     addresses on the left, what they can do on the right, and nothing to scroll
     sideways for. Read-only page, so a card is a card and not a button. */
  .ucards{display:none;--u-role:92px;--u-gap:8px}
  .ucards .pcard,.ucards .pcards-head{display:grid;
    grid-template-columns:minmax(0,1fr) var(--u-role);gap:var(--u-gap);align-items:start}
  .ucards .pcard{cursor:default}
  .ucards .pcard .ucard-name{font-size:var(--fs-sm);font-weight:600;color:var(--ink-2);margin-bottom:3px}
  /* an address is the one thing on this page that must be read in full, so it
     wraps rather than ellipsizing at the column edge — the element in the
     selector outranks the card shell's clip rule */
  .ucards .pcard span.umail{white-space:normal;overflow:visible;text-overflow:clip;
    overflow-wrap:anywhere;margin-top:1px}
  .ucards .pcard span.umail a{display:inline;white-space:normal;overflow:visible;text-overflow:clip}
  @media (max-width:700px){
    #usersview{padding:12px}
    #usersview .tblwrap{display:none}
    #usersview .ucards{display:block}
    /* the wide rights matrix gives way to its card form here */
    #usersview .urtblwrap{display:none}
    #usersview .urcards{display:block}
  }
  @media (max-width:360px){ .ucards{--u-role:78px;--u-gap:6px} }
  /* ===== owners, on its own page ========================================
     Who owns the business. Only the DIRECT stakes are recorded — what a holder
     ends up with of the business itself is arithmetic down the chain — so the
     table carries both and can never disagree with itself.
     Each party holding the business directly is a BAND: its own row, marked
     like the heading it is, with the parties holding IT listed under it. A bare
     indent was what this had before, and it read as "sub-item of the row above"
     rather than "owns the row above" — and left the Share column saying "60%"
     without ever saying 60% of WHAT.
     Phones get the direct holders as cards instead — see .owncards below. */
  /* The band (a direct holder that others hold), the indented sub-row and its
     elbow are the shared .nest-head / .nest-sub / .nest-line treatment up in the
     table shell — the same one the Categories table nests with, so the two read
     as one pattern. Only what is specific to this table stays here. */
  /* what a percentage is a percentage OF — the thing the indent left unsaid */
  #ownersview .owntbl .ofwho{display:block;font-size:var(--fs-2xs);font-weight:400;color:var(--dim);white-space:nowrap}
  #ownersview .owntbl td.own-eff{color:var(--ink-2);font-weight:600;font-variant-numeric:tabular-nums}
  /* an address is prose, not a reference: it keeps the table's own type and is
     allowed to wrap rather than stretch the column to one long line */
  #ownersview .owntbl .txref.own-addr{font-family:inherit;font-size:var(--fs-sm);white-space:normal}
  /* The same holders as cards, for a screen too narrow for the table: the DIRECT
     holders only, each opening its own record — and a holding company's record
     carries its own holders, so the chain is walked a screen at a time. The same
     list is what a counterparty panel shows under "Owners", which is what makes
     that walk work. */
  .owncards{display:none;--own-r:104px;--own-gap:8px}
  .owncards .pcard,.owncards .pcards-head{display:grid;
    grid-template-columns:minmax(0,1fr) var(--own-r) 10px;gap:var(--own-gap);align-items:center}
  .owncards .pcard:last-child{margin-bottom:0}
  /* What the stake works out to of the business: a sentence, not a number, so it
     wraps inside its column rather than clipping to "30% of Taco …". Named down
     to the cell to outrank the shell's blanket clip rule on a card's own lines. */
  .owncards .pcard div.pcard-r span.own-eff{white-space:normal;overflow:visible;text-align:right;line-height:1.3}
  /* a holder with no record behind it (nothing on the roster, nothing on the
     Counterparties tab) is still a row — it just has nothing to open */
  .owncards .pcard.plain{cursor:default}
  /* nested sub-holders (the Owners page, phone) nest the shared way: the
     direct holder heads a banded card (.nest-head-card) and each holder of it
     steps in under the └ elbow (.nest-kid + .nest-line, --d per level) — the
     card echo of the wide table, defined once up in the table shell. */
  /* what a sub-holder's share is a share OF, under the percentage on the right */
  .owncards .pcard-r .ofwho{display:block;font-size:var(--fs-2xs);font-weight:400;color:var(--dim);
    white-space:nowrap;text-align:right}
  /* In a panel it is always the list, at every width — a panel is narrower than
     the page it opens over, and the walk down the chain has to work on a desktop
     too. On a wide screen the panel has room to spare, so the share column takes
     enough of it to keep "30% of <company>" on one line. */
  .invoice-panel .owncards{display:block;--own-r:152px}
  #catsview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:0 0 10px}
  /* account-type filter chips, same as the Counterparties type tabs */
  /* Four columns, and the table still flexes to the viewport instead of
     inheriting .txtable's 760px floor. Fixed layout keeps the description column
     from collapsing around its fluid input. Phones get the cards below instead. */
  .txtable.cats{min-width:0;width:100%;max-width:860px;table-layout:fixed}
  .txtable.cats td{overflow-wrap:anywhere}
  .txtable.cats th.cname{width:32%}
  .txtable.cats th.ctypecol{width:18%}
  /* the totals column: right-aligned and never wrapped, so the figures line up
     down the list the way the Counterparties tab's do */
  .txtable.cats th.ctxcol{width:19%}
  .txtable.cats td.ctxcell{text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}
  /* merchants draws from the .txtable shell but tags its figure cells .u-right,
     not .num, so the shell's tabular-nums (only on td.num) never reached them */
  .mtable td.u-right{font-variant-numeric:tabular-nums}
  #catsview .mrdesc{width:100%;max-width:100%;box-sizing:border-box}
  /* Categories on phones: the name over its type and description, what has been
     filed against it on the right, and a chevron into the detail panel. Same
     shape as a counterparty card, because it answers the same two questions —
     what is this, and what has it come to. See the phone-card shell above for
     the card box and the ellipsis rules. */
  .catcards{display:none;--cat-gap:8px}
  /* the numbers column takes exactly what it needs and the name gives way, as
     on the Counterparties cards — right-aligned against a fixed chevron, so the
     totals still line up down the list however wide each one runs */
  .catcard,.catcards-head{display:grid;grid-template-columns:minmax(0,1fr) auto 10px;gap:var(--cat-gap);align-items:center}
  /* the type keeps the quiet italic it has in the table, and follows the name
     the way a counterparty's does */
  .catcard-meta{margin-top:1px;font-style:italic}
  /* The subaccounts a category rolls up, listed inside its panel: each named
     with what has been filed against it, each opening its own panel. Same walk
     the Owners list in a counterparty panel supports — a level at a time —
     which is why it is the same card rather than a table of its own. It is
     always the list, at every width: a panel is narrower than the page. */
  .catkids .pcard{display:grid;grid-template-columns:minmax(0,1fr) auto 10px;gap:8px;align-items:center}
  .catkids .pcard:last-child{margin-bottom:0}
  /* a retired subaccount still holds money and still has to be reachable, so it
     is listed — dimmed, and saying which it is */
  .catkids .catkid-off{color:var(--amb-tx-2)}
  @media (max-width:700px){
    #catsview .cattable{display:none}
    #catsview .catcards{display:block}
  }
  /* the narrowest phones: give the name column back a few pixels */
  @media (max-width:360px){ .catcards{--cat-gap:6px} }
  /* month-end close: a month's tasks as rows, each with the normal status
     dropdown and, where a check carries, a read-only proposal beneath it */
  #closeview .cmonth{display:flex;align-items:center;gap:10px;margin-bottom:6px;flex-wrap:wrap}
  #closeview .cmonth .ws{font-size:var(--fs-xs);font-weight:bold;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}
  #closeview .cmonth h2{font-size:var(--fs-2xl);margin:0;letter-spacing:-.01em}
  #closeview .cmonth button{font:inherit;font-size:var(--fs-sm);padding:4px 10px;border:1px solid var(--line);background:var(--panel);border-radius:8px;cursor:pointer;color:var(--text)}
  @media (hover:hover){ #closeview .cmonth button:hover:not(:disabled){background:var(--hover)} }
  #closeview .cmonth button:disabled{opacity:.4;cursor:default}
  #closeview .genbtn{font:inherit;font-size:var(--fs-sm);font-weight:bold;color:#fff;background:var(--red);border:1px solid transparent;border-radius:8px;padding:6px 13px;cursor:pointer}
  @media (hover:hover){ #closeview .genbtn:hover{background:var(--red-hover)} }
  #closeview .cmonth .genbtn{margin-left:auto}
  #closeview .cempty .genbtn{margin-top:12px}
  #closeview .csum{font-size:var(--fs-sm);color:var(--text-2);margin:0 2px 16px}
  #closeview .csum b{color:var(--ink-2)}
  #closeview .csum .rdy{color:var(--grn-tx-2);font-weight:bold}
  #closeview .cempty{font-size:var(--fs-md);color:var(--text-2);padding:30px 6px;text-align:center}
  #closeview .cempty p{margin:0}
  #closeview .crow{display:grid;grid-template-columns:112px minmax(0,1fr) 150px 148px;gap:12px;align-items:start;padding:12px 14px;border-top:1px solid var(--grid)}
  #closeview .crow.clk{cursor:pointer}
  @media (hover:hover){ #closeview .crow.clk:hover{background:var(--rowhover)} }
  #closeview .crow.clk:focus-visible{outline:2px solid var(--red-bd);outline-offset:-2px}
  #closeview .crow .o{display:flex;align-items:center;padding-top:2px}
  #closeview .cassign{font:inherit;font-size:var(--fs-sm);color:var(--text-2);background:var(--panel-2);border:1px solid var(--line);border-radius:7px;padding:5px 8px;max-width:100%;cursor:pointer}
  @media (hover:hover){ #closeview .cassign:hover{border-color:var(--line-2)} }
  #closeview .crow:first-child{border-top:0}
  #closeview .crow.done{background:var(--panel-2)}
  #closeview .crow .d{font-weight:bold;color:var(--ink-3);font-size:var(--fs-sm);font-variant-numeric:tabular-nums;padding-top:6px}
  #closeview .crow.late .d{color:var(--red-tx)}
  #closeview .crow .wt{font-weight:bold;color:var(--ink-2);font-size:var(--fs-sm)}
  #closeview .crow.done .wt{color:var(--text-2);font-weight:normal}
  #closeview .crow .cdesc{font-size:var(--fs-xs);color:var(--dim);margin-top:2px;line-height:1.45}
  #closeview .crow .s{display:flex;justify-content:flex-end;padding-top:2px}
  #closeview select.statuspill{font:inherit;font-size:var(--fs-sm);font-weight:600;border-radius:999px;padding:5px 12px;border:1px solid transparent;cursor:pointer;color:#fff}
  #closeview .prop{margin-top:7px;display:inline-flex;align-items:center;gap:8px;font-size:var(--fs-xs);border-radius:8px;padding:4px 9px;border:1px solid;flex-wrap:wrap}
  #closeview .prop.rdy{color:var(--grn-tx-2);background:var(--grn-bg-2);border-color:var(--grn-bd-2)}
  #closeview .prop.bad{color:var(--red-tx);background:var(--red-bg);border-color:var(--red-bd)}
  #closeview .prop.muted{color:var(--dim);background:var(--soft);border-color:var(--line);font-style:italic}
  #closeview .prop.blk{color:var(--amb-tx-3);background:var(--amb-bg);border-color:var(--amb-bd)}
  #closeview .prop .camt{font-variant-numeric:tabular-nums;font-weight:bold}
  #closeview .prop .accept{font:inherit;font-size:var(--fs-2xs);font-weight:bold;color:#fff;background:var(--done);border:0;border-radius:6px;padding:3px 9px;cursor:pointer}
  #closeview .prop a{color:var(--red-tx);font-weight:bold;text-decoration:none}
  #closeview .cfoot{font-size:var(--fs-sm);color:var(--dim);margin:14px 2px 0;line-height:1.55;max-width:800px}
  #closeview .cfoot b{color:var(--text)}
  /* the home close list uses a green ready-flag where issues use an age */
  #homeview .hrow .sev.g{background:var(--grn-bar,var(--done))}
  #homeview .hrow .rt{font-size:var(--fs-2xs);font-weight:bold;white-space:nowrap;padding-right:12px;color:var(--grn-tx-2)}
  @media (max-width:700px){
    #closeview{padding:12px;overflow-x:hidden}
    #closeview .crow{grid-template-columns:1fr;gap:6px}
    #closeview .crow .d{padding-top:0}
    #closeview .crow .o,#closeview .crow .s{justify-content:flex-start}
  }
  /* ===== desktop rows ======================================================
     What the phone cards worked out applies to the tables they replace: a row
     is the same one thing, opens the same panel, and marks itself the same way
     when it still needs work. This is that treatment for the wide layout —
     everything here is what the .pcard rules above say, said in table terms.
     The edge bar is an inset shadow rather than a border: the cells' borders
     are collapsed and shared with the row above, so colouring them would
     repaint the grid rather than draw one bar. */
  .txtable tr.needs td:first-child,.invtable tr.due-soon td:first-child{box-shadow:inset 3px 0 0 var(--amb-bar)}
  .invtable tr.due-now td:first-child{box-shadow:inset 3px 0 0 var(--red-bar)}
  /* a red alert row: a paid-at-the-till ticket whose card failed — a thicker red
     bar than a `needs` and a faint red wash across the row, so it reads as a
     problem to clear, not a note */
  .txtable tr.alert td{background:var(--red-wash)}
  .txtable tr.alert td:first-child{box-shadow:inset 3px 0 0 var(--red-bar)}
  /* an unknown counterparty: the pill carries the flag, as it does on a card.
     The .txtable in the selector is what outranks the .nolink dimming that a
     pill with no folder behind it comes with — see the .pcard rule above. */
  .txtable .mpill.txcard-unknown{color:var(--amb-tx-2);border-color:var(--amb-bd-2);background:var(--amb-bg-2);opacity:1}
  /* the bank's uncleared status, tucked under the amount it explains — amber
     like every other "needs attention" mark, and de-cased so it does not read
     as a second figure */
  .txtable .num .txpend{display:block;font-size:var(--fs-2xs);font-weight:600;color:var(--amb-tx-2);
    text-transform:none;letter-spacing:0;line-height:1.3;margin-top:1px}
  /* a control standing where the missing thing would have been named: the
     category picker with nothing picked, the button offering to connect the
     document that isn't filed */
  .catbtn.miss,#txview button.miss,.txpanel button.miss{color:var(--amb-tx-2);border-color:var(--amb-bd-2)}
  /* A document the amount fits and nothing else points at. Deliberately quieter
     than .miss: this is an offer to check, not a gap to fill, and it sits beside
     the buttons rather than replacing them. */
  .txsuggest{display:inline-flex;align-items:center;gap:.35rem;white-space:nowrap;
    padding:.1rem .45rem;margin-right:.3rem;border:1px dashed var(--dashline);border-radius:999px}
  .txsuggestq{color:var(--dim)}
  .txsuggest .txsuggestok{padding:.05rem .4rem;font-size:.85em}
  /* A transfer between two of our own accounts has no counterparty — both ends
     are us. The cell says what the row IS instead of naming us, in the same
     quiet grey the rest of the row's "nothing to see here" wears. */
  .txxfer{color:var(--dim);font-style:italic}
  /* ...unless the bank's own name for it has never been mapped, which is what
     the amber rail on the row is pointing at. Amber has to have a cause you can
     see, or the rail is a mark with nothing behind it. */
  .txxfer.unknown{color:var(--amb-tx-2)}
  /* The panel's document row when nothing is filed: the reason on one line, the
     answers under it as one button each. */
  .txpanel .sugline{display:block;font-size:var(--fs-xs);color:var(--dim);line-height:1.45}
  .txpanel .sugline b{color:var(--ink-3);font-weight:600}
  .txpanel .btnrow{margin-top:7px;display:flex;gap:6px;flex-wrap:wrap}
  /* The same "connect a document" answers in the Transactions TABLE cell, which
     is narrow enough that they wrap onto a second line. The panel's .btnrow gets
     its air from a flex gap; the table cell lays the buttons out inline, so give
     each one a little vertical room instead — a wrapped button then clears the
     one above it rather than sitting flush against it. */
  .txtable td.txdoc button{margin-top:3px;margin-bottom:3px}
  /* the whole row opens the panel — the editors and links inside it stay their
     own click, so only the row itself takes the pointer */
  .txtable tr[data-txopen],.txtable tr[data-txsplit],.txtable tr[data-cprow],.txtable tr[data-catrow],.txtable tr[data-emp],
  .txtable tr[data-ownopen],.txtable tr[data-rulerow],.txtable tr[data-ordopen],
  #prodview .txtable tr[data-pi],.invtable tr.invrow{cursor:pointer}
  @media (hover:hover){
    .txtable tr[data-txopen]:hover td,.txtable tr[data-cprow]:hover td,.txtable tr[data-catrow]:hover td,
    .txtable tr[data-emp]:hover td,.txtable tr[data-ownopen]:hover td,.txtable tr[data-rulerow]:hover td,
    .txtable tr[data-ordopen]:hover td,#prodview .txtable tr[data-pi]:hover td,
    .invtable tr.invrow:hover td{background:var(--rowhover)}
  }
  /* QuickBooks-style category picker: a button opening a searchable popover
     with the name on the left and the account type on the right */
  /* 240 not 200: the type sits in the cell now (see txCatSel), and the longest
     names ("Travel & Accommodation") still want most of what's left */
  .catbtn{font:inherit;font-size:var(--fs-xs);width:240px;max-width:100%;padding:2px 6px;border:1px solid var(--line);border-radius:5px;background:var(--panel);cursor:pointer;text-align:left;display:inline-flex;align-items:center;gap:6px}
  .catbtn .cbtxt{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* the same muted italic the picker's right-hand column uses, so the cell and
     the menu you picked it from read as one thing */
  .catbtn .cbtype{color:var(--dim);font-style:italic;font-size:var(--fs-2xs);flex:0 0 auto;white-space:nowrap}
  .catbtn .cbarr{color:var(--dim);font-size:var(--fs-3xs);flex:0 0 auto}
  /* retired: amber, the same "still to sort out" colour a missing thing wears */
  .catbtn .cbtype.arch,#catMenu .catrow.arch .ctype{color:var(--amb-tx-2);font-style:normal}
  @media (hover:hover){ .catbtn:hover{background:var(--hover)} }
  /* the same chip standing still: what a rule FILES INTO, on Rules, where the
     category is being reported rather than picked. Sized by its own text
     because it sits in a sentence there, not in a column of pickers. */
  .catbtn.ro{width:auto;cursor:default}
  @media (hover:hover){ .catbtn.ro:hover{background:var(--panel)} }
  #catMenu{position:fixed;display:none;z-index:120;background:var(--panel);border:1px solid var(--line);border-radius:8px;box-shadow:var(--shadow-pop);width:330px;max-width:calc(100vw - 16px);max-height:360px;padding:8px;flex-direction:column}
  #catMenu.open{display:flex}
  #catMenu input{font:inherit;font-size:var(--fs-sm);padding:6px 8px;border:1px solid var(--line);border-radius:6px;margin-bottom:6px}
  /* 16px or iOS zooms the whole page in when the search field takes focus, and
     the menu no longer fits the screen (same guard as the fields elsewhere) */
  @media (pointer:coarse){ #catMenu input{font-size:var(--fs-xl)} }
  #catMenu .list{overflow:auto;flex:1 1 auto}
  #catMenu .catrow{display:flex;align-items:baseline;justify-content:space-between;gap:14px;padding:7px 9px;border-radius:6px;cursor:pointer;font-size:var(--fs-sm)}
  #catMenu .catrow .ctype{color:var(--dim);font-style:italic;font-size:var(--fs-sm);flex:0 0 auto}
  /* a parent is a heading in the list, not a row you can land on: no pointer,
     no hover, and its children indent under it */
  #catMenu .catgroup{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
    padding:9px 9px 3px;font-size:var(--fs-sm);font-weight:600;color:var(--text);cursor:default}
  #catMenu .catgroup .ctype{color:var(--dim);font-style:italic;font-weight:400;flex:0 0 auto}
  #catMenu .catrow.sub{padding-left:20px}
  @media (hover:hover){ #catMenu .catrow:hover{background:var(--rowhover)} }
  #catMenu .catrow.on{background:var(--red-bg)}
  #catMenu .addnew{border-bottom:1px solid var(--grid);border-radius:0;margin-bottom:4px;padding-bottom:9px;font-weight:600;color:var(--red-ink)}
  /* Column filters: the header cell itself is the control — no button, no box
     around the label, so a filterable column reads exactly like every other
     header. The filter glyph carries the state instead of the text: faint
     until the column is narrowing something, then the app's red. */
  /* the two sortable headers (date, amount) — the caret only shows on the one
     in force, so an unsorted header stays as quiet as any other label */
  [data-txsort]{cursor:pointer;user-select:none}
  [data-txsort].on{color:var(--red-ink)}
  .sortcaret{margin-left:4px;font-size:var(--fs-3xs);vertical-align:1px}
  @media (hover:hover){ [data-txsort]:hover{color:var(--red-ink)} }
  th.colf{cursor:pointer;user-select:none;white-space:nowrap}
  th.colf .cficon{width:11px;height:11px;margin-left:5px;vertical-align:-1px;
    fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;opacity:.45}
  @media (hover:hover){ th.colf:hover .cficon{opacity:.9} }
  th.colf.on .cficon{opacity:1;stroke:var(--red)}
  /* The one menu that hangs off a table header (colFilter in the page): the
     values that column holds, a count each, a checkbox each. One panel, one
     rim, one place to change either, for every page that narrows by column. */
  #colMenu{position:fixed;display:none;z-index:120;background:var(--panel);border:1px solid var(--line);border-radius:8px;
    box-shadow:var(--shadow-pop);width:290px;max-width:calc(100vw - 16px);max-height:380px;padding:8px;flex-direction:column}
  #colMenu.open{display:flex}
  /* The search box is for a column that holds many values — the ledger's
     counterparties, the Documents tab's merchants — where a box to narrow them
     earns its place; a column of six values (direction, status) drops it. */
  #colMenu input.cfsearch{font:inherit;font-size:var(--fs-sm);padding:6px 8px;border:1px solid var(--line);border-radius:6px;margin-bottom:6px}
  @media (pointer:coarse){ #colMenu input.cfsearch{font-size:var(--fs-xl)} }
  #colMenu .cfacts{display:flex;gap:6px;margin-bottom:6px}
  #colMenu .cfacts button{font:inherit;font-size:var(--fs-xs);padding:3px 9px;border:1px solid var(--line);border-radius:5px;background:var(--panel);cursor:pointer}
  #colMenu .list{overflow:auto;flex:1 1 auto}
  #colMenu .cfrow{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;cursor:pointer;font-size:var(--fs-sm)}
  #colMenu .cfrow .cfname{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  #colMenu .cfrow .cfn{margin-left:auto;color:var(--dim);font-size:var(--fs-xs);font-variant-numeric:tabular-nums;flex:0 0 auto}
  #colMenu .cfrow input{margin:0;flex:0 0 auto}
  @media (hover:hover){ #colMenu .cfrow:hover{background:var(--rowhover)} }
  #colMenu .cfnone{font-size:var(--fs-sm);color:var(--muted);padding:6px 8px}
  /* The Category column shapes its list like the per-transaction picker: the
     accounting type beside each name (not spelled into it), children tucked
     under their parent. cftype/cfgroup mirror #catMenu's ctype/catgroup. */
  #colMenu .cfrow .cftype{color:var(--dim);font-style:italic;font-size:var(--fs-xs);flex:0 0 auto}
  #colMenu .cfrow.sub{padding-left:26px}
  #colMenu .cfgroup{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
    padding:9px 8px 3px;font-size:var(--fs-sm);font-weight:600;color:var(--text);cursor:default}
  #colMenu .cfgroup .cftype{color:var(--dim);font-style:italic;font-weight:400;flex:0 0 auto}
  /* A value nothing would answer to recedes rather than going: it is still in
     the column, and a row you can see is off is how you learn that. */
  #colMenu .cfrow.zero{color:var(--muted)}
  /* "new merchants for review" banner on the transactions view */
  .txreview{background:var(--amb-bg);border:1px solid var(--amb-bd);border-radius:10px;padding:10px 14px;margin-bottom:14px}
  .txreview h3{margin:0 0 2px;color:var(--amb-tx);font-size:var(--fs-lg)}
  .txreview .sub{font-size:var(--fs-sm);color:var(--amb-tx-3);margin:0 0 6px}
  .txrev-row{display:flex;align-items:center;gap:8px;padding:7px 0;border-top:1px solid var(--amb-line);flex-wrap:wrap;font-size:var(--fs-sm)}
  .txrev-row b{flex:0 1 auto}
  .txrev-row .meta{color:var(--amb-tx-3);font-size:var(--fs-sm)}
  /* a preselected likely match (the bank's spelling of a known name) — green:
     one look, one tap on Save */
  .txrev-row .txrev-sug{color:var(--grn-tx);font-weight:600}
  /* the receipts a till name arrived on: quiet until you go for one, because
     they are a way to answer the row's question, not the answer to it */
  .txrev-row .txrev-ord{font:inherit;font-size:var(--fs-xs);padding:1px 8px;margin-right:4px;
    border:1px solid var(--amb-bd);border-radius:999px;background:transparent;
    color:var(--amb-tx-3);cursor:pointer}
  @media (hover:hover){ .txrev-row .txrev-ord:hover{background:var(--amb-line);color:var(--amb-tx)} }
  .txrev-row .txrev-more{color:var(--amb-tx-3);font-size:var(--fs-xs)}
  .txrev-row select{font:inherit;font-size:var(--fs-xs);max-width:220px;padding:2px 3px;border:1px solid var(--line);border-radius:5px;background:var(--panel)}
  /* home view — the landing page: where the money stands, and what needs a human */
  #homeview h3,#teamview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:22px 0 10px}
  #homeview h3:first-child,#teamview h3:first-child{margin-top:0}
  /* the balances: the total this page is opened for, then the accounts that make
     it up, one line each. What each account is FOR and whether its figure can be
     trusted have not gone: the strip under the total counts the verdicts, and an
     account with something to say still says it on its row. See homeBalanceHtml
     for which of them that is, and why the rest collapse.
     .hbals / .hbal remain for the error state, which is one tile. */
  .hbals{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;margin-bottom:16px}
  .hbal{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px 16px}
  .hbal .lab{font-size:var(--fs-xs);font-weight:bold;letter-spacing:.06em;text-transform:uppercase;color:var(--dim);margin-bottom:2px}
  .hbal .amt{font-size:var(--fs-4xl);font-weight:bold;letter-spacing:-.01em;line-height:1.1;font-variant-numeric:tabular-nums}
  .hbal .amt.neg{color:var(--block-ink)}
  .hbal .hrec{margin-top:9px}
  .hbal .why{font-size:var(--fs-xs);color:var(--dim);margin-top:5px;line-height:1.35}
  /* ---- the total ----
     The one figure on this page that is bigger than a heading, because it is the
     question the page is opened with. Everything under it is a constituent. */
  .hbtot{padding:2px 2px 16px}
  .hbt-lab{font-size:var(--fs-xs);font-weight:bold;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}
  .hbt-amt{display:flex;align-items:center;gap:12px;margin-top:4px;
    font-size:var(--fs-5xl);font-weight:bold;letter-spacing:-.02em;line-height:1.05;
    font-variant-numeric:tabular-nums;color:var(--ink-2)}
  /* what is in the total that is not yet in a bank account */
  .hbt-of{font-size:var(--fs-sm);color:var(--dim);margin-top:6px;line-height:1.4}
  /* Six verdicts on one line. The dots are the state of each account in the
     order they are listed below, and the words are what the dots add up to -
     a dot is the least readable encoding there is, so it never travels alone.
     The whole strip takes the colour of the WORST account on it: a summary that
     stays green while one account is failing is worse than no summary. */
  #homeview button.hbt-health{display:inline-flex;align-items:center;gap:10px;margin-top:13px;
    padding:7px 13px;border-radius:999px;font-size:var(--fs-sm);cursor:pointer;
    border:1px solid var(--line);background:var(--panel);color:var(--text)}
  #homeview button.hbt-health.ok{color:var(--grn-tx-2);background:var(--grn-bg-2);border-color:var(--grn-bd-2)}
  #homeview button.hbt-health.stale{color:var(--amb-tx-3);background:var(--amb-bg);border-color:var(--amb-bd)}
  #homeview button.hbt-health.bad{color:var(--red-tx);background:var(--red-bg);border-color:var(--red-bd)}
  /* the strip is a toggle, so it carries the one mark that says so */
  .hbt-caret{flex:0 0 auto;font-size:var(--fs-3xs);opacity:.75;line-height:1}
  .hbt-dots{display:flex;gap:4px;flex:0 0 auto}
  .hbt-dots i{display:block;width:7px;height:7px;border-radius:50%;background:var(--done)}
  /* ---- the accounts ----
     One panel divided by hairlines rather than a tile each: they are constituents
     of the figure above, and six bordered boxes read as six separate claims. */
  .hbrows{background:var(--panel);border:1px solid var(--line);border-radius:12px;
    overflow:hidden;margin-bottom:16px}
  .hbrow{display:flex;align-items:center;justify-content:space-between;gap:12px;
    padding:12px 14px;border-top:1px solid var(--grid)}
  .hbrow:first-child{border-top:0}
  .hbr-l{min-width:0}
  .hbr-n{font-size:var(--fs-md);color:var(--ink-2)}
  /* What this account's figure IS — the clearing accounts, the loan — and, on the
     two accounts nothing outside the ledger can confirm, that nobody has checked
     it. Under the name rather than beside it: "Revolut (pending settlement)" and
     "Awaiting payout" do not fit on one line of a phone, and a label that wraps
     around the account it qualifies reads as part of its name. */
  .hbr-k{display:block;margin-top:2px;font-size:var(--fs-2xs);color:var(--dim);
    text-transform:uppercase;letter-spacing:.05em}
  .hbr-k.na{color:var(--na-tx)}
  /* The clearing account's two halves. Not uppercase like the label it replaces:
     it is two amounts and a reading of them, and small caps make a figure harder
     to take in at a glance, which is the whole job of this line. `.go` lifts the
     sweepable half out of the qualifying grey — it is the only figure on this
     block anyone acts on — and `.warn` is for the state where there isn't one. */
  .hbr-k.split{text-transform:none;letter-spacing:0;font-size:var(--fs-xs);color:var(--dim)}
  .hbr-k.split.go{color:var(--ink-3)}
  .hbr-k.split.warn{color:var(--amb-tx-3)}
  .hbr-a{font-size:var(--fs-lg);font-weight:600;color:var(--ink-2);font-variant-numeric:tabular-nums;
    white-space:nowrap}
  .hbr-a.neg{color:var(--block-ink)}
  /* the row that has something to say, and the lines it says it in */
  .hbrow.says{align-items:flex-start}
  .hbr-say{margin-top:7px}
  .hbr-say .why{font-size:var(--fs-xs);color:var(--dim);margin-top:5px;line-height:1.35}
  /* the as-of line sits under the verdict, and under the reason when there is
     one — quieter than both, since it qualifies them rather than competing */
  .hbal .why.asof,.hbr-say .why.asof{font-size:var(--fs-2xs);margin-top:4px}
  .hrec{display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-xs);font-weight:bold;padding:4px 10px;border-radius:999px;border:1px solid}
  .hrec.ok{color:var(--grn-tx-2);background:var(--grn-bg-2);border-color:var(--grn-bd-2)}
  .hrec.bad{color:var(--red-tx);background:var(--red-bg);border-color:var(--red-bd)}
  /* nothing outside the ledger states this balance. Grey, not green: an
     unchecked figure must not read as a checked one */
  .hrec.na{color:var(--na-tx);background:var(--soft-2);border-color:var(--line)}
  /* the check passed, but on figures a dead feed stopped refreshing. Amber, not
     green, for the same reason .na is grey: a pass on stale inputs is a weaker
     claim than a pass, and must not look identical to one */
  .hrec.stale{color:var(--amb-tx-3);background:var(--amb-bg);border-color:var(--amb-bd)}
  .hbal .why.feed,.hbr-say .why.feed{color:var(--red-tx)}
  .hrec .delta{font-weight:normal;opacity:.85}
  /* ===== LOANS =============================================================
     One question per level. The page states what the company owes in total,
     each card states what one facility owes, each bar states how far one
     tranche has left to run. A figure is stated once, at the level that
     answers for it — Facility / Outstanding / Advanced / Returned used to
     appear at all three, twelve numbers of equal size with nothing saying
     which one mattered.

     Colour is status and nothing else. Red and amber mean a person has to do
     something (unsigned, unreported, over the limit, behind schedule), so the
     money itself is drawn in neutral tints: light grey for what is repaid, one
     slate accent for what is still out. A data series must not borrow an
     alarm's colour, or the alarm stops being visible — which is what five
     competing accents had done to this page.

     Four words carry every figure: OWED, DRAWN, REPAID, LIMIT. Undrawn is said
     as a sentence where it matters rather than as a fifth tile, and "returned"
     and "principal" are gone — a reader should not have to work out whether
     returned and repaid are the same thing. They were.

     Content is capped at 1100px: wider than that, a label on the left and its
     number on the right stop reading as one fact. */
  .lwrap{max-width:1100px;--l-out:#4c5563;--l-paid:#e0e4e9}
  html[data-theme="dark"] .lwrap{--l-out:#96a1af;--l-paid:#333a45}
  /* the page's one hero: what is owed, once, in one number */
  .lhero{margin:0 0 18px}
  .lhero .n{font-size:var(--fs-5xl);font-weight:bold;line-height:1.1;font-variant-numeric:tabular-nums}
  .lhero .u{font-size:var(--fs-sm);color:var(--dim);margin-left:9px;font-weight:normal}
  .lhero .sub{font-size:var(--fs-sm);color:var(--text-2);margin-top:6px;line-height:1.55;max-width:70ch}
  /* NEEDS ATTENTION — the handful of things actually waiting on a person,
     collected out of the three cards they were scattered across. "Nothing is
     repayable yet" is the most important sentence on this page and it used to
     be grey-on-grey below a header. Amber, because every row here is a
     decision someone has not made yet; each row scrolls to the thing it is
     about, so the block is a route in rather than a second copy. */
  .lattn{background:var(--amb-bg);border:1px solid var(--amb-bd);border-radius:10px;
    padding:2px 15px 6px;margin:0 0 18px}
  .lattn-h{font-size:var(--fs-xs);font-weight:bold;color:var(--amb-tx-3);margin:10px 0 0}
  .lattn-r{display:block;width:100%;text-align:left;font:inherit;font-size:var(--fs-sm);line-height:1.5;
    color:var(--text-2);background:none;border:none;border-top:1px solid var(--amb-bd);
    padding:8px 0;cursor:pointer}
  .lattn-r:first-of-type{border-top:none}
  .lattn-r b{color:var(--text);font-weight:bold}
  .lattn-r .d{color:var(--dim);display:block;margin-top:1px}
  @media (hover:hover){ .lattn-r:hover b{text-decoration:underline} }
  /* TRANCHE BARS — what the chart was trying to say, in the space a chart of
     one fact deserves. Whole width is what was drawn; light grey is repaid,
     the accent is what is still out. */
  .lbars{margin:14px 0 0;display:grid;gap:11px}
  .lbar-h{display:flex;align-items:baseline;gap:8px;font-size:var(--fs-sm);margin-bottom:5px}
  .lbar-h .nm{font-weight:bold}
  .lbar-h .pp{color:var(--dim);font-size:var(--fs-sm);flex:1 1 auto;min-width:0;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .lbar-h .amt{margin-left:auto;font-weight:bold;font-variant-numeric:tabular-nums}
  .lbar-h .amt .u{font-weight:normal;color:var(--dim);font-size:var(--fs-sm)}
  .lbar-t{height:9px;border-radius:999px;background:var(--l-paid);overflow:hidden;display:flex}
  .lbar-t i{display:block;height:100%;background:var(--l-out)}
  /* over the ceiling is the one bar state that IS a problem, so it is the one
     bar that gets to be red. When every bar was red this had nowhere to live. */
  .lbar-t.over i{background:var(--red-tx)}
  .lbar-m{font-size:var(--fs-sm);color:var(--dim);line-height:1.5;margin-top:5px}
  .lbar-m .sep{margin:0 6px;opacity:.6}
  .lbar-m .tag{font-size:var(--fs-2xs);font-weight:bold;padding:1px 6px;border-radius:4px;
    background:var(--tagbg);color:var(--text-2);margin-left:6px}
  .lbar-m .short{color:var(--red-tx);font-weight:bold}
  /* everything below is reference: it opens when asked for and is shut when not */
  .lfold{margin-top:11px;border-top:1px solid var(--grid);padding-top:3px}
  .lfold summary{font-size:var(--fs-sm);font-weight:bold;color:var(--text-2);cursor:pointer;
    padding:6px 0;user-select:none}
  .lfold[open] summary{margin-bottom:7px}
  .lfold .loannote{margin-top:8px}
  /* a fold at page level is its own block, not the top of the card below it */
  .lwrap>.lfold{border-top:none;margin:0 0 14px;padding-top:0}
  /* unfiled draws are the one fold that is a standing to-do, not a footnote:
     an amber card, open by default, so a new loan mark cannot sit unseen and
     out of the facility totals. Amber not red — money to file, not an error. */
  .lwrap>.lfold.lwarn{border:1px solid var(--amb-bd);background:var(--amb-bg);
    border-radius:10px;padding:9px 12px;margin:0 0 14px}
  .lfold.lwarn>summary{color:var(--amb-tx-3)}
  .loantbl{margin-top:2px}
  .loantbl th{white-space:nowrap}
  .loantbl td.num,.loantbl th.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
  .loantbl .tname{font-weight:bold}
  /* explicitly block: a sub-line that inlines runs into the figure above it,
     which is how "5%" and "needs Opening Date" ended up as "5%needs Opening Date" */
  .loantbl .tsub{display:block;color:var(--dim);font-weight:normal;font-size:var(--fs-sm)}
  /* the rate trigger in the agreement's own words: one footnote under the
     table it governs, not a full-width row breaking the table into fragments */
  .loanfn{font-size:var(--fs-sm);color:var(--dim);line-height:1.5;margin-top:8px;max-width:78ch}
  .loanfn b{color:var(--text-2)}
  .loannote{font-size:var(--fs-sm);color:var(--dim);line-height:1.5;margin-top:9px;max-width:74ch}
  .loannote+.loannote{margin-top:7px}
  .loanflag{color:var(--red-tx);font-weight:bold}
  /* entities are things, not prose: lender and borrower read as named parties,
     and the arrow says which way the money went. Neutral — being the lender is
     not a state anyone needs to act on, and it used to be painted alarm-red. */
  .epill{display:inline-block;font-size:var(--fs-sm);font-weight:bold;padding:2px 9px;border-radius:999px;
    background:var(--panel-2);border:1px solid var(--line);color:var(--text-2)}
  /* the lender it USED to be: struck through, because the whole point of
     showing it is that it is no longer who gets paid */
  .epill.was{opacity:.75;font-weight:normal;text-decoration:line-through}
  /* a document is a link to a file, so it looks like one: the Drive mark, the
     title, a border. No pipe separators pretending to be structure. */
  .ldocs{display:flex;flex-wrap:wrap;gap:7px;margin:11px 0 0}
  .dpill{display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-sm);padding:4px 10px 4px 8px;
    border-radius:999px;border:1px solid var(--line);background:var(--panel-2);
    color:var(--text-2);text-decoration:none}
  @media (hover:hover){ .dpill:hover{border-color:var(--line-2);background:var(--panel)} }
  /* THE PROJECTION. Behind a disclosure, because it projects something that
     has not started: every schedule runs from an Opening Date that is not set.
     Stepped, never filled — a balance is flat between monthly payments, and an
     area sloping smoothly across the gap draws a repayment that does not
     happen. One hue at two weights rather than two colours, because red and
     amber are status here and a series must not borrow an alarm. */
  .lchart{margin-top:4px;position:relative}
  /* uniform scale: the old chart stretched its viewBox to the container with
     preserveAspectRatio="none", which scaled every glyph in it 2× horizontally */
  .lchart svg{width:100%;height:auto;display:block;overflow:visible}
  .lgrid{stroke:var(--grid);stroke-width:1}
  .lytick,.lxtick{font-size:var(--fs-3xs);fill:var(--dim)}
  .lytick{text-anchor:end} .lxtick{text-anchor:middle} .lxtick.last{text-anchor:end}
  .lline{fill:none;stroke:var(--l-out);stroke-width:1.6;stroke-linejoin:round}
  .lline.sub{stroke-width:1.2;stroke-opacity:.42}
  /* labels at the line's own end, on the panel — not buried in a fill under a
     text shadow heavy enough to admit the fill should not have been there */
  .ldl{font-size:var(--fs-2xs);font-weight:bold;fill:var(--text-2)}
  .ldl.sub{font-weight:normal;fill:var(--dim)}
  .lcross{stroke:var(--line-2);stroke-width:1;stroke-dasharray:3 3;pointer-events:none}
  [data-lhit]{cursor:crosshair}
  .ltip{position:absolute;top:0;background:var(--panel);border:1px solid var(--line);border-radius:8px;
    padding:7px 10px;font-size:var(--fs-sm);pointer-events:none;box-shadow:var(--shadow-pop);min-width:158px}
  .ltip>b{display:block;margin-bottom:3px}
  .ltip .lrow{display:flex;align-items:center;gap:6px;line-height:1.7;color:var(--text-2)}
  .ltip .lrow b{margin-left:auto;font-variant-numeric:tabular-nums;color:var(--text)}
  .ltip .lrow.tot{border-top:1px solid var(--line);margin-top:3px;padding-top:3px}
  .lchart-f{font-size:var(--fs-xs);color:var(--dim);line-height:1.5;margin-top:7px;max-width:74ch}
  /* ---- the facilities comparison table: attributes down, facilities across --- */
  .lcmpwrap{overflow-x:auto;margin:0 0 8px}
  .lcmp{border-collapse:collapse;width:100%;min-width:600px;font-size:var(--fs-sm)}
  .lcmp col.k{width:180px}
  .lcmp th,.lcmp td{padding:9px 13px;border-bottom:1px solid var(--grid);text-align:left;vertical-align:top}
  .lcmp thead th{border-bottom:1px solid var(--line);vertical-align:bottom}
  .lcmp thead .fac{font-size:var(--fs-lg);font-weight:bold}
  .lcmp thead .facsub{display:block;font-size:var(--fs-2xs);font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dim);margin-top:3px}
  .lcmp td.k{color:var(--text-2);font-weight:600;white-space:nowrap;background:var(--soft)}
  /* the empty top-left corner is not a label cell — leave it unshaded */
  .lcmp thead td.k{background:none}
  .lcmp td.v{font-variant-numeric:tabular-nums}
  .lcmp .vs{display:block;color:var(--dim);font-size:var(--fs-xs);margin-top:2px;font-variant-numeric:normal;font-weight:normal}
  .lcmp tr.sec td{background:var(--panel-2);color:var(--dim);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.06em;font-weight:bold;padding:7px 13px;border-bottom:1px solid var(--line)}
  .lcmp tr.owed td.v{font-size:var(--fs-2xl);font-weight:bold;line-height:1.15}
  .lcmp td.na{color:var(--muted-3)}
  .lcmp .vbar{height:8px;border-radius:999px;background:var(--l-paid);overflow:hidden;display:flex;margin-top:6px;max-width:200px}
  .lcmp .vbar i{display:block;height:100%;background:var(--l-out)}
  .lcmp .vbar.over i{background:var(--red-tx)}
  .lcmp .sep{margin:0 5px;opacity:.55}
  /* the paperwork timeline in a cell */
  .ltl{display:flex;flex-direction:column;gap:11px}
  .ltl-i{position:relative;padding-left:16px}
  .ltl-i::before{content:"";position:absolute;left:2px;top:4px;width:7px;height:7px;border-radius:50%;background:var(--l-out);box-shadow:0 0 0 2px var(--panel)}
  .ltl-i::after{content:"";position:absolute;left:5px;top:12px;bottom:-11px;width:1px;background:var(--line)}
  .ltl-i:last-child::after{display:none}
  .ltl-d{font-size:var(--fs-xs);color:var(--dim);margin-bottom:4px}
  .ltldocs{display:flex;flex-wrap:wrap;gap:6px}
  /* merged detail: grouped-by-tranche rows, section head, per-drawdown report */
  .lsecttl{font-size:var(--fs-2xl);font-weight:bold;margin:22px 2px 10px}
  .loantbl tr.lgrp td{background:var(--band);border-top:1px solid var(--line);font-weight:bold}
  .loantbl tr.lgrp td.num{color:var(--text-2)}
  .loantbl tr.ltot td{border-top:1px solid var(--line);font-weight:bold;color:var(--ink)}
  .loantbl.aligned{table-layout:fixed;width:100%}
  /* the two detail tables sit in full-width wrappers so their shared fixed
     column layout lines up between the loan and the credit line */
  .tblwrap.lfull{width:100%}
  .loantbl.aligned td,.loantbl.aligned th{overflow:hidden;text-overflow:ellipsis}
  .loantbl.aligned td .tsub{white-space:normal}
  .lacc{color:var(--amb-tx-3);font-weight:600}
  .lfree{color:var(--dim);font-weight:600}
  .lsmeta{font-weight:normal;font-size:var(--fs-xs);color:var(--dim);margin-left:6px}
  /* the filing control: inside a card the facility is already the heading, so
     the option is the tranche — "Shareholder Loan Agreement · Tranche…",
     truncated identically on all thirteen rows, told the reader nothing */
  .lalloc{font-size:var(--fs-sm);max-width:190px}
  .lexcl td{opacity:.55}
  .lin{color:var(--done-ink)}
  .lrep{font-size:var(--fs-xs);color:var(--dim);white-space:nowrap}
  /* marking an advance reported is rare and reversible: a quiet link, not four
     stacked buttons competing with the figures they sit beside */
  .lrepbtn{font:inherit;font-size:var(--fs-xs);background:none;border:none;padding:0;cursor:pointer;
    color:var(--link);white-space:nowrap;text-decoration:underline}
  .lrepbtn.due{color:var(--amb-tx-3);font-weight:bold}
  /* action items: one card per thing needing a decision, each a link into the
     page that fixes it, filtered to exactly the rows the count came from */
  .hcards{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px}
  /* A card is a card whichever element it is built on, and it says so in plain
     classes. This used to need "#txview button.hcard" as well: that page's
     toolbar chip is 11px type in 3px of padding, it was reached by an id, and it
     was quietly winning the card's box back off it. The page button is layered
     now (see .view button) and an unlayered rule beats a layered one whatever
     the specificity, so the id rule went — and with it the reason the mobile
     block below could not reach these cards on that one page. */
  /* The three metrics the card is built from, named because the dot and the
     count are positioned AGAINST them below and a hard-coded top would drift
     the moment any of the three changed. */
  .hcard{--cpad-y:12px;--cpad-x:14px;--clab:18px}
  .hcard{display:block;position:relative;text-align:left;background:var(--panel);border:1px solid var(--line);border-radius:10px;
    padding:var(--cpad-y) var(--cpad-x);cursor:pointer;text-decoration:none;color:inherit;font:inherit;width:100%;
    white-space:normal}
  /* The dot and the count are children of the card, because on a phone the card
     is one row and they are slots in it. Here, where it is a block, they take
     the two top corners by position — the same trick the delta pill uses on
     Home's tiles, for the same reason.

     Both are centred on the LABEL's line, which is the line they belong to: the
     dot by half the difference between the label row and its own 6px, the count
     by being given that row's height and centring inside it. The label keeps
     clear of whichever of them is actually there. */
  .hcard > .cdot{position:absolute;left:var(--cpad-x);
    top:calc(var(--cpad-y) + (var(--clab) - 6px)/2)}
  .hcard:not(.nodot) .t{padding-left:12px}
  .hcard > .cbadge{position:absolute;right:var(--cpad-x);top:var(--cpad-y);
    height:var(--clab);display:inline-flex;align-items:center}
  .hcard.haspill .t,.hcard.hasflag .t{padding-right:38px}
  @media (hover:hover){ .hcard:hover{border-color:var(--red);background:var(--panel)}}
  /* Label first, then the figure, then the line that qualifies it. Every stat
     card in the app is this shape — see statCard(), which is the only thing
     that builds one. The label leads because a bare number invites the question
     the label already answers. */
  /* min-height leaves room for the count badge, so a card that has one keeps
     its figure on the same line as a card that does not */
  .hcard .t{display:flex;align-items:center;gap:6px;min-height:var(--clab);font-size:var(--fs-2xs);
    font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--dim);line-height:1.3}
  .hcard .t .ctxt{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* The corner mark on a card you can open (the wait-time card pops out its
     distribution). Pinned top-right so it reads as an affordance, not a value;
     clear of the figure, which right-aligns below it. Faint, brighter on hover. */
  .hcard .clkico{position:absolute;top:8px;right:10px;width:13px;height:13px;opacity:.4;pointer-events:none}
  .hcard:hover .clkico{opacity:.9}
  /* the (i) that holds an Orders card's explainer (see statInfoPop and ordCard).
     A small ringed glyph, brighter on hover/focus; it borrows the card's state
     colour so a warn or due card's (i) reads in the same key. Positioned in the
     corner by the .ocard rules below. */
  .ocard .cinfo{flex:0 0 auto;width:15px;height:15px;border-radius:50%;border:1.2px solid var(--muted);
    color:var(--muted);font-size:10px;font-weight:bold;font-style:italic;line-height:1;
    display:inline-flex;align-items:center;justify-content:center;cursor:pointer;
    font-family:Georgia,"Times New Roman",serif;text-transform:none;letter-spacing:0}
  @media (hover:hover){ .ocard .cinfo:hover{border-color:var(--ink);color:var(--ink)} }
  .ocard .cinfo:focus-visible{outline:2px solid var(--red);outline-offset:1px}
  .ocard.warn .cinfo{border-color:var(--amb-tx);color:var(--amb-tx)}
  .ocard.due .cinfo{border-color:var(--red-tx);color:var(--red-tx)}
  #statScrim{position:fixed;inset:0;z-index:60;display:none}
  #statScrim.on{display:block}
  #statPop{position:fixed;z-index:61;max-width:240px;background:var(--panel);color:var(--ink);
    border:1px solid var(--line);border-radius:8px;padding:10px 12px;font-size:var(--fs-sm);
    line-height:1.45;box-shadow:var(--shadow-pop);display:none}
  #statPop.on{display:block}
  #statPop .sp-t{font-weight:bold;font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.06em;
    color:var(--dim);margin-bottom:3px}
  /* ---- .ocard — the Orders strip's stat card ------------------------------
     A SEPARATE component from .hcard (the key stat tile). Its own two-row shape
     and nothing else's: a title on the top line with an optional count trailing
     at the right, the figure on the line below — never more than two lines of
     text. The title keeps a full line, so a long one ellipses rather than
     wrapping into a third row, and the (i) and any corner icon (the wait
     pop-out, the Unreconciled chevron) leave the text for the bottom-right
     corner. See ordCard() for the markup. Home and every page's .hcard strip
     are untouched by any of this. */
  .ocards{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;margin-bottom:14px}
  .ocard{position:relative;display:flex;flex-direction:column;justify-content:center;gap:6px;
    min-height:74px;padding:12px 13px 13px;background:var(--panel);border:1px solid var(--line);
    border-radius:10px;text-align:left;text-decoration:none;color:inherit;font:inherit;width:100%;cursor:default}
  button.ocard{cursor:pointer}
  @media (hover:hover){ button.ocard:hover{border-color:var(--line-2)} }
  /* row one: the title, and its count pushed to the right on the same baseline */
  .ocard-h{display:flex;align-items:baseline;gap:8px;min-width:0}
  .ocard-t{flex:1;min-width:0;font-size:var(--fs-2xs);font-weight:600;letter-spacing:.07em;
    text-transform:uppercase;color:var(--dim);line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .ocard-c{flex:0 0 auto;font-size:var(--fs-2xs);color:var(--dim);white-space:nowrap}
  /* row two: the figure, big and left */
  .ocard-n{font-size:var(--fs-3xl);font-weight:bold;line-height:1;white-space:nowrap;color:var(--ink)}
  .ocard-n .neg{color:var(--red-tx)}
  /* state — the tint the card carries, and the figure and (i) that read in its key */
  .ocard.warn{border-color:var(--amb-bd);background:var(--amb-bg)}
  .ocard.warn .ocard-n{color:var(--amb-tx)}
  .ocard.due{border-color:var(--red-bd);background:var(--red-bg)}
  .ocard.due .ocard-n{color:var(--red-tx)}
  /* the corner marks: the icon at the very corner, the (i) just inside it when
     the card carries both (every icon card here also carries an (i)) */
  .ocard .cinfo{position:absolute;bottom:8px;right:10px}
  .ocard.hasico .cinfo{right:30px}
  .ocard .clkico{position:absolute;top:auto;bottom:8px;right:10px;width:13px;height:13px;opacity:.55;pointer-events:none}
  button.ocard:hover .clkico{opacity:.95}
  .hcard .cdot{width:6px;height:6px;border-radius:50%;flex:0 0 auto}
  .hcard .cdot.ok{background:var(--done)} .hcard .cdot.stale{background:var(--prog)}
  .hcard .cdot.bad{background:var(--block)} .hcard .cdot.na{background:var(--muted-3)}
  .hcard .cbadge{letter-spacing:0;font-size:var(--fs-2xs);font-weight:bold;
    color:var(--amb-tx);background:var(--amb-bg);border:1px solid var(--amb-bd);
    border-radius:20px;padding:1px 7px}
  .hcard .s.gap{color:var(--red-tx)}
  /* the account tile the ledger is filtered by */
  .hcard.on{border-color:var(--red);box-shadow:inset 0 0 0 1px var(--red)}
  .hcard .n .neg{color:var(--red-tx)}
  .hcard .n{font-size:var(--fs-3xl);font-weight:bold;line-height:1.15;margin-top:5px}
  /* hero figures ride the display face (Archivo); labels and body stay system sans. §04.
     The set is every big bold figure: the Home/Sales KPI tiles (.tsum .sv), the money-tile
     headline (.tbig) and cash grand total (.hbt-amt), the stat/orders cards (.hcard .n,
     .ocard-n), a drawer's headline amount (.phead .pamt), and the legacy/loans heroes. */
  .hcard .n,.ocard-n,.hbal .amt,.lhero .n,
  .tsum .sv,.tbig,.hbt-amt,.phead .pamt{font-family:var(--disp)}
  .hcard .s{font-size:var(--fs-xs);color:var(--dim);margin-top:4px;line-height:1.35}
  /* nothing to do reads as calm, not as an error */
  .hcard.zero .n{color:#2fae66}
  .hcard.warn{border-color:var(--amb-bd);background:var(--amb-bg)}
  .hcard.warn .n{color:var(--amb-tx)}
  .hcard.due{border-color:var(--red-bd);background:var(--red-bg)}
  .hcard.due .n{color:var(--red-tx)}
  /* coming up: a dated run of what has to be paid, invoices and fixed
     obligations in one sequence, because cash doesn't care which is which */
  .hup{background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden}
  .hempty{font-size:var(--fs-sm);color:var(--dim);padding:12px 14px}
  /* "Nothing assigned to you — 40 open in all". The way out of a correctly
     empty list, at the same size as the sentence it finishes. */
  #homeview .hempty .tgo{font-size:inherit;color:var(--text-2);font-weight:normal}
  /* ---- Transactions: the account header -----------------------------------
     The same stat card every other page's header strip is made of, plus the two
     things an account has that a count of products does not: a reconciliation
     verdict, and how many of its rows still want a person. */
  /* start, not stretch: a tile with something extra to say (a ledger that
     disagrees) grows downwards on its own rather than making every other tile
     as tall as itself. Every figure still begins on the same line. */
  .atiles{margin-bottom:12px;align-items:start}

  /* ---- Home: the tile bands ------------------------------------------------
     Three bands, nine tiles. Trading across the top because it is what changes
     week to week, money under it because it changes slower and matters more,
     and the two lists at the foot because they are where you actually click.
     All three are titled: the lists were the odd one out, an untitled pair
     floating under a labelled band, and a reader scanning the headings down the
     page lost the thread exactly where the page wanted them to act. */
  #homeview .hband,#salesview .hband{margin-bottom:18px}
  #homeview .hbandhead,#salesview .hbandhead{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:9px}
  #homeview .hbandhead h4,#salesview .hbandhead h4{font-size:var(--fs-2xs);font-weight:600;letter-spacing:.1em;text-transform:uppercase;
    color:var(--dim);margin:0}
  .hlegend{display:flex;gap:14px;align-items:center}
  .hlegend span{display:inline-flex;align-items:center;gap:5px;font-size:var(--fs-2xs);color:var(--text)}
  .hlegend i{width:9px;height:9px;border-radius:2px;display:block}
  .hlegend i.now{background:var(--c-now)} .hlegend i.prev{background:var(--c-prev)}
  .htiles{display:grid;gap:10px}
  .htiles.t4{grid-template-columns:repeat(4,minmax(0,1fr))}
  .htiles.t3{grid-template-columns:repeat(3,minmax(0,1fr))}
  /* a tile is a stat card with something extra below the figure */
  .htile{border:1px solid var(--line);border-radius:10px;background:var(--panel);padding:13px 14px 11px;min-width:0}
  /* a fixed header height, so the figure under it starts at the same y on all
     three money tiles whether or not the tile carries a button */
  .htile .th{display:flex;align-items:center;gap:8px;min-width:0;min-height:22px}
  .htile .tk{flex:1;min-width:0;font-size:var(--fs-2xs);font-weight:600;letter-spacing:.07em;text-transform:uppercase;
    color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* the label is the way into the page behind the tile, and it resets the
     button's CHROME and nothing else — .tk owns the type, so a label reads the
     same whether or not it happens to be clickable.

     That last sentence was the intention before it was true. This used to be
     scoped "#homeview .tgo" to outrank #homeview button, and the hover below
     used a doubled class to outrank #homeview button:hover — and even so the
     TYPE still leaked: font:inherit;font-size:var(--fs-sm) beat .tk's 10px/600, so a
     tile label you could click rendered two points larger and lighter than the
     identical label beside it that you could not. The page's button is layered
     now (see .view button), which an unlayered class beats outright, so the
     scoping and the doubled class are both gone and the type finally lands. */
  .tgo{display:inline-flex;align-items:center;gap:3px;background:none;border:0;
    padding:0;border-radius:0;font-family:inherit;cursor:pointer}
  #homeview .hlist-h .tgo{font-size:var(--fs-xs);color:var(--dim);font-weight:normal}
  .tgo .caret{opacity:.45;font-size:1.05em;line-height:1}
  /* a label is not a chip: it takes the accent and no filled bar behind it */
  @media (hover:hover){ .tgo:hover{color:var(--red-ink)} .tgo:hover .caret{opacity:1} }
  /* the home/sales up/dn/flat delta indicator. Renamed from .dpill, which
     collided with the loans document pill (.ldocs .dpill) — one name, two
     unrelated components. Carries the 1px --line border it used to inherit by
     cascade from that pill, so the rename is a no-op visually; it no longer
     picks up that pill's hover, which had wrongly swapped the tint on hover. */
  .deltapill{display:inline-flex;align-items:center;padding:2px 6px;border-radius:20px;font-size:var(--fs-2xs);
    font-weight:600;white-space:nowrap;border:1px solid var(--line)}
  .deltapill.up{color:var(--grn-tx);background:var(--grn-bg)}
  .deltapill.dn{color:var(--red-tx);background:var(--red-bg)}
  .deltapill.flat{color:var(--dim);background:var(--track)}
  .tchart{display:block;width:100%;margin-top:9px;overflow:visible}
  .tchart .hit{fill:transparent;cursor:crosshair}
  /* Toggled by CLASS, not by an opacity attribute — a presentation attribute
     loses to any CSS rule, so setAttribute("opacity","1") never showed it. */
  .tchart .cross{stroke:var(--dim);stroke-width:1;opacity:0;transition:opacity .08s}
  .tchart .cross.on{opacity:.75}
  /* one tooltip for the whole view, moved to whichever chart is under the
     pointer — a per-chart node would be eight of them fighting for z-index */
  #homeTip{position:absolute;pointer-events:none;opacity:0;transition:opacity .1s;z-index:40;max-width:92%;
    background:var(--ink-strong);color:var(--panel);font-size:var(--fs-xs);padding:6px 8px;border-radius:5px;
    white-space:nowrap;transform:translate(-50%,-100%);box-shadow:0 2px 8px rgba(0,0,0,.25)}
  #homeTip.on{opacity:1}
  #homeview{position:relative}
  .txlab{display:flex;justify-content:space-between;margin-top:2px;font-size:var(--fs-3xs);color:var(--dim)}
  .tmlab{display:grid;margin-top:4px;text-align:center;font-size:var(--fs-3xs);color:var(--dim)}
  .tsub{font-size:var(--fs-xs);color:var(--dim);margin-top:7px;line-height:1.4}
  /* ---- the money tile's headline, in both layouts --------------------------
     Wide: label, figure, qualifier stacked — exactly what .th + .tbig + .tsub
     drew before. Narrow: one row, figure right, chevron opens the rest. One
     element, so the two can never drift apart. */
  .tsum{display:block;position:relative}
  .tsum .sdot{display:none}
  /* the delta keeps the corner it has always had here. It is no longer inside
     the label's row — the folded grid needs it as a child it can place — so it
     is put back by position, the way the hide-balances eye used to be. */
  .tsum > .deltapill{position:absolute;top:1px;right:0}
  .tsum .sl{display:flex;align-items:center;gap:8px;min-height:22px}
  /* …and the label keeps clear of it, but only on a tile that carries one */
  .tsum.haspill .sl{padding-right:64px}
  .tsum .ss{display:block;font-size:var(--fs-xs);color:var(--dim);margin-top:4px;line-height:1.4}
  /* a qualifier the wide layout does not need — see homeTileHead's `phoneSub` */
  .tsum .ss.ss-p{display:none}
  .tsum .sv{font-size:var(--fs-4xl);font-weight:bold;line-height:1.12;margin-top:6px;letter-spacing:-.02em}
  .tsum .sv.neg{color:var(--red-tx)}
  /* the trailing-7-day figure in the series colour, tying it to the legend's swatch */
  .tsum .sv.now{color:var(--c-now)}
  .tsum .foldbtn{display:none}
  .tdetail{display:block}

  .tbig{font-size:var(--fs-4xl);font-weight:bold;line-height:1.12;margin-top:5px;letter-spacing:-.02em}
  .tbig.neg{color:var(--red-tx)}
  .trow{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:5px 0;
    border-bottom:1px solid var(--line);font-size:var(--fs-sm)}
  .trow:last-child{border-bottom:0}
  .trow .nm{color:var(--text);min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .tsep{border-top:1px solid var(--line);margin-top:8px;padding-top:8px}
  .plleg{display:flex;gap:11px;margin-top:6px;font-size:var(--fs-2xs);color:var(--text)}
  .plleg span{display:inline-flex;align-items:center;gap:4px;white-space:nowrap}
  .plleg i{width:8px;height:8px;border-radius:2px;display:block;flex:0 0 auto}
  .plleg i.inc{background:var(--c-inc)} .plleg i.exp{background:var(--c-exp)}
  .plleg i.net{width:11px;height:2px;border-radius:1px;background:var(--c-net)}
  /* the payables ladder: ONE grid for every rung, never a grid per row, or a
     short amount buys its row a wider bar than a long one and a smaller rung
     out-draws a larger one. Only the filled span is painted. */
  .hladder{margin-top:12px;display:grid;grid-template-columns:auto minmax(0,1fr) auto;
    column-gap:10px;row-gap:11px;align-items:center;font-size:var(--fs-xs)}
  .hladder .lk{color:var(--text);white-space:nowrap}
  .hladder .lbar{height:10px}
  .hladder .lbar i{display:block;height:100%;border-radius:2px;min-width:2px}
  .hladder .la{white-space:nowrap;color:var(--text);text-align:right}
  .f-bad{background:var(--red-bar)} .f-warn{background:var(--amb-bar)} .f-dim{background:var(--c-prev)}
  /* the two foot lists */
  /* Rounded on the OUTSIDE only, the same 10px the .tblwrap cards use, with the
     rows square inside it — so two of these sitting side by side read as one
     surface rather than a strip of separate buttons. The rows are <button>s and
     #homeview button hands them a 6px radius they must give back. */
  .hlist{border:1px solid var(--line);border-radius:10px;background:var(--panel);overflow:hidden}
  .hlist-h{padding:11px 14px;border-bottom:1px solid var(--line);display:flex;align-items:baseline;
    justify-content:space-between;gap:10px}
  .hlist-h b{font-size:var(--fs-sm)}
  .hlist-h .meta{font-size:var(--fs-xs);color:var(--dim)}
  /* Scoped to the view: "#homeview button" carries an id and was winning every
     one of these back — the radius, the four borders and the fill — so each row
     drew as its own little bordered chip inside the panel. A row is a row. */
  #homeview .hrow{display:grid;grid-template-columns:4px minmax(0,1fr) auto auto;gap:8px;
    align-items:center;padding:8px 14px 8px 0;width:100%;text-align:left;background:none;
    border:0;border-bottom:1px solid var(--line);border-radius:0;font:inherit;cursor:pointer}
  #homeview .hrow:last-child{border-bottom:0}
  @media (hover:hover){ #homeview .hrow:hover{background:var(--rowhover)} }
  .hrow .sev{width:4px;align-self:stretch;border-radius:0}
  .hrow .sev.c{background:var(--red-bar)} .hrow .sev.w{background:var(--amb-bar)} .hrow .sev.n{background:var(--track)}
  .hrow .txt{min-width:0}
  .hrow .txt b{display:block;font-weight:500;font-size:var(--fs-sm);white-space:nowrap;overflow:hidden;
    text-overflow:ellipsis;color:var(--ink)}
  .hrow .txt i{display:block;font-style:normal;font-size:var(--fs-xs);color:var(--dim);margin-top:1px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .hrow .amt{font-size:var(--fs-sm);color:var(--text);white-space:nowrap}
  .hrow .age{font-size:var(--fs-2xs);color:var(--dim);white-space:nowrap;min-width:42px;text-align:right;padding-right:12px}
  .hrow .age.hot{color:var(--red-tx)}
  .hscore{font-size:var(--fs-2xs);font-weight:600;color:var(--text);background:var(--track);
    border-radius:5px;padding:2px 5px;white-space:nowrap}
  @media (max-width:900px){
    .htiles.t4,.htiles.t3{grid-template-columns:1fr}
    /* ---- THE COLLAPSED STAT TILE, and there is one of it -----------------
       A stat tile on a phone is a row: the verdict in a fixed gutter at the
       left, the label and its qualifier stacked in the middle, the figure at
       the right. That is true of Home's folded tiles and of every stat card on
       every other page, and it was true of only the first — the cards stayed
       full-height blocks, so five of them ran past the bottom of the screen
       before the table they head.

       Two functions build them (homeTileHead carries a fold and a delta,
       statCard carries a count badge and a note line) and they are not going to
       become one: the differences are real. The GEOMETRY is not a difference,
       so it is written once, here, and each adds only what is its own below. */
    .htile.mfold .tsum,.hcard{display:grid;align-items:center;
      grid-template-rows:auto auto;column-gap:10px;row-gap:2px;padding:11px 12px}
    .htile.mfold .tsum .sv,.hcard .n{font-size:var(--fs-xl);margin-top:0;
      white-space:nowrap;text-align:right}
    .htile.mfold .tsum .sl .tk,.hcard .t{line-height:1.3}
    .htile.mfold .tsum .ss,.hcard .s{margin-top:0;font-size:var(--fs-2xs);line-height:1.35;
      white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}

    /* THE COLUMNS, and which of the four a tile gets. They are NAMED, so every
       slot below places itself by name and none of them has to know whether the
       gutter is there — which is the whole reason a tile can drop it.

       The gutter exists to hold a status mark or a chevron. A tile with neither
       — most cards on most pages — does not pay 32px for it, and its label and
       qualifier start at the card's edge instead.

       The pill's column is fixed because it is the LAST thing on the row, so
       its width is what decides where the figure beside it ends. Sized to
       content, three tiles ended their figures in three different places. */
    .htile.mfold .tsum,.hcard{
      grid-template-columns:[gut] 22px [main] minmax(0,1fr) [fig] auto [end]}
    /* TWO KINDS OF PILL, one column.

       A MEASURE is the same shape every time — an arrow and a percentage, a
       ratio — and there is one on every tile in its band. Its column is FIXED,
       because the pill is the last thing on the row and its width is what
       decides where the figure beside it ends: sized to content the three
       trading tiles ended their figures in three different places. The pill
       fills that column, so the pills line up as well as the figures.

       A FLAG is a count of something wanting attention. It is on a tile only
       while there is something to say, its width is whatever the number needs,
       and the tiles beside it have no pill at all — so there is no column of
       figures for a fixed width to line up, and stretching "1" to the width of
       "17" only draws a box with a number rattling around in it. Its column
       takes the mark's own width. */
    .htile.mfold .tsum,.hcard{--pillw:58px}
    .hcard.hasflag{--pillw:auto}
    .htile.mfold .tsum.haspill,.hcard.haspill,.hcard.hasflag{
      grid-template-columns:[gut] 22px [main] minmax(0,1fr) [fig] auto [pill] var(--pillw) [end]}
    .htile.mfold .tsum.nodot.nofold,.hcard.nodot.nofold{
      grid-template-columns:[main] minmax(0,1fr) [fig] auto [end]}
    .htile.mfold .tsum.nodot.nofold.haspill,.hcard.nodot.nofold.haspill,
    .hcard.nodot.nofold.hasflag{
      grid-template-columns:[main] minmax(0,1fr) [fig] auto [pill] var(--pillw) [end]}

    /* THE SLOTS. Four of them, and both components fill them with their own
       children — a tile's .sl/.sv/.ss/.deltapill, a card's .t/.n/.s/.cbadge. */
    /* Three things the wide layout does to a card's label row that have to be
       undone here, and all three were the same mistake — a rule written for the
       block form reaching the row form:
         · min-height reserves 18px for the count badge, which sits on that line
           up there and has a column of its own down here. It made every card
           5px taller than the tile beside it.
         · the padding keeps the label clear of the two corner marks, which are
           grid slots here — and it left the label indented 12px from its own
           qualifier, which is what a person actually notices. */
    .htile.mfold .tsum .sl,.hcard .t{grid-column:main;grid-row:1;min-height:0}
    .hcard .t,.hcard:not(.nodot) .t,.hcard.haspill .t,.hcard.hasflag .t{padding-left:0;padding-right:0}
    /* …and the dot is placed by the grid here, not by a corner offset */
    .hcard > .cdot{position:static}
    .htile.mfold .tsum .ss{grid-column:main;grid-row:2}
    .hcard .s{grid-column:main}          /* a card may carry a second one */
    .htile.mfold .tsum .sv,.hcard .n{grid-column:fig;grid-row:1/-1}
    .htile.mfold .tsum > .deltapill,.hcard > .cbadge{grid-column:pill;grid-row:1/-1;
      position:static;align-self:center}
    /* a measure fills its fixed column; a flag is only as wide as it needs */
    .htile.mfold .tsum.haspill > .deltapill,.hcard.haspill > .cbadge{
      justify-self:stretch;text-align:center}
    .hcard.hasflag > .cbadge{justify-self:end}

    /* THE GUTTER'S TWO MARKS, and the three ways they can fall.
         status + expansion  the dot is the tile's verdict, so it sits on the
                             tile's NAME; the chevron opens what the qualifier
                             summarises, so it sits on the QUALIFIER.
         status, no expansion  nothing under it to line up with, so the dot
                             centres on the tile.
         expansion, no status  likewise, the chevron centres on the tile.
       They used to be `end` and `start` of their rows, which put them nose to
       nose in the middle of the gutter and level with neither line. */
    .htile.mfold .tsum .sdot,.hcard .cdot{grid-column:gut;grid-row:1;
      justify-self:center;align-self:center}
    .htile.mfold .tsum.nofold .sdot,.hcard.nofold .cdot{grid-row:1/-1}
    .htile.mfold .tsum .foldbtn{grid-column:gut;grid-row:2;
      justify-self:center;align-self:center}
    .htile.mfold .tsum.nodot .foldbtn{grid-row:1/-1}
    /* Folded: the headline becomes one row and the detail goes behind the
       chevron. Three stacked money tiles were 685px on a 393px phone — taller
       than the screen — before the lists you actually act on. */
    .htiles.t3 .htile.mfold,.htiles.t4 .htile.mfold{padding:0}
    /* Three columns and a gutter that holds two marks. The row used to run
       dot · label · figure · chevron across the width, and the chevron — 28px
       of button plus its gap — was taken out of the label, which is the only
       part of the row that can shrink: at 360px it left "PAYABLES OUTSTAN…"
       with the chevron jammed against the card's edge. The gutter already
       existed for the status dot and was 6px of a 22px column's worth of
       space, so the chevron goes UNDER the dot and the row ends on its figure.
       Both marks are small and neither is text; the label gets the 24px back.
       Every child is placed by name, so the wide layout is free to nest them
       however it reads best — the label and its qualifier are siblings now, and
       this grid puts them one above the other without caring.

       Each mark is centred on the LINE it belongs to rather than pushed to the
       inner edge of the gutter: the dot is the tile's verdict, so it sits on
       the tile's name, and the chevron opens what the qualifier summarises, so
       it sits on the qualifier. They were `end` and `start` of their rows,
       which put them nose to nose in the middle of the gutter and level with
       neither line.

       Where there is no verdict the chevron has no neighbour to line up under,
       and holding it on the second row would leave the gutter weighted to the
       bottom of a row whose top half is empty. On those tiles it spans both
       rows instead and centres on the tile. */
    .htile.mfold .tsum{cursor:pointer}
    .htile.mfold .tsum .sdot{display:block;width:6px;height:6px;border-radius:50%}
    .htile.mfold .tsum .sdot.ok{background:var(--done)}
    .htile.mfold .tsum .sdot.stale{background:var(--prog)}
    .htile.mfold .tsum .sdot.bad{background:var(--block)}
    .htile.mfold .tsum .sdot.na,.htile.mfold .tsum .sdot.none{background:var(--muted-3)}
    /* a tile with no verdict to give — the trading three — keeps the gutter and
       draws nothing in it, so six tiles share one left edge */
    .htile.mfold .tsum .sdot.off{background:transparent}
    /* The nodot+nofold template drops the gutter (no verdict, nothing to open),
       so its columns start at [main]. The dot's `grid-column:gut` then names a
       line that does not exist and the browser opens a phantom trailing column,
       which pushed the figure and pill in off the right edge. The dot is `off`
       here anyway, so drop it and let main/fig/pill fill the width. */
    .htile.mfold .tsum.nodot.nofold .sdot{display:none}
    /* the label has the column to itself here — the delta is a column of its
       own at the end of the row, and the padding that keeps them apart on a
       wide screen would only eat into the label */
    .htile.mfold .tsum .sl,.htile.mfold .tsum.haspill .sl{
      display:flex;align-items:center;min-height:0;min-width:0;padding-right:0}
    .htile.mfold .tsum .ss.ss-p{display:block}
    /* the chevron wants to be just a chevron. This arrived scoped "#homeview
       …" for two reasons and only one of them is still true: it had to beat
       #homeview button, which no longer exists (the page's button is layered
       now — see .view button — and any unlayered class beats it), and it has
       to beat ".tsum .foldbtn{display:none}" above, which it still does. So it
       takes the selector its two neighbours here already use rather than an id.
       Square, and the glyph inside it is drawn rather than typed, so the open
       state turns the mark about its own centre instead of appearing to hop. */
    /* 14px, not 16: on a tile that has a status mark the chevron sits on the
       second row BESIDE the qualifier, so anything taller than that 14px line
       sets the row's height itself — and made the three money tiles two pixels
       taller than the three trading tiles above them and every card on every
       other page. The row is the qualifier's; the chevron fits inside it. */
    .htile.mfold .tsum .foldbtn{display:flex;align-items:center;
      justify-content:center;width:14px;height:14px;padding:0;border:0;
      background:none;box-shadow:none;color:var(--dim);
      line-height:1;cursor:pointer;transition:transform .15s}
    .htile.mfold .tsum .foldbtn svg{width:14px;height:14px;display:block;
      fill:none;stroke:currentColor;stroke-width:1.8;
      stroke-linecap:round;stroke-linejoin:round}
    .htile.mfold .tsum .foldbtn:hover{background:none;color:var(--text)}
    .htile.mfold.open .tsum .foldbtn{transform:rotate(180deg)}
    .htile.mfold .tdetail{display:none;padding:0 12px 11px}
    .htile.mfold.open .tdetail{display:block}
  }

  .iact{display:inline-flex;align-items:center;gap:5px;max-width:100%;min-width:0;padding:1px 8px;
    border:1px solid var(--line);border-radius:999px;background:var(--panel);cursor:pointer;font-size:var(--fs-xs);color:var(--ink-4)}
  /* the resolve tick, beside the action pill on rows a person or an agent filed
     (the page's own aggregates have none — they answer to their count) */
  .iresolve{flex:0 0 auto;padding:1px 7px;border:1px solid var(--line);border-radius:999px;
    background:var(--panel);cursor:pointer;font-size:var(--fs-xs);line-height:1.7;color:var(--dim)}
  @media (hover:hover){ .iresolve:hover{border-color:#2fae66;color:#2fae66} }
  .iadot{display:inline-block;width:8px;height:8px;border-radius:50%;flex:0 0 auto;vertical-align:baseline}
  .iact .iatxt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .iact .iax{flex:0 0 auto;margin-left:2px;padding:0 2px;color:var(--dim);font-size:var(--fs-sm);line-height:1}
  @media (hover:hover){ .iact:hover{border-color:var(--red)} .iact .iax:hover{color:var(--red-ink)} }
  .iact.none{color:var(--dim);border-style:dashed;background:transparent}
  /* ---- Status ------------------------------------------------------------
     One table per section, each stating its own verdict on its header. The
     table shell is the app's own (.txtable, .pcard) so a check reads like a
     transaction or a document reads; what is local here is the verdict pill
     and the tinted left edge, which are the two things you scan the page for.
     ---------------------------------------------------------------------- */
  /* The same shell every other view has: a flex child of #main that owns its
     own scrollbar. Without `flex`/`overflow` the page itself grows instead, and
     scrolling the checks scrolls the whole document — sidebar and all. */
  /* Two pages set their own padding, and both are exceptions the shell
     expects: an id outranks .view, so each states only what it changes.
     Status is scoped to the desktop because that is all it ever meant —
     it carried its own 12px phone rule before there was one to inherit,
     and without the scope its 20px sides would follow the page onto a
     phone, which is the one width it was never given. */
  @media (min-width:701px){ #statusview{padding:16px 20px 40px} }
  .sthdr{margin-bottom:18px}
  .sthdr-n{font-size:var(--fs-2xl);font-weight:bold;color:var(--ink-2)}
  .sthdr-n.bad{color:var(--red-tx)}
  .sthdr-n.ok{color:var(--grn-tx)}
  .sthdr-s{font-size:var(--fs-sm);color:var(--dim);margin-top:3px}
  .sterr{font-size:var(--fs-sm);color:var(--red-tx-2);margin:-10px 0 16px}
  .stsec{margin-bottom:22px}
  .stsec-h{display:flex;align-items:baseline;gap:10px;font-size:var(--fs-sm);font-weight:bold;
    color:var(--ink-3);margin-bottom:6px}
  .stcount{font-size:var(--fs-2xs);font-weight:normal;padding:1px 8px;border-radius:999px;
    border:1px solid var(--line-2);color:var(--text-2);background:var(--soft-2)}
  .stcount.ok{color:var(--grn-tx);background:var(--grn-bg);border-color:var(--grn-bd)}
  .stcount.warn{color:var(--amb-tx-2);background:var(--amb-bg-2);border-color:var(--amb-bd-2)}
  .stcount.bad{color:var(--red-tx-2);background:var(--red-bg);border-color:var(--red-bd)}
  .stwrap{max-width:1060px}
  .stchktbl{width:100%;min-width:760px;table-layout:fixed}
  .stchktbl th:nth-child(2),.stchktbl td:nth-child(2){width:104px}
  .stchktbl th:nth-child(3),.stchktbl td:nth-child(3){width:132px}
  .stchktbl th:nth-child(4),.stchktbl td:nth-child(4){width:70px}
  .stchktbl th:nth-child(5),.stchktbl td:nth-child(5){width:206px}
  .stchktbl tr.strow{cursor:pointer}
  @media (hover:hover){ .stchktbl tr.strow:hover{background:var(--rowhover)} }
  /* the edge bar is the scan: amber wants you this week, red wants you today.
     Green gets none — a page of coloured bars says nothing about which of them
     to read. */
  .stchktbl tr.strow.warn td:first-child{box-shadow:inset 3px 0 0 var(--amb-bar)}
  .stchktbl tr.strow.bad td:first-child{box-shadow:inset 3px 0 0 var(--red-bar)}

  .stt{display:block;font-size:var(--fs-sm);color:var(--ink-3)}
  .stm{display:block;font-size:var(--fs-xs);color:var(--text-2)}
  .stn{display:block;font-size:var(--fs-xs);color:var(--dim);overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap}
  .stchktbl td.stfound,.stchktbl td.stage{white-space:nowrap;font-variant-numeric:tabular-nums;
    font-size:var(--fs-sm);color:var(--text-2)}
  .stchktbl td.stact{white-space:nowrap}
  .stchktbl td.stact .iact{max-width:calc(100% - 28px)}
  /* the verdict, in the same family as every other status chip in the app */
  .stpill{display:inline-block;white-space:nowrap;font-size:var(--fs-2xs);line-height:1.5;
    padding:1px 8px;border-radius:999px;border:1px solid var(--line-2);
    color:var(--text-2);background:var(--soft-2)}
  .stpill.ok{color:var(--grn-tx);background:var(--grn-bg);border-color:var(--grn-bd)}
  .stpill.warn{color:var(--amb-tx-2);background:var(--amb-bg-2);border-color:var(--amb-bd-2)}
  .stpill.bad{color:var(--red-tx-2);background:var(--red-bg);border-color:var(--red-bd)}
  /* checking and not-checked share the quiet grey on purpose: neither is an
     answer, and drawing a pending check in a colour would make it look like one */
  .stpill.checking,.stpill.todo{color:var(--dim);border-style:dashed;background:transparent}
  /* resolved just now: still on screen so the tick can be taken back, gone on
     the next load (the list only ever fetches open issues) */
  .strow.hdone,.stcard.hdone{opacity:.5}
  .stcards{display:none}
  /* the schedule card: the same table furniture as the checks above it, one
     size quieter — it is a reference, not a verdict */
  .stjobs{margin-top:18px}
  .stjtbl{width:100%;min-width:520px;table-layout:fixed}
  .stjtbl th:nth-child(2),.stjtbl td:nth-child(2),
  .stjtbl th:nth-child(3),.stjtbl td:nth-child(3){width:132px}
  .stjtbl td{vertical-align:top}
  .stjtbl td.stjw{white-space:nowrap;font-variant-numeric:tabular-nums;
    font-size:var(--fs-sm);color:var(--text-2)}
  .stj-none{color:var(--dim)}
  /* a late feed is marked here too, in the same colours as its row above:
     reading this card is how you find out something should have run by now */
  .stjtbl tr.warn td:first-child{box-shadow:inset 3px 0 0 var(--amb-bar)}
  .stjtbl tr.bad td:first-child{box-shadow:inset 3px 0 0 var(--red-bar)}
  /* the same jobs as cards, for the phone, where the table above is hidden with
     every other .tblwrap on this page. Off by default and turned on at the
     breakpoint alongside .stcards — the two have to switch together or the page
     shows both renderings of half of itself. Not clickable: a job has no panel
     of its own (its FEED does, from the row above), and a card that looks like
     a button and does nothing is worse than one that doesn't. */
  .stjcards{display:none;margin-top:8px}
  .stjcard{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;
    align-items:start;cursor:default}
  @media (hover:hover){ .stjcard:hover{background:transparent} }
  .stjcard.warn{box-shadow:inset 3px 0 0 var(--amb-bar)}
  .stjcard.bad{box-shadow:inset 3px 0 0 var(--red-bar)}
  .stjcard .pcard-r .stn{font-variant-numeric:tabular-nums;color:var(--text-2)}
  .stjcard .pcard-r .stj-none{color:var(--dim)}
  .stjobs .tmnote{max-width:1060px}
  /* the panel: what the check asks, then what it found, then — for a feed —
     every run it has made */
  .stwhy{padding:12px 16px 0;font-size:var(--fs-sm);line-height:1.55;color:var(--text)}
  .stbl-h{padding:14px 16px 6px;font-size:var(--fs-sm);font-weight:bold;color:var(--ink-3)}
  /* one run per block rather than a row of a table: a drawer is about 500px
     wide, and "what it did" is both the longest cell and the one worth reading */
  .struns{padding:0 16px}
  .strun{padding:8px 0;border-top:1px solid var(--grid);cursor:pointer}
  .strun:first-child{border-top:none}
  @media (hover:hover){ .strun:hover .strun-w{color:var(--ink-2)} }
  .strun-t{display:flex;align-items:baseline;gap:8px}
  .strun-w{font-size:var(--fs-sm);color:var(--ink-3);white-space:nowrap}
  .strun-ok{font-size:var(--fs-xs);font-weight:bold}
  .strun-ok.good{color:var(--grn-tx-3)}
  .strun-ok.bad{color:var(--red-tx-2)}
  .strun-via{flex:1 1 auto;min-width:0;font-size:var(--fs-xs);color:var(--dim);text-align:right;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .strun-s{margin-top:2px;font-size:var(--fs-xs);color:var(--text)}
  .strun .strunpre{margin-top:8px}
  .strunsnone{padding:12px 16px;font-size:var(--fs-sm);color:var(--dim)}
  .strunpre{margin:0;padding:8px;background:var(--soft-2);border-radius:6px;
    font-size:var(--fs-xs);white-space:pre-wrap;word-break:break-all;max-height:50vh;overflow:auto}
  .stfoot{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:16px;
    border-top:1px solid var(--grid);margin-top:14px}
  .stfoot-l{font-size:var(--fs-xs);color:var(--dim)}
  .stfoot .phint{flex-basis:100%;margin:0}
  .stgo{margin-left:auto;padding:5px 12px;border:1px solid var(--line);border-radius:999px;
    background:var(--panel);cursor:pointer;font:inherit;font-size:var(--fs-sm);color:var(--ink-4)}
  @media (hover:hover){ .stgo:hover{border-color:var(--red);color:var(--red-ink)} }
  @media (max-width:760px){
    /* five columns is one column too many on a phone; the card keeps the check
       and its verdict on the line, with the count and the age under the pill.
       The task pill and the resolve tick go with the table — both live in the
       panel the card opens, which is where there is room to read them. */
    #statusview .tblwrap{display:none}
    .stcards{display:block}
    /* the schedule card's own cards, switched in the same block as the checks'
       — this rule and the hide above are the pair, and splitting them is how
       the schedule came to be hidden with nothing put in its place */
    .stjcards{display:block}
    .stcard{display:grid;grid-template-columns:minmax(0,1fr) auto 10px;gap:8px;
      align-items:start}
    .stcard .pcard-r{display:flex;flex-direction:column;align-items:flex-end;gap:3px}
    .stcard.warn{box-shadow:inset 3px 0 0 var(--amb-bar)}
    .stcard.bad{box-shadow:inset 3px 0 0 var(--red-bar)}
    .stcard .stn{white-space:normal}
  }
  /* team view — the roster is an accounting table: same .txtable shell as
     Transactions and Invoices, same .pcard cards on phones, same amber
     edge bar on a row that still needs work. Only the flag glyphs are local. */
  #teamview .txtable{min-width:900px}
  .txtable tr.flag-amber td:first-child{box-shadow:inset 3px 0 0 var(--amb-bar)}
  /* the Contract / Payslips flags are status pills, the same family as the
     Documents tab's open/partial/settled chip: quiet grey by default, green when
     it's in order, yellow when something is missing. Metrics match .cinvst so a
     roster status reads like every other status in the app — text, no glyph. */
  .tmflag{display:inline-flex;align-items:center;gap:4px;max-width:100%;white-space:nowrap;
    font-size:var(--fs-2xs);line-height:1.5;
    padding:1px 8px;border-radius:999px;border:1px solid var(--line-2);
    color:var(--text-2);background:var(--soft-2);text-decoration:none}
  /* the repair offered on a folder that isn't in the HR drive; sits beside the
     folder pill, so it reads as an action on that row rather than a heading */
  .hrfix{margin-left:6px;padding:2px 8px;border:1px solid var(--line);border-radius:999px;
    background:var(--panel);cursor:pointer;font-size:var(--fs-xs);color:var(--amb-tx);white-space:nowrap}
  @media (hover:hover){ .hrfix:hover{border-color:var(--amb-tx)} }
  .hrfix:disabled{color:var(--dim);cursor:default}
  .tmflag.ok{color:var(--grn-tx);background:var(--grn-bg);border-color:var(--grn-bd)}
  .tmflag.warn{color:var(--amb-tx-2);background:var(--amb-bg-2);border-color:var(--amb-bd-2)}
  .tmflag.muted{color:var(--text-2);background:var(--soft-2);border-color:var(--line-2)}
  .tmnote{background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:10px 14px;font-size:var(--fs-sm);color:var(--text);margin-top:14px}
  .tmnote b{color:var(--ink-3)}
  /* the roster's Contact cell: the number, then each address, one per line */
  .tmcontact{display:flex;flex-direction:column;gap:2px;font-size:var(--fs-sm)}
  .tmcontact a{color:var(--link);text-decoration:none}
  .tmcontact a:hover{text-decoration:underline}
  /* Roster cards on phones, in three columns and a chevron: who they are (name
     over role and contract type), what they cost (salary over roster status),
     and whether their paperwork is in order. One grid for the header and every
     card, so the money and the flags line up down the list and the name is what
     gives way — the same shape the other tabs' cards take. The two right columns
     are only as wide as their content needs, because the name is what wants the
     rest. */
  /* the key-stat strip needs the standard gap below it before the roster — grid
     gap only spaces the cards from each other, not the strip from what follows */
  .tmstats{margin-bottom:16px}
  .tmcards{display:none;--tm-mid:52px;--tm-r:88px;--tm-gap:7px}
  .tmcards .pcard,.tmcards .pcards-head{display:grid;
    grid-template-columns:minmax(0,1fr) var(--tm-mid) var(--tm-r) 10px;
    gap:var(--tm-gap);align-items:center}
  /* salary is free text off the sheet, trimmed to its number for the card — but
     an oddly written one wraps inside its column rather than clipping to
     "€1,0…". The element in the selector outranks the shell's clip rule. */
  .tmcards .pcard span.pcard-n{white-space:normal;overflow:visible;font-size:var(--fs-sm);line-height:1.3}
  /* the paperwork column: one chip per line, right-aligned against the chevron.
     The element outranks .pcard>div, which stacks a cell's lines to the left. */
  .tmcards .pcard div.tmchips{display:flex;flex-direction:column;align-items:flex-end;gap:4px;max-width:100%}
  /* a roster card with paperwork outstanding carries the same amber edge bar the
     Transactions and Counterparties cards use, so "needs work" reads the same
     everywhere. One tier: every paperwork gap — no signed contract, paid with no
     payslip, or signed-but-nothing-filed — is a warning, not an error, so they
     all take the amber edge that matches the table's row edge.
     Absolute, so the bar costs the content no width. */
  .tmcards .pcard{position:relative;overflow:hidden}
  .tmcards .pcard.needs{border-color:var(--amb-bd-2)}
  .tmcards .pcard.needs:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--amb-bar)}
  /* the base .tmflag pill above carries the look; this element-prefixed rule
     only restores the pill's inline-flex over the .pcard rule that blocks out
     every non-pill span — same trick .txcard-unknown uses above */
  .tmcards .pcard a.tmflag,.tmcards .pcard span.tmflag{display:inline-flex}
  @media (max-width:760px){
    #teamview .tblwrap{display:none}
    .tmcards{display:block}
    /* the owners table drops away on a phone; the list of direct holders
       takes over, and going deeper is a tap into each one */
    #ownersview .tblwrap{display:none}
    #ownersview .owncards{display:block}
    /* the panel is the width of the phone now: the share column gives its spare
       back to the name, and the "of <company>" line wraps instead */
    .invoice-panel .owncards{--own-r:104px}
    /* Team put its two stats side by side in smaller type, because a card was
       a block four lines tall and a row each pushed the roster off the screen.
       A card IS a row now, so the squeeze buys nothing and cost the page the
       standard metrics — this page's stats were the one pair that did not
       match the rest of the app. */
  }
  /* the narrowest phones: give the name column back a few pixels */
  @media (max-width:360px){ .tmcards{--tm-mid:50px;--tm-r:86px;--tm-gap:6px} }
  /* the team member panel. The shell is the shared .invoice-panel; these are the
     bits the other panels scope to their own view id, restated for this one. */
  .invoice-panel h4{color:var(--red-ink);font-size:var(--fs-sm);margin:18px 0 6px;
    text-transform:uppercase;letter-spacing:.04em}
  .paneltx-more,.invoice-panel .paneltx-more{display:block;width:100%;box-sizing:border-box;
    margin-top:8px;font:inherit;font-size:var(--fs-sm);text-align:left;white-space:normal;color:var(--info-tx);
    background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:8px 11px;cursor:pointer}
  @media (hover:hover){ .paneltx-more:hover{color:var(--red-ink);border-color:var(--red)} }
  /* the panel's payments get a third column: date & type, amount, the document
     behind the payment (the payslip, for payroll) — so payslips and payments
     read as one list rather than two sections saying the same thing */
  .empanel-tx .txcard{grid-template-columns:minmax(0,1fr) var(--tx-amt) minmax(0,1fr) 10px}
  /* products view — the menu master list, one row per product. Same .txtable
     shell as the other accounting tables; the category is the first column, shown
     once per group with a caret that folds the group away, so a long menu can be
     scrolled a category at a time while the money columns stay in one set of
     columns down the whole page and margins are comparable at a glance. */
  #prodview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:18px 0 8px}
  #prodview h3:first-of-type{margin-top:0}
  /* the table spans the page and never scrolls sideways on a desktop: the
     category lives in a group row (not a column), and the text columns wrap
     inside modest minimums instead of pushing the table wider */
  #prodview .tblwrap{width:100%}
  #prodview .txtable{width:100%;min-width:660px}
  /* Description and Tags are the two columns the table only earns when the
     window is wide. Everything a product is beyond the four that always show is
     one click away in its panel, so these fold out rather than push the table
     into a sideways scroll. */
  @media (max-width:1180px){ #prodview .txtable .colwide{display:none} }
  /* the category name reads like the Board's lane name and the Budgets page's
     merchant: bold, ink-colored, as written — no uppercase styling */
  #prodview td.catcell{font-weight:bold;font-size:var(--fs-md);white-space:nowrap}
  /* the editable cells: money, description, and the VAT / status pickers */
  #prodview input.pmoney{font:inherit;font-size:var(--fs-sm);width:64px;padding:3px 5px;border:1px solid var(--line);
    border-radius:5px;text-align:right;font-variant-numeric:tabular-nums;background:var(--panel)}
  /* the product panel's Unit price / Unit cost fields: sized for the 2–3 digits
     they usually hold (€5.50, €32.04), not the full field width */
  .invoice-panel .prow input.pmoney{width:60px;text-align:right;font-variant-numeric:tabular-nums}
  /* description and internal notes: textareas that wrap, grow with their
     content, and stretch to the full row height (the td height:1px trick
     gives the 100% something to resolve against) */
  #prodview textarea.pdesc{font:inherit;font-size:var(--fs-sm);width:100%;min-width:130px;padding:3px 5px;
    border:1px solid var(--line);border-radius:5px;background:var(--panel);color:var(--text);
    display:block;resize:none;overflow:hidden;min-height:100%;field-sizing:content}
  #prodview textarea.pdesc[data-f="description"]{min-width:170px}
  /* the section's Portuguese heading, in brackets after its name; the tag's
     Portuguese the same way inside its pill. Both read as the translation of
     the word beside them, and clicking either is how it is set. */
  #prodview .tagpill i,.invoice-panel .tagpill i{font-style:normal;color:var(--text-2);margin-left:3px}
  #prodview .tagpill [data-tagpt],.invoice-panel .tagpill [data-tagpt]{cursor:pointer}
  #prodview td.fillcell{height:1px}
  /* Rules' status control is this control. It was carrying .pvat .pstat while
     only the .pstat half was written for both views, so the select on Rules
     came out in the browser's own chrome at the browser's own font — smaller,
     and visibly not the thing on Products. The BASE is what has to be shared,
     not just the two colour overrides below. */
  #prodview select.pvat,#rulesview select.pvat{font:inherit;font-size:var(--fs-xs);max-width:110px;padding:2px 3px;
    border:1px solid var(--line);border-radius:5px;background:var(--panel);color:var(--text)}
  #prodview input.pmoney:focus,#prodview textarea.pdesc:focus,
  #prodview select.pvat:focus,#rulesview select.pvat:focus{border-color:var(--red);outline:none}
  @media (pointer:coarse){ #prodview input.pmoney,#prodview textarea.pdesc,
    #prodview select.pvat,#rulesview select.pvat{font-size:var(--fs-xl)} }
  /* margin cells: the % leads, the € sits under it */
  /* the tags cell: pills plus a bare inline input; suggestions live in #tagMenu */
  #prodview .tagbox,.invoice-panel .tagbox{display:flex;flex-wrap:wrap;gap:3px;align-items:center;min-width:120px;
    border:1px solid var(--line);border-radius:5px;background:var(--panel);padding:3px 5px;cursor:text}
  #prodview .tagbox:focus-within,.invoice-panel .tagbox:focus-within{border-color:var(--red)}
  #prodview .tagpill,.invoice-panel .tagpill{background:var(--chip);border:0;border-radius:10px;
    padding:1px 6px;font-size:var(--fs-2xs);white-space:nowrap}
  #prodview .tagpill b,.invoice-panel .tagpill b{cursor:pointer;color:var(--dim);font-weight:normal;margin-left:3px}
  @media (hover:hover){ #prodview .tagpill b:hover,.invoice-panel .tagpill b:hover{color:var(--red-ink)} }
  /* the derived till name reads as derived, the way it did on the row it came
     from — nothing here can edit it */
  .invoice-panel .txchip.derived{font-style:italic;color:var(--text-2)}
  /* the counterparty panel's Roles editor: a column of role switches, then any
     derived roles (Employee/Owner) shown as read-only chips with their one line */
  .invoice-panel .cproles{display:flex;flex-direction:column;gap:6px}
  .invoice-panel .cpderived{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin-top:2px}
  .invoice-panel .aliasadd{font:inherit;font-size:var(--fs-xs);padding:2px 7px;border:1px solid var(--line);
    border-radius:6px;background:var(--panel);color:var(--ink-2);min-width:150px;margin:1px 0}
  .invoice-panel .aliasadd:focus{border-color:var(--red);outline:none}
  .invoice-panel .aliasadd::placeholder{color:var(--muted-4)}
  #prodview .tagin,.invoice-panel .tagin{font:inherit;font-size:var(--fs-xs);border:none;outline:none;min-width:56px;flex:1;background:transparent;padding:2px}
  #prodview .tagin::placeholder,.invoice-panel .tagin::placeholder{color:var(--muted-4)}
  /* In a panel row the generic input box (.invoice-panel .prow input[type=text])
     would otherwise wrap the tag input in a second border inside the .tagbox —
     a cell within a cell. The tagbox is the cell; the input stays bare. */
  .invoice-panel .prow .tagbox .tagin{border:0;border-radius:0;background:transparent;padding:2px}
  #tagMenu{display:none;position:fixed;z-index:80;background:var(--panel);border:1px solid var(--line);border-radius:8px;
    box-shadow:var(--shadow-pop);max-height:180px;overflow:auto;min-width:140px}
  #tagMenu .tmi{padding:6px 10px;font-size:var(--fs-sm);cursor:pointer}
  #tagMenu .tmi.new{color:var(--text);font-style:italic}
  /* Syrve sync modal — the two-sided catalogue diff, built in the same visual
     language as the Wise recipients sync (#wrecipModal): a fixed header and
     footer around a scrolling body, stat tiles, and a 3-column ledger that names
     each side (SYRVE | resolve | CATALOGUE) so a value never floats beside a bare
     "vs". Per-field, the middle column carries the direction choice. */
  .syncdiff{--symono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;--sygut:20px;
    display:none;position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:75}
  .syncdiff.open{display:flex;align-items:center;justify-content:center}
  .syncdiff .box{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:0;
    max-width:760px;width:calc(100% - 32px);max-height:86vh;display:flex;flex-direction:column;
    box-shadow:var(--shadow-modal)}
  .syncdiff .syhead{display:flex;align-items:flex-start;gap:11px;padding:15px 16px 13px;border-bottom:1px solid var(--line)}
  .syncdiff .sy-mark{flex:0 0 auto;width:30px;height:30px;border-radius:8px;margin-top:1px;display:grid;place-items:center;background:var(--soft);border:1px solid var(--line);color:var(--text-2)}
  .syncdiff .sy-mark svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
  .syncdiff .sy-tt{min-width:0;display:flex;flex-direction:column;gap:2px;padding-top:1px}
  .syncdiff .sy-tt>b{font-size:var(--fs-lg);color:var(--ink-strong);font-weight:600;line-height:1.15}
  .syncdiff .sy-msg{font-size:var(--fs-sm);color:var(--text-3)}
  .syncdiff .sy-x{margin-left:auto;flex:0 0 auto;width:28px;height:28px;border-radius:7px;display:grid;place-items:center;background:transparent;border:1px solid var(--line);color:var(--text-2);cursor:pointer}
  .syncdiff .sy-x:hover{background:var(--hover);color:var(--ink)}
  .syncdiff .sy-x svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
  .syncdiff .systats{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;padding:12px 16px 3px}
  .syncdiff .systats:empty{display:none}
  .syncdiff .systat{background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:7px 10px;display:flex;flex-direction:column;gap:2px}
  .syncdiff .systat .n{font-family:var(--symono);font-size:var(--fs-xl);font-variant-numeric:tabular-nums;line-height:1;color:var(--ink-strong)}
  .syncdiff .systat .l{font-size:var(--fs-2xs);letter-spacing:.06em;text-transform:uppercase;color:var(--text-3)}
  .syncdiff .systat.warn{border-color:var(--amb-bd);background:var(--amb-bg)}
  .syncdiff .systat.warn .n,.syncdiff .systat.warn .l{color:var(--amb-tx-2)}
  .syncdiff .systat.on{border-color:var(--grn-bd);background:var(--grn-bg)}
  .syncdiff .systat.on .n,.syncdiff .systat.on .l{color:var(--grn-tx)}
  #syrvesyncBody{overflow:auto;min-height:0;flex:1 1 auto;padding:4px 16px 12px}
  /* the amber Phase-0 banner and the calm all-agree note */
  .syncdiff .synote{display:flex;gap:10px;padding:11px 13px;border-radius:9px;margin-top:8px;align-items:flex-start}
  .syncdiff .synote.warn{background:var(--amb-bg);border:1px solid var(--amb-bd)}
  .syncdiff .synote.calm{background:var(--soft);border:1px solid var(--line)}
  .syncdiff .synote .tx{font-size:var(--fs-sm);line-height:1.45;color:var(--text)}
  .syncdiff .synote.warn .tx{color:var(--amb-tx-2)}
  .syncdiff .synote .tx b{font-weight:600}
  .syncdiff .synote code{font-family:var(--symono);font-size:var(--fs-xs)}
  /* sticky column header — admin (master) LEFT, external RIGHT; values align
     INWARD to a fixed gutter, no header rule */
  .syncdiff .sycols{display:grid;grid-template-columns:1fr 176px 1fr;align-items:end;position:sticky;top:0;background:var(--panel);padding:8px 0 6px;z-index:2}
  .syncdiff .sych{font-size:var(--fs-2xs);font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--text-2)}
  .syncdiff .sych.left{text-align:right;padding-right:var(--sygut)}
  .syncdiff .sych.right{text-align:left;padding-left:var(--sygut)}
  .syncdiff .sych.mid{text-align:center;color:var(--text-3);display:flex;flex-direction:column;align-items:center;gap:4px}
  /* "Push all" in the Resolution header: a selection, not a write, so it wears
     the neutral toggle look and goes green like a chosen Push once it has taken */
  .syncdiff .sypushall{font:inherit;font-size:var(--fs-2xs);font-weight:600;letter-spacing:0;text-transform:none;padding:3px 10px;border:1px solid var(--line-2);border-radius:20px;background:var(--soft);color:var(--text);cursor:pointer;white-space:nowrap}
  .syncdiff .sypushall:hover{background:var(--hover);color:var(--ink)}
  .syncdiff .sypushall.on{background:var(--grn-tx);border-color:var(--grn-tx);color:#fff}
  html[data-theme="dark"] .syncdiff .sypushall.on{color:#12261a}
  /* one product = one group; a hairline trails the name + category */
  .syncdiff .sygrp{margin-top:16px}
  .syncdiff .sygh{font-size:var(--fs-sm);font-weight:600;color:var(--ink-2);margin:0 0 1px;display:flex;align-items:baseline;gap:8px}
  .syncdiff .sygh .cat{font-size:var(--fs-xs);color:var(--dim);font-weight:400}
  .syncdiff .sygh::after{content:"";flex:1 1 auto;height:1px;background:var(--line);align-self:center}
  /* one field = one 3-col row; no per-row line */
  .syncdiff .syrow{display:grid;grid-template-columns:1fr 176px 1fr;align-items:center;padding:7px 0}
  .syncdiff .syside{min-width:0}
  .syncdiff .syside.left{text-align:right;padding-right:var(--sygut)}
  .syncdiff .syside.right{text-align:left;padding-left:var(--sygut)}
  /* the value — plain text, quietly clickable (no card, no fill) */
  .syncdiff .syvb{font-family:inherit;font-size:var(--fs-sm);line-height:1.35;color:var(--ink-2);background:none;border:0;padding:0;margin:0;text-align:inherit;cursor:pointer;word-break:break-word;font-variant-numeric:tabular-nums;transition:color .1s}
  .syncdiff .syvb:hover:not([disabled]){color:var(--grn-tx);font-weight:600}
  .syncdiff .syvb[disabled]{cursor:not-allowed}
  /* the colour swatch on a colour row — a touch bigger than the inline catswatch
     so the two sides' colours read at a glance */
  .syncdiff .catswatch{width:14px;height:14px;border-radius:3px;margin-right:8px}
  .syncdiff .syempty{color:var(--dim)}
  /* keeper bold green, loser dim + struck (decoration on the element so hover can clear it).
     res-overwrite = push, catalogue (left) wins; res-pull = Syrve (right) wins */
  .syncdiff .syrow.res-overwrite .syside.left .syvb,.syncdiff .syrow.res-pull .syside.right .syvb{color:var(--grn-tx);font-weight:600;text-decoration:none}
  .syncdiff .syrow.res-overwrite .syside.right .syvb,.syncdiff .syrow.res-pull .syside.left .syvb{color:var(--dim);text-decoration:line-through;font-weight:400}
  .syncdiff .syrow.res-overwrite .syside.right .syvb:hover,.syncdiff .syrow.res-pull .syside.left .syvb:hover{color:var(--grn-tx);text-decoration:none;font-weight:600}
  /* centre resolve column: label + segmented Push/Pull toggle */
  .syncdiff .symid{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;padding:0 4px;text-align:center}
  .syncdiff .sylbl{font-size:var(--fs-3xs);font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--text-3);display:flex;align-items:center;gap:5px;flex-wrap:wrap;justify-content:center}
  .syncdiff .syacts{display:inline-flex;border:1px solid var(--line-2);border-radius:20px;overflow:hidden;background:var(--soft)}
  .syncdiff .syb{font:inherit;font-size:var(--fs-2xs);font-weight:600;display:inline-flex;align-items:center;gap:4px;padding:4px 11px;border:0;background:transparent;color:var(--text);cursor:pointer;white-space:nowrap}
  .syncdiff .syb+.syb{border-left:1px solid var(--line-2)}
  .syncdiff .syb:hover:not(.on):not([disabled]){background:var(--hover);color:var(--ink)}
  .syncdiff .syb svg{width:11px;height:11px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
  .syncdiff .syb.on{background:var(--grn-tx);color:#fff}
  html[data-theme="dark"] .syncdiff .syb.on{color:#12261a}
  .syncdiff .syb[disabled]{opacity:.4;cursor:not-allowed}
  .syncdiff .sygate{font-size:var(--fs-3xs);font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--amb-tx-2);border:1px solid var(--amb-bd);background:var(--amb-bg);border-radius:4px;padding:0 4px;margin-left:5px}
  .syncdiff .syflag{font-size:var(--fs-3xs);font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--amb-tx-2);border:1px solid var(--amb-bd);background:var(--amb-bg);border-radius:4px;padding:0 4px;margin-left:5px}
  .syncdiff .syrow.flag{background:var(--amb-bg)}
  .syncdiff .syrow.flag .syvv{font-size:var(--fs-sm);color:var(--ink-2);word-break:break-word;font-variant-numeric:tabular-nums}
  .syncdiff .synote-sm{font-size:var(--fs-2xs);color:var(--amb-tx-2);text-align:center;max-width:220px}
  /* surfaced-only sections (creates / pulls / ambiguous / unmatched groups) */
  .syncdiff .sysec{margin-top:14px}
  .syncdiff .sysh{font-size:var(--fs-2xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);margin:0 0 5px;display:flex;align-items:center;gap:8px}
  .syncdiff .sysh.attn{color:var(--amb-tx-2)}
  .syncdiff .sysh .rule{flex:1 1 auto;height:1px;background:var(--line)}
  .syncdiff .syitem{display:flex;align-items:center;gap:8px;padding:4px 0;font-size:var(--fs-sm);color:var(--ink-2)}
  .syncdiff .syitem .g{color:var(--dim);font-size:var(--fs-xs)}
  .syncdiff .syitem .flag{margin-left:auto;font-size:var(--fs-3xs);font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--amb-tx-2);border:1px solid var(--amb-bd);border-radius:5px;padding:1px 6px}
  .syncdiff .syitem .symap{margin-left:auto;flex:0 0 auto;max-width:44%;font:inherit;font-size:var(--fs-xs);padding:3px 6px;border:1px solid var(--line-2);background:var(--soft);color:var(--ink);border-radius:6px;cursor:pointer}
  .syncdiff .syitem .sydel{flex:0 0 auto;font:inherit;font-size:var(--fs-xs);padding:3px 9px;border:1px solid var(--red-bd);background:var(--red-bg);color:var(--red-tx-2);border-radius:6px;cursor:pointer}
  .syncdiff .syitem .sydel:hover{background:var(--red-bd);color:#fff}
  .syncdiff .syitem .sydel:disabled{opacity:.5;cursor:default}
  .syncdiff .sycreate{flex:0 0 auto;font:inherit;font-size:var(--fs-xs);font-weight:600;letter-spacing:0;text-transform:none;padding:3px 9px;border:1px solid var(--grn-bd);background:var(--grn-bg);color:var(--grn-tx-2);border-radius:6px;cursor:pointer}
  .syncdiff .syitem .sycreate{margin-left:auto}
  .syncdiff .sycreate:hover{background:var(--grn-bd)}
  .syncdiff .sycreate:disabled{opacity:.5;cursor:default}
  .syncdiff details.syaudit-wrap{margin:0 0 10px 0}
  .syncdiff details.syaudit-wrap summary{cursor:pointer;font-size:var(--fs-2xs);color:var(--dim);user-select:none}
  .syncdiff .syaudit{width:100%;border-collapse:collapse;font-size:var(--fs-2xs);margin-top:4px}
  .syncdiff .syaudit td{padding:2px 6px;border-top:1px solid var(--line);vertical-align:top}
  .syncdiff .syaudit td.k{color:var(--text-3);white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
  .syncdiff .syaudit td.v{color:var(--ink-2);word-break:break-all}
  .syncdiff .syaudit td.s{color:var(--dim);white-space:nowrap;text-align:right;line-height:1.35}
  .syncdiff .syaudit tr.warn td.s{color:var(--amb-tx-2);font-weight:600}
  .syncdiff .syitem .symap:hover{background:var(--hover)}
  .syncdiff .syitem .symap:disabled{opacity:.5;cursor:default}
  .syncdiff .sywarow .sygrename{margin-left:auto;flex:0 0 auto;max-width:52%;font:inherit;font-size:var(--fs-xs);padding:3px 6px;border:1px solid var(--amb-bd);background:var(--panel);color:var(--ink);border-radius:6px;cursor:pointer}
  .syncdiff .sywarow .sygrename:hover{background:var(--hover)}
  .syncdiff .sywarow .sygrename:disabled{opacity:.5;cursor:default}
  .syncdiff .sywarow{display:flex;align-items:center;gap:10px;padding:8px 11px;border-radius:8px;background:var(--amb-bg);border:1px solid var(--amb-bd);margin-bottom:5px;font-size:var(--fs-sm)}
  .syncdiff .sywarow .who{font-weight:600;color:var(--ink-2)}
  .syncdiff .sywarow .meta{color:var(--text-2);font-size:var(--fs-xs)}
  .syncdiff .sywarow .flag{margin-left:auto;font-size:var(--fs-3xs);font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--amb-tx-2);border:1px solid var(--amb-bd);border-radius:5px;padding:2px 7px}
  /* footer */
  .syncdiff .syfoot{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:11px 16px;border-top:1px solid var(--line)}
  .syncdiff .syfoot .fh{font-size:var(--fs-2xs);color:var(--dim);line-height:1.3;max-width:300px}
  .syncdiff .syfoot .sp{flex:1 1 auto}
  .syncdiff .syfoot button{font:inherit;font-size:var(--fs-sm);font-weight:500;padding:7px 13px;border:1px solid var(--line-2);background:var(--soft);color:var(--ink);border-radius:7px;cursor:pointer;white-space:nowrap}
  .syncdiff .syfoot button:hover{background:var(--hover)}
  .syncdiff .syfoot button.go{background:var(--red);border-color:var(--red);color:#fff;font-weight:600}
  .syncdiff .syfoot button.go:hover{background:var(--red-bar)}
  .syncdiff .syfoot button[disabled]{opacity:.5;cursor:default}
  .syncdiff .syfoot button .cnt{font-family:var(--symono);font-weight:700}
  /* On a phone the 3-column ledger doesn't fit. Each field becomes a compact
     block: the field label and the decision control share the top line, then the
     two systems sit stacked and labelled beneath — Admin first, external second.
     The value text stays tappable; the keeper stays green. */
  @media (max-width:560px){
    .syncdiff .systats{grid-template-columns:repeat(2,1fr)}
    /* no columns to head on a phone — keep only the centre cell, for Push all */
    .syncdiff .sycols{display:flex;justify-content:flex-end;padding:6px 0}
    .syncdiff .sych.left,.syncdiff .sych.right{display:none}
    .syncdiff .sych.mid{flex-direction:row;gap:8px}
    .syncdiff .syrow{display:flex;flex-direction:column;align-items:stretch;gap:3px;padding:9px 0 11px}
    /* label + toggle on one line, pulled to the top of the block */
    .syncdiff .symid{order:-1;flex-direction:row;justify-content:space-between;align-items:center;width:100%;gap:10px;padding:0 0 3px;text-align:left}
    .syncdiff .sylbl{justify-content:flex-start;flex-wrap:nowrap}
    .syncdiff .syacts{flex:0 0 auto}
    /* each system: an inline tag then its value; Admin left, external right.
       .left/.right included so this beats the desktop rules' higher specificity. */
    .syncdiff .syside,.syncdiff .syside.left,.syncdiff .syside.right{display:flex;align-items:baseline;gap:8px;text-align:left;padding:0;width:100%}
    .syncdiff .syside::before{flex:0 0 64px;font-size:var(--fs-3xs);font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-3)}
    .syncdiff .syside.left::before{content:"Admin"}
    .syncdiff .syside.right::before{content:"Syrve"}
    #vsync .syside.right::before{content:"Vendus"}
    .syncdiff .syvb{flex:1 1 auto;min-width:0;text-align:left}
    /* footer stays one row: helper text on the left, Cancel + Apply together on
       the right, vertically centred with them ("Apply 0" stays one line) */
    .syncdiff .syfoot .fh{flex:1 1 auto;max-width:none}
    .syncdiff .syfoot .sp{display:none}
  }
  @media (hover:hover){ #tagMenu .tmi:hover{background:var(--hover)} }
  #prodview .pctmain,.invoice-panel .pctmain{font-variant-numeric:tabular-nums}
  #prodview .pctmain.under,.invoice-panel .pctmain.under{color:var(--amb-tx);font-weight:bold}
  /* rows off the menu stay editable but read muted */
  #prodview tr.inactive .prodname{color:var(--dim);font-weight:normal}

  /* where a product sits on the menu: its rank in the section, and the section */
  #prodview td.ordcell{white-space:nowrap;text-align:center}
  /* rank is set by nudging the row up or down against its neighbours, not by
     typing a number — the same pair of arrows the section header carries. Used
     both in the table's Order column and in the product panel's Rank row. */
  #prodview .prodord,.invoice-panel .prodord{display:inline-flex;gap:2px}
  #prodview .prodord b,.invoice-panel .prodord b{display:inline-block;font-weight:normal;font-size:var(--fs-2xs);cursor:pointer;
    color:var(--dim);border:1px solid var(--line);border-radius:5px;padding:0 5px;line-height:18px}
  #prodview .prodord b.off,.invoice-panel .prodord b.off{opacity:.35;cursor:default}
  @media (hover:hover){ #prodview .prodord b:not(.off):hover,.invoice-panel .prodord b:not(.off):hover{color:var(--red-ink);border-color:var(--red)} }
  /* the way in to everything else about the product: the name itself */
  #prodview button.prodname{font:inherit;font-weight:600;text-align:left;color:var(--ink-strong);
    background:none;border:0;padding:0;cursor:pointer;max-width:220px}
  @media (hover:hover){ #prodview button.prodname:hover{color:var(--red-ink)} }
  /* status and what Vendus makes of it, stacked — and centred in the row rather
     than pinned to its top: the row's height is set by the description and notes
     boxes beside it, and a two-line block hanging at the top of a tall row reads
     as belonging to whatever is level with it */
  #prodview td.statcell{white-space:nowrap;vertical-align:middle}
  /* ---- sync-drift banner + per-row Resolve flag -------------------------
     The page is silent when admin, Vendus and Syrve agree; the banner says so
     once, at the top, and only a diverged row grows a flag. Banner modelled on
     .txreview; a green "in sync" variant sits beside the amber "N out of sync". */
  #prodview .prodsync{border-radius:10px;padding:9px 13px;margin-bottom:14px;font-size:var(--fs-sm);
    display:flex;align-items:center;gap:8px;flex-wrap:wrap}
  #prodview .prodsync b{font-weight:700}
  #prodview .prodsync.ok{background:var(--grn-bg);border:1px solid var(--grn-bd);color:var(--grn-tx)}
  #prodview .prodsync.ok b{color:var(--grn-tx)}
  #prodview .prodsync.warn{background:var(--amb-bg);border:1px solid var(--amb-bd);color:var(--amb-tx)}
  #prodview .prodsync.warn b{color:var(--amb-tx)}
  #prodview .prodsync.warn .pwarn{font-weight:700}
  #prodview .prodsync.checking{background:var(--panel-2);border:1px solid var(--line);color:var(--dim)}
  #prodview .prodsync .pwhen{color:var(--dim);font-size:var(--fs-sm)}
  #prodview .prodsync-review{margin-left:auto;font:inherit;font-size:var(--fs-sm);font-weight:700;padding:4px 11px;
    border-radius:7px;border:1px solid var(--amb-bd-2);background:var(--amb-bg-2);color:var(--amb-tx);cursor:pointer}
  /* the flag: an alert with an action, never mistaken for the Active/Inactive
     select beside it. Amber for a decision, red for "live where it shouldn't be";
     the ellipsis says a click opens a further choice rather than acting. */
  #prodview .pflag{display:flex;width:fit-content;align-items:center;gap:6px;margin-top:4px;font:inherit;
    font-size:var(--fs-2xs);font-weight:700;cursor:pointer;padding:2px 8px;border-radius:7px;white-space:nowrap;
    background:var(--amb-bg);color:var(--amb-tx);border:1px solid var(--amb-bd)}
  #prodview .pflag.attn{background:var(--red-bg);color:var(--red-tx);border-color:var(--red-chip)}
  #prodview .pflag .pact{text-decoration:underline;text-underline-offset:2px;font-weight:700}
  @media (hover:hover){ #prodview .pflag:hover{box-shadow:0 1px 3px rgba(0,0,0,.14)} }
  #prodview .cardflag{margin-top:6px}
  /* the name and what it is right now — on the menu or off it — on one line */
  #prodview .pnhead{display:flex;align-items:center;flex-wrap:wrap;gap:5px}
  #prodview select.pstat,#rulesview select.pstat{font-size:var(--fs-2xs);padding:1px 3px;max-width:none}
  .invoice-panel select.pstat.on,#prodview select.pstat.on,#rulesview select.pstat.on,#discountsview select.pstat.on{color:var(--grn-tx);background:var(--grn-bg);border-color:var(--grn-bd)}
  .invoice-panel select.pstat.off,#prodview select.pstat.off,#rulesview select.pstat.off,#discountsview select.pstat.off{color:var(--red-tx);background:var(--red-bg);border-color:var(--red-chip)}
  /* Menu → Discounts (2026-09-15): a card per rule — the deal's name, an
     Active/Inactive status picker, and a read-only line saying what it does.
     Reuses the card/pstat idioms; only the card layout is its own. */
  #discountsview .disc-cards{display:grid;gap:12px;max-width:560px}
  #discountsview .disc-card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 18px}
  #discountsview .disc-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
  #discountsview .disc-head h4{margin:0;font-size:var(--fs-md);color:var(--ink)}
  #discountsview .disc-ex{font-size:var(--fs-sm);color:var(--dim)}
  /* On the printed menu / Hidden — green when it prints, amber when held off it
     (a live POS item that is not a menu dish, so amber not red). */
  #prodview select.pmvis{font-size:var(--fs-2xs);padding:1px 3px;max-width:none}
  .invoice-panel select.pmvis.on,#prodview select.pmvis.on{color:var(--grn-tx);background:var(--grn-bg);border-color:var(--grn-bd)}
  .invoice-panel select.pmvis.off,#prodview select.pmvis.off{color:var(--amb-tx);background:var(--amb-bg);border-color:var(--amb-bd)}
  /* the second line in a money cell */
  #prodview .prodsub{color:var(--dim);font-size:var(--fs-xs);margin-top:1px}
  /* the customer-facing description, one clamped line under the name (edited in
     full in the panel) — takes its colour and size from .prodsub */
  #prodview .pdescline{max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* the "Not on printed menu" marker under a hidden product's name — amber */
  #prodview .prodsub.offmenu{color:var(--amb-tx)}
  /* under the after-VAT margin target: amber row edge, and the number itself
     carries the weight */
  /* an amber rail flags a row that needs attention: below the margin target, or
     (uncosted) an active product with no unit cost to judge a margin against */
  #prodview .txtable tr.over td:first-child,#prodview .txtable tr.uncosted td:first-child{box-shadow:inset 3px 0 0 var(--amb-bar)}
  #prodview .pct{font-variant-numeric:tabular-nums;color:var(--dim)}
  #prodview .pct.under{color:var(--amb-tx);font-weight:bold}
  #prodview .nocost{color:var(--dim)}
  .prodcards{display:none}
  @media (max-width:760px){
    #prodview .tblwrap{display:none}
    .prodcards{display:block}
    /* the same panel every other list takes; "over" is the amber edge bar rather
       than a border and a wash, so a run of them reads as rows of one list */
    #prodview .prodcard{position:relative;overflow:hidden;background:transparent;border:0;
      border-top:1px solid var(--grid);border-radius:0;padding:10px 12px;font-size:var(--fs-sm)}
    #prodview .prodcard:first-child{border-top:0}
    #prodview .prodcard.over:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;
      background:var(--amb-bar)}
    #prodview .prodcard .r{display:flex;justify-content:space-between;gap:10px;color:var(--text-2);font-size:var(--fs-sm);margin-top:3px}
    /* name + price on one row; the routing under the name, the cost and margin
       stacked under the price on the right */
    #prodview .pctop{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}
    #prodview .pcid{min-width:0;flex:1 1 auto}
    #prodview .pcstation{font-size:var(--fs-xs);color:var(--text-2);margin-top:3px}
    #prodview .pcstation.offmenu{color:var(--amb-tx)}
    #prodview .pcmoney{flex:0 0 auto;display:flex;flex-direction:column;align-items:flex-end;gap:2px;
      white-space:nowrap;text-align:right}
    #prodview .pcprice{font-weight:bold;font-variant-numeric:tabular-nums}
    #prodview .pccost{color:var(--text-2);font-size:var(--fs-sm);font-variant-numeric:tabular-nums}
  }
  /* sales view — read model over the order store; tables share .txtable */
  /* ---- Orders ----------------------------------------------------------
     The list is the app's own .tblwrap/.txtable and the drawer the app's own
     panel shell, so almost nothing lives here. What does: the payment pill's
     red variant (the .cinvst family has amber and green but no "this failed"),
     the run-count inside a pill, and the VAT sub-rows in the totals list. */
  /* the red variant lives with its family below, not here: it was scoped to
     #ordersview and the drawer renders in #panelhost, so one order read
     `refunded` in red on the row and in neutral grey in its own panel */
  #ordersview .cinvst+.cinvst{margin-left:3px}
  #ordersview td .infopill{vertical-align:baseline}
  /* Over a span, a time alone does not say which day it is on, so the day sits
     under it — dim, the way the order id sits under its description. */
  #ordersview .ordwhen{white-space:nowrap}
  #ordersview .oday{display:block;font-size:var(--fs-2xs);color:var(--dim);margin-top:1px;
    font-variant-numeric:tabular-nums}
  /* What the status pill could not fit: how much went back, a split bill, the
     declines a later payment superseded. Same treatment as the day under a time
     and the id under a description — dim, on its own line, so the pill stays the
     thing the eye lands on. */
  #ordersview .ostat{display:block;font-size:var(--fs-2xs);color:var(--dim);margin-top:2px}
  #ordersview .ostat.amb{color:var(--amb-tx-2)}
  /* the warning beside a green `paid` pill whose card payment never settled — the
     pill keeps Syrve's own word, the triangle says it is not done yet. Amber while
     a tap might still arrive; red (.bad) once the gap is money at risk, so it reads
     the same tone as its row. */
  /* the orders table AND the order drawer both hang this on the `paid` pill */
  #ordersview .ordwarn,.invoice-panel .ordwarn{width:13px;height:13px;margin-left:4px;vertical-align:-2px;
    fill:none;stroke:var(--amb-tx-2);stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  #ordersview .ordwarn.bad,.invoice-panel .ordwarn.bad{stroke:var(--red-tx-2)}
  /* the wait column: on time, getting late, late */
  #ordersview .owait.ok{color:var(--grn-tx-2)}
  #ordersview .owait.amb{color:var(--amb-tx-2)}
  #ordersview .owait.late{color:var(--red-tx-2);font-weight:600}
  /* WHAT WAS RUNG UP, under what it came to. A sale that absorbed its refund
     prints its net on the line and the original struck beneath it — stacked,
     not beside, because a second figure across the Gross column widens every
     row on the page for the two that need it. Same treatment as the day under
     a time and the id under a description: dim, small, on its own line. */
  #ordersview .was{display:block;font-size:var(--fs-2xs);color:var(--dim);margin-top:1px;
    font-variant-numeric:tabular-nums;text-decoration:line-through}
  #ordersview .ordcard-g .was{font-weight:normal}
  /* THE GAP-CONNECT CONTROL — a card capture with no till order, a refund with
     no sale, a card-tendered Syrve order with no tap. Each is a gap somebody
     closes, so it wears the amber "gap to fill" pill the + Connect affordances
     use (.miss), whether it sits on a table row or in the drawer. It stays
     sparse — only the unmatched few carry it — so amber draws the eye to the
     rows that want a hand rather than tiling into a wall (that is why the dense
     Documents table keeps the neutral .cinv-connect instead). Once the gap is
     filled, "Connect another" goes neutral, as "Find another…" does beside an
     attached document. */
  .linkbtn{font:inherit;font-size:var(--fs-xs);line-height:1;padding:4px 9px;
    border:1px solid var(--amb-bd-2);border-radius:6px;background:var(--panel);
    color:var(--amb-tx-2);cursor:pointer;text-decoration:none;white-space:nowrap}
  .linkbtn.another{color:var(--text-2);border-color:var(--line)}
  /* red where the gap it closes is money at risk (an overdue or failed card tap),
     so the connect button reads the same tone as the row it sits on */
  .linkbtn.bad{color:var(--red-tx-2);border-color:var(--red-bd)}
  @media (hover:hover){ .linkbtn:hover{color:var(--red-ink);border-color:var(--red)} }
  /* The picker, on the same fixed-popover pattern as the day and filter menus,
     for the same reason: the actions band scrolls and a positioned popover
     inside it would be clipped. */
  /* A centred modal, the shape the "Connect a…" pickers use: a coral title, a
     search, the scrolling list, Cancel. #ordLinkBd dims the page on any size;
     the phone override further down redraws it as a bottom sheet. */
  #ordLinkMenu{position:fixed;display:none;z-index:120;top:50%;left:50%;
    transform:translate(-50%,-50%);flex-direction:column;
    background:var(--panel);border:1px solid var(--line);border-radius:12px;
    box-shadow:var(--shadow-modal);
    max-width:560px;width:calc(100vw - 32px);max-height:78vh;overflow:hidden;font-size:var(--fs-sm)}
  #ordLinkMenu.open{display:flex}
  #ordLinkMenu .lkhead{display:flex;align-items:flex-start;gap:10px;padding:14px 14px 8px;font-weight:bold}
  #ordLinkMenu .lkttl{flex:1 1 auto;min-width:0;color:var(--red-ink)}
  #ordLinkMenu .lkttl span{display:block;font-weight:normal;color:var(--dim);
    font-size:var(--fs-2xs);margin-top:3px;line-height:1.4}
  #ordLinkMenu .lkx{flex:0 0 auto;font:inherit;font-size:var(--fs-xl);line-height:1;
    background:none;border:0;padding:0 2px;margin:-4px -4px 0 0;color:var(--dim);cursor:pointer}
  @media (hover:hover){ #ordLinkMenu .lkx:hover{color:var(--ink)} }
  #ordLinkMenu .lksearch{font:inherit;font-size:var(--fs-md);margin:0 14px 8px;padding:8px 10px;
    border:1px solid var(--line);border-radius:7px;background:var(--panel-2);color:var(--ink)}
  #ordLinkMenu .lksearch:focus{border-color:var(--red);outline:none}
  #ordLinkMenu .lklist{overflow:auto;flex:1 1 auto;min-height:0}
  #ordLinkMenu .lkfoot{display:flex;justify-content:flex-end;padding:10px 14px;border-top:1px solid var(--grid)}
  #ordLinkMenu .lkfoot button{font:inherit;font-size:var(--fs-sm);padding:6px 12px;border:1px solid var(--line);
    background:var(--panel);border-radius:6px;color:var(--ink-3);cursor:pointer}
  @media (pointer:coarse){ #ordLinkMenu .lksearch{font-size:var(--fs-xl)} }
  #ordLinkMenu .lkrow{display:grid;grid-template-columns:46px minmax(0,1fr) auto auto;
    gap:10px;align-items:center;padding:8px 12px;border-bottom:1px solid var(--grid);
    width:100%;background:none;border-left:0;border-right:0;border-top:0;
    font:inherit;color:var(--ink);text-align:left;cursor:pointer}
  #ordLinkMenu .lkrow:last-child{border-bottom:0}
  @media (hover:hover){ #ordLinkMenu .lkrow:hover:not([disabled]){background:var(--hover)} }
  #ordLinkMenu .lkrow[disabled]{cursor:default;color:var(--muted)}
  #ordLinkMenu .lkw{color:var(--dim);font-variant-numeric:tabular-nums}
  #ordLinkMenu .lkg{font-variant-numeric:tabular-nums;white-space:nowrap}
  #ordLinkMenu .lkwhy{display:block;color:var(--dim);font-size:var(--fs-2xs);margin-top:1px}
  /* A tap already paying another order offers two actions in its last cell — move
     the pairing here, or say it also covered this order. The row is a div, not a
     button, and its two buttons stack so they fit the narrow action column. */
  #ordLinkMenu .lkrow-acts{cursor:default}
  #ordLinkMenu .lkacts{display:flex;flex-direction:column;gap:4px;align-items:stretch}
  #ordLinkMenu .lkacts .linkbtn{white-space:nowrap}
  /* A tickable row in the tap→orders split picker: a checkbox, then the same
     time / name / amount as a plain row. Flex rather than the row grid so the
     checkbox column does not disturb the mobile layout below. */
  #ordLinkMenu .lkpick{display:flex;align-items:center;gap:10px;padding:8px 12px;
    border-bottom:1px solid var(--grid);cursor:pointer}
  #ordLinkMenu .lkpick:last-child{border-bottom:0}
  #ordLinkMenu .lkpick input{flex:none;width:16px;height:16px;accent-color:var(--link)}
  #ordLinkMenu .lkpick>span:nth-child(3){flex:1;min-width:0}
  #ordLinkMenu .lkpick .lkg{margin-left:auto}
  @media (hover:hover){ #ordLinkMenu .lkpick:hover{background:var(--hover)} }
  /* The split picker's foot carries a running total on the left and Apply/Cancel
     on the right: exact turns green, over turns red. */
  #ordLinkMenu .lkfootsplit{justify-content:space-between;align-items:center;gap:10px}
  #ordLinkMenu .lksum{font-size:var(--fs-sm);color:var(--dim);font-variant-numeric:tabular-nums}
  #ordLinkMenu .lksum.ok{color:var(--grn-tx)}
  #ordLinkMenu .lksum.over{color:var(--red-tx)}
  #ordLinkMenu .lkfootbtns{display:flex;gap:8px}
  #ordLinkMenu .lknone{padding:12px;color:var(--dim);line-height:1.5}
  /* The fold the two card pickers wear — the line between what is still short
     its card money and what a tap already (or cash) covers, and the switch that
     opens the second. Mirrors #txpick .pickmore. */
  #ordLinkMenu .lkmore{width:100%;border:0;border-top:1px solid var(--line);border-radius:0;
    background:none;padding:9px 12px;font:inherit;font-size:var(--fs-xs);color:var(--dim);
    text-align:left;cursor:pointer}
  @media (hover:hover){ #ordLinkMenu .lkmore:hover{color:var(--ink)} }
  /* ON A PHONE IT IS A SHEET, not a popover — see ordLinkPlace. The drawer it
     opens from is the whole screen and the button is wherever that drawer has
     scrolled to, so a popover hung off the button's left edge ran off the
     right of the screen and wrapped every item name one word to a line. Full
     width off the bottom edge instead, a backdrop of its own so the tap that
     dismisses it does not land on the drawer underneath, and each row on two
     lines: when and what beside the money, why it was not taken beside the
     word that links it. Phone-sized type, because it is read at arm's length. */
  #ordLinkBd{display:none}
  #ordLinkBd.open{display:block;position:fixed;inset:0;z-index:119;background:rgba(0,0,0,.42)}
  @media (max-width:700px){
    #ordLinkMenu{left:0;right:0;bottom:0;top:auto;transform:none;max-width:none;width:auto;
      max-height:min(88vh,calc(100vh - 40px));font-size:var(--fs-md);
      border-left:0;border-right:0;border-bottom:0;border-radius:16px 16px 0 0;
      padding-bottom:env(safe-area-inset-bottom)}
    #ordLinkMenu .lkhead{padding:14px 16px 8px}
    #ordLinkMenu .lksearch{margin:0 16px 8px}
    #ordLinkMenu .lkttl span{font-size:var(--fs-xs)}
    #ordLinkMenu .lkx{min-width:36px;min-height:36px;margin:-8px -10px -8px 0}
    /* every cell placed by hand: an item given a row and left to find its
       column is placed BEFORE the auto-placed ones, so the text — told only
       "span both rows" — took column one and pushed the time into its place */
    #ordLinkMenu .lkrow{grid-template-columns:44px minmax(0,1fr) auto;gap:2px 10px;padding:11px 16px}
    #ordLinkMenu .lkrow .lkw{grid-column:1;grid-row:1}
    #ordLinkMenu .lkrow>span:nth-child(2){grid-column:2;grid-row:1/3}
    #ordLinkMenu .lkrow .lkwhy{font-size:var(--fs-xs)}
    #ordLinkMenu .lkrow .lkg:nth-child(3){grid-column:3;grid-row:1}
    #ordLinkMenu .lkrow .lkg:last-child{grid-column:3;grid-row:2;font-size:var(--fs-xs);color:var(--link);text-align:right}
    #ordLinkMenu .lkrow[disabled] .lkg:last-child{color:inherit}
    #ordLinkMenu .lknone{padding:16px}
  }
  /* the order id under its description: a reference, in the ledger's own mono */
  #ordersview .oid{display:block;font-family:ui-monospace,Menlo,monospace;font-size:var(--fs-2xs);
    color:var(--dim);margin-top:1px}
  /* An order that never became a sale still belongs on the day — it happened —
     but it is not takings, so its figures recede rather than reading as money
     the eye should be adding up. */
  #ordersview tr.ordvoid td{color:var(--muted)}
  #ordersview tr.ordvoid td.num,#ordersview tr.ordvoid .vatcell{opacity:.6}
  /* Fee and VAT are the same kind of figure — both come off the gross — so
     they read as a pair rather than one as a number and one as a note. */
  /* The day picker. Fixed, like every other popover here, so the scrolling
     actions band cannot clip it. */
  #ordDayMenu,#txDateMenu{position:fixed;display:none;z-index:120;background:var(--panel);
    border:1px solid var(--line);border-radius:8px;box-shadow:var(--shadow-pop);
    width:280px;max-width:calc(100vw - 16px);padding:8px;flex-direction:column;gap:6px}
  #ordDayMenu.open,#txDateMenu.open{display:flex}
  /* Products' export menu: the day-picker shell, narrower, holding one button a
     row. Each is a full-width choice, not a grid, because the names are long
     ("PDF menu (Português)") and read down a list rather than across one. */
  #mobExportMenu{position:fixed;display:none;z-index:120;background:var(--panel);
    border:1px solid var(--line);border-radius:8px;box-shadow:var(--shadow-pop);
    width:220px;max-width:calc(100vw - 16px);padding:6px;flex-direction:column;gap:4px}
  #mobExportMenu.open{display:flex}
  #mobExportMenu button{font:inherit;font-size:var(--fs-sm);text-align:left;padding:9px 11px;
    border:0;background:transparent;border-radius:6px;cursor:pointer;color:var(--ink-2)}
  @media (hover:hover){ #mobExportMenu button:hover{background:var(--rowhover);color:var(--red-ink)} }
  #ordDayMenu .odm-span,#txDateMenu .odm-span{display:flex;gap:8px}
  #ordDayMenu .odm-span label,#txDateMenu .odm-span label{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:3px;
    font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--dim)}
  #ordDayMenu .odm-span input,#txDateMenu .odm-span input{font:inherit;font-size:var(--fs-sm);padding:4px 6px;border:1px solid var(--line);
    border-radius:6px;background:var(--panel);color:var(--ink-2);min-width:0}
  #ordDayMenu .odm-span input:focus,#txDateMenu .odm-span input:focus{border-color:var(--red);outline:none}
  #ordDayMenu .odm-head,#txDateMenu .odm-head{font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;
    color:var(--dim);padding:2px 2px 0;border-top:1px solid var(--grid);margin-top:2px;padding-top:8px}
  #ordDayMenu .odm-list{overflow:auto;max-height:260px;display:flex;flex-direction:column}
  /* one row per day that traded, with what it took — the whole point of the
     control is that a day with nothing behind it is not in this list at all */
  #ordDayMenu .odm-day{display:grid;grid-template-columns:minmax(0,1fr) 28px 72px;gap:8px;
    align-items:baseline;font:inherit;font-size:var(--fs-sm);text-align:left;padding:5px 7px;
    border:0;background:transparent;border-radius:6px;cursor:pointer;color:var(--ink-2)}
  #ordDayMenu .odm-day .odm-n,#ordDayMenu .odm-day .odm-t{text-align:right;color:var(--dim);
    font-size:var(--fs-xs);font-variant-numeric:tabular-nums}
  #ordDayMenu .odm-day.on{background:var(--red-bg);color:var(--red-ink);font-weight:bold}
  @media (hover:hover){ #ordDayMenu .odm-day:not(.on):hover{background:var(--rowhover)} }
  /* The period popover the Transactions funnel opens is the day picker reused:
     the same shell and the same From/To span, with a grid of the usual named
     periods (three to a row, like the reference control) where Orders lists the
     days that traded. A preset only fills the two dates, which stay the single
     source of truth the ledger filters on. */
  #txDateMenu .odm-presets{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
  #txDateMenu .odm-preset{font:inherit;font-size:var(--fs-xs);text-align:center;padding:7px 6px;
    border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--ink-2);cursor:pointer;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  #txDateMenu .odm-preset.wide{grid-column:1/-1}
  #txDateMenu .odm-preset.on{background:var(--red-bg);color:var(--red-ink);border-color:var(--red);font-weight:bold}
  @media (hover:hover){ #txDateMenu .odm-preset:not(.on):hover{background:var(--rowhover);color:var(--red-ink)} }
  /* The button that opens it, in the header band beside the search box (see
     #mobDate). It wears the calendar it stands for and names the range in force.
     A pill on the phone, the height of the round buttons it stands among; on the
     desktop band it matches the chips (28px). It borrows .txfilter's look by
     hand rather than being a slice — a range is one narrowing with two ends. */
  .datebtn{flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;height:38px;padding:0 13px;
    border:1px solid var(--line-2);border-radius:19px;background:var(--soft);color:var(--ink);
    font:inherit;font-size:var(--fs-md);cursor:pointer;max-width:46vw}
  .datebtn>.dlabel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-variant-numeric:tabular-nums}
  .datebtn svg{flex:0 0 auto;width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.6;
    stroke-linecap:round;stroke-linejoin:round}
  /* a set date range is an active filter, so it wears the same filled-red pill as
     every filter chip, with a ✕ that clears it (brand guide §07 Filters) — not the
     old outline-red-without-✕. The calendar glyph rides currentColor to white. */
  .datebtn.on{border-color:var(--red);background:var(--red);color:#fff}
  /* the ✕ on a set range, shown only while on; a tap on it resets to the default
     (Any date, or the landing day) rather than opening the picker */
  .datebtn>.dclear{display:none;flex:0 0 auto;margin:0 -4px 0 1px;padding:2px;font-size:12px;line-height:1;opacity:.85}
  .datebtn.on>.dclear{display:inline-flex;align-items:center}
  @media (hover:hover){ .datebtn:not(.on):hover{border-color:var(--red);color:var(--red-ink)}
    .datebtn.on:hover{background:var(--red-hover);border-color:var(--red-hover)}
    .datebtn.on>.dclear:hover{opacity:1} }
  @media (min-width:701px){
    #actionsBar #mobDate{height:28px;padding:0 12px;border-radius:14px;max-width:220px}
    #actionsBar #mobDate svg{width:13px;height:13px}
  }
  /* The date stepper in the band: ‹ label ›. The arrows are one control with the
     label between them; they show only on a phone, where the date lives in the
     band centre — on the desktop the wrapper collapses to the plain pill (#mobDate
     keeps its own radius), because the desktop keeps its fuller day stepper in the
     slice row. */
  /* the band's single-filter control (Categories' Type select, Counterparties'
     Type button) — a pill the height of the search box beside it. */
  #mobExtra{align-items:center;gap:8px}
  .mobextra-btn{flex:1 1 auto;min-width:0;display:inline-flex;align-items:center;justify-content:space-between;gap:6px;height:38px;
    padding:0 11px 0 13px;border:1px solid var(--line-2);border-radius:19px;background:var(--soft);color:var(--ink);
    font:inherit;font-size:var(--fs-md);cursor:pointer}
  .mobextra-btn svg{flex:0 0 auto;width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  .mobextra-btn>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* a named ACTION in the band (Rules' Run rules): a play glyph plus its label,
     centred, no chevron — it does something rather than opening a menu */
  .mobextra-btn.act{justify-content:center;gap:8px}
  .mobextra-btn.act svg{fill:currentColor;stroke:none;width:12px;height:12px}
  #mobExtra .mobextra-btn.nar{border-color:var(--red);color:var(--red-ink);background:var(--red-bg)}
  @media (min-width:701px){ #mobExtra{display:none!important} }  /* the band control is a phone affordance; the desktop uses the column header */
  .mobstep{display:inline-flex;align-items:stretch;flex:0 0 auto}
  .mobstep .stparw{display:none}            /* desktop: no arrows, just the pill */
  .mobstep .stplab{flex:0 0 auto}
  @media (max-width:700px){
    #actionsBar .mobstep{flex:1 1 auto;min-width:0}
    #actionsBar .mobstep .stparw{display:grid;place-items:center;flex:0 0 auto;width:34px;
      border:1px solid var(--line-2);background:var(--soft);color:var(--ink);cursor:pointer}
    #actionsBar .mobstep .stparw svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;
      stroke-linecap:round;stroke-linejoin:round}
    #actionsBar .mobstep .stparw:first-child{border-radius:19px 0 0 19px;border-right:0}
    #actionsBar .mobstep .stparw:last-child{border-radius:0 19px 19px 0;border-left:0}
    #actionsBar .mobstep .stplab{flex:1 1 auto;min-width:0;border-radius:0;justify-content:center;max-width:none}
    @media (hover:hover){ #actionsBar .mobstep .stparw:hover{color:var(--red-ink);border-color:var(--red)} }
  }
  /* Back, the span, forward — one control in three parts, so the arrows read as
     belonging to the day beside them rather than as two more chips. */
  .ordstep{display:inline-flex;align-items:stretch;flex:0 0 auto}
  .ordstep>.txfilter{border-radius:0;margin:0}
  .ordstep>.txfilter:first-child{border-radius:14px 0 0 14px}
  .ordstep>.txfilter:last-child{border-radius:0 14px 14px 0}
  .ordstep>.txfilter+.txfilter{border-left:0}
  .ordarrow{padding:5px 9px;font-size:var(--fs-lg);line-height:1;color:var(--text-2)}
  @media (hover:hover){ .ordarrow:hover{color:var(--red-ink)} }
  /* Sales Summary range bar: the preset segmented control, an optional custom
     day pair, and the comparison basis. .srange reuses the .ordstep idiom; the
     active preset carries .on like the merchant filter. */
  .srbar{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0 0 14px}
  .srange>.txfilter.on{background:var(--red-ink);color:#fff;border-color:var(--red-ink)}
  .srcustom{display:inline-flex;align-items:center;gap:6px}
  .srcustom[hidden]{display:none}   /* an explicit display beats the UA [hidden] rule, so restate it */
  .srcustom input[type=date]{padding:4px 8px;font-variant-numeric:tabular-nums}
  .srto{color:var(--dim)}
  /* the compare-against basis. Once the day range grew to five options the
     segmented row overflowed a phone with nothing to scroll it, so it is a native
     select — which gets the platform's own picker on a phone — with a short
     explainer beside it (the date it lands on, or "average day"). The group wraps
     as one so the label, picker and hint stay together under the range row. */
  .srcmpwrap{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}
  .srcmplab{color:var(--dim);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.04em;margin-left:4px}
  .srcmpsel{font:inherit;font-size:var(--fs-sm);padding:5px 28px 5px 11px;border:1px solid var(--c-prev);
    border-radius:14px;background:var(--panel) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23948A82' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>") no-repeat right 10px center;
    color:var(--ink-2,var(--text));cursor:pointer;appearance:none;-webkit-appearance:none;font-weight:600}
  @media (hover:hover){ .srcmpsel:hover{border-color:var(--c-prev)} }
  .srcmp{color:var(--dim);font-size:var(--fs-sm);font-style:italic;font-variant-numeric:tabular-nums}
  /* the primary trend chart's Cumulative / Per-point toggle — its "on" draws in
     the navy the current-period line carries, so the switch and the line agree */
  .srtrend>.txfilter.on{background:var(--c-now);color:#fff;border-color:var(--c-now)}
  /* the "now" marker rides ABOVE the SVG as HTML, not inside it: the chart is drawn
     preserveAspectRatio=none (the lines are meant to stretch), which would pull a
     drawn circle into an ellipse — the stretched blob bug. Positioned as a percent
     of the box, so it lands exactly on the line's last point and stays round. */
  #salesview .strendbox{position:relative}
  #salesview .strendbox>.tchart{margin:0}
  .strenddot{position:absolute;transform:translate(-50%,-50%);pointer-events:none;z-index:2}
  .strenddot>i{display:block;width:9px;height:9px;border-radius:50%;background:var(--c-now);border:2px solid var(--panel);box-sizing:border-box}
  .strenddot>b{position:absolute;left:50%;bottom:9px;transform:translateX(-50%);font-weight:700;font-size:var(--fs-3xs);color:var(--c-now);white-space:nowrap}
  /* The button that opens it wears the calendar it stands for, and holds a
     FIXED width so the arrows either side never move. The label runs from
     "Sunday 3 May" to "Wednesday 30 September", and letting the button size to
     it meant every step of the arrows moved the arrows — you cannot click
     through a week without chasing the button across the band. Sized to the
     longest label there is, centred, and anything unexpected ellipsises rather
     than pushing them again. */
  .orddaybtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;
    width:190px;flex:0 0 190px}
  .orddaybtn>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
    font-variant-numeric:tabular-nums}
  .orddaybtn svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.6;
    stroke-linecap:round;stroke-linejoin:round}
  #ordersview .vatcell{font-size:var(--fs-sm);color:var(--text-2);white-space:nowrap;
    font-variant-numeric:tabular-nums}
  /* A fee that has not been struck yet, and one that has been struck at nothing.
     Both are words in a column of figures, so neither takes a pill — a pill in a
     money column breaks the scan down it. Amber for the one still to come, dim
     for the settled zero, which is a fact and not a gap. */
  #ordersview .ofeep{color:var(--amb-tx-2)}
  #ordersview .ofee0{color:var(--dim)}
  #ordersview tfoot .ofeep{font-weight:normal}

  /* ---- ORDERS, ON A PHONE ------------------------------------------------
     Ten columns is a table that can only be read sideways, and the four that
     answer "is this one all right" — Order, Payment, Method, Filing — are the
     last four of the ten: on a phone the list opened at the end of a shift
     showed a time cut in half at one edge and a gross cut in half at the other.

     So below 700px the table becomes a two-line row, and the second line is a
     SENTENCE rather than a strip of pills. That is the whole design: at 11px,
     "4 items · method unknown · part refunded · undeclared" measures about
     275px against the 314px a row has left once the time and the gross have
     taken theirs, so every fact fits as words. As pills it does not — a pill
     spends sixteen pixels of padding and a border on each one, and three of
     them push the filing state off the row. Colour does what the pill's fill
     did: green where the sale stands, amber where it does not, grey where the
     order was abandoned, red where money never came back. A column of forty
     "paid"s recedes; the two rows that are not green do not.

     `.ordlist`, not `.ordcards`: that name belongs to the ITEM list inside an
     order's drawer, and its `.ordcards .pcard` outranks a bare `.ordcard` — the
     row silently inherited the drawer's three columns the first time round.

     The shell is the shared one (.pgrp / .pcard / .pcards-foot, and .txday over
     each day of a span); what lives here is this list's four columns, the
     sentence under them, and the two marks the wide row also carries.

     Nearly every rule is scoped through #ordersview. The card shell blockifies
     and ellipsises every bare span inside a .pcard (four classes deep), which
     is right for the order's name and wrong for a sentence that must wrap and
     for the coloured words inside it; an id outranks any number of classes. */
  /* --ord-* are this list's own; --tx-* are what the borrowed .txday reads for
     the day headings over a span. An undefined var in a grid-template-columns
     makes the whole declaration invalid at computed-value time — the track list
     falls back to `none` and the heading's figure drops onto a second row,
     which is exactly what it did. They are the ledger's values, and they land
     the day's gross on the same right edge the cards' grosses sit on: 18px in,
     the chevron column plus its gap. */
  .ordlist{display:none;--ord-t:42px;--ord-gap:8px;--tx-amt:92px;--tx-gap:8px}
  /* time · what was sold · what it came to · the way in. Baseline-aligned, so
     the 12px time and the 13px gross sit on one line with the name between. */
  .ordcard{position:relative;overflow:hidden;display:grid;
    grid-template-columns:var(--ord-t) minmax(0,1fr) auto 10px;gap:var(--ord-gap);
    align-items:baseline}
  /* the amber edge the wide row wears for the same two gaps — an unmapped till
     name, or a completed sale with no payment behind it. Absolute, so it costs
     the content no width, and the hairline between two cards crosses it. */
  .ordcard.needs:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;
    background:var(--amb-bar)}
  #ordersview .ordcard-t{font-size:var(--fs-sm);color:var(--text-2);white-space:nowrap;
    font-variant-numeric:tabular-nums}
  #ordersview .ordcard-nm{font-size:var(--fs-sm);color:var(--ink-2)}
  #ordersview .ordcard-g{font-size:var(--fs-md);font-weight:bold;color:var(--ink-2);text-align:right;
    white-space:nowrap;font-variant-numeric:tabular-nums}
  #ordersview .ordcard-g.txneg{color:var(--red-tx-2)}
  #ordersview .ordcard .pcard-more{grid-column:4;grid-row:1;align-self:center}
  /* The lines that hang under the name, each starting where the name starts
     rather than under the time — the time heads the row, it does not indent it.
     They wrap: the sentence is the tender, the status and any note, and the amber
     line is the count of till names nothing on Products matches. Neither is worth
     reading half of. The note and miss lines run the full width beneath. */
  #ordersview .ordcard-note,#ordersview .ordcard .pcard-miss{
    grid-column:2 / span 3;white-space:normal;overflow:visible;text-overflow:clip;margin-top:2px}
  /* the payment line sits in the NAME column only (col 2), so the struck old
     price can ride the same second row on the right (col 3) rather than stacking
     under the gross and stretching row 1 — one tight second row, no gap */
  #ordersview .ordcard-sub{grid-column:2;grid-row:2;white-space:normal;overflow:visible;
    text-overflow:clip;margin-top:2px;font-size:var(--fs-xs);color:var(--dim)}
  /* the list price before the discount (or before a folded refund): under the
     gross, pinned to the TOP of its cell so it hugs the price above it */
  #ordersview .ordcard-was{grid-column:3;grid-row:2;justify-self:end;align-self:start;
    margin-top:-3px;font-size:var(--fs-2xs);color:var(--dim);white-space:nowrap;
    font-variant-numeric:tabular-nums;text-decoration:line-through}
  /* the tender mark that leads the payment line: card = rounded rectangle,
     cash = coins. One grey weight so it never competes with the green/amber
     status word beside it; amber only where the tender itself is our gap. */
  #ordersview .ordcard-pay{width:15px;height:15px;display:inline-block;vertical-align:-3px;
    margin-right:4px;color:var(--dim);stroke:currentColor;fill:none;stroke-width:2;
    stroke-linecap:round;stroke-linejoin:round}
  #ordersview .ordcard-pay.amb{color:var(--amb-tx-2)}
  /* which discount was applied: its own line under the payment, led by a price
     tag, the amount in the same amber the discount figure always takes */
  #ordersview .ordcard-disc{grid-column:2 / span 3;white-space:normal;overflow:visible;
    text-overflow:clip;margin-top:2px;font-size:var(--fs-xs);color:var(--dim)}
  #ordersview .ordcard-tag{width:13px;height:13px;display:inline-block;vertical-align:-2px;
    margin-right:4px;color:var(--muted-2);stroke:currentColor;fill:none;stroke-width:2;
    stroke-linecap:round;stroke-linejoin:round}
  #ordersview .ordcard-disc .ordcard-damt{color:var(--amb-tx-2);font-variant-numeric:tabular-nums}
  /* the coloured words inside the sentence stay IN the sentence — the shell's
     `.pcard span{display:block}` would otherwise stack the name and amount (and
     the payment line's status words) each on their own line */
  #ordersview .ordcard-sub span,#ordersview .ordcard-disc span{display:inline}
  #ordersview .ordcard-sub .ok{color:var(--grn-tx)}
  #ordersview .ordcard-sub .warn{color:var(--amb-tx-2)}
  #ordersview .ordcard-sub .bad{color:var(--red-tx-2)}
  #ordersview .ordcard-sub .dull{color:var(--muted-2)}
  /* what the status word could not fit: how much went back, a split bill, the
     declines a later payment superseded. Its own line, and only where there is
     one — the same facts the table hangs under its status pill. */
  #ordersview .ordcard-note{font-size:var(--fs-2xs);color:var(--dim)}
  /* an order that never became a sale still belongs on the day, but it is not
     takings: it recedes here exactly as tr.ordvoid does in the table */
  #ordersview .ordcard.ordvoid .ordcard-nm,#ordersview .ordcard.ordvoid .ordcard-g{color:var(--muted)}
  /* the table's totals row, on the cards' own columns: what was counted on the
     left, the one figure it comes to on the right, over the same 2px rule every
     other card list closes with */
  .ordlist-foot{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px}
  @media (max-width:700px){
    /* The last thing on this page is its totals, and the "+ Document" button
       floats over the bottom-right corner — which is where the gross total
       lands. The list ends above it rather than under it. */
    #ordersview{padding:12px 12px calc(78px + env(safe-area-inset-bottom));overflow-x:hidden}
    #ordersview .tblwrap{display:none}
    #ordersview .ordlist{display:block}
  }
  /* the narrowest phones: give the order's own name back a few pixels */
  @media (max-width:360px){ .ordlist{--ord-t:38px;--ord-gap:6px;--tx-amt:84px;--tx-gap:6px} }

  /* a breakdown under the line it breaks down, as the statements table does it:
     dim, indented, and still in the same number column */
  .prows .prow.vsub{padding-top:2px;padding-bottom:2px;border-bottom:0}
  .prows .prow.vsub .pk{padding-left:12px;color:var(--dim)}
  .prows .prow.vsub .pv{color:var(--dim)}
  /* when a terminal state happened, beside the pill that says it did */
  /* Inline spacing from the value comes from the row's flex gap (6px), not a
     margin — so a note that WRAPS to its own line lands flush at the column's
     left edge instead of hanging indented under the value it followed. */
  .prow .pwhen{font-size:var(--fs-xs);color:var(--dim)}
  /* a reference you read rather than a chip you click — the order id under the
     amount, the ledger key behind the day's takings */
  /* PAYMENTS. One bordered group like .pgrp, but three columns rather than the
     item list's three: an instrument badge, what it was and when, and the money.
     The two summary lines close it the way Revolut's own panel does. */
  .invoice-panel .paylist{background:var(--panel);border:1px solid var(--line);
    border-radius:10px;overflow:hidden}
  .invoice-panel .payrow{display:grid;grid-template-columns:34px minmax(0,1fr) auto;
    gap:10px;align-items:center;padding:9px 12px;border-bottom:1px solid var(--grid);
    font-size:var(--fs-sm)}
  .invoice-panel .paylist>*:last-child{border-bottom:0}
  /* the instrument, as a badge the width of a card chip — three or four
     characters, so TAP and CASH both fit without the column moving */
  .invoice-panel .payicon{width:34px;height:24px;border-radius:5px;
    border:1px solid var(--line-2);background:var(--soft-2);display:flex;
    align-items:center;justify-content:center;font-size:var(--fs-3xs);font-weight:700;
    color:var(--text-2);line-height:1.05;text-align:center}
  .invoice-panel .payicon.back{background:var(--red-bg);border-color:var(--red-bd);
    color:var(--red-tx-2)}
  /* an attempt that moved no money: dashed, so it reads as an outline of a
     payment rather than one that happened */
  .invoice-panel .payicon.dead{background:transparent;color:var(--muted-2);border-style:dashed}
  .invoice-panel .payname{font-weight:600;color:var(--ink-2);display:block}
  .invoice-panel button.payname{background:none;border:0;padding:0;font:inherit;
    font-weight:600;cursor:pointer;text-align:left}
  .invoice-panel .paywhen{font-size:var(--fs-xs);color:var(--dim);display:block;margin-top:1px}
  /* the row's own name, under what it is and when — the same reference pill the
     rest of the page uses, so one gesture copies any id anywhere */
  .invoice-panel .payid{display:block;margin-top:2px}
  .invoice-panel .payid .txref{font-size:var(--fs-2xs)}
  .invoice-panel .paymoney{font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
  .invoice-panel .paymoney.neg{color:var(--red-tx-2)}
  .invoice-panel .paysum{display:flex;justify-content:space-between;gap:12px;
    padding:7px 12px;font-size:var(--fs-sm);color:var(--text-2);background:var(--panel-2);
    border-bottom:1px solid var(--grid)}
  .invoice-panel .paysum b{font-variant-numeric:tabular-nums;color:var(--ink-2)}
  .invoice-panel .paysum.net{background:var(--soft-2);font-weight:600;color:var(--ink-2)}
  /* nothing settled, money owed: the summary lines of a failed-card ticket in red */
  .invoice-panel .paysum.alert{background:var(--red-bg);font-weight:600}
  .invoice-panel .paysum.alert b{color:var(--red-tx-2)}
  /* an open card payment still in its grace window: amber, awaiting Revolut */
  .invoice-panel .paysum.warn{background:var(--amb-bg);font-weight:600}
  .invoice-panel .paysum.warn b{color:var(--amb-tx-2)}
  .invoice-panel .ordref{font-family:ui-monospace,Menlo,monospace;font-size:var(--fs-xs);color:var(--dim)}
  .phead .psub .txref{font-size:var(--fs-sm)}
  #ordersview .oid .txref{font-size:var(--fs-2xs)}
  /* The order's items. Every card list in this app sets its own columns — the
     shared .pcard only says a card is a row in a panel, not how wide its cells
     are — and without this the three headings ran together as ITEMQTYTOTAL and
     the qty stacked on top of the total. */
  .ordcards .pcard{display:grid;
    grid-template-columns:minmax(0,1fr) 96px 84px;gap:10px;align-items:center}
  .ordcards .pcard{cursor:default}
  .ordcards .pcard>div.pcard-r{text-align:right;align-items:flex-end}
  /* Nothing between .pcard and <body> sets a size — .pcard is `font:inherit`
     and .invoice-panel does not declare one — so an item name was coming out at
     the browser's 16px while every other line in the drawer sits at 11–12.5px.
     Every other card list sizes its own name for the same reason (see
     .ucard-name); this one had no class on that span at all. */
  .ordcards .pcard{font-size:var(--fs-sm)}
  /* Name and section on one baseline — the cleaned name in bold, its section
     trailing in grey — the same shape the product sync modal draws (.sygh). */
  .ordcards .pcard .ordtitle{display:flex;align-items:baseline;gap:8px;min-width:0;max-width:100%}
  .ordcards .pcard .ordname{font-size:var(--fs-sm);font-weight:600;color:var(--ink-2);
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .ordcards .pcard .pcard-n{font-size:var(--fs-sm)}
  /* a line the till VOIDED off the ticket — never sold, not in the count or the
     totals. Grey, not amber: it is an accurate record, not an unresolved gap
     (see the brand guide's affordance rule). Name struck, a neutral "Voided"
     tag where the section would sit, and an em dash where the price would be —
     so it can never be mistaken for a €0.00 sale. */
  .ordcards .pcard.void .ordname{color:var(--na-tx);text-decoration:line-through;
    text-decoration-color:var(--muted-4)}
  .ordcards .pcard.void .pcard-sub{color:var(--na-tx)}
  /* the "Voided" badge on a struck-through line in the order drawer. Its own
     class, NOT `.ordvoid`: that name is also the STATE class on a cancelled or
     comped table row (tr.ordvoid) and phone card (.ordcard.ordvoid), and a bare
     `.ordvoid{font-size…text-transform:uppercase}` leaked this badge's
     typography onto whole rows — the uppercase, bold, shrunk cancelled/comped
     rows. Scoped to the badge, the state class carries colour only. */
  .voidtag{flex:0 0 auto;font-size:var(--fs-3xs);font-weight:700;letter-spacing:.05em;
    text-transform:uppercase;color:var(--na-tx);background:var(--soft);
    border:1px solid var(--line);border-radius:5px;padding:1px 6px}
  .ordcards .pcard .pcard-dash{font-size:var(--fs-sm);font-weight:600;color:var(--muted-4)}
  /* the one-line note under the "Order · N items" head, when a line was voided */
  .ordh3note{margin:-4px 0 8px;font-size:var(--fs-xs);color:var(--dim)}
  /* A tally, not a form: the label takes the width it needs and the figure
     lines up on the right edge with every other figure. */
  .ordtot .prow>.pk{flex:0 1 auto}
  .ordtot .prow>.pv{justify-content:flex-end;text-align:right;
    font-variant-numeric:tabular-nums}
  .ordtot .prow.vsub{padding-top:2px;padding-bottom:2px;border-bottom:0}
  .ordtot .prow.vsub>.pk{padding-left:14px;color:var(--dim)}
  .ordtot .prow.vsub>.pv{color:var(--dim)}
  /* A breakdown belongs to the figure above it, so no rule is drawn between
     VAT and its rates. The last rate carries the one that closes the block
     instead — which is the line above Post-VAT sales. The :last-child guard is
     for a breakdown that ever ends a list: there would be nothing under it to
     separate, and .prows already drops that border. */
  .ordtot .prow:has(+ .prow.vsub){border-bottom:0}
  .ordtot .prows>.prow.vsub:not(:has(+ .prow.vsub)):not(:last-child){
    border-bottom:1px solid var(--grid);padding-bottom:9px}
  /* The qualifier sits UNDER the figure. Beside it, it pushed the figure left
     out of the column the row above says it lines up in. */
  .ordtot .prow>.pv>.pwhen{flex:0 0 100%;margin-left:0}
  #ordersview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:18px 0 8px}
  #salesview{position:relative}
  #salesview h3,#salesreconview h3{color:var(--red-ink);font-size:var(--fs-sm);font-weight:700;margin:18px 0 8px}
  #salesview h3 span,#salesreconview h3 span{font-size:var(--fs-xs)}
  #salesview h3:first-of-type,#ordersview h3:first-of-type,#salesreconview h3:first-of-type{margin-top:0}
  #salesview .txtable{min-width:640px}
  #salesview .pct{font-variant-numeric:tabular-nums;color:var(--dim)}
  #salesview .nocost{color:var(--dim)}
  /* a sortable header: the whole cell is the hit target, the arrow says which
     column is carrying the sort and which way. */
  #salesview .txtable th.so{cursor:pointer;user-select:none;white-space:nowrap}
  #salesview .txtable th.so.on{color:var(--red-ink)}
  #salesview .txtable th .sarr{font-size:var(--fs-3xs);margin-left:3px}
  #salesview .txtable tr.prodtot td{font-weight:700;border-top:2px solid var(--line);
    border-bottom:0;background:var(--panel-2)}
  #salesview .txtable tr.prodtot:hover{background:var(--panel-2)}
  /* a click on a category card narrows the product table to that category,
     and the card lights to say so */
  #salesview #salesCats .hcard.sel{border-color:var(--red-bd);box-shadow:0 0 0 2px var(--red-bg)}
  /* the one-line "today so far" pulse — figures inline, link to the feed at the end */
  #salesview .tstrip{display:flex;gap:20px;flex-wrap:wrap;align-items:center;background:var(--panel);
    border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:var(--fs-sm);margin-bottom:6px}
  #salesview .tstrip b{font-variant-numeric:tabular-nums}
  #salesview .tstrip .grow{margin-left:auto}
  /* vertical bar charts: revenue by day, and average by weekday. One hue, the
     same navy the trend lines carry; a bar is titled with its own figure. */
  #salesview .chartcard{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 14px;width:100%}
  #salesview .barchart{display:block;width:100%;height:120px;overflow:visible}
  #salesview .barchart rect.sdbar{fill:var(--c-now)}
  #salesview .barx{display:flex;justify-content:space-between;margin-top:4px;font-size:var(--fs-3xs);color:var(--dim)}
  #salesview .barx.even span{flex:1;text-align:center}
  /* point-aligned axis: each label anchored at its data point's x (see mklab),
     so the labels sit under the line and the "now" marker rather than in slots */
  #salesview .barx.pts{display:block;position:relative;height:1.2em}
  #salesview .barx.pts span{position:absolute;top:0;white-space:nowrap}
  /* the product table fills the page rather than shrink-wrapping to content */
  #salesview .salesprodwrap{width:100%;max-width:none}
  #salesview #salesProdTable{width:100%}
  /* the phone card view of the product table — hidden on a wide screen, shown
     under 700px where the table hides (same convention as #txview) */
  /* The phone view of the product table: the shared joined-panel shape every
     other card list uses — one rounded .pgrp panel, hairline dividers between
     rows — carrying the standard 12.5px name / 11px sub type of Orders and
     Transactions, not a bespoke size. Name and revenue head the row; the rest —
     category, units, profit, margin — wrap on the line beneath, where they fit
     without pushing the amount off a narrow screen. */
  #salesview .salesprodcards{display:none}
  #salesview .spcard{padding:9px 12px;border-top:1px solid var(--grid)}
  #salesview .spcard:first-child{border-top:0}
  #salesview .spcard .sptop{display:flex;justify-content:space-between;align-items:baseline;gap:10px}
  #salesview .spcard .spname{font-size:var(--fs-sm);font-weight:600;color:var(--ink-2)}
  #salesview .spcard .sprev{font-size:var(--fs-sm);font-weight:600;color:var(--ink-2);
    font-variant-numeric:tabular-nums;white-space:nowrap}
  #salesview .spcard .spmeta{display:flex;flex-wrap:wrap;gap:2px 10px;margin-top:3px;
    font-size:var(--fs-xs);color:var(--dim);font-variant-numeric:tabular-nums}
  #salesview .spcard.sptot{background:var(--track)}
  /* The phone sort control. On a phone the table — and its clickable headers — is
     hidden, so "click any header to sort" is both wrong and useless: hide it, and
     give the card list its own sort, a column picker and a direction toggle that
     reads the same ▲▼ the table headers show. */
  #salesview #salesProdSort{display:none}
  #salesview .spsort-ic{flex:0 0 auto;display:flex;color:var(--dim)}
  #salesview .spsort-ic svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
  #salesview #salesProdSortSel{flex:1;min-width:0;font:inherit;font-size:var(--fs-lg);height:36px;padding:0 10px;
    border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink-2)}
  #salesview #salesProdSortDir{flex:0 0 auto;width:36px;height:36px;padding:0;border:1px solid var(--line);
    border-radius:8px;background:var(--panel);color:var(--ink-2);cursor:pointer;font-size:var(--fs-md);line-height:1}
  @media (max-width:700px){
    #salesview .salesprodwrap{display:none}
    #salesview .salesprodcards{display:block}
    #salesview #salesProdNote{display:none}
    #salesview #salesProdSort{display:flex;align-items:center;gap:8px;margin:0 0 10px}
    /* category stat cards go one-per-row on a phone, like the KPI tiles above
       and Home's money tiles — a full-width row reads cleanly where a 2-up grid
       cramped the figure */
    #salesview #salesCats{grid-template-columns:1fr}
  }
  /* the money flow: gross card takings walked down, line by line, to cash in
     the bank. Stage rows carry a running balance; step rows the movement that
     gets there. The Wise row is the destination and wears settled green. */
  #salesview table.mflow,#salesreconview table.mflow{min-width:560px}
  #salesview .mflow td,#salesreconview .mflow td{padding:6px 10px;border-bottom:1px solid var(--line);font-size:var(--fs-sm);vertical-align:baseline}
  #salesview .mflow td.num,#salesreconview .mflow td.num{text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums;width:110px}
  #salesview .mflow td.stepname,#salesreconview .mflow td.stepname{padding-left:26px}
  #salesview .mflow tr.stage td,#salesreconview .mflow tr.stage td{font-weight:700;background:var(--panel)}
  #salesview .mflow tr.stage.cash td,#salesreconview .mflow tr.stage.cash td{color:var(--grn-tx);background:var(--grn-bg)}
  #salesview .mflow tr.warn td,#salesreconview .mflow tr.warn td{background:var(--amb-bg);color:var(--amb-tx)}
  #salesview .mflow .mnote,#salesreconview .mflow .mnote{color:var(--dim);font-weight:400;font-size:var(--fs-xs)}
  /* On a phone the ladder must fit rather than scroll off to its amounts: drop
     the min-width so the label wraps, size the number columns to their content
     (the empty spacer cell collapses to its padding), and pull the step indent
     in. Both amount columns stay visible, the step's still offset from the
     stage's, and nothing hides behind a sideways scroll. */
  @media (max-width:700px){
    #salesview table.mflow,#salesreconview table.mflow{min-width:0}
    #salesview .mflow td.num,#salesreconview .mflow td.num{width:1%;padding-left:6px;padding-right:6px}
    #salesview .mflow td.stepname,#salesreconview .mflow td.stepname{padding-left:14px}
    /* the Cash box stat cards go one-per-row too, like the Summary's categories.
       Targeted by the container's id, not `.hcards`, so it re-dresses the grid,
       never a stat card through a view id (home-tiles-test guards that). */
    #salesreconview #reconCashCards{grid-template-columns:1fr}
    /* The Orders strip has enough cards that one-per-row runs the page off the
       screen before the list starts; two up fits them and keeps the figures
       big. Keyed on the .ocard component's own container class, so it never
       touches another page's cards. The title keeps its own line (the figure is
       below it, not beside it), so two-up needs no wrap — a long title ellipses
       rather than growing a third row. */
    .ocards{grid-template-columns:1fr 1fr}
  }
  /* recon: the slim waterfall above the ladder, and the per-row fiscal status pill */
  #salesreconview .wmini{height:12px;border-radius:6px;overflow:hidden;display:flex;border:1px solid var(--line);margin:2px 0 14px}
  #salesreconview .wmini i{display:block}
  /* ---- ORDERS · RECONCILE — the till against the card reader, side by side.
     Till orders down the left, Revolut taps down the right, one clock between
     them, and a line where a tap paid for an order. Drawn with the ROADMAP'S
     parts on purpose — the same 20px .bar, the same red-ringed .linkdot to drag
     a link from, the same svg.arrows curve — because that page already taught
     everyone here what "drag a line between two things" looks like, and a
     second vocabulary for the same gesture would be one more thing to learn.
     Colour is the roadmap's too, by job: done-green for a pair, the amber of
     "in progress" for a bar still wanting its other half, todo-grey for a bar
     that is not money to reconcile (cash, a staff meal, a tap that never
     completed). Everything is scoped through #reconview: the classes are the
     roadmap's, the overrides are this page's. */
  #reconview .rcwrap{display:flex;flex-direction:column;gap:8px}
  #reconview .rcsum{font-size:var(--fs-sm);color:var(--text);display:flex;gap:14px;flex-wrap:wrap;align-items:baseline}
  #reconview .rcsum b{font-weight:bold;color:var(--ink-2)}
  #reconview .rcsum .amb{color:var(--amb-tx-2)}
  #reconview .rcscroll{overflow:auto}
  #reconview .rcgrid{position:relative;min-width:720px;width:100%;box-sizing:border-box;background:var(--panel);border:1px solid var(--line);border-radius:8px;overflow:hidden}
  #reconview .rchead{display:flex;align-items:center;height:30px;border-bottom:1px solid var(--line);background:var(--panel)}
  #reconview .rchead span{font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--dim);font-weight:bold;padding:0 10px;white-space:nowrap}
  #reconview .rcbody{position:relative}
  #reconview .rcaxis{position:absolute;top:0;bottom:0;width:0;border-left:1px solid var(--line);pointer-events:none}
  /* a stretch of the evening with nothing on either side is folded, and says so */
  #reconview .rcbreak{position:absolute;left:0;right:0;height:0;border-top:1px dashed var(--line-2);pointer-events:none}
  #reconview .rcbreak span{position:absolute;top:-7px;transform:translateX(-50%);font-size:var(--fs-2xs);color:var(--dim);background:var(--panel);padding:0 6px;white-space:nowrap}
  /* the roadmap's bar, positioned by the clock rather than by its row */
  #reconview .bar{cursor:pointer;font-variant-numeric:tabular-nums}
  #reconview .bar.rcok{background:var(--done)}
  #reconview .bar.rcgap{background:var(--prog)}
  #reconview .bar.rcquiet{background:var(--todo);color:var(--ink-2);box-shadow:none}
  #reconview .bar.sel{outline:2px solid var(--red);outline-offset:1px}
  /* label truncates, the trouble chip does not — it names why a left-hand sale
     needs a person (no payment vs short vs over) so the amber bars read apart */
  #reconview .bar .rclbl{min-width:0;overflow:hidden;text-overflow:ellipsis}
  #reconview .bar .rctag{flex:0 0 auto;margin-right:5px;padding:0 5px;border-radius:3px;
    font-size:var(--fs-2xs);font-weight:700;letter-spacing:.02em;
    background:rgba(0,0,0,.32);color:#fff}
  #reconview .bar .rctag-none{background:var(--red);color:#fff}   /* nothing paid it */
  #reconview .bar .rctag-over{background:rgba(255,255,255,.92);color:#3a2600}   /* too much came in */
  /* the dots are always faintly there — this page IS the linking — and full on
     hover or while a line is being dragged (body.linking is the roadmap's) */
  #reconview .linkdot{opacity:.45;touch-action:none}
  #reconview .rcbody:hover .linkdot{opacity:1}
  #reconview .rclegend{display:flex;gap:12px;font-size:var(--fs-2xs);color:var(--dim);flex-wrap:wrap;align-items:center}
  #reconview .rclegend i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:3px;vertical-align:middle}
  #reconview .rchint{font-size:var(--fs-xs);color:var(--blu-dot);min-height:16px}
  #reconview .rchint.bad{color:var(--red-tx-2)}
  /* the phone view — two columns, orders on the left, payments on the right, each
     an independent list (the desktop's two feeds, stacked instead of clocked).
     Long-press a card and drag it onto a card in the OTHER column to link them
     (rcCardWire); a plain tap opens its drawer; a vertical drag scrolls the page
     (touch-action:pan-y, so the browser owns the scroll until the long press). */
  #reconview .rccols{display:flex;gap:8px;align-items:flex-start}
  #reconview .rccol{flex:1;min-width:0;display:flex;flex-direction:column;gap:8px}
  #reconview .rccolh{font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--dim);padding:0 2px}
  #reconview .rccolempty{color:var(--dim);font-size:var(--fs-xs);padding:6px 2px}
  #reconview .rccard{display:flex;flex-direction:column;width:100%;text-align:left;font:inherit;background:var(--panel);
    border:1px solid var(--line);border-radius:10px;padding:9px 11px;cursor:pointer;touch-action:pan-y;-webkit-user-select:none;user-select:none}
  #reconview .rccard .rclbl{font-size:var(--fs-sm);color:var(--ink);line-height:1.3;font-variant-numeric:tabular-nums;word-break:break-word}
  #reconview .rccard .rctag{align-self:flex-start;margin-bottom:4px;padding:1px 6px;border-radius:4px;
    font-size:var(--fs-2xs);font-weight:700;letter-spacing:.02em;background:var(--amb-bg);color:var(--amb-tx-2);border:1px solid var(--amb-bd)}
  #reconview .rccard .rctag-none{background:var(--red);color:#fff;border-color:var(--red)}
  #reconview .rccard.gap{border-color:var(--amb-bd)}
  #reconview .rccard.rccard-drag{opacity:.6;border-color:var(--red);box-shadow:0 4px 16px rgba(0,0,0,.2)}
  #reconview .rccard.rctarget{border-color:var(--red);background:var(--red-bg)}
  #reconview .rcempty{padding:28px 12px;color:var(--dim);font-size:var(--fs-sm);line-height:1.5;text-align:center}
  /* the empty day, standing on its own rather than inside the fixed-width grid */
  #reconview .rcempty-solo{background:var(--panel);border:1px solid var(--line);border-radius:8px}
  /* budgets view */
  #costsview h3,#invoicesview h3{color:var(--red-ink);font-size:var(--fs-lg);margin:0 0 10px}
  /* in-row actions ("+ Connect …") take the compact size #txview gives its rows'
     buttons, not the view's 12px toolbar size. The :not()s leave the status and
     invoice chips, and the Wise send chip, their own metrics. */
  #invoicesview .ctable button:not(.cinvst):not(.txipill):not(.wisebtn){font-size:var(--fs-xs);padding:3px 8px}
  .ctable{border-collapse:collapse;font-size:var(--fs-sm);min-width:900px}
  .ctable.costs{min-width:960px}
  /* money columns: equal width, right-aligned headers and cells */
  .ctable.costs th.cmon{text-align:right;width:92px}
  .ctable.invs th.cmon,.ctable.invs td.cmon{text-align:right;white-space:nowrap}
  .ctable.costs th.cph{width:110px}
  .ctable.costs td.cph{font-size:var(--fs-xs);color:var(--text)}
  .ctable.costs th.cinvh{width:280px}
  /* grouping rows — one pattern shared by Budgets (per-merchant), Products
     (per-category) and echoed by the Board's lanes: the shaded band, a caret
     that folds the group away, the group's own figures under the columns they
     belong to, and an "N items" count beside the name */
  tr.cgroup{cursor:pointer;user-select:none}
  .cgroup td{background:var(--band);border-top:2px solid var(--line);font-size:var(--fs-sm)}
  @media (hover:hover){ tr.cgroup:hover td{background:var(--band-h)} }
  .grpcaret{display:inline-block;width:14px;color:var(--muted-2);font-size:var(--fs-xs)}
  /* the section's colour, worn on its band before the name; --sw is the stored
     hex, set inline as data (the Edit-categories modal is where it is chosen) */
  .catswatch{display:inline-block;width:10px;height:10px;border-radius:3px;margin-right:7px;
    vertical-align:baseline;background:var(--sw);border:1px solid var(--line-2)}
  .cgrpstat{font-weight:normal;color:var(--muted-2);font-size:var(--fs-xs);margin-left:8px}
  /* how many of a section's recipes are fully costed — right of its header */
  .recgrpcost{float:right;font-weight:normal;color:var(--muted-2);font-size:var(--fs-xs)}
  .cnote{color:var(--muted);font-size:var(--fs-2xs);max-width:210px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-top:2px}
  /* Paid cell: the input and, only when its documents record more, a one-click
     "match documents" chip beside it — inline so it never grows the row's height
     and knocks the Paid column out of line the way a stacked note did */
  .cpaidcell{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
  .cfixpaid{flex:0 0 auto;display:inline-flex;align-items:center;gap:3px;white-space:nowrap;cursor:pointer;
    font-size:var(--fs-2xs);font-weight:700;line-height:1;padding:3px 7px;border-radius:999px;
    color:var(--amb-tx);background:var(--amb-bg);border:1px solid var(--amb-bd)}
  .cfixpaid:hover{background:var(--amb-bg-2);border-color:var(--amb-bd-2)}
  .ctable input.cinvdue{font:inherit;font-size:var(--fs-xs);padding:2px 3px;border:1px solid var(--line);border-radius:4px;color:var(--text);width:118px}
  .cid{color:var(--muted-2);font-size:var(--fs-xs);white-space:nowrap}
  /* checkbox + bucket ID share the Budgets table's first cell */
  .cselcell{white-space:nowrap}
  .cselcell .cid{margin-left:6px;vertical-align:1px}
  .ctable input[type=text].cinvamt{width:78px;text-align:right;font:inherit;font-size:var(--fs-sm);padding:3px;border:1px solid var(--line);border-radius:4px}
  .ctable input[type=text].cinvpaid{width:66px;text-align:right;font:inherit;font-size:var(--fs-sm);padding:3px;border:1px solid var(--line);border-radius:4px}
  .ctable input[type=text].cinvlink{width:96px;font:inherit;font-size:var(--fs-sm);padding:3px;border:1px solid var(--line);border-radius:4px}
  /* invoice status chip: open / partially_paid / settled_paid / settled_manual /
     archived. As a button it's also the switch for settled_manual; "forced" (a
     dashed ring) keeps a hand-settled invoice distinguishable from one the money
     paid off. Archived is never that button — see invStatusChip. */
  /* the id-prefixed selectors restate the pill's look over the per-view button
     rules ("#costsview button,#invoicesview button" above) — same trick as
     .txipill: without them the chip renders as a plain rectangular button
     wherever it doubles as the settled_manual switch. Metrics match .dirchip
     and .infopill, so every fact-pill on a row reads as one family. */
  .cinvst{flex:0 0 auto;font-size:var(--fs-2xs);padding:1px 8px;border-radius:999px;border:1px solid var(--line-2);color:var(--text-2);background:var(--soft-2);white-space:nowrap}
  .cinvst.partial{color:var(--amb-tx-2);border-color:var(--amb-bd-2);background:var(--amb-bg-2)}
  /* a fine, ordinary outcome that is nonetheless not done — captured but not yet
     settled: the plain neutral pill, so it never competes with amber's "chase me" */
  .cinvst.neutral{color:var(--text-2);border-color:var(--line-2);background:var(--soft-2)}
  .cinvst.paid{color:var(--grn-tx);border-color:var(--grn-bd);background:var(--grn-bg)}
  /* "this one did not stand" — the fourth tone the family was missing. Global,
     like the amber and the green above it: a pill states the same fact in the
     same colour wherever it is drawn, and a state that changes colour between
     a row and its drawer reads as two different states. */
  .cinvst.bad{color:var(--red-tx-2);border-color:var(--red-bd);background:var(--red-bg)}
  /* settled by hand (.forced) wears the same solid green as paid — the label and
     tooltip carry the difference; a dashed border read as "something's wrong" */
  /* archived is the quietest chip on the row: nothing to chase and nothing owed,
     so it recedes instead of colouring like a state somebody has to answer */
  .cinvst.archived{color:var(--muted-2);background:transparent}
  /* given away: not a state to chase and not a sale — the one blue on the row */
  .cinvst.comp{color:var(--blu-tx);border-color:var(--blu-bd);background:var(--blu-bg)}
  button.cinvst{font-family:inherit;line-height:1.5;cursor:pointer}
  @media (hover:hover){ button.cinvst:hover{border-color:var(--red);color:var(--red-ink)} }
  /* which way the money goes. Payable is the ordinary case and stays quiet;
     receivable is the exception worth spotting in a list, so it carries the
     colour — green, money coming in — and should never look like another bill.
     .infopill is the same quiet pill for any read-only fact on a row (the cost
     bundle a document belongs to, how many transactions are linked): the row
     itself opens the panel, so the fact doesn't need to be a control. */
  .dirchip,.infopill{display:inline-block;font-size:var(--fs-2xs);padding:1px 8px;border-radius:999px;border:1px solid var(--line-2);
    color:var(--dim);background:var(--panel-2);white-space:nowrap;vertical-align:middle}
  /* the one loud infopill: a paid-at-the-till ticket whose card failed — an
     inconsistency to chase, so it wears the amber the rest of the page uses for one */
  .infopill.warn{color:var(--amb-tx-2);background:var(--amb-bg-2);border-color:var(--amb-bd-2)}
  /* louder still — a FILLED red badge for the same, when the point is that money
     is owed and nobody paid it: the one place the row shouts rather than notes */
  .infopill.alert{color:#fff;background:var(--red-tx-2);border-color:var(--red-tx-2);font-weight:500}
  /* the mobile card's red alert, mirroring the wide row's bar */
  .ordcard.alert{box-shadow:inset 3px 0 0 var(--red-bar);background:var(--red-wash)}
  /* THE PANEL ALERT BANNER — one component, every panel (panelAlert() in
     07-panels.js; documented in the brand guide (/brand) §Panel alerts). A row
     that highlights amber or red in a list opens to a panel whose first line
     explains the highlight: the problem said once, loudly, above the rows.
     Red is the default (money or data at risk now); .amber is the softer
     variant (a gap that is ours to close, with time in hand). A dark wash
     carries brighter text than the flatter --red-bg did, so the words read;
     a stroked SVG triangle, not a webfont glyph, so the icon actually draws.
     (Was .ordalert — generalised from the Orders card banner 2026-09-09.) */
  /* top margin sets it off from the header line above (the .phead border only
     leaves 4px on its own) — the banner should breathe, not hug the rule */
  .palert{display:flex;gap:10px;align-items:flex-start;margin:12px 0 12px;padding:11px 13px;
    border:1px solid var(--red-bd);border-left:3px solid var(--red-bar);border-radius:0 8px 8px 0;
    background:var(--red-wash)}
  .palert-i{flex:0 0 auto;width:18px;height:18px;margin-top:1px;fill:none;stroke:var(--red-tx-2);
    stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  .palert b{display:block;color:var(--red-tx-2);font-size:var(--fs-sm);margin-bottom:3px}
  .palert span{display:block;color:var(--text);font-size:var(--fs-xs);line-height:1.55}
  .palert.amber{border-color:var(--amb-bd);border-left-color:var(--amb-bar);background:var(--amb-bg)}
  .palert.amber .palert-i{stroke:var(--amb-tx-2)}
  .palert.amber b{color:var(--amb-tx-2)}
  .dirchip.receivable{color:var(--grn-tx);border-color:var(--grn-bd);background:var(--grn-bg)}
  /* what the document IS. Same pill metrics as .dirchip so a row reads as one
     line of facts, and the same logic behind the colour: an invoice is what
     nearly every row is, so it stays quiet, and anything that ISN'T a bill —
     a credit note, a payslip, an orçamento — carries the mark, because that is
     the row somebody would otherwise skim past as one more invoice.
     Unclassified is dashed rather than coloured: it is not a state to fix
     urgently, it is an answer nobody has given yet, and it must not look like
     an invoice by being blank. */
  .typechip{display:inline-block;font-size:var(--fs-2xs);padding:1px 8px;border-radius:999px;border:1px solid var(--line-2);
    color:var(--dim);background:var(--panel-2);white-space:nowrap;vertical-align:middle}
  .typechip.marked{color:var(--amb-tx-3);border-color:var(--amb-bd);background:var(--amb-bg)}
  .typechip.none{color:var(--na-tx);background:transparent;border-style:dashed}
  /* the panel's type picker: a select, because ten values is a list to read
     rather than a row of buttons to scan */
  .invoice-panel select.invtab-type{font:inherit;font-size:var(--fs-sm);padding:4px 7px;border:1px solid var(--line);
    border-radius:6px;background:var(--panel);color:var(--ink-4);max-width:100%}
  /* the panel's payable/receivable switch: two halves of one control, so the
     unpicked side is always visible as the other answer */
  /* One control, not two buttons that happen to touch. The frame and its
     rounded corners belong to the GROUP; the buttons inside carry no edge of
     their own, only the divider between them. They used to keep their own
     square-cornered border and be clipped to the rounded box, which is what
     sliced the corners flat.
     Which way the money goes is a fact, so both answers stay on screen and the
     one that holds is filled in. Named for the view AND for the panel: the
     per-view button rules are ID-scoped and a bare class cannot beat them. */
  .dirtoggle{display:inline-flex;margin:0 0 12px;border:1px solid var(--line);border-radius:7px;overflow:hidden}
  #invoicesview .dirtoggle button,.invoice-panel .dirtoggle button{font-size:var(--fs-xs);padding:5px 13px;
    border:0;border-radius:0;margin:0;color:var(--text-3);background:var(--panel)}
  #invoicesview .dirtoggle button+button,.invoice-panel .dirtoggle button+button{border-left:1px solid var(--line)}
  #invoicesview .dirtoggle button.on,.invoice-panel .dirtoggle button.on{background:var(--grn-bg);color:var(--grn-tx);font-weight:bold}
  #invoicesview .dirtoggle button.on[data-dirset="payable"],
  .invoice-panel .dirtoggle button.on[data-dirset="payable"]{background:var(--hover);color:var(--ink-4)}
  /* the panel's generic button:hover would grey out the chosen half, so the
     chosen half keeps its own fill and only the other one lights up */
  @media (hover:hover){
    #invoicesview .dirtoggle button:hover:not(.on),.invoice-panel .dirtoggle button:hover:not(.on){background:var(--soft);color:var(--ink-4)}
    #invoicesview .dirtoggle button.on:hover,.invoice-panel .dirtoggle button.on:hover{background:var(--grn-bg)}
    #invoicesview .dirtoggle button.on[data-dirset="payable"]:hover,
    .invoice-panel .dirtoggle button.on[data-dirset="payable"]:hover{background:var(--hover)}
  }
  /* 16px or iOS zooms the page in when the field takes focus */
  .ctable select.ivattach{font:inherit;font-size:var(--fs-xs);padding:2px;border:1px solid var(--line);border-radius:4px;color:var(--text);max-width:150px}
  /* row action buttons (+ Invoice / Split / No budget): one compact line */
  .crowact{white-space:nowrap}
  .crowact .crowact-btns{display:flex;flex-direction:row;gap:4px;align-items:center}
  #costsview .crowact button{font-size:var(--fs-xs);padding:3px 7px;white-space:nowrap}
  /* merchant pill: links to the merchant's Drive folder (contracts & receipts).
     .ipill is the same pill for an invoice reference — an invoice reads the same
     way wherever it appears (invoices, cost buckets, transactions). */
  /* An entity pill is a NAMED THING you click through — a counterparty, a
     document, a recipe. It carries a name, so it reads at the app's body size
     (12px) EVERYWHERE, in a table row as much as a panel row: a 10px pill next
     to 12px cells and labels reads as undersized. Only STATUS chips (the
     .cinvst family — a state word like "open"/"paid") stay 10px, as compact
     state markers. The system is two sizes: 12px content (cells, labels,
     values, entity pills, txchip state/name chips), 10px annotation (the
     .cinvst status chips and the sub-hints). See brand guide §07–§08. */
  .mpill,.ipill{display:inline-block;font-size:var(--fs-sm);padding:2px 8px;border-radius:999px;background:var(--pill-bg);border:1px solid var(--pill-bd);
    color:var(--ink-blue);text-decoration:none;margin:1px 4px 1px 0;white-space:nowrap;max-width:200px;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}
  @media (hover:hover){ a.mpill:hover,a.ipill:hover{border-color:var(--red);color:var(--red-ink)} }
  .mpill.nolink,.ipill.nolink{opacity:.65;border-style:dashed}
  /* PANEL TYPE HIERARCHY — two sizes, and no more (see brand guide §08).
     LEVEL 1 (12px, --fs-sm): the row label (.pk), its value (.pv), and every
       chip. Entity pills (.mpill/.ipill) are already 12px above; the alias
       chips (.txchip) and tags (.tagpill) are bumped to match here.
     LEVEL 2 (10px, --dim): the .phint qualifier under a value — a step down in
       size AND colour, so it reads as secondary to the figure it explains.
     The panel HEAD keeps its own scale (the big .pamt name, its .psub). */
  .invoice-panel .txchip,.invoice-panel .tagpill{font-size:var(--fs-sm)}
  /* ---- the split pill -------------------------------------------------
     One shape, two targets: the name opens that thing's own side panel —
     where its facts, its payments and its editors are — and the Drive glyph
     on the right opens the folder or the file itself. Both were wanted, and
     before this the pill could only be one of them. A pill with nothing
     behind one half simply loses that half. The padding moves off the pill
     and onto the two halves so each is a full-height hit area. */
  .mpill.pill2,.ipill.pill2{display:inline-flex;align-items:stretch;padding:0;overflow:hidden}
  /* The label half is PART OF THE PILL, not a button sitting in one: it takes
     the pill's own background and no edge of its own, so the two halves read as
     one shape divided by the rule between them.
     Every view styles its own buttons through an ID ("#txview button", …), and
     an ID outranks any number of classes — left to itself the label rendered as
     a white capsule with its own border, floating inside the pill. So the views
     are named here and beaten on their own terms; same trick as .cinvst below. */
  .pill2>.pill-l{
    font:inherit;font-size:inherit;color:inherit;background:none;border:0;border-radius:0;margin:0;cursor:pointer;
    padding:2px 6px 2px 9px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}
  /* the separator, and the Drive half it divides off */
  .pill2>.pill-d{display:flex;align-items:center;padding:2px 7px;color:inherit;text-decoration:none;flex:0 0 auto;
    border-left:1px solid var(--pill-div)}
  .pill2:not(:has(.pill-d))>.pill-l{padding-right:9px}
  /* A solo pill is ONE link end to end — a file, a folder, with no panel behind
     it. The rule between the halves is what says "these are two targets", so a
     pill with only one target does without it. */
  .pill2.solo>.pill-d{border-left:0;padding-left:2px}
  /* Google's own mark, so "this leaves for Drive" needs no explaining. It is a
     brand colour and stays one — it does not take the pill's hover red the way
     a drawn glyph would, so the half gets a tint instead to show it is a target. */
  .pill2>.pill-d svg{width:12px;height:12px;display:block}
  /* the issuer tag inside a document pill — "FS #494 EFSTA": a small dim caps
     word naming who issued the document, where it is not our own Vendus series
     (an EFSTA till cheque). Rides at the end of the pill's label. */
  .ipill .iss{font-size:var(--fs-2xs);color:var(--dim);margin-left:5px;
    text-transform:uppercase;letter-spacing:.04em}
  @media (hover:hover){
    .pill2:hover{border-color:var(--red)}
    .pill2>.pill-l:hover{color:var(--red-ink)}
    .pill2>.pill-d:hover{background:var(--pill-dh)}
    /* a solo pill is one link end to end, so it lights up as one */
    a.pill2.solo:hover{color:var(--red-ink)}
    a.pill2.solo:hover>.pill-d{background:none}
  }
  /* a tap target that small is a miss on a phone — give both halves room */
  @media (pointer:coarse){
    .pill2>.pill-l{padding-top:4px;padding-bottom:4px}
    .pill2>.pill-d{padding:4px 9px}
  }
  /* the state dot stays at the head of the label, ahead of the name — on a
     document pill it is the first thing read: green settled, amber still owed */
  .pill2>.pill-l>.idot{flex:0 0 auto}
  /* Outstanding invoices: cost id + invoice pill share one row; the pill gives
     up width (ellipsis) rather than wrapping under the id */
  .cinvline{display:flex;align-items:center;gap:5px;min-width:0}
  .cinvline .idtag{flex:0 0 auto}
  .cinvline .mpill,.cinvline .ipill{flex:0 1 auto;min-width:0;max-width:100%;margin-right:0}
  /* floating selection bar: keeps Merge reachable however far down the table you are */
  .cselbar{position:fixed;left:50%;transform:translateX(-50%);bottom:calc(64px + env(safe-area-inset-bottom));z-index:40;
    display:flex;align-items:center;gap:10px;background:var(--panel);border:1px solid var(--line);border-radius:8px;
    box-shadow:var(--shadow-pop);padding:8px 14px;font-size:var(--fs-sm);white-space:nowrap}
  .cselbar .cselwarn{color:var(--red-ink);font-size:var(--fs-xs);white-space:normal;max-width:230px}
  /* the summary is three narrow columns: let it shrink to phone width */
  .ctable.sum{min-width:0;width:100%;max-width:560px}
  /* phones: the Outstanding invoices table must fit the viewport — tighter cells,
     merchant pills wrap, dates/amounts stay on one line */
  @media (max-width:640px){
    /* summary: three money columns next to the phase name — tighter cells so all four fit */
    .ctable.sum th,.ctable.sum td{padding:5px 4px;font-size:var(--fs-xs)}
    .ctable.sum td.crem{font-size:var(--fs-xs)}
    /* invoices: tight cells and shrunken editors so the table fits a phone;
       pills ellipsize instead of pushing the table off-screen */
    .ctable.invs th,.ctable.invs td{padding:5px 4px;font-size:var(--fs-xs)}
    .ctable.invs td{word-break:break-word}
    .ctable.invs .mpill{font-size:var(--fs-2xs);padding:2px 6px;max-width:100%}
    .ctable.invs td.crem{font-size:var(--fs-xs)}
    .ctable.invs input[type=text].cinvamt,.ctable.invs input[type=text].cinvpaid{width:54px;font-size:var(--fs-xs)}
    .ctable.invs input[type=text].cinvlink{width:76px;font-size:var(--fs-xs)}
    .ctable.invs input.cinvdue{width:96px;font-size:var(--fs-2xs)}
    .ctable.invs select.ivattach{max-width:88px;font-size:var(--fs-2xs)}
  }
  /* cell + header treatment lives in the shared desktop table shell above */
  .ctable input[type=text]{width:170px;font:inherit;font-size:var(--fs-sm);padding:3px;border:1px solid var(--line);border-radius:4px}
  /* amount fields: narrower than a plain text cell, and money aligns on the decimal point */
  .ctable input.cnum{width:88px;text-align:right}
  /* a totals row reads the same in every wide table, the Budgets one included */
  .ctable tfoot td,.txtable tfoot td{font-weight:bold;background:var(--red-wash)}
  /* what a signed net is made of, under the word Totals: a quiet second line, so
     the number the eye lands on is still the one in the money column */
  .txfoot-io{display:block;font-weight:400;font-size:var(--fs-2xs);color:var(--dim);text-transform:none;
    letter-spacing:0;font-variant-numeric:tabular-nums;margin-top:2px}
  .cact{display:flex;align-items:flex-start;gap:5px;margin:2px 0}
  .cact[data-id]{cursor:pointer}
  @media (hover:hover){ .cact[data-id]:hover{background:var(--rowhover)} }
  .iv-arch,.iv-detach{font:inherit;font-size:var(--fs-lg);line-height:1;padding:0 5px;border:1px solid var(--line);border-radius:4px;background:var(--panel);color:var(--muted-2);cursor:pointer}
  @media (hover:hover){ .iv-arch:hover{color:var(--red-ink);border-color:var(--red)} .iv-detach:hover{color:var(--ink-blue);border-color:var(--ink-blue)} }
  /* Neutral, NOT the amber .miss "gap" pill, though it is the same "connect the
     missing thing" action: these sit one-per-row down a table, where amber on
     every row would be a wall of orange. Amber is for the FOCAL gap — one in a
     drawer (see #panelhost .linkbtn). The tone is chosen by density, not meaning. */
  .cinv-connect{font:inherit;font-size:var(--fs-sm);line-height:1;padding:3px 8px;border:1px solid var(--line);border-radius:4px;background:var(--panel);color:var(--text-2);cursor:pointer;margin-top:2px}
  @media (hover:hover){ .cinv-connect:hover{color:var(--red-ink);border-color:var(--red)} }
  .cact .pill{margin-top:4px}
  /* Invoice detail belongs to an estimate, but stays out of the accounting overview. */
  .cinv-summary{display:inline-flex;align-items:center;gap:5px;box-sizing:border-box;max-width:100%;padding:4px 10px!important;font-size:var(--fs-xs)!important;line-height:1.25;color:var(--info-tx);background:var(--soft)!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .cinv-summary.hasdue{color:var(--amb-tx-3);background:var(--amb-bg)!important;border-color:var(--amb-bd)!important}
  .cinv-summary.allpaid{color:var(--grn-tx-2);background:var(--grn-bg-2)!important;border-color:var(--grn-bd-2)!important}
  .cinv-summary.empty{color:var(--text-3)}
  /* summary pill + "Connect document" sit side by side while there's room and stack
     flush left once they wrap: the gap hangs off the summary's right, never as a
     left margin that would indent the wrapped button */
  .cinv-cell .cinv-summary{margin-right:5px}
  .cinv-cell .cinv-connect{margin-left:0;padding:3px 7px!important;font-size:var(--fs-xs)!important;line-height:1.25}
  .invoice-panel-backdrop{position:fixed;inset:0;background:rgba(25,31,45,.24);z-index:55}
  /* While a panel is on screen a right-swipe belongs to the panel, so keep the
     horizontal overscroll from reaching the browser's back gesture and walking
     out of the app — including from the parts of the panel the gesture leaves
     alone, like the field you're editing. Scoped to an open panel: everywhere
     else swipe-back still works. */
  html:has(.invoice-panel){overscroll-behavior-x:contain}
  /* and the panel's own scrolling never chains to the page behind it */
  .invoice-panel{position:fixed;z-index:56;right:0;top:0;bottom:0;width:min(560px,94vw);background:var(--panel);border-left:1px solid var(--line);box-shadow:var(--shadow-drawer);overflow:auto;overscroll-behavior:contain;padding:20px}
  /* ---- the shared panel shell: header, action menu, detail list ----
     Every panel leads with the money it is about and puts everything else in
     one list, once. See panelHeadHtml. */
  .phead{display:flex;align-items:flex-start;gap:12px;border-bottom:1px solid var(--line);padding-bottom:14px;margin-bottom:4px}
  .phead .pgrow{flex:1 1 auto;min-width:0}
  .phead .pamt{font-size:var(--fs-4xl);font-weight:700;line-height:1.05;letter-spacing:-.01em;overflow-wrap:anywhere}
  .phead .pamt.neg{color:var(--red-ink)}
  .phead .pamt.due{color:var(--amb-tx-2)}
  .phead .pamt.ok{color:var(--grn-tx)}
  .phead .pamt.name{font-size:var(--fs-2xl)}
  .phead .plbl{font-size:var(--fs-xs);font-weight:400;color:var(--dim)}
  /* an amount that is saying something — overdue, settled, money out — says it
     across the whole line, label included, not in the figure alone */
  .phead .pamt.due>.plbl,.phead .pamt.ok>.plbl,.phead .pamt.neg>.plbl{color:inherit;opacity:.85}
  .phead .psub{font-size:var(--fs-sm);color:var(--dim);margin-top:6px;display:flex;align-items:center;gap:7px;flex-wrap:wrap;line-height:1.4}
  /* the refund that reversed this sale, on its own line under the sale's own:
     one event, two lines, rather than a second fact to be found further down */
  .phead .psub-r{flex:0 0 100%;display:flex;align-items:center;gap:7px;margin-top:2px}
  .phead .pacts{display:flex;gap:6px;flex:0 0 auto;align-items:flex-start}
  .invoice-panel .pact{font:inherit;font-size:var(--fs-sm);padding:5px 10px;border:1px solid var(--line);background:var(--panel);
    border-radius:7px;cursor:pointer;color:var(--text);white-space:nowrap}
  @media (hover:hover){ .invoice-panel .pact:hover{border-color:var(--red);color:var(--red-ink);background:var(--panel)} }
  .invoice-panel .pact.pk{padding:5px 8px;font-size:var(--fs-lg);line-height:1}
  /* the header's copy-link (⛓) — an icon button the size of the ×, not a text
     pill; the shared .pact hover already turns it red */
  .invoice-panel .pact.plink{padding:5px 8px;display:inline-flex;align-items:center;color:var(--text-3)}
  .invoice-panel .pact.plink svg{width:16px;height:16px;display:block}
  .pkebab{position:relative}
  .pmenu{display:none;position:absolute;right:0;top:calc(100% + 5px);z-index:6;min-width:200px;
    background:var(--panel);border:1px solid var(--line);border-radius:8px;box-shadow:var(--shadow-pop);padding:5px}
  .pkebab.open .pmenu{display:block}
  .invoice-panel .pmenu button{display:block;width:100%;text-align:left;font:inherit;font-size:var(--fs-sm);padding:7px 10px;
    border:0;background:none;border-radius:6px;cursor:pointer;color:var(--ink-3);white-space:nowrap}
  @media (hover:hover){ .invoice-panel .pmenu button:hover{background:var(--hover);color:var(--red-ink)} }
  .invoice-panel .pmenu button.danger{color:var(--red-tx-3)}
  @media (hover:hover){ .invoice-panel .pmenu button.danger:hover{background:var(--red-bg);color:var(--red-tx-3)} }
  .pmenu .prule{height:1px;background:var(--grid);margin:4px 6px}
  /* the list. One row per fact: label left, value and its editor right. */
  /* The list is fenced by the header's rule above it and the raw block's below,
     so it draws no edge of its own — only the hairlines BETWEEN its rows. */
  .prows{margin-top:4px}
  .prow{display:flex;align-items:flex-start;gap:12px;padding:9px 2px;border-bottom:1px solid var(--grid);font-size:var(--fs-sm)}
  .prows>.prow:last-child{border-bottom:0}
  .prow>.pk{flex:0 0 108px;color:var(--dim);padding-top:3px}
  .prow>.pv{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:6px;flex-wrap:wrap}
  .prow>.pv>.pnone{color:var(--dim)}
  .prow.stack>.pv{display:block}
  /* level-2 in a panel row: 10px + dim (the type scale's "caption" rung) — a
     clear step below the 12px value it qualifies, by size AND colour, not colour
     alone. See brand guide §08 "Type inside a panel". */
  .prow .phint{font-size:var(--fs-2xs);color:var(--dim)}
  .invoice-panel .prow select,.invoice-panel .prow input[type=text],.invoice-panel .prow input[type=tel],
  .invoice-panel .prow input[type=date],.invoice-panel .prow textarea{font:inherit;font-size:var(--fs-sm);padding:5px 7px;
    border:1px solid var(--line);border-radius:6px;background:var(--panel);max-width:100%}
  .invoice-panel .prow textarea{width:100%;resize:vertical;min-height:44px}
  .invoice-panel .prow .full{width:100%}
  /* an editable party field beside its copy button. These four are the values
     most often retyped into somewhere else — a NIF onto an invoice, an address
     into a form — and selecting text inside a field you can also overwrite is a
     worse click than a button next to it. */
  .invoice-panel .prow .pswitch{display:flex;align-items:center;gap:7px;cursor:pointer}
  .invoice-panel .prow .pswitch input{margin:0;flex:0 0 auto}
  .invoice-panel .prow .pfield{display:flex;align-items:center;gap:6px}
  .invoice-panel .prow .pfield>input{flex:1 1 auto;min-width:0}
  /* the pen / cross beside a pill */
  .invoice-panel .prowbtn{width:24px;height:24px;padding:0;display:inline-flex;align-items:center;justify-content:center;
    border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer;color:var(--muted-3);flex:0 0 auto}
  @media (hover:hover){ .invoice-panel .prowbtn:hover{border-color:var(--red);color:var(--red-ink)} }
  .prowbtn svg{width:12px;height:12px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
  /* a click-to-copy reference reads as the machine string it is */
  .pref{display:inline-flex;align-items:center;gap:6px;font:var(--fs-sm)/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;
    background:var(--soft);border:1px solid var(--line);border-radius:6px;padding:3px 7px;cursor:pointer;color:var(--ink-4);max-width:100%}
  @media (hover:hover){ .pref:hover{border-color:var(--red);color:var(--red-ink)} }
  .pref .t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .pref svg{width:12px;height:12px;fill:none;stroke:currentColor;stroke-width:1.7;flex:0 0 auto}
  /* the collapsed raw block at the foot of a panel */
  .praw{margin-top:20px;border-top:1px solid var(--line);padding-top:14px}
  .invoice-panel .prawtog{display:flex;align-items:center;gap:8px;font:inherit;font-size:var(--fs-sm);color:var(--dim);
    background:none;border:0;padding:0;cursor:pointer;text-transform:uppercase;letter-spacing:.04em;font-weight:bold}
  @media (hover:hover){ .invoice-panel .prawtog:hover{color:var(--red-ink);background:none} }
  .prawtog .chev{display:inline-block;font-size:var(--fs-2xs);transition:transform .15s}
  .praw.open .prawtog .chev{transform:rotate(90deg)}
  .prawbody{display:none;margin-top:6px}
  .praw.open .prawbody{display:block}
  .prawbody .prow>.pk{flex:0 0 150px}
  .invoice-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;border-bottom:1px solid var(--line);padding-bottom:14px;margin-bottom:12px}
  .invoice-panel-head h2{font-size:var(--fs-xl);margin:0 0 4px;color:var(--ink-2);overflow-wrap:anywhere}
  .invoice-panel-head p{font-size:var(--fs-sm);color:var(--text-2);margin:0;line-height:1.4}
  /* Buttons inside a panel. Each view styles its own buttons ("#costsview
     button", "#merchview button", …) and the panels used to inherit that by
     being rendered inside them; out here in #panelhost they need it stated. */
  .invoice-panel button{font:inherit;font-size:var(--fs-sm);padding:6px 10px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
  @media (hover:hover){ .invoice-panel button:hover:not(:disabled){background:var(--hover)} }
  .invoice-panel button:disabled{opacity:.4;cursor:default}
  /* the one destructive control in the task panel: last, spaced off the rest,
     and never dressed as the thing to press next */
  .taskdel{margin-top:18px;padding-top:12px;border-top:1px solid var(--line)}
  .invoice-panel .close{font-size:var(--fs-2xl)!important;line-height:1!important;padding:4px 9px!important;color:var(--text-3)}
  /* the state pills and chips a panel carries are not that button */
  .invoice-panel button.cinvst{flex:0 0 auto;font-size:var(--fs-2xs);padding:1px 8px;border-radius:999px}
  .invoice-panel button.invoice-linkbtn{font-size:var(--fs-xs);padding:3px 7px}
  /* ---- the panel stack ------------------------------------------------
     Panels open over one another — a payment's, then the document that
     settles it, then that document's merchant. Only the top one is on
     screen; the ones under it are held in the stack and come back as this
     one closes, which is why the close control is a "back" rather than a
     dismissal once there is something behind it. The whole stack lives in
     #panelhost, outside every view, so opening a panel never changes the
     page behind it. */
  .panel-back{display:flex;align-items:center;gap:5px;font:inherit;font-size:var(--fs-xs);padding:3px 9px 3px 6px;margin:0 0 10px;
    border:1px solid var(--line);background:var(--panel);border-radius:999px;cursor:pointer;color:var(--text);max-width:100%;
    overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
  .panel-back .chev{font-size:var(--fs-md);line-height:1;color:var(--dim)}
  @media (hover:hover){ .panel-back:hover{color:var(--red-ink);border-color:var(--red)} }
  /* ---- the raw record ------------------------------------------------- */
  .rawjson{margin:6px 0 0}
  .rawjson pre{margin:6px 0 0;padding:10px 11px;background:var(--raw-bg);color:var(--raw-tx);border-radius:8px;
    font:var(--fs-xs)/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;max-height:340px;overflow:auto;white-space:pre;
    overscroll-behavior:contain}
  .rawjson .rawhead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
  .rawjson .rawnote{font-size:var(--fs-xs);color:var(--dim)}
  /* a bundle's invoices in its panel: pills only, wrapping down the panel rather
     than scrolling it sideways, and big enough to hit with a thumb since each is
     the way through to its invoice */
  .cinv-pills{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 8px}
  /* a pill and the cross that unhooks it wrap as one item, never apart */
  .cinv-pills .cinv-pill{display:inline-flex;align-items:center;gap:3px;max-width:100%;min-width:0}
  .cinv-pills .cinv-pill>.txipill{min-width:0}
  .cinv-pills .txipill{font-size:var(--fs-sm);margin:0;max-width:100%}
  .invoice-panel .cinv-pills .txipill>.pill-l{padding:5px 6px 5px 11px}
  .cinv-pills .idot{width:8px;height:8px}
  /* Linked transactions. A payment reads the same wherever you meet it, so a
     panel lists the Transactions tab's own card rather than a table of its own —
     tap one and the same detail panel opens over the panel you're already in.
     The card grid's column sizes live on .txcards, which no panel uses, so this
     wrapper carries them, with a narrower amount column: a panel is narrower
     than the page. Inside a panel the counterparty is the panel's own subject —
     an invoice has one merchant, a counterparty is one — so the card leads with
     the date rather than repeating the heading a row at a time. */
  .paneltx{--tx-amt:88px;--tx-gap:8px}
  /* The parts of a split read the stock card's two columns the other way
     round: what the part came to over what it was for, then the paper it
     bought. Both tracks are fractions rather than the stock fixed amount
     column, because the document side is what needs the width here — a receipt
     number with what kind of paper it is beside it — and a fraction still lines
     the columns up down the list (every card is its own grid, but they are all
     the same width) while following the panel as it narrows on a phone. */
  .paneltx-parts .txcard{grid-template-columns:minmax(0,1fr) minmax(0,1.6fr) 10px}
  /* the document column keeps its pill and its type chip on ONE line, against
     the chevron — .pcard>div stacks a cell's lines, which is right for a card
     whose cell is two facts and wrong for one that is a thing and its label */
  .paneltx-parts .txcard>div.partdoc{flex-direction:row;flex-wrap:wrap;align-items:center;
    justify-content:flex-end;gap:5px}
  .paneltx .txcard:last-child{margin-bottom:0}
  /* the tail of a capped list: how much was left out, and where the rest lives.
     Styled once, on the panel — see the .paneltx-more rule above. */
  .invoice-linkbtn{font-size:var(--fs-xs)!important;padding:3px 7px!important;white-space:nowrap}
  .invoice-empty{padding:22px 0;color:var(--text-3);font-size:var(--fs-sm)}
  /* Drafting payables into Wise. The bar is only the drafts nobody released
     now — sending is a button on the row it belongs to. The lists read as prose
     rather than a second table: what matters is which refusals. */
  .paybar{margin-top:18px;padding-top:14px;border-top:1px solid var(--line)}
  .paycell{width:30px}
  /* the mark belongs to the document beside it, so the two sit together: the
     cell keeps only its left gutter and the document column steps in to meet
     it, instead of a 20px button floating in 40px of column. The selectors
     carry .ctable because the base rule (.ctable td) outranks a bare class
     and would otherwise keep its 10px either side. */
  .ctable th.paycell,.ctable td.paycell{padding-right:0}
  .ctable th.paycell+th,.ctable td.paycell+td{padding-left:6px}
  /* Wise's own mark, in Wise's own colours, on every open payable. It is the
     one button in this app that hands work to another company, and it is read
     at a glance in a column of a hundred rows — so it wears that company's
     badge rather than a house glyph that would have to be learnt.

     The mark stands on the page's own ground rather than inside a filled chip.
     At 22px the chip was the thing you saw and the logo was a detail inside it;
     as ink it reads as the logo at a smaller size than the chip ever could.
     Wise publishes the pair as a pair, so the mark simply takes whichever half
     the page is not: forest green on the light page, bright green on the dark
     one. That is not a themed brand colour — it is the same two colours either
     way, swapped so the mark stays the figure and the page stays the ground.

     Hover fills the chip back in. This is the one control on the row that hands
     the work somewhere else, so it answers the pointer before it is clicked. */
  /* the id-prefixed selectors restate the mark over the per-view button rules
     ("#costsview button,#invoicesview button" below) — same trick as .cinvst
     and .txipill: without them this renders as a plain grey rectangle with a
     near-invisible glyph in it, which is exactly what a brand mark must not do.
     The ink is held in --wise-mark rather than set directly, so dark mode can
     restate it without also outranking the hover and focus fills — but the dark
     rule has to carry the id-prefixed selector too. Without it the override is
     (0,2,1) against the base rule's (1,1,1) and LOSES, which left the mark
     forest green on the dark page: the one combination where it sinks into the
     ground rather than standing on it. */
  .wisebtn,#invoicesview button.wisebtn{--wise-mark:#163300;
    width:20px;height:20px;padding:0;border:0;
    border-radius:5px;cursor:pointer;background:none;color:var(--wise-mark);
    display:flex;align-items:center;justify-content:center;flex:0 0 auto;
    transition:background-color .12s ease,color .12s ease,transform .12s ease}
  html[data-theme="dark"] .wisebtn,
  html[data-theme="dark"] #invoicesview button.wisebtn{--wise-mark:#9FE870}
  .wisebtn svg,#invoicesview button.wisebtn svg{width:13px;height:13px;fill:currentColor}
  .wisebtn[disabled],#invoicesview button.wisebtn[disabled]{opacity:.5;cursor:progress}
  @media (hover:hover){
    .wisebtn:hover:not(:disabled),#invoicesview button.wisebtn:hover:not(:disabled){
      background:#9FE870;color:#163300;transform:scale(1.1)}
  }
  /* the keyboard gets the same answer the pointer does */
  .wisebtn:focus-visible,#invoicesview button.wisebtn:focus-visible{
    background:#9FE870;color:#163300;outline:none}
  .wisebtn:active:not(:disabled),#invoicesview button.wisebtn:active:not(:disabled){transform:scale(.94)}
  @media (prefers-reduced-motion:reduce){ .wisebtn{transition:none} }
  .paygrp{margin-top:12px}
  .payh{font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.04em;color:var(--text-3);margin-bottom:4px}
  .payrow{font-size:var(--fs-sm);padding:3px 0}
  .paydim{color:var(--dim)}
  .paybad{color:var(--amb-tx-2)}
  /* The confirm. One document, one recipient, one figure — a summary you read
     rather than a form you fill, except where the supplier has more than one
     account and the choice is genuinely open. */
  #paywise .box{max-width:460px}
  #paywise #pwBody{display:flex;flex-direction:column;min-height:0}
  .pwline{font-size:var(--fs-md);color:var(--text-3);margin:8px 0 12px;line-height:1.55}
  .pwline b{color:var(--ink-2)}
  .pwrows{border-top:1px solid var(--line);padding-top:8px;font-size:var(--fs-sm);overflow:auto;min-height:0}
  .pwrow{display:flex;justify-content:space-between;gap:12px;padding:4px 0}
  .pwrow>span:first-child{color:var(--text-3);flex:0 0 auto}
  .pwrow>span:last-child{text-align:right;color:var(--ink-3)}
  .pwrow select{font:inherit;font-size:var(--fs-sm);padding:4px 6px;border:1px solid var(--line);
    border-radius:5px;background:var(--panel);color:var(--ink-3);max-width:260px}
  .pwrow select:focus{border-color:var(--red);outline:none}
  .pwok{color:var(--grn-tx)}
  .pwfoot{margin-top:12px;padding-top:10px;border-top:1px solid var(--line);
    font-size:var(--fs-sm);color:var(--muted-2);line-height:1.55}
  .pwfoot a{color:var(--link)}
  .pwerr{font-size:var(--fs-sm);color:var(--amb-tx-2);margin:8px 0 0;line-height:1.55}
  #paywise .pwacts{margin-top:12px;display:flex;gap:8px;justify-content:flex-end}
  #paywise .pwacts button[disabled]{opacity:.45;cursor:not-allowed}
  /* Wise recipient sync — a two-sided LEDGER in a dialog. WISE recipients down
     the left, our counterparties down the right, a faint spine between them; each
     row's connector shows which way this run WRITES — green flows IN from Wise (a
     recipient recorded as one of our counterparties), the brand red flows OUT to
     Wise (our counterparty name pushed onto the recipient as its nickname). Built
     entirely on the app's own theme tokens, so it holds in light and dark. */
  #wrecipModal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:75}
  #wrecipModal.open{display:flex;align-items:center;justify-content:center}
  #wrecipModal .box{--wmono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
    background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:0;
    max-width:672px;width:calc(100% - 32px);max-height:84vh;display:flex;flex-direction:column;
    box-shadow:var(--shadow-modal)}
  #wrecipModal .wreciphead{display:flex;align-items:flex-start;gap:11px;padding:15px 16px 13px;border-bottom:1px solid var(--line)}
  #wrecipModal .wrecip-mark{flex:0 0 auto;width:30px;height:30px;border-radius:8px;margin-top:1px;display:grid;place-items:center;background:var(--soft);border:1px solid var(--line);color:var(--text-2)}
  #wrecipModal .wrecip-mark svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
  #wrecipModal .wrecip-tt{min-width:0;display:flex;flex-direction:column;gap:2px;padding-top:1px}
  #wrecipModal .wrecip-tt>b{font-size:var(--fs-lg);color:var(--ink-strong);font-weight:600;line-height:1.15}
  #wrecipModal .wrecip-x{margin-left:auto;flex:0 0 auto;width:28px;height:28px;border-radius:7px;display:grid;place-items:center;background:transparent;border:1px solid var(--line);color:var(--text-2);cursor:pointer}
  #wrecipModal .wrecip-x:hover{background:var(--hover);color:var(--ink)}
  #wrecipModal .wrecip-x svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
  .wrecip-msg{font-size:var(--fs-sm);color:var(--text-3)}
  .wrecip-msg.wrecip-bad{color:var(--amb-tx-2)}
  /* summary tiles — the answer before the detail */
  #wrecipModal .wrecip-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;padding:12px 16px 3px}
  #wrecipModal .wrecip-stats:empty{display:none}
  #wrecipModal .wstat{background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:7px 10px;display:flex;flex-direction:column;gap:2px}
  #wrecipModal .wstat .n{font-family:var(--wmono);font-size:var(--fs-xl);font-variant-numeric:tabular-nums;line-height:1;color:var(--ink-strong)}
  #wrecipModal .wstat .l{font-size:var(--fs-2xs);letter-spacing:.06em;text-transform:uppercase;color:var(--text-3)}
  #wrecipModal .wstat.in{border-color:var(--grn-bd);background:var(--grn-bg)}
  #wrecipModal .wstat.in .n,#wrecipModal .wstat.in .l{color:var(--grn-tx)}
  #wrecipModal .wstat.warn{border-color:var(--amb-bd);background:var(--amb-bg)}
  #wrecipModal .wstat.warn .n,#wrecipModal .wstat.warn .l{color:var(--amb-tx-2)}
  #wrecipModal #wrecipOut{overflow:auto;min-height:0;flex:1 1 auto;padding:4px 16px 12px}
  #wrecipModal .wloading{font-size:var(--fs-sm);color:var(--text-3);padding:16px 2px}
  /* the two columns and the direction legend between them */
  #wrecipModal .wcols{display:grid;grid-template-columns:1fr 118px 1fr;align-items:end;position:sticky;top:0;background:var(--panel);padding:9px 0 7px;z-index:2}
  #wrecipModal .wch{font-size:var(--fs-2xs);font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--text-2);display:flex;align-items:center}
  #wrecipModal .wch.left{justify-content:flex-end;padding-right:6px}
  #wrecipModal .wch.right{justify-content:flex-start;padding-left:6px}
  #wrecipModal .wch.mid{justify-content:center}
  #wrecipModal .wgrp{margin-top:13px}
  #wrecipModal .wgh{font-size:var(--fs-2xs);font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--text-3);margin:0 0 5px;display:flex;align-items:center;gap:8px}
  #wrecipModal .wgh .c{font-family:var(--wmono);color:var(--text-2)}
  #wrecipModal .wgh .rule{flex:1 1 auto;height:1px;background:var(--line)}
  #wrecipModal .wgh.attn{color:var(--amb-tx-2)}
  #wrecipModal .wrow{display:grid;grid-template-columns:1fr 118px 1fr;align-items:center;padding:8px 0}
  #wrecipModal .wside{min-width:0;display:flex;flex-direction:column;gap:2px}
  #wrecipModal .wside.left{text-align:right;padding-right:6px}
  #wrecipModal .wside.right{text-align:left;padding-left:6px}
  #wrecipModal .wnm{font-size:var(--fs-sm);color:var(--ink-2);line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  #wrecipModal .wside.left .wnm{font-weight:600;color:var(--ink-strong)}
  #wrecipModal .wsub{font-size:var(--fs-2xs);color:var(--dim);display:flex;gap:6px;align-items:center}
  #wrecipModal .wside.left .wsub{justify-content:flex-end}
  #wrecipModal .wacct{font-family:var(--wmono);font-variant-numeric:tabular-nums}
  #wrecipModal .wtag{font-size:var(--fs-3xs);font-weight:600;padding:1px 6px;border-radius:5px;border:1px solid var(--line-2);color:var(--text-2);background:var(--soft);letter-spacing:.02em;white-space:nowrap}
  #wrecipModal .wtag.fact{color:var(--grn-tx);border-color:var(--grn-bd);background:var(--grn-bg)}
  #wrecipModal .wtag.guess{color:var(--amb-tx-2);border-color:var(--amb-bd);background:var(--amb-bg)}
  #wrecipModal .wtag.block{color:var(--red-tx-2);border-color:var(--red-bd);background:var(--red-bg)}
  /* the connector: the spine, and one pill per direction this row writes */
  #wrecipModal .wconn{position:relative;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px}
  #wrecipModal .wconn::before{content:"";position:absolute;top:-8px;bottom:-8px;left:50%;width:1px;transform:translateX(-.5px);background:var(--line)}
  #wrecipModal .wflow{position:relative;z-index:1;display:inline-flex;align-items:center;gap:4px;font-size:var(--fs-3xs);font-weight:700;letter-spacing:.03em;text-transform:uppercase;padding:2px 7px;border-radius:20px;background:var(--panel);border:1px solid var(--line-2);white-space:nowrap}
  #wrecipModal .wflow svg{width:11px;height:11px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
  #wrecipModal .wflow.in{color:var(--grn-tx);border-color:var(--grn-bd);background:var(--grn-bg)}
  #wrecipModal .wflow.out{color:var(--text-2);border-color:var(--line-2);background:var(--panel)}
  /* unchanged mappings collapse to one disclosure — the net is what's left open */
  #wrecipModal .wset{margin-top:12px;border:1px solid var(--line);border-radius:9px;background:var(--soft);overflow:hidden}
  #wrecipModal .wset>summary{list-style:none;cursor:pointer;padding:9px 12px;display:flex;align-items:center;gap:8px;font-size:var(--fs-sm);color:var(--text)}
  #wrecipModal .wset>summary::-webkit-details-marker{display:none}
  #wrecipModal .wset>summary .chev{width:13px;height:13px;color:var(--dim);transition:transform .2s;flex:0 0 auto}
  #wrecipModal .wset[open]>summary .chev{transform:rotate(90deg)}
  #wrecipModal .wset>summary svg{fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  #wrecipModal .wset>summary b{color:var(--ink-2);font-weight:600;font-family:var(--wmono)}
  #wrecipModal .wset>summary .grow{flex:1 1 auto}
  #wrecipModal .wset>summary .hint{font-size:var(--fs-2xs);color:var(--dim)}
  #wrecipModal .wsetlist{padding:0 12px 9px}
  #wrecipModal .wsetitem{display:flex;align-items:center;gap:7px;padding:5px 0;font-size:var(--fs-sm);border-top:1px solid var(--line)}
  #wrecipModal .wsetitem .p{color:var(--ink-2);font-weight:600;flex:0 0 auto;max-width:45%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  #wrecipModal .wsetitem .a{color:var(--dim)}
  #wrecipModal .wsetitem .h{color:var(--dim);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  #wrecipModal .wsetitem .wacct{margin-left:auto;color:var(--dim);font-size:var(--fs-2xs);flex:0 0 auto}
  #wrecipModal .wsetmore{padding-top:6px;font-size:var(--fs-2xs);color:var(--dim)}
  /* notices for the all-settled and applied states */
  #wrecipModal .wnote{display:flex;gap:10px;padding:12px 13px;border-radius:9px;margin-top:6px;align-items:center}
  #wrecipModal .wnote.calm{background:var(--soft);border:1px solid var(--line)}
  #wrecipModal .wnote.good{background:var(--grn-bg);border:1px solid var(--grn-bd)}
  #wrecipModal .wnote .ic{flex:0 0 auto;width:24px;height:24px;border-radius:6px;display:grid;place-items:center}
  #wrecipModal .wnote.calm .ic{background:var(--soft-2);color:var(--text-2)}
  #wrecipModal .wnote.good .ic{background:var(--grn-bd);color:var(--grn-tx)}
  #wrecipModal .wnote svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  #wrecipModal .wnote .tx{font-size:var(--fs-sm);line-height:1.45;color:var(--text)}
  #wrecipModal .wnote .tx b{font-weight:600;color:var(--ink-2)}
  #wrecipModal .wnote.good .tx b{color:var(--grn-tx)}
  #wrecipModal .wbad{font-size:var(--fs-sm);color:var(--amb-tx-2);background:var(--amb-bg);border:1px solid var(--amb-bd);border-radius:8px;padding:8px 11px;margin-top:8px;line-height:1.4}
  /* rows that need a person: unmatched (amber), conflict / stranded (red) */
  #wrecipModal .warow{display:flex;align-items:center;gap:10px;padding:8px 11px;border-radius:8px;background:var(--amb-bg);border:1px solid var(--amb-bd);margin-bottom:5px}
  #wrecipModal .warow.hard{background:var(--red-bg);border-color:var(--red-bd)}
  #wrecipModal .warow .who{font-size:var(--fs-sm);font-weight:600;color:var(--ink-2)}
  #wrecipModal .warow .meta{font-size:var(--fs-xs);color:var(--text-2)}
  #wrecipModal .warow .badge{margin-left:auto;flex:0 0 auto;font-size:var(--fs-3xs);font-weight:700;letter-spacing:.03em;text-transform:uppercase;padding:2px 7px;border-radius:5px;color:var(--amb-tx-2);border:1px solid var(--amb-bd)}
  #wrecipModal .warow.hard .badge{color:var(--red-tx-2);border-color:var(--red-bd)}
  #wrecipModal .wrecipfoot{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:11px 16px;border-top:1px solid var(--line)}
  #wrecipModal .wrecipfoot .fh{font-size:var(--fs-2xs);color:var(--dim);line-height:1.3;max-width:230px}
  #wrecipModal .wrecipfoot .sp{flex:1 1 auto}
  #wrecipModal .wrecipfoot button{font:inherit;font-size:var(--fs-sm);font-weight:500;padding:7px 13px;border:1px solid var(--line-2);background:var(--soft);color:var(--ink);border-radius:7px;cursor:pointer}
  #wrecipModal .wrecipfoot button:hover{background:var(--hover)}
  #wrecipModal .wrecipfoot button.go{background:var(--red);border-color:var(--red);color:#fff;font-weight:600}
  #wrecipModal .wrecipfoot button.go:hover{background:var(--red-bar)}
  #wrecipModal .wrecipfoot button.go.ghost{background:var(--soft);border-color:var(--line-2);color:var(--text);font-weight:500}
  #wrecipModal .wrecipfoot button.go.ghost:hover{background:var(--hover);color:var(--ink)}
  #wrecipModal .wrecipfoot button[disabled]{opacity:.5;cursor:default}
  #wrecipModal .wrecipfoot button .cnt{font-family:var(--wmono);font-weight:700}
  @media (max-width:600px){
    #wrecipModal .wrecip-stats{grid-template-columns:repeat(2,1fr)}
    #wrecipModal .wcols,#wrecipModal .wrow{grid-template-columns:1fr 92px 1fr}
    #wrecipModal .wrecipfoot{flex-wrap:wrap}
    #wrecipModal .wrecipfoot .fh{max-width:none;order:3;flex-basis:100%}
  }
  /* Wait-time distribution: the "Median wait time" card on Orders opens this.
     Same overlay language as #wrecipModal — a fixed header and a scrolling body
     — but the body is a histogram drawn as SVG, the three summary figures, and
     one plain-language line for anyone who does not read percentiles. */
  #wtdistModal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:75}
  #wtdistModal.open{display:flex;align-items:center;justify-content:center}
  #wtdistModal .box{width:min(560px,calc(100vw - 28px));max-height:calc(100vh - 40px);padding:0;
    display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);
    border-radius:12px;box-shadow:var(--shadow-modal);overflow:hidden}
  #wtdistModal .wthead{display:flex;align-items:center;gap:11px;padding:15px 16px 13px;border-bottom:1px solid var(--line)}
  #wtdistModal .wt-mark{flex:0 0 auto;width:30px;height:30px;border-radius:8px;display:grid;place-items:center;
    background:var(--grn-bg);border:1px solid var(--grn-bd);color:var(--grn-tx)}
  #wtdistModal .wt-mark svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
  #wtdistModal .wt-tt{min-width:0;display:flex;flex-direction:column;gap:2px}
  #wtdistModal .wt-tt>b{font-size:var(--fs-lg);color:var(--ink-strong);font-weight:600;line-height:1.15}
  #wtdistModal .wt-tt>span{font-size:var(--fs-2xs);color:var(--dim)}
  #wtdistModal .wt-x{margin-left:auto;flex:0 0 auto;width:28px;height:28px;border-radius:7px;display:grid;place-items:center;
    background:transparent;border:1px solid var(--line);color:var(--text-2);cursor:pointer}
  #wtdistModal .wt-x:hover{background:var(--hover);color:var(--ink)}
  #wtdistModal .wt-x svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
  #wtdistModal .wtbody{overflow:auto;min-height:0;padding:14px 16px 16px}
  #wtdistModal .wt-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
  #wtdistModal .wtstat{background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:8px 11px;display:flex;flex-direction:column;gap:3px}
  #wtdistModal .wtstat .l{font-size:var(--fs-2xs);color:var(--text-3);line-height:1.2}
  #wtdistModal .wtstat .n{font-size:var(--fs-xl);font-weight:600;font-variant-numeric:tabular-nums;line-height:1;color:var(--ink-strong)}
  #wtdistModal .wtchart{position:relative;margin-top:16px}
  #wtdistModal .wtchart svg{width:100%;height:auto;display:block;touch-action:none}
  #wtdistModal .wtchart text{font-family:inherit}
  #wtdistModal .wthit{cursor:pointer}
  #wtdistModal .wt-tip{position:absolute;transform:translateX(-50%);pointer-events:none;z-index:3;
    background:var(--panel);border:1px solid var(--line-2);border-radius:7px;padding:5px 9px;
    font-size:12px;color:var(--text-2);white-space:nowrap;box-shadow:0 2px 8px rgba(0,0,0,.16);line-height:1.35}
  #wtdistModal .wt-tip b{display:block;font-weight:600;color:var(--ink-strong);font-size:13px}
  #wtdistModal .wt-cap{margin:9px 0 0;font-size:var(--fs-2xs);color:var(--dim);text-align:center}
  #wtdistModal .wt-empty{font-size:var(--fs-sm);color:var(--text-3);padding:20px 2px;text-align:center}
  @media (max-width:600px){
    #wtdistModal .wt-stats{gap:6px}
    #wtdistModal .wtstat{padding:8px 9px}
  }
  /* Documents tab on phones: number + merchant, remaining + total, due date +
     countdown, and a chevron to the detail panel. See the phone-card shell above
     for the card box and the pill/ellipsis rules. */
  .invcards{display:none;--inv-rem:88px;--inv-due:74px;--inv-gap:8px}
  /* fixed money/due columns: the same grid for every card and the header, so the
     columns line up down the list and the invoice number is what gives way */
  .invcard,.invcards-head,.invcards-foot{display:grid;grid-template-columns:minmax(0,1fr) var(--inv-rem) var(--inv-due) 10px;
    gap:var(--inv-gap);align-items:center}
  /* the send column only exists on a list that holds something to send, exactly
     as its column does in the table — otherwise every card gives up 30px to a
     gap that is never filled */
  .invcards.paying .invcard,.invcards.paying .invcards-head,.invcards.paying .invcards-foot{
    grid-template-columns:minmax(0,1fr) var(--inv-rem) var(--inv-due) 22px 10px}
  .invcard-pay{display:flex;justify-content:center}
  .pcard .invcard-num{font-size:var(--fs-sm);margin-bottom:1px}
  /* the due column is coloured by the row's urgency, so its lines take that
     colour rather than the shell's greys */
  .invcard-due .pcard-n,.invcard-due .pcard-sub{color:inherit}
  .invcard-due .pcard-sub{opacity:.8}
  /* an invoice still owed and near (or past) its due date carries the border and
     edge bar the Transactions and Counterparties cards use for "needs attention",
     in whichever of the two urgency colours its due column is already showing:
     amber inside three days, red on the day and after. The bar is absolute, so it
     costs the content no width. An invoice that's settled is never marked. */
  .invcard{position:relative;overflow:hidden}
  .invcard.due-soon:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--amb-bar)}
  .invcard.due-now:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--red-bar)}
  @media (max-width:700px){
    #invoicesview .invtable{display:none}
    #invoicesview .invcards{display:block}
  }
  /* the narrowest phones: give the money columns back a few pixels */
  @media (max-width:360px){ .invcards{--inv-rem:78px;--inv-due:68px;--inv-gap:6px} }
  /* Budgets on phones: the table is eleven columns of accounting, and most of
     them are detail you read once you've picked a bundle. A card keeps only the
     two numbers a bundle is read for — what's still left to spend against the
     estimate, and what's invoiced and still owed — and the chevron opens the
     panel holding everything else: the bundle ID, its phase and progress, its
     actions, the amount editors and the invoices. See the phone-card shell
     above for the card box, the column header and the ellipsis rules. */
  .costcards{display:none;--cost-rem:82px;--cost-inv:82px;--cost-gap:8px}
  .costcard,.costcards-head,.costcards-foot,.costgrp{display:grid;
    grid-template-columns:minmax(0,1fr) var(--cost-rem) var(--cost-inv) 10px;
    gap:var(--cost-gap);align-items:center}
  /* the merchant a run of cards belongs to: the table's group header, minus the
     columns the cards dropped. It reads as a heading, not as a card — but its
     subtotals sit on the cards' own columns, so a merchant's total lines up over
     the bundles it is the sum of. That column is what says which number this is;
     spelling out "left" / "owed" here only pushed the pair out of them. */
  /* 12px card padding + 1px border, the same inset .costcards-head uses, so the
     grid's columns land on the cards' rather than on the list's outer edge */
  .costgrp{margin:18px 0 6px;padding:0 13px}
  .costgrp:first-child{margin-top:0}
  /* name and count on one line, like .cinvline: a long merchant gives up width
     (ellipsis) rather than wrapping and pushing the count onto a line of its own */
  .costgrp-who{display:flex;align-items:center;gap:6px;flex-wrap:nowrap;min-width:0}
  .costgrp-who .mpill{flex:0 1 auto;min-width:0;max-width:100%}
  .costgrp-who .cgrpstat{flex:0 0 auto}
  .costgrp .mpill{margin:0}
  .costgrp b{font-size:var(--fs-sm);color:var(--ink-blue)} /* "(no merchant)" — a pill's weight, not a heading's */
  .costgrp .cgrpstat{margin-left:0}
  .costgrp .costgrp-n{text-align:right;font-size:var(--fs-xs);color:var(--dim);font-variant-numeric:tabular-nums;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* a bundle whose invoices are coming due wears the same edge bar as an invoice
     card in the same state. The bar is absolute, so it costs the content no width. */
  .costcard{position:relative;overflow:hidden}
  .costcard.due-soon{border-color:var(--amb-bd-2)}
  .costcard.due-soon:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--amb-bar)}
  .costcard.due-now{border-color:var(--red-bd)}
  .costcard.due-now:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--red-bar)}
  /* the bundle's name is the one thing on the card you read as words, so it wraps
     to a second line rather than ellipsizing after four. It has to out-specify
     the shell's one-line rule, hence the repeated :not()s. */
  .costcard>div:first-child span.pcard-n:not(.ipill):not(.mpill){white-space:normal;
    display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  /* the table's totals row, on the cards' own columns so it lines up under them.
     .pcards-foot is the shared half, as .pcards-head is for the header: each
     list keeps its own class for the grid its columns are on. */
  .costcards-foot,.pcards-foot{padding:10px 13px;margin-top:4px;border-top:2px solid var(--ink);font-size:var(--fs-2xs);
    text-transform:uppercase;letter-spacing:.04em;color:var(--dim)}
  .costcards-foot span,.pcards-foot span{text-align:right}
  .costcards-foot span:first-child,.pcards-foot span:first-child{text-align:left}
  .costcards-foot b,.pcards-foot b{display:block;font-size:var(--fs-md);color:var(--ink-2);font-variant-numeric:tabular-nums;
    text-transform:none;letter-spacing:0;margin-top:2px}
  /* "Tasks with no budget" gets the same treatment: the action and who it's for,
     and the button that puts it back in the budget. */
  .nccards{display:none}
  .nccard{display:grid;grid-template-columns:minmax(0,1fr) auto 10px;gap:8px;align-items:center}
  #costsview .nccard button{font-size:var(--fs-xs);padding:3px 8px}
  /* the bundle panel: the same drawer the invoice ones use, with the sheet's own
     editors inside. Its sections follow the Counterparties panel's headings. */

  /* .cact takes its size from the table it normally sits in; in a panel it has none */
  .invoice-panel .cact{font-size:var(--fs-sm);padding:3px 0}
  /* outside a .ctable these inputs have no width of their own — give them one */
  .invoice-panel input.cnum{width:130px;font:inherit;font-size:var(--fs-md);padding:6px 8px;
    text-align:right;border:1px solid var(--line);border-radius:6px}
  @media (max-width:700px){
    #costsview .costtable,#costsview .nctable{display:none}
    #costsview .costcards{display:block}
    #costsview .nccards{display:block}
  }
  @media (max-width:360px){ .costcards{--cost-rem:74px;--cost-inv:74px;--cost-gap:6px} }
  .cact .idtag{margin-top:2px} /* smaller font than the action name: nudge to center on the first line, like the pill */
  .ctable td.cacts{max-width:480px}
  .cagg{color:var(--text-2);font-size:var(--fs-xs);white-space:nowrap}
  .ctable td.crem,.ctable th.crem{white-space:nowrap;text-align:right} /* beats the table-wide text-align:left */
  /* sidebar cost box */
  .costinfo{border:1px solid var(--line);border-radius:6px;padding:8px;font-size:var(--fs-sm);background:var(--panel-2)}
  .costinfo .crow{display:flex;justify-content:space-between;margin:2px 0}
  .costinfo.none{color:var(--muted)}
  .costnote{color:var(--muted);font-size:var(--fs-xs);margin-top:4px}

  /* ---- task panel redesign: a document (title + description) over a compact
         properties block, the way Linear frames an issue. Everything here is
         scoped to #detail so the shared .invoice-panel shell is untouched. ---- */
  #detail .dhead{padding:12px 20px 10px}
  #detail .dhead-top{display:flex;align-items:center;gap:8px}
  #detail .crumb{font-size:var(--fs-xs);color:var(--muted);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  #detail .crumb .sep{color:var(--dim);margin:0 3px}
  #detail .dtools{margin-left:auto;display:flex;align-items:center;gap:3px;flex:0 0 auto}
  #detail .dtool{font:inherit;font-size:var(--fs-lg);line-height:1;width:26px;height:26px;display:grid;place-items:center;
    border:1px solid transparent;border-radius:6px;background:none;color:var(--muted-3);cursor:pointer;padding:0}
  @media (hover:hover){ #detail .dtool:hover:not(:disabled){background:var(--rowhover);color:var(--ink-3)} }
  #detail .dtool:disabled{opacity:.35;cursor:default}
  #detail .dhead .dtool.close{float:none;color:var(--muted-2)}
  #detail .navgrp{display:inline-flex;border:1px solid var(--line);border-radius:6px;overflow:hidden}
  #detail .navgrp .dtool{width:24px;border-radius:0;border:0}
  #detail .navgrp .dtool+.dtool{border-left:1px solid var(--line)}
  #detail .dtool svg{width:15px;height:15px}
  #detail .dmore{position:relative}
  #detail .dmoremenu{position:absolute;top:calc(100% + 4px);right:0;z-index:30;width:230px;background:var(--panel);
    border:1px solid var(--line);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px}
  #detail .dmoremenu[hidden]{display:none}
  #detail .dmoreitem{display:block;width:100%;text-align:left;font:inherit;font-size:var(--fs-sm);padding:7px 8px;border:0;border-radius:6px;background:none;cursor:pointer;color:var(--ink-3)}
  #detail .dmoreitem.danger{color:var(--red-tx-3)}
  @media (hover:hover){ #detail .dmoreitem:hover{background:var(--rowhover)} }
  #detail .dmorenote{font-size:var(--fs-xs);color:var(--muted-3);padding:4px 8px 3px;line-height:1.4}
  #detail .dtitle-row{display:flex;align-items:flex-start;gap:8px;margin-top:9px}
  #detail .idtag.big{font-size:var(--fs-2xs);font-weight:bold;color:var(--red-ink);background:var(--red-bg);border-radius:5px;
    padding:3px 6px;flex:0 0 auto;text-align:center;margin-top:2px}
  #detail h3.dtitle{margin:-2px -5px;font-size:var(--fs-xl);font-weight:600;color:var(--ink-strong);text-transform:none;letter-spacing:0;
    flex:1;min-width:0;border-radius:5px;padding:2px 5px;outline:none;cursor:text;line-height:1.35}
  @media (hover:hover){ #detail h3.dtitle:hover{background:var(--rowhover)} }
  #detail h3.dtitle:focus{background:var(--rowhover);box-shadow:0 0 0 2px var(--red-bg)}
  #detail .prio-line{margin-top:8px;font-size:var(--fs-xs)}

  #detail .dprops{display:grid;grid-template-columns:86px 1fr;margin:18px 0 6px;padding-top:2px;border-bottom:1px solid var(--line)}
  #detail .dprops .prow{display:contents}
  #detail .dprops .pk{font-size:var(--fs-sm);color:var(--muted);padding:8px 8px 8px 0;align-self:center}
  #detail .dprops .pv{padding:3px 0;align-self:center;min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:6px}
  #detail .dprops input{width:auto;max-width:100%}
  /* phase / area / merchant read as ordinary bordered dropdowns of one consistent
     width, so a short value doesn't leave the chevron floating far to the right */
  #detail .dprops select:not(.statuspill){width:100%;box-sizing:border-box;border:1px solid var(--line);background:var(--panel-2);padding:6px 8px;border-radius:6px;font-size:var(--fs-sm)}
  @media (hover:hover){ #detail .dprops select:not(.statuspill):hover{border-color:var(--line-2)} }
  #detail select.statuspill{width:auto;font-weight:600;border-radius:999px;padding:4px 12px;color:#fff;border-color:transparent}
  #detail #f_desc{min-height:120px}
  /* the ACTIVITY trail — audit events + comments, shared by the task and order
     drawers (both are #detail). One timeline: a system event on a neutral dot, a
     comment in a bubble under an initials avatar; a filter above, a comment box
     below. The vertical line is a pseudo-element behind the 24px node column. */
  .invoice-panel .actsec{margin-top:18px;border-top:1px solid var(--line);padding-top:14px}
  .invoice-panel .acthead{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
  .invoice-panel .actseg-row{display:flex;gap:6px}
  .invoice-panel .actseg{font:inherit;font-size:var(--fs-xs);padding:4px 10px;border-radius:999px;border:1px solid var(--line);background:transparent;color:var(--text-2);cursor:pointer}
  .invoice-panel .actseg.on{background:var(--panel-2);border-color:var(--line-2);color:var(--ink-2)}
  .invoice-panel .actfeed{position:relative}
  .invoice-panel .actfeed:before{content:"";position:absolute;left:11px;top:4px;bottom:6px;width:2px;background:var(--line)}
  .invoice-panel .actfeed.empty:before{display:none}
  .invoice-panel .actrow{display:grid;grid-template-columns:24px 1fr;gap:11px;padding-bottom:14px}
  .invoice-panel .actnode{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:var(--fs-3xs);font-weight:700;flex:0 0 auto;position:relative;z-index:1;box-sizing:border-box}
  .invoice-panel .actnode.ev{background:var(--panel);border:1px solid var(--line-2);color:var(--muted)}
  .invoice-panel .actnode svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  .invoice-panel .actnode.av{background:var(--tagbg);color:var(--ink-3)}
  .invoice-panel .actmeta{font-size:var(--fs-xs);color:var(--text-2);line-height:1.5;padding-top:3px}
  .invoice-panel .actname{color:var(--ink-2);font-weight:600}
  .invoice-panel .actwhen{color:var(--muted-2)}
  .invoice-panel .actorigin{font-size:var(--fs-3xs);text-transform:uppercase;letter-spacing:.03em;color:var(--muted);border:1px solid var(--line);border-radius:4px;padding:0 4px}
  .invoice-panel .actbubble{background:var(--soft);border:1px solid var(--line);border-radius:10px;padding:8px 11px;font-size:var(--fs-sm);color:var(--ink-2);line-height:1.5;margin-top:4px;white-space:pre-wrap}
  .invoice-panel .actpill{font-size:var(--fs-3xs);font-weight:600;padding:2px 8px;border-radius:999px;white-space:nowrap;background:var(--muted-3);color:#fff}
  .invoice-panel .actpill[data-sc="todo"]{background:var(--todo);color:var(--ink-3)}
  .invoice-panel .actpill[data-sc="prog"]{background:var(--prog)}
  .invoice-panel .actpill[data-sc="block"]{background:var(--block)}
  .invoice-panel .actpill[data-sc="done"]{background:var(--done)}
  .invoice-panel .actpill[data-sc="group"]{background:var(--group);color:var(--ink-3)}
  .invoice-panel .actwait,.invoice-panel .actempty{font-size:var(--fs-xs);color:var(--muted);padding:4px 0 10px}
  .invoice-panel .actcomp{display:flex;flex-direction:column;gap:8px;margin-top:6px}
  .invoice-panel textarea.actinput{min-height:38px;width:100%;box-sizing:border-box}
  .invoice-panel .actcomp-foot{display:flex;justify-content:flex-end}
  /* the Comment button wears the shared panel button (.pact); only its disabled
     state is added here, for the brief moment a comment is posting */
  .invoice-panel .actsend:disabled{opacity:.6;cursor:default}
  .invoice-panel .actfeed[data-filter="comment"] .actrow[data-kind="event"]{display:none}
  .invoice-panel .actfeed[data-filter="event"] .actrow[data-kind="comment"]{display:none}
  #detail .dprops .prowbtn{flex:0 0 auto}
  #detail .dprops input[type=date]{font-size:var(--fs-sm);padding:4px 6px;border-color:transparent;border-radius:6px}
  @media (hover:hover){ #detail .dprops input[type=date]:hover{background:var(--rowhover);border-color:var(--line)} }
  #detail .dprops .dates{gap:4px}
  #detail .dates .darr{color:var(--dim)}
  #detail .dates .ddur{font-size:var(--fs-xs);color:var(--dim)}

  #detail .ownerwrap{position:relative}
  #detail .pbtn{font:inherit;font-size:var(--fs-sm);display:inline-flex;align-items:center;gap:6px;
    border:1px solid transparent;border-radius:6px;background:none;color:var(--ink-3);cursor:pointer;padding:5px 8px;max-width:100%}
  @media (hover:hover){ #detail .pbtn:hover{background:var(--rowhover);border-color:var(--line)} }
  #detail .pbtn.empty{color:var(--muted-3)}
  #detail .pbtn .pchev{color:var(--dim);font-size:var(--fs-3xs)}
  #detail .uav{width:18px;height:18px;border-radius:50%;display:inline-grid;place-items:center;font-size:var(--fs-2xs);font-weight:bold;color:#fff;flex:0 0 auto}
  #detail .uav.none{background:var(--panel-2);color:var(--muted-3);box-shadow:inset 0 0 0 1px var(--line-2)}
  #detail .umenu{position:absolute;top:calc(100% + 4px);left:0;z-index:30;width:230px;background:var(--panel);
    border:1px solid var(--line);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px}
  #detail .umenu[hidden]{display:none}
  #detail .umrow{display:flex;align-items:center;gap:9px;width:100%;text-align:left;background:none;border:0;border-radius:6px;padding:6px 8px;cursor:pointer;font:inherit;color:var(--ink-3)}
  @media (hover:hover){ #detail .umrow:hover{background:var(--rowhover)} }
  #detail .umname{font-size:var(--fs-sm);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  #detail .umtag{font-size:var(--fs-2xs);color:var(--muted-3);background:var(--panel-2);border-radius:5px;padding:1px 6px}
  #detail .umsep{height:1px;background:var(--line);margin:5px 6px}

  #detail .dseclabel{font-size:var(--fs-xs);font-weight:bold;letter-spacing:.05em;text-transform:uppercase;color:var(--dim);margin:16px 0 8px}
  #detail .dsched{border-top:1px solid var(--line);margin-top:12px}
  #detail .dsched>summary{list-style:none;cursor:pointer;padding:11px 0;display:flex;align-items:center;gap:8px;font-size:var(--fs-sm);color:var(--muted)}
  #detail .dsched>summary::-webkit-details-marker{display:none}
  #detail .dsched>summary .tw{color:var(--dim);font-size:var(--fs-2xs);transition:transform .15s ease;display:inline-block}
  #detail .dsched[open]>summary .tw{transform:rotate(90deg)}
  @media (hover:hover){ #detail .dsched>summary:hover{color:var(--ink-3)} }
  #detail .dsched>summary .scount{margin-left:auto;font-size:var(--fs-xs);color:var(--dim)}
  #detail .dsched-body{padding:0 0 8px}
  #detail .dsched-body.flags label{display:flex;align-items:center;gap:7px;font-weight:normal;color:var(--ink-3);margin:8px 0 0;font-size:var(--fs-sm)}
  #detail .dsched-body.flags label .fhint{margin-left:auto;color:var(--dim);font-size:var(--fs-sm);cursor:help}
  #detail .dsched-body.flags input[type=checkbox]{width:auto;flex:0 0 auto}

  #detail .budgetadd{color:var(--red-ink);border:1px solid var(--line);background:var(--red-bg)}
  @media (hover:hover){ #detail .budgetadd:hover{border-color:var(--red)} }
  #detail .costinfo.none{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
  /* unsaved-changes bar: hidden until an edit makes the board dirty */
  #syncbar{display:none;align-items:center;gap:8px;padding:8px 12px;background:var(--amb-bg);border-top:1px solid var(--amb-bd)}
  #syncbar.show{display:flex}
  /* in preview mode the bar is a standing statement about the app, not a nudge
     about a few edits, so it wears the switch's amber rather than a hairline */
  #syncbar.preview{border-top:2px solid var(--amb-bar)}
  #syncbar .msg{flex:1;font-size:var(--fs-sm);color:var(--amb-tx-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:2px}
  @media (hover:hover){ #syncbar .msg:hover{color:var(--amb-tx)} }
  #syncbar button{font:inherit;font-size:var(--fs-sm);padding:6px 12px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer;white-space:nowrap;flex:0 0 auto}
  @media (hover:hover){ #syncbar button:hover{background:var(--hover)} }
  #syncbar button.primary{background:var(--red);color:#fff;border-color:var(--red)}
  @media (hover:hover){ #syncbar button.primary:hover{background:var(--red-hover);border-color:var(--red-hover)} }
  /* A sync pulls from an outside feed — the tills, Wise, Revolut — and that round
     trip is a few seconds where the app looks like it is doing nothing. This thin
     bar along the very bottom says it is working, on desktop and phone alike. It
     is indeterminate: the feed does not report how far it has got, so the fill
     travels rather than grows. Shown from beginSync, cleared from endSync. */
  #syncprog{display:none;position:fixed;left:0;right:0;bottom:0;height:3px;z-index:9998;
    overflow:hidden;pointer-events:none;background:var(--red-bg)}
  #syncprog.show{display:block}
  #syncprog>i{position:absolute;top:0;bottom:0;width:34%;background:var(--red);
    animation:syncprog 1.05s ease-in-out infinite}
  @keyframes syncprog{0%{left:-34%}100%{left:100%}}
  .hintbar{display:flex;align-items:center;gap:8px;padding:5px 12px;padding-bottom:calc(5px + env(safe-area-inset-bottom));background:var(--panel);border-top:1px solid var(--line)}
  .hint{font-size:var(--fs-xs);color:var(--muted);flex:1}
  .hintbar button{font:inherit;font-size:var(--fs-sm);padding:6px 10px;border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer}
  @media (hover:hover){ .hintbar button:hover:not(:disabled){background:var(--hover)} }
  .hintbar button:disabled{opacity:.4;cursor:default}
  .hintbar button.primary{background:var(--red);color:#fff;border-color:var(--red)}
  /* priorities view */
  .prio-wrap{max-width:940px}
  .prio-empty{font-size:var(--fs-md);color:var(--muted);padding:16px;border:1px dashed var(--line);border-radius:8px}
  .prio-owner{margin:0 0 22px;border:1px solid var(--line);border-radius:10px;overflow:hidden}
  .prio-oname{font-weight:bold;font-size:var(--fs-md);padding:8px 12px;background:var(--panel-2);border-bottom:1px solid var(--line)}
  .prio-ocount{font-weight:normal;color:var(--muted-2);font-size:var(--fs-xs);margin-left:6px}
  .prio-line{font-size:var(--fs-sm);color:var(--dim);margin-top:3px}
  .prio-line.late{color:var(--block-ink)}
  /* Score | Task | Due | Why — real table columns, but no visible vertical rules */
  .prio-tbl{width:100%;border-collapse:collapse;table-layout:fixed;font-size:var(--fs-sm)}
  .prio-tbl td{padding:6px 8px;cursor:pointer;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;vertical-align:middle}
  .prio-tbl td:first-child{padding-left:12px}
  .prio-tbl td:last-child{padding-right:12px}
  @media (hover:hover){ .prio-row:hover td{background:var(--rowhover)} .prio-row.top:hover td{background:var(--red-bg-h)} }
  .p-score{width:62px;text-align:center}
  .prio-score{display:inline-block;min-width:30px;text-align:center;font-weight:bold;color:var(--ink-4);background:var(--grid);border-radius:5px;padding:2px 4px;font-size:var(--fs-xs)}
  .p-task .pill{display:inline-block;vertical-align:middle;margin:-2px 7px 0 0}
  .p-due{width:118px;font-size:var(--fs-sm);color:var(--dim);text-align:right}
  .p-due.late{color:var(--block-ink)}
  .p-why{width:24%;font-size:var(--fs-xs);color:var(--muted-3);text-align:right}
  .prio-row.top td{background:var(--red-wash);border-bottom:1px solid var(--line);padding-top:12px;padding-bottom:12px}
  .prio-row.top .p-task{font-size:var(--fs-lg);font-weight:bold}
  .prio-row.top .p-task .pill{width:9px;height:9px}
  /* phones: give the whole width to the task name — drop the explainer, shrink
     the due column to just the countdown ("in 3 days") */
  @media (max-width:640px){
    .p-why,.p-due .p-date{display:none}
    .p-score{width:46px}
    .p-due{width:88px}
    .prio-tbl td:first-child{padding-left:8px}
    .prio-tbl td:last-child{padding-right:10px}
  }
  .prio-more{font-size:var(--fs-xs);color:var(--muted-2);padding:2px 14px 9px}
  /* kanban board */
  /* no top padding: the sticky column header sticks at the very top, so a band
     above it would show scrolled cards through. Its own padding spaces it. */
  /* the board keeps its own padding at every width — unlike Status it never
     had a phone rule, and inventing one here would be a change, not a tidy */
  #kanbanview{padding:0 18px 14px}
  /* ── Hiring pipeline (applicants board) ─────────────────────────────────
     A role-lane × stage-column matrix, the same shape as the task board. The
     stage colour is STATE, not decoration (brand guide §03): it rides --sc off
     a data-stage attribute — set once here, never inline — so a card, a column
     dot and a stepper bubble all read the same status fill. */
  #applicantsview{padding:0 18px 14px;overflow-x:auto;-webkit-overflow-scrolling:touch}
  /* the board and its lane bands share one column count, carried on data-cols
     (a status filter narrows it to a single column) — never an inline template */
  /* every size, gap and radius below is the task board's (.kb-*): the two
     boards are the same shape and read as one component */
  .hp-board{display:grid;gap:10px;align-items:start;padding-top:8px}
  .hp-board[data-cols="1"],.hp-board[data-cols="1"] .hp-lane{grid-template-columns:repeat(1,minmax(160px,1fr))}
  .hp-board[data-cols="2"],.hp-board[data-cols="2"] .hp-lane{grid-template-columns:repeat(2,minmax(150px,1fr))}
  .hp-board[data-cols="3"],.hp-board[data-cols="3"] .hp-lane{grid-template-columns:repeat(3,minmax(150px,1fr))}
  .hp-board[data-cols="4"],.hp-board[data-cols="4"] .hp-lane{grid-template-columns:repeat(4,minmax(150px,1fr))}
  .hp-board[data-cols="5"],.hp-board[data-cols="5"] .hp-lane{grid-template-columns:repeat(5,minmax(150px,1fr))}
  .hp-board[data-cols="6"],.hp-board[data-cols="6"] .hp-lane{grid-template-columns:repeat(6,minmax(138px,1fr))}
  [data-stage=new]{--sc:var(--todo)}
  [data-stage=shortlisted],[data-stage=trial],[data-stage=offer]{--sc:var(--prog)}
  [data-stage=hired]{--sc:var(--done)}
  [data-stage=rejected]{--sc:var(--block)}
  .hp-colhead{display:flex;align-items:center;gap:6px;padding:5px 8px;border-bottom:2px solid var(--line);
    font-size:var(--fs-xs);font-weight:bold;text-transform:uppercase;letter-spacing:.05em;color:var(--text)}
  .hp-dot{width:9px;height:9px;border-radius:2px;flex:0 0 9px;background:var(--sc,var(--line))}
  .hp-colcount{margin-left:auto;font-weight:normal;color:var(--muted-2);letter-spacing:0;font-variant-numeric:tabular-nums}
  /* the lane band is a grid on the SAME tracks as the board, so each cell's
     count sits under its stage column — the role total rides in the first cell
     beside the name, a per-column sub-count in every cell (like the task board) */
  .hp-lane{grid-column:1/-1;display:grid;gap:10px;align-items:center;
    background:var(--band);border:1px solid var(--line);
    border-radius:6px;padding:7px 0;cursor:pointer;margin-top:10px}
  @media (hover:hover){ .hp-lane:hover{background:var(--band-h)} }
  .hp-lanecell{display:flex;align-items:center;gap:6px;min-width:0;padding:0 8px}
  .hp-caret{color:var(--muted);font-size:var(--fs-xs);width:12px;flex:0 0 12px}
  .hp-lname{font-weight:bold;font-size:var(--fs-md);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .hp-lname.uns{color:var(--amb-tx-2)}
  .hp-ltot{color:var(--muted-2);font-size:var(--fs-xs);flex:0 0 auto}
  .hp-lcount{margin-left:auto;color:var(--muted-2);font-size:var(--fs-xs);font-variant-numeric:tabular-nums}
  .hp-col{display:flex;flex-direction:column;gap:6px;background:var(--panel-2);border:1px solid var(--grid);
    border-radius:6px;padding:6px;min-height:56px;max-height:440px;overflow-y:auto}
  .hp-col.over{background:var(--rowhover);border-color:var(--line-2);border-style:dashed}
  /* body size set on the card, as .kb-card does — without it the name inherits
     the browser's 16px default and reads a size up from the task board */
  .hp-card{background:var(--panel);border:1px solid var(--line);border-left:4px solid var(--sc,var(--line));
    border-radius:6px;padding:6px 8px;font-size:var(--fs-sm);cursor:grab;box-shadow:0 1px 2px rgba(0,0,0,.06)}
  @media (hover:hover){ .hp-card:hover{background:var(--rowhover)} }
  .hp-card.dragging{opacity:.4;cursor:grabbing}
  .hp-who{display:flex;align-items:center;gap:8px}
  .hp-av{width:28px;height:28px;border-radius:50%;flex:0 0 28px;display:grid;place-items:center;
    font-weight:bold;font-size:var(--fs-xs);background:var(--track);color:var(--text)}
  .hp-av.lg{width:42px;height:42px;font-size:var(--fs-lg)}
  .hp-nm{font-weight:bold;line-height:1.35;overflow-wrap:anywhere}
  @media (hover:hover){ .hp-card:hover .hp-nm{color:var(--red-ink)} }
  .hp-sub{font-size:var(--fs-2xs);color:var(--dim)}
  .hp-subrow{display:flex;align-items:center;gap:8px;margin-top:2px;flex-wrap:wrap}
  /* the two stage moves, in the drawer header beside the × — compact */
  .hp-headmain{flex:1;min-width:0}
  .hp-headacts{display:flex;gap:6px;align-items:center;margin:0 8px}
  .invoice-panel .hp-hbtn{font:inherit;font-size:var(--fs-xs);font-weight:bold;padding:4px 10px;border:1px solid var(--line);
    border-radius:6px;background:var(--panel);color:var(--ink);white-space:nowrap}
  @media (hover:hover){ .invoice-panel .hp-hbtn:hover{border-color:var(--red);color:var(--red-ink)} }
  /* the stage stepper in the drawer — done green, current amber ring, rejected red */
  .hp-stepper{display:flex;flex-direction:column;margin:2px 0}
  /* .invoice-panel button (border + panel fill) would box each step into a card
     and paint over the connector line — scope past it to keep the steps flat */
  .invoice-panel .hp-step{display:flex;align-items:center;gap:10px;padding:5px 0;position:relative;border:0;
    background:transparent;border-radius:0;text-align:left;width:100%;color:var(--text);font-size:var(--fs-sm);
    cursor:pointer;font-family:inherit}
  @media (hover:hover){ .invoice-panel .hp-step:hover{background:transparent} }
  .hp-bub{width:19px;height:19px;border-radius:50%;border:2px solid var(--track);flex:0 0 19px;display:grid;place-items:center;background:var(--panel)}
  .hp-bub svg{width:11px;height:11px}
  .hp-step:not(:last-child)::before{content:"";position:absolute;left:8.5px;top:24px;bottom:-5px;width:2px;background:var(--track)}
  .hp-step.done .hp-bub{background:var(--done);border-color:var(--done);color:#fff}
  .hp-step.done:not(:last-child)::before{background:var(--done)}
  .hp-step.cur .hp-bub{border-color:var(--prog);box-shadow:0 0 0 3px var(--amb-bg)}
  .hp-step.cur .hp-lbl{font-weight:bold;color:var(--ink)}
  .hp-step.rej{cursor:default}
  .hp-step.rej .hp-bub{background:var(--block);border-color:var(--block)}
  .hp-step.rej .hp-lbl{color:var(--red-tx);font-weight:bold}
  @media (hover:hover){ .hp-step:not(.cur):not(.rej):hover .hp-lbl{color:var(--red-ink)} }
  .hp-when{margin-left:auto;font-size:var(--fs-2xs);color:var(--dim);font-variant-numeric:tabular-nums;white-space:nowrap}
  .hp-when b{color:var(--text);font-weight:bold}
  .hp-pname{outline:none;cursor:text;border-radius:4px}
  .hp-pname:empty::before{content:attr(data-ph);color:var(--dim)}
  .hp-rolesel{font:inherit;font-size:var(--fs-sm);padding:4px 7px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--ink)}
  .hp-notes{width:100%;box-sizing:border-box;font:inherit;font-size:var(--fs-sm);color:var(--ink);background:var(--soft);
    border:1px solid var(--line);border-radius:8px;padding:8px 10px;resize:vertical;min-height:52px}
  /* the board is 880px wide and never narrows — its four lanes don't collapse
     into a phone's width the way a table's rows do. The page body can't scroll
     sideways (.view is overflow-x:hidden on a phone, so wide tables scroll
     inside their own .tblwrap instead), so the board carries its own sideways
     scroll here — the .kb-wrap slides under a fixed viewport. An id beats the
     .view class, so this re-opens overflow-x for this one page only. */
  @media (max-width:700px){ #kanbanview{overflow-x:auto;-webkit-overflow-scrolling:touch} }
  .kb-wrap{min-width:880px}
  .kb-grid{display:grid;grid-template-columns:repeat(4,minmax(190px,1fr));gap:10px}
  .kb-head{position:sticky;top:0;z-index:2;background:var(--bg);padding:8px 0 6px}
  .kb-colhead{display:flex;align-items:center;gap:6px;padding:5px 8px;border-bottom:2px solid var(--line);
    font-size:var(--fs-xs);font-weight:bold;text-transform:uppercase;letter-spacing:.05em;color:var(--text)}
  .kb-colhead i{width:9px;height:9px;border-radius:2px;flex:0 0 9px}
  .kb-colcount{margin-left:auto;font-weight:normal;color:var(--muted-2);letter-spacing:0}
  .kb-lane{margin:0 0 20px}
  /* the lane header shares the column grid, so each owner's per-status count
     sits directly above its own column; the name lives in the first cell */
  /* same shaded band as the Budgets/Products group rows */
  .kb-lanename{display:grid;grid-template-columns:repeat(4,minmax(190px,1fr));gap:10px;
    font-weight:bold;font-size:var(--fs-md);padding:7px 0;margin-bottom:8px;background:var(--band);
    border:1px solid var(--line);border-radius:6px;cursor:pointer;user-select:none}
  @media (hover:hover){ .kb-lanename:hover{background:var(--band-h)} }
  .kb-lanecell{display:flex;align-items:center;gap:6px;min-width:0;padding:0 8px}
  .kb-lanecell .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .kb-lanecell .cgrpstat{flex:0 0 auto}
  .kb-caret{display:inline-block;width:12px;color:var(--muted);font-size:var(--fs-xs);flex:0 0 12px}
  .kb-lanecount{margin-left:auto;font-weight:normal;color:var(--muted-2);font-size:var(--fs-xs)}
  .kb-col{background:var(--panel-2);border:1px solid var(--grid);border-radius:6px;padding:6px;min-height:56px;max-height:440px;overflow-y:auto}
  .kb-col.over{background:var(--rowhover);border-color:var(--blu-dot)}
  .kb-add{display:block;width:100%;font:inherit;font-size:var(--fs-sm);text-align:left;padding:5px 6px;
    border:1px dashed var(--line);border-radius:6px;background:transparent;color:var(--muted-3);cursor:pointer}
  @media (hover:hover){ .kb-add:hover{color:var(--red-ink);border-color:var(--red);background:var(--panel)} }
  /* Done and Not doing share the last column: both mean closed, both are painted
     --done, and two near-empty columns were spending a fifth of the board's width
     on the distinction. The column only splits while a card is in flight — two
     dashed halves lie over it, Done on top and Not doing below — so a drop still
     picks between the two statuses without either owning a column of its own.
     The overlay is absolute over the column, not over the scroller inside it, so
     the halves stay put no matter how far the cards under them are scrolled. */
  .kb-colsplit{position:relative;display:flex;flex-direction:column;padding:0;overflow:hidden;min-height:84px}
  .kb-colscroll{flex:1 1 auto;min-height:0;overflow-y:auto;padding:6px}
  .kb-split{display:none;position:absolute;inset:0;flex-direction:column;gap:3px;padding:3px;
    background:var(--panel-2)}
  body.kb-drag .kb-colsplit .kb-split{display:flex}
  .kb-half{flex:1 1 50%;display:flex;align-items:center;justify-content:center;text-align:center;
    border:2px dashed var(--grid);border-radius:5px;background:var(--panel);
    font-size:var(--fs-xs);font-weight:bold;text-transform:uppercase;letter-spacing:.05em;color:var(--muted-2)}
  .kb-half.over{border-style:solid;border-color:var(--blu-dot);background:var(--rowhover);color:var(--text)}
  .kb-card{background:var(--panel);border:1px solid var(--line);border-left:4px solid var(--todo);border-radius:6px;
    padding:6px 8px;margin-bottom:6px;font-size:var(--fs-sm);cursor:grab;box-shadow:0 1px 2px rgba(0,0,0,.06)}
  @media (hover:hover){ .kb-card:hover{background:var(--rowhover)} }
  .kb-card.dragging{opacity:.4;cursor:grabbing}
  /* the merged column holds both closed statuses, and they share a colour — so a
     "Not doing" card says so on its face rather than looking like finished work */
  .kb-ndtag{display:inline-block;vertical-align:middle;margin-left:4px;padding:0 5px;
    border:1px solid var(--line);border-radius:999px;background:var(--panel-2);
    font-size:var(--fs-3xs);font-weight:bold;text-transform:uppercase;letter-spacing:.04em;color:var(--muted-2)}
  .kb-line{font-size:var(--fs-2xs);color:var(--dim);margin-top:2px}
  .kb-line.late{color:var(--block-ink)}
  .kb-cardname{font-weight:bold;line-height:1.35;overflow-wrap:anywhere}
  .kb-cardname .idtag{font-weight:normal}
  /* ---- mobile ---- */
  @media (max-width:700px){
    :root{--lblw:200px}
    /* the full "12 of 37 tasks completed" can't shrink and would squeeze the
       phase/area name out entirely - show a compact (12/37) instead */
    .gstat.sfull{display:none}
    .gstat.sshort{display:inline}
    #sidebar{position:fixed;z-index:80;left:0;top:0;bottom:0;transform:translateX(-100%);transition:transform .18s ease;box-shadow:8px 0 24px rgba(0,0,0,.18)}
    #sidebar.open{transform:translateX(0)}
    #workspace::before{content:"";display:none;position:fixed;z-index:79;inset:0;background:rgba(0,0,0,.28)}
    #workspace.navopen::before{display:block}
    .topbar{padding:8px 10px;gap:8px}
    header.topbar{padding:0 10px}
    /* THE BAND, ON A PHONE. It used to be one sideways-scrolling lane holding a
       page's actions AND its filter pills, so the actions took the visible width
       and the chips — which on most of these pages are the work list — started
       past the right edge with nothing saying they were there. It is two buttons
       now: one opens the sheet the chips have moved into, the other the page's
       actions. Transactions draws its own header inside the view instead,
       because it has a search box to carry as well; see #txmob. */
    /* the band stacks: what you search with over what is narrowed, and the two
       round buttons ride the search row's right end */
    #actionsBar.on{flex-direction:column;align-items:stretch;gap:9px;overflow:visible;
      padding:9px 10px}
    #actionsBar .actionsrow{display:none}
    #actionsBar #filterRow{display:none}
    #actionsBar #mobBar{display:flex;gap:8px;align-items:center}
    #actionsBar #mobActive{display:flex;margin-top:0}
    #actionsBar #mobActive:empty{display:none}
    /* a page with nothing to search by gives the width back to the buttons */
    #actionsBar.nosearch #mobBar{justify-content:flex-end}
    #actionsBar.nosearch #mobSearch{display:none}
    /* ---- search keeps the room it has; folds only when the date needs it ------
       The search bar stays a full field beside a page's controls whenever the row
       has space for it — one tap fewer. The one control wide enough to force it
       out is the date stepper, so on a DATED page (`scollapsed`) search rests as a
       round icon to give the stepper the centre, and expands back over the row
       (`msearch`) when tapped. mobSyncFilterHome sets both, from mobSearchOpen and
       whether the page is dated. */
    #actionsBar.scollapsed #mobSearch{flex:0 0 auto;width:38px;padding:0;justify-content:center;cursor:pointer}
    #actionsBar.scollapsed #mobSearch input{display:none}
    #actionsBar.scollapsed #mobSearch svg{stroke:var(--ink)}
    #actionsBar.msearch #mobSearch{flex:1 1 auto}
    #actionsBar.msearch #mobStep,#actionsBar.msearch #mobFilterBtn,
    #actionsBar.msearch #mobExport,#actionsBar.msearch #mobActsBtn,
    #actionsBar.msearch #mobExtra{display:none!important}
    #mobSearchClose{display:none}
    #actionsBar.msearch #mobSearchClose{display:grid}
    /* a single narrowing rides in the band beside search rather than behind the
       funnel — Categories' Type, Counterparties' Type. Fed by syncMobExtra. */
    #mobExtra{display:none}
    #actionsBar #mobExtra.on{display:inline-flex}
    /* ---- and the pages that have room for what the button stands in for ----
       Budgets is two chips, and the monthly ledger is one month picker. Neither
       has an action or a search box beside it, so folding what it does have
       behind a round button left a band that was one round button, with a
       drawer behind it holding a choice that fits on screen twice over.
       mobSyncFilterHome hangs .chipsinline when that is the shape of the page;
       the chips wrap here rather than scrolling, and are drawn a size up from
       the desktop's — a chip in a toolbar is aimed at with a cursor, one here
       with a thumb.

       The actions stay behind their ⋯ whatever the page: two full-width labels
       in the band read as the page's own content rather than as its toolbar,
       and every page that has actions has either a search box or its chips to
       put beside them. */
    #actionsBar.chipsinline #filterRow{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;
      overflow:visible}
    #actionsBar.chipsinline #filterRow .txfilter{height:auto;padding:7px 12px;border-radius:16px}
    /* the pickers that ride with the chips get the same size up — the monthly
       ledger's month is the whole of that page's row, so on a phone this is the
       control itself in the band rather than a button that opens a drawer to it.
       The chevron's 26px on the right is the band's own, and stays. */
    #actionsBar.chipsinline #filterRow select.stmonth{height:auto;padding:7px 26px 7px 11px;
      border-radius:16px;max-width:100%}
    #actionsBar.chipsinline #filterRow .filtersep{display:none}
    /* no search box and neither round button: an empty flex row is still a row,
       and it was spacing the band out from nothing */
    #actionsBar.nomobbar #mobBar{display:none}
    /* …and once that row is gone, the whole band is an empty stripe under the
       page name — a page with nothing to search, narrow or do (Team, whose one
       action is the floating button now) should carry no band at all. The
       chip-inline pages (Budgets, the monthly ledger) keep theirs: their chips
       live in the band, so it is not empty. */
    #actionsBar.nomobbar:not(.chipsinline){display:none}
    /* moved into the sheet, it is a wrapping set of chips again rather than the
       band's hidden one */
    #sheetSlot #filterRow{display:flex}
    #navToggle{display:block;font-size:var(--fs-2xl)!important;line-height:1;padding:5px 8px}
    /* Page actions scroll within their own row instead of widening the page. */
    .actionsrow{margin-left:0;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;gap:5px;min-width:0}
    .actionsrow::-webkit-scrollbar{display:none}
    .actionsrow button,.actionsrow label{white-space:nowrap;flex:0 0 auto}
    .legend{display:none}
    /* every panel, this one included, clears the home indicator */
    .invoice-panel{padding:16px 14px calc(16px + env(safe-area-inset-bottom))}
    #detail .dhead{margin:-16px -14px 10px;padding:12px 14px 8px}
  }
  /* ===== Restaurant -> Checklists (2026-08-31) — all classes cl- prefixed ===== */
  /* Search, the page action and the status slices ride the shared band above,
     the same as every other page — so the page owns no toolbar of its own. */
  .cl-sp{flex:1 1 auto}
  .cl-msg{font-size:13px;color:var(--muted);padding:26px 4px}
  .cl-msg.err{color:var(--red-tx)}

  /* The runs and templates lists use the shared table shell (.tblwrap/.txtable)
     and the shared phone-card panel (.pgrp/.pcard); only what is particular to
     them is kept — the rows are clickable and their cells stay on one line, and
     the Status column header is a filter trigger (a funnel in the column, not a
     slice in the toolbar) that wears the shell header's own type. */
  #runsview .txtable tbody td,#templatesview .txtable tbody td{white-space:nowrap}
  #runsview .txtable tbody tr,#templatesview .txtable tbody tr{cursor:pointer}
  @media (hover:hover){ #runsview .txtable tbody tr:hover,#templatesview .txtable tbody tr:hover{background:var(--rowhover)} }
  .txtable td.cl-empty{text-align:center;color:var(--muted);padding:26px 14px;cursor:default;white-space:normal}
  .cl-name{font-weight:bold;color:var(--ink-2);max-width:340px;overflow:hidden;text-overflow:ellipsis}
  .cl-sub{font-size:11px;color:var(--muted);margin-top:2px;max-width:340px;overflow:hidden;text-overflow:ellipsis}
  .cl-when{color:var(--text-2);font-size:12px;font-variant-numeric:tabular-nums}
  .cl-subtle{color:var(--muted-3)}
  .cl-num{font-variant-numeric:tabular-nums}
  .cl-owner{display:inline-flex;align-items:center;gap:7px}
  .cl-av{width:22px;height:22px;border-radius:50%;flex:0 0 22px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:bold;color:#fff}
  .cl-prog{display:flex;align-items:center;gap:8px}
  /* a 140px bar on desktop (so the column reads as a real progress meter), that
     can still shrink down to 44px when the table is squeezed rather than
     spilling past the cell into the Status column */
  .cl-ptrack{flex:0 1 140px;width:140px;min-width:44px;height:6px;background:var(--track);border-radius:999px;overflow:hidden}
  .cl-pfill{height:100%;background:var(--done);border-radius:999px}
  .cl-pfill.amb{background:var(--prog)}
  .cl-pnum{flex:0 0 auto;font-size:11px;color:var(--text-2);font-variant-numeric:tabular-nums}
  /* keep the bar+count off the Status column — a little breathing room right */
  .txtable td.cl-progcell{padding-right:28px}

  .cl-st{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:bold;padding:3px 9px;border-radius:999px;white-space:nowrap;border:1px solid transparent}
  .cl-st .cl-dot{width:7px;height:7px;border-radius:50%;flex:0 0 7px}
  .cl-st-sched{background:var(--soft-2);color:var(--text-2);border-color:var(--line)} .cl-st-sched .cl-dot{background:var(--muted-2)}
  .cl-st-prog{background:var(--amb-bg);color:var(--amb-tx);border-color:var(--amb-bd)} .cl-st-prog .cl-dot{background:var(--prog)}
  .cl-st-stale{background:var(--blu-bg);color:var(--blu-tx);border-color:var(--blu-bd)} .cl-st-stale .cl-dot{background:var(--blu-dot)}
  .cl-st-done{background:var(--grn-bg);color:var(--grn-tx);border-color:var(--grn-bd)} .cl-st-done .cl-dot{background:var(--done)}
  .cl-st-partial{background:var(--soft-2);color:var(--grn-tx);border-color:var(--grn-bd);border-style:dashed;opacity:.72} .cl-st-partial .cl-dot{background:var(--grn-bd)}
  .cl-st-exp{background:var(--red-bg);color:var(--red-tx);border-color:var(--red-bd)} .cl-st-exp .cl-dot{background:var(--block)}
  .cl-st-canc{background:var(--red-bg);color:var(--red-tx);border-color:var(--red-bd)} .cl-st-canc .cl-dot{background:var(--block)}
  .cl-st-missed{background:var(--red-bg);color:var(--red-tx);border-color:var(--red-bd)} .cl-st-missed .cl-dot{background:var(--block)}
  .cl-st-active{background:var(--grn-bg);color:var(--grn-tx);border-color:var(--grn-bd)} .cl-st-active .cl-dot{background:var(--done)}
  .cl-st-draft{background:var(--info-bg);color:var(--info-tx);border-color:var(--pill-bd)} .cl-st-draft .cl-dot{background:var(--muted-2)}
  .cl-st-inactive{background:var(--soft-2);color:var(--muted);border-color:var(--line)} .cl-st-inactive .cl-dot{background:var(--group)}
  .cl-startnow{font-size:12px;font-weight:bold;padding:5px 12px;border:1px solid var(--red);background:var(--red);color:#fff;border-radius:7px;cursor:pointer;white-space:nowrap}
  .cl-startnow:hover{background:var(--red-hover)}

  /* mobile cards — the shared .pgrp panel + .pcard rows; only the column grid
     and the stale rail are particular to these lists */
  .cl-cards{display:none}
  .cl-cards .pcard{display:grid;grid-template-columns:40px 1fr auto 12px;align-items:center;gap:11px;color:var(--ink-3)}
  .cl-cards .pcard.warn{box-shadow:inset 3px 0 0 var(--blu-dot)}
  .cl-cav{width:40px;height:40px;border-radius:50%;flex:0 0 40px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:13px;font-weight:bold}
  .cl-cav.glyph{background:var(--soft-2);color:var(--muted)}
  .cl-cav.glyph svg{width:19px;height:19px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
  .cl-cl{min-width:0;display:flex;flex-direction:column;gap:3px}
  .cl-cnm{font-weight:bold;color:var(--ink-2);font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .cl-csub{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .cl-cr{display:flex;flex-direction:column;align-items:flex-end;gap:5px;white-space:nowrap}
  .cl-cr2{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
  .cl-chev{width:12px;height:12px;stroke:var(--muted-3);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}

  /* drawers + modals (appended to body) */
  /* no page dimming behind a drawer — the drawer's own border/shadow separates
     it; the scrim stays only to catch an outside click that closes it */
  .cl-scrim{position:fixed;inset:0;background:transparent;z-index:60;opacity:0;pointer-events:none;transition:opacity .16s}
  .cl-scrim.on{opacity:1;pointer-events:auto}
  .cl-drawer{position:fixed;z-index:61;right:0;top:0;bottom:0;width:min(620px,96vw);background:var(--panel);border-left:1px solid var(--line);box-shadow:var(--shadow-drawer);overflow:auto;scrollbar-gutter:stable;overscroll-behavior:contain;transform:translateX(100%);transition:transform .2s ease}
  .cl-drawer.on{transform:translateX(0)}
  .cl-dhead{position:sticky;top:0;background:var(--panel);border-bottom:1px solid var(--line);padding:16px 20px;display:flex;align-items:flex-start;gap:12px;z-index:2}
  .cl-htxt{flex:1 1 auto;min-width:0}
  .cl-htxt h2{margin:0;font-size:16px;color:var(--ink-strong)}
  .cl-hsub{font-size:12px;color:var(--muted);margin-top:5px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
  .cl-dx{border:1px solid var(--line);background:var(--panel);border-radius:7px;width:32px;height:32px;cursor:pointer;color:var(--text);flex:0 0 32px;display:flex;align-items:center;justify-content:center}
  .cl-dx svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round}
  .cl-dbody{padding:18px 20px 40px}
  .cl-dfoot{position:sticky;bottom:0;background:var(--panel);border-top:1px solid var(--line);padding:12px 20px;display:flex;gap:10px;align-items:center}
  .cl-dbtn{font-size:13px;padding:8px 14px;border:1px solid var(--line);background:var(--panel);border-radius:7px;cursor:pointer;color:var(--ink-3);white-space:nowrap}
  .cl-dbtn:hover{background:var(--hover)}
  .cl-dbtn.primary{background:var(--red);color:#fff;border-color:var(--red)}
  .cl-dbtn.primary:hover{background:var(--red-hover)}
  .cl-dbtn.danger{color:var(--red-tx);border-color:var(--red-bd);background:var(--red-bg)}
  .cl-dbtn:disabled{opacity:.45;cursor:not-allowed}
  .cl-dbtn.primary:disabled:hover{background:var(--red)}

  .cl-runmeta{display:grid;grid-template-columns:auto 1fr;gap:8px 16px;font-size:12px;margin-bottom:16px;padding:12px 14px;background:var(--soft);border:1px solid var(--line);border-radius:9px}
  .cl-runmeta dt{color:var(--muted);font-weight:bold} .cl-runmeta dd{margin:0;color:var(--ink-3)}
  .cl-banner{display:flex;align-items:center;gap:10px;font-size:12px;padding:10px 12px;border-radius:9px;margin-bottom:16px;border:1px solid var(--blu-bd);background:var(--blu-bg);color:var(--blu-tx)}
  .cl-banner svg{width:16px;height:16px;flex:0 0 16px;fill:none;stroke:currentColor;stroke-width:2}

  .cl-step{border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:10px;background:var(--panel)}
  .cl-step.done{background:var(--grn-bg);border-color:var(--grn-bd)}
  .cl-step.invalid{border-color:var(--red-bd);background:var(--red-wash)}
  .cl-srow{display:flex;align-items:flex-start;gap:11px}
  .cl-chk{width:22px;height:22px;flex:0 0 22px;border-radius:6px;border:2px solid var(--line-2);background:var(--panel);cursor:pointer;display:flex;align-items:center;justify-content:center;margin-top:1px}
  .cl-step.done .cl-chk{background:var(--done);border-color:var(--done)}
  .cl-chk svg{width:13px;height:13px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;opacity:0}
  .cl-step.done .cl-chk svg{opacity:1}
  .cl-chk.shake{animation:cl-shake .32s}
  @keyframes cl-shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-4px)}75%{transform:translateX(4px)}}
  .cl-stmid{flex:1 1 auto;min-width:0}
  .cl-stnum{font-size:11px;color:var(--muted);font-weight:bold}
  .cl-sttitle{font-weight:bold;color:var(--ink-2);font-size:13px;margin:1px 0 3px}
  .cl-step.done .cl-sttitle{text-decoration:line-through;text-decoration-color:var(--grn-bd);color:var(--text-2)}
  .cl-stmeta{font-size:10px;color:var(--muted);margin-top:7px}
  .cl-steperr{margin-top:8px;font-size:11.5px;color:var(--red-tx);background:var(--red-bg);border:1px solid var(--red-bd);border-radius:7px;padding:7px 9px;display:flex;align-items:center;gap:7px}
  .cl-steperr[hidden]{display:none}
  .cl-steperr svg{width:14px;height:14px;flex:0 0 14px;stroke:currentColor;fill:none;stroke-width:2}

  .cl-ev{display:flex;flex-direction:column;gap:11px;margin-top:10px}
  .cl-ratewrap{display:flex;align-items:center;gap:11px;flex-wrap:wrap}
  .cl-ratelbl{font-size:11px;font-weight:bold;text-transform:uppercase;letter-spacing:.04em;color:var(--dim);display:inline-flex;align-items:center;gap:5px}
  .cl-rating{display:inline-flex;gap:6px}
  .cl-face{width:34px;height:34px;border-radius:9px;border:1px solid var(--line);background:var(--panel);cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--muted-2);padding:0}
  .cl-face svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
  .cl-face:hover{background:var(--hover)}
  .cl-face.sel[data-r="good"]{background:var(--grn-bg);border-color:var(--grn-bd);color:var(--grn-tx)}
  .cl-face.sel[data-r="ok"]{background:var(--amb-bg);border-color:var(--amb-bd-2);color:var(--amb-tx)}
  .cl-face.sel[data-r="bad"]{background:var(--red-bg);border-color:var(--red-bd);color:var(--red-tx)}
  .cl-notebox{position:relative}
  .cl-notebox textarea{width:100%;min-height:38px;font:inherit;font-size:12.5px;line-height:1.4;padding:8px 32px 8px 11px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink-2);resize:vertical}
  .cl-notebox textarea:focus{outline:none;border-color:var(--red)}
  .cl-pip{display:inline-flex;width:13px;height:13px}
  .cl-pip svg{width:13px;height:13px;fill:none;stroke-linecap:round;stroke-linejoin:round}
  .cl-pip.req svg{stroke:var(--red-ink);stroke-width:2.6}
  .cl-pip.opt svg{stroke:var(--muted-3);stroke-width:1.7}
  .cl-notepip{position:absolute;top:9px;right:10px}
  .cl-photorow{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
  .cl-camchip{display:inline-flex;align-items:center;gap:8px;font:inherit;font-size:12.5px;padding:8px 12px;border:1px solid var(--line-2);background:var(--panel);border-radius:8px;cursor:pointer;color:var(--ink-3)}
  .cl-camchip:hover{background:var(--hover)}
  .cl-camchip svg.cam{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
  .cl-thumbs{display:flex;gap:6px;flex-wrap:wrap}
  .cl-thumbs:empty{display:none}
  .cl-thumb{position:relative;width:52px;height:52px;border-radius:8px;background:var(--soft-2);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;color:var(--muted);overflow:hidden}
  .cl-thumb.has{cursor:pointer}
  @media (hover:hover){ .cl-thumb.has:hover{border-color:var(--pill-bd);color:var(--text-2)} }
  .cl-thumb.warn{border-color:var(--red-bd);color:var(--red-tx)}
  .cl-thumb .cl-timg{width:22px;height:22px}
  .cl-tlive{position:absolute;top:3px;left:3px;font-size:7px;font-weight:bold;letter-spacing:.06em;background:var(--red);color:#fff;border-radius:3px;padding:1px 3px}
  .cl-tlive.warn{background:var(--amb-bar)}
  .cl-tt{position:absolute;bottom:2px;right:4px;font-size:8px;font-weight:bold;color:var(--muted);font-variant-numeric:tabular-nums}
  /* the "uploading…" chip shown while a capture is sent to Drive */
  .cl-thumb.up{width:auto;min-width:52px;padding:0 12px;gap:8px;color:var(--muted);cursor:default}
  .cl-thumb.up .cl-tt{position:static;font-size:11px;font-weight:normal}
  .cl-spin{width:16px;height:16px;flex:0 0 16px;border:2px solid var(--line-2);border-top-color:var(--red);border-radius:50%;animation:cl-spin .7s linear infinite}
  @keyframes cl-spin{to{transform:rotate(360deg)}}

  /* editor */
  .cl-fld{margin-bottom:16px}
  .cl-fld>label{display:block;font-size:11px;font-weight:bold;letter-spacing:.04em;text-transform:uppercase;color:var(--dim);margin-bottom:6px}
  .cl-inp{width:100%;min-width:0;font:inherit;font-size:13px;padding:8px 10px;border:1px solid var(--line);border-radius:7px;background:var(--panel);color:var(--ink-2)}
  .cl-inp.timein{text-align:center}
  .cl-row2{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px}
  .cl-hint{font-size:11px;color:var(--muted);margin-top:5px}
  .cl-edstep{display:flex;align-items:flex-start;gap:10px;border:1px solid var(--line);border-radius:9px;padding:10px 12px;margin-bottom:8px;background:var(--panel)}
  .cl-grip{color:var(--muted-3);font-size:15px;line-height:1;padding-top:6px;cursor:grab}
  .cl-edmid{flex:1 1 auto;min-width:0}
  .cl-edtitle{width:100%;font:inherit;font-size:13px;font-weight:bold;padding:5px 7px;border:1px solid transparent;border-radius:6px;background:var(--soft);color:var(--ink-2)}
  .cl-edtitle:focus{outline:none;border-color:var(--pill-bd);background:var(--panel)}
  .cl-evpair{display:flex;flex-direction:column;gap:9px;margin-top:9px}
  .cl-evone{display:flex;align-items:center;gap:12px}
  /* fixed, non-shrinking label slot so TEXT/PHOTO never gets clipped or run over
     by the segmented control next to it */
  /* NB: not class "lbl" — a global .lbl (the roadmap's sticky row label) sets
     position:sticky, a big width and a border-right, which overlapped the
     Optional|Required control. This is its own class so it inherits none of it. */
  .cl-evone .cl-evlbl{flex:0 0 auto;min-width:44px;font-size:10px;color:var(--muted);font-weight:bold;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap}
  .cl-evsel{flex:0 0 auto;display:inline-flex;border:1px solid var(--line);border-radius:7px;overflow:hidden}
  /* schedule: a row of day toggles + the time it becomes due */
  .cl-days{display:flex;flex-wrap:wrap;gap:6px}
  .cl-day{font:inherit;font-size:12px;font-weight:bold;min-width:44px;padding:7px 0;text-align:center;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--text-2);cursor:pointer}
  .cl-day:hover{background:var(--hover)}
  .cl-day.on{background:var(--grn-bg);border-color:var(--grn-bd);color:var(--grn-tx)}
  .cl-timefld{display:inline-flex;align-items:center;gap:8px;font-size:12px;color:var(--muted)}
  .cl-timefld .timein{max-width:110px}
  .cl-evsel button{font-size:11px;padding:3px 8px;border:0;border-right:1px solid var(--line);background:var(--panel);color:var(--text-2);cursor:pointer}
  .cl-evsel button:last-child{border-right:0}
  .cl-evsel button.on{background:var(--grn-bg);color:var(--grn-tx);font-weight:bold}
  .cl-edrm{border:0;background:transparent;color:var(--muted-3);cursor:pointer;font-size:16px;padding:4px 6px}
  .cl-edrm:hover{color:var(--red-tx)}
  .cl-addstep{font-size:12px;padding:8px 12px;border:1px dashed var(--dashline);background:transparent;border-radius:8px;cursor:pointer;color:var(--text-2);width:100%;text-align:center}
  .cl-addstep:hover{background:var(--soft-2)}

  /* camera + confirm */
  .cl-camwrap{position:fixed;inset:0;z-index:90;background:rgba(0,0,0,.72);display:flex;align-items:center;justify-content:center;padding:18px}
  /* an explicit display beats the UA [hidden] rule, so restate it — else the
     camera/confirm modals show the moment they are appended to the body */
  .cl-camwrap[hidden],.cl-confirm[hidden],.cl-lightbox[hidden]{display:none}
  .cl-thumbimg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:7px}
  .cl-lightbox{position:fixed;inset:0;z-index:95;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;padding:24px}
  .cl-lightbox img{max-width:100%;max-height:100%;object-fit:contain;border-radius:10px}
  .cl-lbx{position:absolute;top:16px;right:16px;width:40px;height:40px;border-radius:9px;border:1px solid #3a4250;background:rgba(0,0,0,.45);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center}
  .cl-lbx svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round}
  .cl-cambox{width:min(420px,100%);background:#0c0e12;border:1px solid #2a2f38;border-radius:16px;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.6)}
  .cl-camview{position:relative;aspect-ratio:3/4;max-height:60vh;background:radial-gradient(120% 90% at 50% 30%,#20242c,#0c0e12);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;overflow:hidden}
  .cl-camview video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
  .cl-camlive{position:absolute;top:12px;left:12px;z-index:2;display:flex;align-items:center;gap:6px;font-size:11px;font-weight:bold;letter-spacing:.06em;color:#fff}
  .cl-camdot{width:8px;height:8px;border-radius:50%;background:var(--red);animation:cl-blink 1.1s infinite}
  @keyframes cl-blink{50%{opacity:.25}}
  .cl-camicon{width:56px;height:56px;stroke:#5a636f;fill:none;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round;z-index:1}
  .cl-camhint{font-size:11.5px;color:#8b929e;padding:0 24px;text-align:center;z-index:1}
  .cl-camctrls{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background:#0c0e12}
  .cl-shutter{width:60px;height:60px;border-radius:50%;background:#fff;border:4px solid #3a4250;cursor:pointer}
  .cl-shutter:active{transform:scale(.93)}
  .cl-camx{width:64px;font:inherit;font-size:13px;color:#c4cad4;background:transparent;border:0;cursor:pointer;text-align:left}
  .cl-confirm{position:fixed;inset:0;z-index:92;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;padding:20px}
  .cl-cfbox{width:min(420px,100%);background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px;box-shadow:var(--shadow-modal)}
  .cl-cfbox h3{margin:0 0 10px;font-size:15px;color:var(--ink-strong)}
  .cl-cfbox p{margin:0 0 10px;font-size:13px;line-height:1.5;color:var(--text)}
  .cl-cfrow{display:flex;justify-content:flex-end;gap:10px;margin-top:16px}

  .cl-sech{font-size:11px;font-weight:bold;text-transform:uppercase;letter-spacing:.04em;color:var(--dim);margin:2px 0 9px}
  .cl-sech.mt{margin-top:20px}
  .cl-startpick{border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:9px;background:var(--panel);cursor:pointer;display:flex;align-items:center;gap:10px}
  .cl-startpick:hover{background:var(--hover)}
  .cl-startpick .m{flex:1 1 auto;min-width:0}
  .cl-startpick .t{font-weight:bold;color:var(--ink-2);font-size:13px}
  .cl-startpick .s{font-size:11px;color:var(--muted);margin-top:2px}
  .cl-cfnote{font-size:12px;color:var(--muted)}
  @media (max-width:760px){
    #runsview .tblwrap,#templatesview .tblwrap{display:none}
    .cl-cards{display:block}
    .cl-drawer{width:100vw}
    .cl-row2{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
    /* iOS zooms the page in on focus of any control under 16px; hold every
       drawer field at 16px on a phone so selecting the note doesn't zoom */
    .cl-notebox textarea,.cl-inp,.cl-inp.timein,.cl-edtitle{font-size:16px}
    /* the run/editor footer holds up to four buttons — let them wrap into rows
       instead of overrunning off the right edge, and clear the home indicator */
    .cl-dfoot{flex-wrap:wrap;gap:8px;padding:10px 14px calc(10px + env(safe-area-inset-bottom))}
    .cl-dfoot .cl-sp{display:none}
    .cl-dfoot .cl-dbtn{flex:1 1 auto;text-align:center;white-space:nowrap}
  }

  /* ===== WHAT USED TO BE WRITTEN INLINE ======================================
     Until 2026-09-07 the script carried ~270 style="…" attributes and the
     markup 18 more: the same "color:var(--dim)" typed twenty-three times, the
     same error note sixteen, the same dialog button row nine. Each one was a
     styling decision made somewhere no stylesheet reader would look, and none
     of them could be changed in one place. They are classes now, and this is
     the one place they are defined.

     Two kinds live here:

     · u-* UTILITIES: one job each, named for the job (u-dim, u-nowrap, u-full).
       They are declared !important on purpose. An inline style beats every
       rule in the sheet, and these replaced inline styles one for one — so
       the day they moved, nothing on the page changed. Without it a page rule
       with an id in it (#detail .crow b) would quietly win over a utility on
       the same element, and the loss would show only on that page.
     · COMPONENTS that had been spelled out inline — the dialog's button row,
       the audit log's payload cell, the sales-recon amber note, the proposal
       banner. Ordinary rules, named for what they are.

     What STAYS inline, by rule: a value the script computes (a bar's width, a
     marker's left, a nesting depth in --d, an avatar's colour) and
     display:none as a state the script toggles. Neither is a styling decision;
     both are data. brand-sheet-test.mjs holds the line — a new static style=
     fails it, and so does a hex colour in the script.
     ========================================================================= */
  /* ---- ink ---- */
  .u-dim{color:var(--dim)!important}
  .u-muted{color:var(--muted)!important}
  .u-muted2{color:var(--muted-2)!important}
  .u-muted4{color:var(--muted-4)!important}
  .u-text{color:var(--text)!important}
  .u-text2{color:var(--text-2)!important}
  .u-text3{color:var(--text-3)!important}
  .u-red{color:var(--red-tx-2)!important}      /* the error red, on a note */
  .u-red3{color:var(--red-tx-3)!important}     /* the due / overdue red, on a figure */
  .u-redtx{color:var(--red-tx)!important}
  .u-redink{color:var(--red-ink)!important}    /* brand red as text */
  .u-amb{color:var(--amb-tx-2)!important}      /* the warning amber, on a word */
  .u-ambtx{color:var(--amb-tx)!important}
  .u-grn{color:var(--grn-tx)!important}
  .u-grn3{color:var(--grn-tx-3)!important}
  /* a "go somewhere" link inside prose: brand red, no underline, a little weight */
  /* an inline "follow →" text link (View today's orders →, Sales recon): a link,
     so --link + hover-underline, not the red it wore before (see brand-guide §06) */
  .u-golink{color:var(--link)!important;text-decoration:none!important;font-weight:600!important}
  @media (hover:hover){ .u-golink:hover{text-decoration:underline!important} }
  /* ---- type ---- */
  .u-2xs{font-size:var(--fs-2xs)!important}
  .u-xs{font-size:var(--fs-xs)!important}
  .u-sm{font-size:var(--fs-sm)!important}
  .u-normal{font-weight:normal!important}
  .u-semi{font-weight:600!important}
  .u-bold{font-weight:700!important}
  .u-italic{font-style:italic!important}
  .u-roman{font-style:normal!important}
  .u-num{font-variant-numeric:tabular-nums!important}
  .u-nowrap{white-space:nowrap!important}
  .u-breakall{word-break:break-all!important}
  .u-faded{opacity:.72!important}
  /* the plain note a page shows while it loads, when a list is empty, or when
     nothing matches: body size, body colour, nothing else */
  .u-note{font-size:var(--fs-sm)!important;color:var(--text)!important}
  /* …and the same note when the read failed: the error red */
  .u-err{font-size:var(--fs-sm)!important;color:var(--red-tx-2)!important}
  /* ---- layout ---- */
  .u-full{flex:1 1 100%!important}             /* a hint that takes the whole row under its field */
  .u-left{text-align:left!important}
  .u-right{text-align:right!important}
  .u-selfcenter{align-self:center!important}
  .u-mlauto{margin-left:auto!important}
  .u-block{display:block!important}
  .u-static{cursor:default!important}          /* a card that is not a button */
  .u-pointer{cursor:pointer!important}
  .u-m0{margin:0!important}
  .u-mt6{margin-top:6px!important}
  .u-mt8{margin-top:8px!important}
  .u-mt10{margin-top:10px!important}
  .u-mt14{margin-top:14px!important}
  .u-mt24{margin-top:24px!important}
  .u-mb3{margin-bottom:3px!important}
  .u-mb8{margin-bottom:8px!important}
  .u-mb10{margin-bottom:10px!important}
  .u-mb16{margin-bottom:16px!important}
  .u-ml4{margin-left:4px!important}
  .u-ml8{margin-left:8px!important}
  .u-up8{margin:-2px 0 8px!important}          /* a hint pulled up under the row above it */
  .u-up10{margin:-2px 0 10px!important}
  .u-pad8{padding:8px!important}
  .u-pad14{padding:14px!important}
  .u-pb10{padding:0 0 10px!important}
  .u-pb12{padding:0 0 12px!important}
  .u-minw1280{min-width:1280px!important}      /* the Documents table, which has that many columns */
  .u-w11{width:11%} .u-w12{width:12%} .u-w15{width:15%} .u-w21{width:21%} .u-w29{width:29%}  /* <col> widths, Loans */
  /* a row of controls that wraps: the sales range header, the Syrve mapping row */
  .u-row{display:flex;gap:7px;align-items:center;flex-wrap:wrap}
  .u-row-wide{gap:12px;margin:2px 0 7px}
  /* a heading that folds the block under it */
  .u-foldhead{cursor:pointer;user-select:none}
  .u-caret{display:inline-block;width:14px;font-size:var(--fs-sm);color:var(--muted)}
  /* the small in-line button beside a heading: "+ Document", "show paid" */
  .u-btn-xs{font-size:var(--fs-xs)!important;padding:3px 8px!important;vertical-align:middle!important}
  /* ---- SVG fills and the status swatches (legends, column heads) ---- */
  .u-fill-dim{fill:var(--dim)}
  .u-fill-red{fill:var(--red-ink)}
  .u-fill-faint{fill-opacity:.22}
  .u-sw-todo{background:var(--todo)!important}
  .u-sw-prog{background:var(--prog)!important}
  .u-sw-block{background:var(--block)!important}
  .u-sw-done{background:var(--done)!important}
  .u-sw-muted{background:var(--muted-4)!important}
  .u-ms1{color:var(--ms-1)}                     /* the roadmap legend's milestone swatch */
  /* an element the script measures and never shows */
  .u-measure{position:absolute;visibility:hidden;white-space:pre}

  /* ---- components that had been spelled out inline ---- */
  /* a dialog's button row: right-aligned, a little air above */
  .dlg-actions{margin-top:12px;display:flex;gap:8px;justify-content:flex-end}
  /* the Edit-categories modal: one row per section — reorder arrows, a colour
     picker and its clear, then the editable name. Scrolls inside the box. */
  #catedit .box{max-width:460px}
  .catedit{display:flex;flex-direction:column;gap:6px;overflow-y:auto;margin-top:10px}
  .catrow{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
  .catrow .prodord{flex:0 0 auto}
  .catrow .catcol{flex:0 0 auto;width:26px;height:26px;padding:0;border:1px solid var(--line);
    border-radius:6px;background:var(--sw,var(--panel));cursor:pointer}
  .catrow .catcol.noclr{background:var(--panel);opacity:.5}
  /* the palette grid that drops below a row when its swatch is tapped */
  .catrow .catswatches{flex:0 0 100%;display:grid;grid-template-columns:repeat(8,1fr);gap:6px;
    padding:8px;margin-top:2px;border:1px solid var(--line);border-radius:8px;background:var(--panel)}
  .catrow .catsw{width:100%;aspect-ratio:1.4;padding:0;border:1px solid var(--line);border-radius:5px;
    background:var(--sw);cursor:pointer}
  .catrow .catsw.on{outline:2px solid var(--ink);outline-offset:1px}
  .catrow .catcolclr{flex:0 0 auto;width:20px;height:20px;line-height:1;padding:0;font-size:var(--fs-xs);
    border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--dim);cursor:pointer}
  .catrow .catcolclr.off{opacity:.25;cursor:default;border-color:transparent}
  .catrow .catname{flex:1 1 auto;min-width:0;font:inherit;font-size:var(--fs-sm);padding:5px 8px;
    border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--ink)}
  /* a drop-down menu with nothing to list */
  .umenu-empty{padding:7px 9px;font-size:var(--fs-sm);color:var(--dim)}
  /* the sales-recon amber note: split bills awaiting the payments report, and
     the deeper amber for sales with no payment detail at all */
  .sr-note{font-size:var(--fs-sm);margin:8px 0 4px;padding:8px 10px;border-radius:6px;
    color:var(--amb-tx);background:var(--amb-bg);border:1px solid var(--amb-bd)}
  .sr-note.deep{margin:8px 0 10px;color:var(--amb-tx-3);background:var(--amb-bg-2)}
  /* the summary's amber note (unresolved payments) wears the same tint */
  .tmnote.amb{margin:2px 0 14px;border-color:var(--amb-bd);background:var(--amb-bg);color:var(--amb-tx)}
  /* the audit log: the payload cell clipped to one line, and the row that
     opens under it with the whole record */
  .audit-payload{font-family:ui-monospace,monospace;font-size:var(--fs-xs);color:var(--text);
    max-width:420px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .audit-full{margin:0;padding:8px;background:var(--soft-2);border-radius:6px;font-size:var(--fs-xs);
    white-space:pre-wrap;word-break:break-all}
  /* the checklist camera: a spacer the width of the close button, so the
     shutter sits centred between them */
  .cl-camspacer{width:64px}
  /* the "proposed by a coworker" banner, pinned over the page until reviewed */
  .proposal-banner{position:fixed;top:10px;left:50%;transform:translateX(-50%);z-index:9999;
    background:var(--proposal);color:#fff;font:600 var(--fs-md)/1.3 system-ui,-apple-system,sans-serif;
    padding:7px 14px;border-radius:999px;box-shadow:0 2px 10px rgba(0,0,0,.25);cursor:pointer;user-select:none}
  /* ---- the roadmap's dependency lines ---- */
  /* a link is dim; one that is violated (the blocked task starts first) is red.
     The preview, while a link is being drawn, is red and dashed. */
  .link{stroke:var(--dim)}
  .link.violated{stroke:var(--red-ink)}
  .previewline{stroke:var(--red-ink)}
  /* …and the body while a link or a reconcile pairing is being dragged */
  body.linking{cursor:crosshair}
  /* ---- status selects ---- */
  /* The task panel's status and the month-end close's step status are selects
     painted the status colour, white text on the fills and ink on the pale
     "to do". data-sc carries the token's name (statusSlug). !important for the
     same reason as the utilities: the page rules that dress selects by id
     (#closeview select.statuspill) would otherwise win. */
  select[data-sc]{color:#fff!important}
  select[data-sc="todo"]{background:var(--todo)!important;color:var(--ink-3)!important}
  select[data-sc="done"]{background:var(--done)!important}
  select[data-sc="prog"]{background:var(--prog)!important}
  select[data-sc="block"]{background:var(--block)!important}

  /* ---- Recipes & Ingredients (2026-09-07) --------------------------------
     Two pages off one read; the tables reuse .txtable, so this styles only the
     cost-basis strip above them, the status badges, the small cell inputs, and
     the bill-of-materials + comparison inside the recipe panel. Colours are all
     tokens, so light and dark and any tenant re-tint follow for free. */
  .recbasis{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 12px}
  .recasof{font-size:var(--fs-sm);color:var(--text);font-weight:bold}
  .recbasis .sub{font-size:var(--fs-xs);color:var(--muted)}
  .recbasis-btn{font:inherit;font-size:var(--fs-sm);padding:5px 10px;border:1px solid var(--line);
    border-radius:6px;background:var(--panel);color:var(--ink-3);cursor:pointer}
  @media (hover:hover){ .recbasis-btn:hover{border-color:var(--red-bd);color:var(--red-ink)} }
  .recpill{display:inline-block;font-size:var(--fs-2xs);padding:1px 8px;border-radius:999px;
    border:1px solid var(--line);background:var(--pill-bg);color:var(--text-2)}
  .recpill.done{background:var(--grn-bg);border-color:var(--grn-bd);color:var(--done-ink)}
  .recpill.part{background:var(--amb-bg);border-color:var(--amb-bd);color:var(--amb-tx,var(--ink-3))}
  .recpill.no{background:var(--panel-2);border-color:var(--line);color:var(--muted-2)}
  /* a unit that can't convert is an ERROR, not just an unpriced input — red */
  .recpill.err{background:var(--red-bg);border-color:var(--red-bd);color:var(--red-tx)}
  /* the kitchen review stage — draft / in review / approved — its own pill so it
     reads apart from the cost pill next to it. Grey while a draft, blue in review
     (a hand-off, not a fault), green once approved. */
  .recstage{display:inline-block;font-size:var(--fs-2xs);font-weight:bold;padding:1px 8px;border-radius:10px;
    border:1px solid var(--line);background:var(--pill-bg);color:var(--text-2)}
  .recstage.draft{background:var(--panel-2);border-color:var(--line);color:var(--muted-2)}
  .recstage.in_review{background:var(--blue-bg,var(--panel-2));border-color:var(--pill-bd);color:var(--ink-blue)}
  .recstage.approved{background:var(--grn-bg);border-color:var(--grn-bd);color:var(--done-ink)}
  /* recipe names read as text (no default-button box), the whole row reacts on
     hover like every other table, and a name that IS a linked product is a pill */
  #recipesview button.prodname{font:inherit;font-weight:normal;text-align:left;color:var(--ink-strong);background:none;border:0;padding:0;cursor:pointer}
  @media (hover:hover){ #recipesview button.prodname:hover{color:var(--red-ink)} }
  #recipesview .txtable tr[data-recref]{cursor:pointer}
  @media (hover:hover){ #recipesview .txtable tr[data-recref]:hover td{background:var(--rowhover)} }
  /* the Status (kitchen stage) picker in the Recipes table — the same inline
     .pvat select the Products status column uses, coloured three ways to match
     the stage pill: grey draft, blue in review, green approved */
  #recipesview select.pvat{font:inherit;font-size:var(--fs-xs);max-width:120px;padding:2px 6px;
    border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--ink);cursor:pointer}
  #recipesview select.pvat:focus{border-color:var(--red);outline:none}
  /* unscoped so the same coloured picker reads in the recipe PANEL too, where it
     lives in #panelhost, not #recipesview */
  select.recstagesel.draft{color:var(--muted-2);background:var(--panel-2);border-color:var(--line)}
  select.recstagesel.in_review{color:var(--ink-blue);background:var(--blue-bg,var(--panel-2));border-color:var(--pill-bd)}
  select.recstagesel.approved{color:var(--grn-tx);background:var(--grn-bg);border-color:var(--grn-bd)}
  /* ---- the redesigned recipe panel ------------------------------------------
     A linked recipe IS its product: the identity row shows the product as a pill
     with a pen, no name field. The economics are their own panel rows. Method and
     Notes run the full width. The bill of materials gains a "＋ Add input" button
     in place of an always-open add row. */
  .invoice-panel .recprodpill{font:inherit;font-size:var(--fs-sm);color:var(--ink-blue);
    background:var(--pill-bg);border:1px solid var(--pill-bd);border-radius:999px;padding:2px 12px;white-space:nowrap}
  .invoice-panel .reclinkbtn{font:inherit;font-size:var(--fs-sm);color:var(--ink-blue);background:none;border:0;padding:0;cursor:pointer}
  @media (hover:hover){ .invoice-panel .reclinkbtn:hover{color:var(--red-ink);text-decoration:underline} }
  .invoice-panel .recsync{font-weight:bold;font-size:var(--fs-xs)}
  .invoice-panel .recsync.ok{color:var(--grn-tx)}
  .invoice-panel .recsync.warn{color:var(--amb-tx)}
  /* every bill-of-materials input is the app's standard pill (pillHtml) that
     opens its own panel — an ingredient's or a sub-recipe's — with a small
     caption under it saying which. The same pill lists the recipes an ingredient
     is used in, on its panel. */
  .bominput{display:inline-flex;align-items:center;gap:8px}
  /* the input name is the primary content of its column, so it reads at the BOM's
     body size (12px) like the Qty/Cost cells — not the 10px it wears as a bare
     .mpill in a table. The ingredient/recipe caption sits beside it (.bominput is
     a row, there is room) and stays level-2 10px.
     (§08 panel type hierarchy — the panel-row rule doesn't reach a table cell.) */
  .bom .bominput .mpill{font-size:var(--fs-sm)}
  .bomcap{color:var(--muted-2);font-size:var(--fs-2xs)}
  /* the add-a-line button: a full-width dashed row that reads as "one more line" */
  .invoice-panel .recaddbtn{display:flex;align-items:center;gap:8px;width:100%;margin-top:8px;
    padding:8px 10px;border:1px dashed var(--line-2);border-radius:8px;background:transparent;
    color:var(--ink-blue);font:inherit;font-size:var(--fs-sm);cursor:pointer}
  .invoice-panel .recaddbtn svg{width:15px;height:15px}
  @media (hover:hover){ .invoice-panel .recaddbtn:hover{border-color:var(--red);color:var(--red-ink)} }
  /* Method and Notes fill the panel — they were rendering at a textarea's default
     column width because nothing sized them outside #prodview */
  .invoice-panel textarea.recdesc{width:100%;box-sizing:border-box;font:inherit;font-size:var(--fs-sm);
    padding:6px 8px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink);resize:vertical}
  .invoice-panel textarea.recdesc:focus{border-color:var(--red);outline:none}
  /* the ingredient Notes reuses that textarea but is one field in a column of
     them, not the panel's whole subject like the recipe's Method/Notes — so it
     keeps the left start and grows TALLER (resize + content-sized rows) without
     stretching to the full field width. Capped, not 100%. */
  .invoice-panel textarea.recdesc[data-ingf="notes"]{max-width:440px}
  #recipesview .recnamepill{font:inherit;font-size:var(--fs-xs);font-weight:normal;color:var(--ink-blue);background:var(--pill-bg);border:1px solid var(--pill-bd);border-radius:999px;padding:2px 10px;cursor:pointer}
  @media (hover:hover){ #recipesview .recnamepill:hover{border-color:var(--red);color:var(--red-ink)} }
  #recipesview .rectype{font-size:var(--fs-xs);color:var(--text-2)}
  td .unit{color:var(--muted);font-size:var(--fs-xs)}
  .ingdel{border:0;background:transparent;color:var(--muted-3);font-size:var(--fs-sm);cursor:pointer;
    margin-left:8px;padding:0 2px;line-height:1}
  @media (hover:hover){ .ingdel:hover{color:var(--red-ink)} }
  .ingname{font-weight:normal}
  /* the name reads as text and is the way into the ingredient's panel — no
     longer an editor; the whole row opens the panel, the name is just the handle */
  #ingredientsview .ingnamebtn{font:inherit;font-weight:normal;text-align:left;color:var(--ink-strong);
    background:none;border:0;padding:0;cursor:pointer}
  @media (hover:hover){ #ingredientsview .ingnamebtn:hover{color:var(--red-ink)} }
  #ingredientsview .txtable tr[data-iref]{cursor:pointer}
  @media (hover:hover){ #ingredientsview .txtable tr[data-iref]:hover td{background:var(--rowhover)} }
  /* stretch the table to the full page width and let the Note column (the last,
     and the only one that isn't content-sized) soak up the slack, so notes get
     room to read instead of being pinned at their minimum */
  #ingredientsview .tblwrap{width:100%}
  #ingredientsview .txtable{width:100%}
  #ingredientsview .txtable th:last-child,#ingredientsview .txtable td:last-child{width:100%}
  /* free-text note (e.g. "AI-assisted estimate, Sept 8"); reads as text, its
     border appearing only on hover/focus so the column is not a wall of boxes */
  #ingredientsview input.ingnote{text-align:left;border:1px solid transparent;background:transparent;color:var(--text-2);padding:3px 6px;border-radius:5px;width:100%;min-width:170px}
  @media (hover:hover){ #ingredientsview input.ingnote:hover{border-color:var(--line);background:var(--panel)} }
  #ingredientsview input.ingnote:focus{border-color:var(--red);background:var(--panel);color:var(--ink);outline:none}
  /* Paid and For qty read right, lining up with the derived cost beside them,
     and are a fixed narrow width — a money field, not a cell-wide box */
  #ingredientsview td.num input.pmoney{text-align:right;font-variant-numeric:tabular-nums;width:108px;max-width:108px}
  /* the For qty box right-aligns to the column edge (under "FOR QTY"), and its
     unit hangs just outside to the right — absolute, so a wide unit ("unit")
     never shifts the box's edge from one row to the next */
  #ingredientsview .ingqty{position:relative;display:inline-block}
  #ingredientsview .ingqty .unit{position:absolute;left:100%;top:50%;transform:translateY(-50%);padding-left:6px;white-space:nowrap}
  /* room between the For qty unit that hangs into this gap and the Unit cost column */
  #ingredientsview .ingcost{padding-left:26px}
  /* the "Used in" column header sorts the table by usage on click */
  .txtable th.ingsort{cursor:pointer;user-select:none}
  @media (hover:hover){ .txtable th.ingsort:hover{color:var(--ink-3)} }
  /* the bill of materials inside the recipe panel */
  .bom{border-collapse:collapse;width:100%;font-size:var(--fs-sm);margin:2px 0 6px}
  .bom th{font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--dim);
    font-weight:600;text-align:left;padding:4px 6px;border-bottom:1px solid var(--line)}
  .bom td{padding:5px 6px;border-bottom:1px solid var(--grid);vertical-align:middle}
  .bom td.num,.bom th.num{text-align:right}
  .bom td.num{font-variant-numeric:tabular-nums}
  .bom .qty{width:56px}
  .bom .tag{display:inline-block;font-size:var(--fs-2xs);padding:1px 6px;border-radius:9px;
    background:var(--pill-bg);border:1px solid var(--pill-bd);color:var(--text-2);margin-left:4px}
  .bom tr.recadd td{border-bottom:0;padding-top:8px}
  input.qty{font:inherit;font-size:var(--fs-sm);padding:3px 5px;border:1px solid var(--line);
    border-radius:4px;background:var(--panel);color:var(--ink);text-align:right;width:64px}
  .subref{border:0;background:transparent;color:var(--link);font:inherit;font-size:var(--fs-sm);
    cursor:pointer;padding:0;text-align:left}
  .subref:hover{text-decoration:underline}
  /* the computed-vs-stored comparison under the linked-product row */
  .reccompare{display:flex;gap:16px;margin-top:8px;padding:10px 12px;border:1px solid var(--line);
    border-radius:8px;background:var(--soft)}
  .reccompare .k{font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--dim)}
  .reccompare .v{font-size:var(--fs-lg);font-weight:bold;color:var(--ink-2);font-variant-numeric:tabular-nums}
  .reccompare .v.grn{color:var(--done-ink)}
  /* ---- Recipes & Ingredients on a phone --------------------------------------
     The wide tables — a recipe's cost and stage, an ingredient's price, unit and
     usage — do not fit a phone, so below the Products breakpoint each page
     becomes the app's shared card list, exactly as the Products menu and the
     Transactions day-list are: a .cardcat heading over a .pgrp panel for each
     category. A Recipe card opens the recipe panel, the way its table row does;
     an Ingredient card keeps its price inputs, because an ingredient is edited in
     place with no panel behind it — the inputs carry the same data-if + data-iref
     the table cells do, so the page's own change handler saves a card edit
     without a line of new wiring. */
  /* the category heading over a group of cards — label left, count right, the
     day-heading look the ledger uses, as a flex so any card shape lines up under
     it. Shared by the Products, Recipes and Ingredients phone lists. */
  .cardcat{display:flex;justify-content:space-between;align-items:baseline;gap:10px;
    padding:15px 13px 6px;font-size:var(--fs-sm)}
  .cardcat:first-child{padding-top:2px}
  .cardcat-l{font-weight:bold;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .cardcat-n{font-size:var(--fs-xs);color:var(--dim);white-space:nowrap;text-align:right}
  .reccards,.ingcards{display:none}
  @media (max-width:760px){
    #recipesview .tblwrap,#ingredientsview .tblwrap{display:none}
    #recipesview .reccards,#ingredientsview .ingcards{display:block}
    /* a recipe as a card: its name (or the linked product's) over a line of marks
       — the kitchen stage, the cost state and whether it is a prep — with the
       per-serving cost on the right, where the table kept it */
    #recipesview .reccard{position:relative;padding:10px 12px;font-size:var(--fs-sm);
      border-top:1px solid var(--grid);cursor:pointer}
    #recipesview .reccard:first-child{border-top:0}
    #recipesview .reccard:focus-visible{outline:2px solid var(--red);outline-offset:-2px}
    #recipesview .reccard .r{display:flex;justify-content:space-between;gap:10px;align-items:baseline;margin-top:4px}
    #recipesview .reccard .r:first-child{margin-top:0}
    #recipesview .reccard .marks{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
    #recipesview .reccard .reccost{font-weight:bold;color:var(--ink-2);font-variant-numeric:tabular-nums;white-space:nowrap}
    #recipesview .reccard .reccost .nocost{font-weight:normal;color:var(--dim)}
    /* an ingredient is a one-line row: name on the left, its unit cost + a dot
       for how the price is backed + a caret on the right; the whole card taps to
       open the ingredient panel, where it is edited */
    #ingredientsview .ingcard{padding:11px 12px;font-size:var(--fs-sm);border-top:1px solid var(--grid);cursor:pointer}
    #ingredientsview .ingcard:first-child{border-top:0}
    #ingredientsview .ingcard:focus-visible{outline:2px solid var(--red);outline-offset:-2px}
    #ingredientsview .inghead{display:flex;align-items:center;gap:10px}
    #ingredientsview .ingnm{flex:1 1 auto;min-width:0;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    #ingredientsview .ingsum{flex:0 0 auto;display:flex;align-items:center;gap:8px;font-size:var(--fs-sm);
      font-variant-numeric:tabular-nums;color:var(--text-2)}
    #ingredientsview .ingunit{white-space:nowrap}
    #ingredientsview .ingnocost{color:var(--amb-tx)}
    #ingredientsview .ingdot{width:7px;height:7px;border-radius:50%;flex:0 0 auto}
    #ingredientsview .ingdot.has{background:var(--done-ink)}      /* a receipt backs the price */
    #ingredientsview .ingdot.nolink{background:var(--muted-3)}    /* priced by hand, no receipt */
    #ingredientsview .ingdot.nocost{background:var(--amb-bar)}    /* still waiting on a price */
    #ingredientsview .ingcaret{color:var(--muted-2)}
  }
