/* GENERATED — do not edit by hand. Source: invoiceTest/src/styles.css + styles-owner.css,
   scoped to body.vt by scripts/build-vt-css.mjs (owner 2026-09-05: transplanted tenant screens
   identical to invoiceTest). Edit the invoiceTest sheets, then re-run the script. */

/* ── from src/styles.css ── */
body.vt{
  color-scheme: dark;
  --bg:#0c2429; --surface:#15414a; --surface-2:#123840; --panel:#0e2e33;
  --ink:#eaf3f2; --muted:#8fb1b0; --faint:#5f8b8a;
  --line:#2e575e; --line-soft:#22434b;
  --brand:#2ae0e0; --brand-ink:#08252b;
  /* SEMANTIC COLOUR LADDER (owner 2026-07-29: "green is good, yellow warning, red bad").
     --ok    good / settled / healthy  · --warn escalating attention · --warn-2 worse · --bad bad.
     Anything INERT (draft, void, converted, a role, a contact type) stays neutral grey and takes
     NO ladder colour — colour here means a judgement, so a neutral fact must not borrow one.
     --info/--violet are NOT status colours; keep them off pills. */
  --ok:#4bd6a0; --warn:#e6b45a; --warn-2:#e08a4a; --bad:#f27a7a; --info:#63bcd8; --violet:#a99be0;
  --neutral:#cfe0df; --neutral-bg:rgba(143,177,176,.16);
  --radius:12px; --radius-sm:8px;
  --sans:"Sora","Segoe UI",system-ui,-apple-system,Roboto,sans-serif;
  --mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
}

/* LIGHT THEME (UI-standards audit 2026-08-07 — Xero/QBO/Zoho are light-default; dark stays OUR
   default, this is the ☀/🌙 toggle's other half). Same variable contract as :root; WCAG-checked:
   ink 12+:1 on bg, muted ≥5:1, brand-as-text ≥4.5:1, brand button 4.7:1. The invoice paper is
   deliberately theme-independent (always white). Flip the default by shipping data-theme="light". */
html[data-theme="light"] body.vt{
  color-scheme: light;
  --bg:#edf3f3; --surface:#ffffff; --surface-2:#f3f8f8; --panel:#e4eeee;
  --ink:#10282c; --muted:#4e7373; --faint:#4e7175;
  --line:#c2d6d7; --line-soft:#d4e2e3;
  --brand:#0a7f7f; --brand-ink:#ffffff;
  --ok:#157a56; --warn:#92651a; --warn-2:#9d5218; --bad:#bb3f3f; --info:#1d6f86; --violet:#6353ad;
  --neutral:#3d5c60; --neutral-bg:rgba(64,96,100,.14);
}

html[data-theme="light"] body.vt .alert{color:#a12c28}
                        /* was hardcoded pale-red for dark */
html[data-theme="light"] body.vt thead th{background:rgba(0,0,0,.05)}
        /* dark 20% overlay is too heavy on white */
html[data-theme="light"] body.vt table.list tr.clickable:hover td{background:rgba(10,127,127,.07)}

html[data-theme="light"] body.vt .ss-opt.sel:not(.active){background:rgba(10,127,127,.12)}


body.vt *{box-sizing:border-box}

body.vt{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);line-height:1.5;-webkit-font-smoothing:antialiased;font-size:14.5px}

body.vt h1,
body.vt h3{margin:0;text-wrap:balance}

body.vt code,
body.vt .mono{font-family:var(--mono)}

body.vt .num{font-variant-numeric:tabular-nums}

body.vt .ta-r{text-align:right}

body.vt .muted{color:var(--muted)}

body.vt .eyebrow{font-size:10.5px;text-transform:uppercase;letter-spacing:.15em;color:var(--faint);font-weight:700}


/* topbar */
/* z-index 50: robust top nav — always paints above page content and in-page sticky bars (cashbook
   summary, table headers), and below popovers (60)/drawer (70)/modals (100)/toasts. Nothing in the
   scrolling content can cover it (v2-parity: the nav bar is a fixed layer). */
/* One row, never two: flex-wrap:nowrap keeps the nav from breaking onto a second line when the window
   narrows (owner 2026-09-15). No horizontal scrollbar and no resizing of the controls — space-between
   simply lets the gap between the title and the action group close up (the group "slides" toward the
   title) as the window shrinks, and the 280px search itself is hidden below 760px by the media query
   further down, so the fixed-size controls always have room on desktop. */
body.vt .topbar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 22px;flex-wrap:nowrap;
  background:var(--panel);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:50}
/* The right-hand action group was flex-shrink:0, so a long company + user name pushed it past the
   viewport and clipped "Sign out". Let it shrink (grow stays 0 → it keeps hugging the right) and let
   its content reflow; the name ellipsis below handles the very narrow end. */
body.vt .topbar>.row-actions:not(.app-bar-left){flex:0 1 auto;min-width:0}
/* Both topbar groups stay on ONE line internally too — without this the general .row-actions
   flex-wrap:wrap (further down) let the search box stack ABOVE the role icons when the window
   narrowed, which read as the nav "breaking up" (owner 2026-09-15). Scoped to .topbar so the
   wrapping .row-actions used elsewhere (editor bars, dropzones) is untouched. */
body.vt .topbar .row-actions{flex-wrap:nowrap}


/* simulated role switcher (demo lens, not real auth) */
body.vt .role-switch{display:flex;align-items:center;gap:8px;flex-wrap:nowrap}
body.vt .user-name{max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;vertical-align:middle}

body.vt .role-pill{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.09em;padding:3px 9px;border-radius:999px}

body.vt .role-pill.role-admin{color:var(--brand-ink);background:var(--brand)}

body.vt .role-pill.role-employee{color:var(--neutral);background:var(--neutral-bg)}

body.vt .role-select{width:auto;font-size:12.5px;padding:6px 8px}

body.vt .brand{display:flex;align-items:center;gap:9px;font-weight:800;font-size:18px;letter-spacing:-.01em}

body.vt .brand .dot{width:13px;height:13px;border-radius:50%;background:var(--brand);box-shadow:0 0 0 4px rgba(42,224,224,.15),0 0 12px rgba(42,224,224,.45)}

body.vt .brand .sub{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.14em;color:var(--brand);opacity:.85;margin-left:4px}


/* buttons */
body.vt .btn{font-family:inherit;font-size:13.5px;font-weight:650;border-radius:var(--radius-sm);padding:8px 14px;
  border:1px solid var(--line);background:var(--surface);color:var(--ink);cursor:pointer;white-space:nowrap}

body.vt .btn:hover{border-color:var(--brand)}

body.vt .btn.primary{background:var(--brand);border-color:var(--brand);color:var(--brand-ink);box-shadow:0 3px 12px rgba(42,224,224,.22)}
/* Tenant primaries authored `btn btn-primary` (hyphen) must read green like `btn primary` — the
   base `body.vt .btn` paints them white otherwise (owner 2026-09-17: "make it green like the others").
   :not(.btn-danger) leaves a destructive-primary alone. */
body.vt .btn.btn-primary:not(.btn-danger){background:var(--brand);border-color:var(--brand);color:var(--brand-ink);box-shadow:0 3px 12px rgba(42,224,224,.22)}

body.vt .btn.ghost{background:transparent}

body.vt .btn.sm{padding:4px 9px;font-size:12px;margin-left:6px}

body.vt .btn.bad{border-color:var(--bad);color:var(--bad)}

body.vt .row-actions{display:flex;gap:9px;flex-wrap:wrap}

body.vt .nowrap{white-space:nowrap}

body.vt .form-err{color:var(--bad);font-size:12.5px}


/* page scaffold */
body.vt .page{max-width:1000px;margin:0 auto;padding:26px 22px 80px}

body.vt .page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px;flex-wrap:wrap}

body.vt .page-head h1{font-size:24px;font-weight:800;letter-spacing:-.02em;margin-top:3px}

body.vt .empty{color:var(--muted);text-align:center;padding:60px 0;border:1px dashed var(--line);border-radius:var(--radius)}

body.vt .empty b{color:var(--brand)}

body.vt h3{font-size:14px;font-weight:700;margin:22px 0 9px;display:flex;align-items:baseline;gap:8px}

body.vt h3 small{color:var(--faint);font-weight:400;font-size:11.5px}


/* tables */
body.vt .tbl-wrap{overflow-x:auto;border:1px solid var(--line-soft);border-radius:var(--radius-sm)}

body.vt table{width:100%;border-collapse:collapse;font-size:13.5px}

body.vt thead th{font-size:10.5px;text-transform:uppercase;letter-spacing:.09em;color:var(--faint);font-weight:700;text-align:left;
  padding:10px 13px;background:rgba(0,0,0,.2);border-bottom:1px solid var(--line-soft)}

body.vt tbody td{padding:10px 13px;border-bottom:1px solid var(--line-soft);vertical-align:middle}

body.vt tbody tr:last-child td{border-bottom:none}

body.vt table.list tr.clickable{cursor:pointer}

body.vt table.list tr.clickable:hover td{background:rgba(42,224,224,.06)}

body.vt .c-check{width:36px;text-align:center}

body.vt .cell-sub{display:block;color:var(--faint);font-size:11px}

body.vt .bulk-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:12px;padding:8px 12px;border:1px solid var(--brand);border-radius:var(--radius-sm);background:rgba(42,224,224,.08)}

body.vt .bulk-bar .btn.sm{margin-left:0}


/* unified Billing tab bar (Invoices / Estimates / Claims) */
body.vt .billing-tabbar{max-width:1000px;margin:0 auto;padding:16px 22px 0}

body.vt .billing-tabbar .seg{font-size:13px;padding:7px 16px}

body.vt .billing-tabbar.sub{padding-top:8px}

body.vt .billing-umbrellas{max-width:1000px;margin:0 auto;padding:16px 22px 0}

body.vt .billing-umbrellas .seg{font-size:13.5px;font-weight:800;padding:8px 22px}

/* quick-create card (Claims-style form above the grid) */
body.vt .qc{border-color:var(--brand)}

body.vt .qc-row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}

body.vt .qc-row .ss{min-width:240px;flex:0 1 auto}


/* list toolbar: search + status tabs + sortable headers + pager */
body.vt .list-toolbar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px}

body.vt .search{max-width:300px}
@media (max-width:767px){ body.vt .search,body.vt .doc-tabhead .doc-search-inline{max-width:100%;width:100%} }

body.vt .segs{display:flex;gap:5px;flex-wrap:wrap}

body.vt .seg{font-family:inherit;font-size:12.5px;font-weight:650;border:1px solid var(--line);background:var(--surface);color:var(--muted);border-radius:999px;padding:5px 12px;cursor:pointer;display:inline-flex;align-items:center;gap:6px}

body.vt .seg:hover{border-color:var(--brand)}

body.vt .seg.on{background:var(--brand);border-color:var(--brand);color:var(--brand-ink)}

body.vt .seg-n{font-size:11px;opacity:.8;font-variant-numeric:tabular-nums}

body.vt .subtabs{margin:0 0 16px}
   /* in-screen tab bar spacing (Products & services: Items | Categories) */
body.vt th.srt{cursor:pointer;user-select:none;white-space:nowrap}

body.vt th.srt:hover{color:var(--brand)}

body.vt .pager{display:flex;align-items:center;justify-content:center;gap:14px;margin-top:14px;flex-wrap:wrap}

body.vt .pager-info{font-size:12.5px;color:var(--muted)}

body.vt .pager-per{display:inline-flex;align-items:center;gap:6px;color:var(--muted);font-size:12.5px}

body.vt .pager-per select{font-family:inherit;font-size:12.5px;padding:4px 6px;background:var(--surface);color:var(--ink);
  border:1px solid var(--line);border-radius:6px}


/* app-bar global search (desktop chrome — mobile deferred, owner 2026-08-07) */
body.vt .global-search .ss{width:280px}

body.vt .theme-toggle{padding:8px 10px}

@media (max-width:760px){
body.vt .global-search{display:none}
}


/* editor validation banner — sits under the page-head, at the click point (audit 2026-08-07) */
body.vt .editor-alert{margin-bottom:14px}


/* ── UX deep-search round (2026-08-07) ─────────────────────────────────────────────────────────── */
/* action feedback (finding E): transient notices, bottom-centre, above everything */
body.vt .notice-stack{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);z-index:10000;display:flex;
  flex-direction:column;align-items:center;gap:8px;pointer-events:none;max-width:min(92vw,560px)}

body.vt .app-notice{padding:10px 16px;border-radius:10px;font-size:13.5px;font-weight:600;border:1px solid;
  background:var(--panel);box-shadow:0 6px 24px rgba(0,0,0,.35);animation:noticeIn .18s ease-out}

body.vt .app-notice.is-ok{color:var(--ok);border-color:var(--ok)}

body.vt .app-notice.is-bad{color:var(--bad);border-color:var(--bad)}

body.vt .app-notice.is-info{color:var(--info);border-color:var(--info)}

body.vt .app-notice.is-leaving{opacity:0;transition:opacity .3s ease}

@keyframes noticeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

html[data-theme="light"] body.vt .app-notice{box-shadow:0 6px 24px rgba(16,40,44,.18)}


/* the saved row flashes so you never hunt for it after Save (finding E / parent-repo flashRow) */
@keyframes rowFlash{from{background:rgba(42,224,224,.28)}to{background:transparent}}

body.vt tr.row-hl td{animation:rowFlash 1.8s ease-out}

html[data-theme="light"] body.vt tr.row-hl td{animation-name:rowFlashLight}

@keyframes rowFlashLight{from{background:rgba(10,127,127,.22)}to{background:transparent}}


/* unsaved-changes bar in the editor (finding A). The [hidden] rule is LOAD-BEARING: display:flex
   on the class beats the UA's [hidden]{display:none}, so without it the bar shows on every editor
   from mount and "Keep editing" looks dead (owner hit exactly this, 2026-08-07). */
body.vt .leave-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px;padding:10px 14px;
  border:1px solid var(--warn);border-radius:var(--radius-sm);background:rgba(230,180,90,.12);color:var(--warn);font-size:13.5px;font-weight:600}

body.vt .leave-bar[hidden]{display:none}

body.vt .leave-bar .btn.sm{margin-left:0}


/* keyboard users get their caret back (finding K) */
body.vt .btn:focus-visible,
body.vt [role="button"]:focus-visible,
body.vt .nav-item:focus-visible,
body.vt summary:focus-visible{
  outline:2px solid var(--brand);outline-offset:2px}


/* per-row line actions in the editor (finding H) */
body.vt button.x.dup{margin-right:2px}


/* bulk print batch — hidden on screen; only it prints */
body.vt .print-batch{display:none}

@media print{

  body.vt.printing-batch #app{display:none !important}

  body.vt.printing-batch .print-batch{display:block}

  body.vt .print-page{page-break-after:always}

  body.vt .print-page:last-child{page-break-after:auto}

}


/* pills */
body.vt .pill{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:3px 9px;border-radius:999px;display:inline-flex;align-items:center;gap:6px}

body.vt .pill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}

body.vt .pill-draft{color:var(--neutral);background:var(--neutral-bg)}
                 /* inert: not issued yet */
body.vt .pill-due{color:var(--warn);background:rgba(230,180,90,.15)}
                   /* waiting on payment */
body.vt .pill-partial{color:var(--warn);background:rgba(230,180,90,.15)}
               /* part paid, still owed */
body.vt .pill-overdue{color:var(--bad);background:rgba(242,122,122,.15)}

body.vt .pill-paid{color:var(--ok);background:rgba(75,214,160,.15)}

body.vt .pill-credited{color:var(--neutral);background:var(--neutral-bg)}
              /* settled by credit — inert */
body.vt .pill-void{color:var(--faint);background:rgba(95,139,138,.15)}

body.vt .pill-written_off{color:var(--bad);background:rgba(242,122,122,.15)}
           /* a loss — bad */

/* editor form */
body.vt .grid2{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:18px}

body.vt .fld{display:flex;flex-direction:column;gap:5px;font-size:12px;color:var(--muted)}

body.vt .fld>span{font-size:10.5px;text-transform:uppercase;letter-spacing:.11em;color:var(--faint);font-weight:700}

body.vt .fld.inline{flex-direction:row;align-items:center;gap:8px}

body.vt .fld.inline>span{white-space:nowrap}

body.vt input,
body.vt select,
body.vt textarea{font-family:inherit;font-size:13.5px;color:var(--ink);background:var(--surface-2);
  border:1px solid var(--line);border-radius:7px;padding:8px 10px;width:100%}

/* Uniform field-control height so a row of fields lines up cleanly regardless of control type — a
   text input, a native date input and the search-selector otherwise render at 36/38/… px (owner
   2026-08-28: "text is not aligned"). Textareas keep their own multi-line height. */
body.vt .fld>input:not([type=checkbox]),
body.vt .fld>select,
body.vt .fld .ss-input,
body.vt .fld .fixed-val{box-sizing:border-box;height:38px}

body.vt input:focus,
body.vt select:focus,
body.vt textarea:focus{outline:2px solid rgba(42,224,224,.5);outline-offset:-1px;border-color:var(--brand)}

body.vt textarea{resize:vertical}

body.vt input.is-invalid,
body.vt select.is-invalid,
body.vt textarea.is-invalid{border-color:var(--bad);background:rgba(242,122,122,.08)}

body.vt .req{color:var(--bad)}


/* search-selector — the single control that replaces every native <select>/<datalist>.
   Popup (.ss-list) is fixed-position on <body> so it never clips inside a .tbl-wrap table. */
body.vt .ss{position:relative;display:block}

body.vt .ss-input{width:100%;cursor:text;padding-right:26px}

/* dropdown chevron (owner 2026-09-14) — positioned by the base .ss-caret rule; tenant tint only */
body.vt .ss-caret{color:var(--faint)}

body.vt .ss-list{position:fixed;z-index:9999;margin:0;padding:4px;list-style:none;max-height:264px;overflow-y:auto;overflow-x:hidden;
  background:var(--surface);border:1px solid var(--brand);border-radius:var(--radius-sm);box-shadow:0 12px 34px rgba(0,0,0,.5)}

body.vt .ss-opt{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:7px 10px;border-radius:6px;
  font-size:13.5px;color:var(--ink);cursor:pointer}

body.vt .ss-opt .ss-lab{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

body.vt .ss-opt .ss-sub{color:var(--faint);font-size:11.5px;font-variant-numeric:tabular-nums;flex-shrink:0;white-space:nowrap}

body.vt .ss-opt.active{background:var(--brand);color:var(--brand-ink)}

body.vt .ss-opt.active .ss-sub{color:var(--brand-ink);opacity:.8}

body.vt .ss-opt.sel:not(.active){background:rgba(42,224,224,.12)}

body.vt .ss-opt.is-empty{color:var(--faint);cursor:default;justify-content:flex-start}

body.vt .role-switch .ss{width:auto}

body.vt .ss-input.role-select{width:132px}

body.vt .adj .fld.inline .ss{width:auto}

body.vt .adj .fld.inline .ss-input{width:96px}


body.vt table.lines input.in,
body.vt table.lines select.in{padding:6px 8px;background:transparent;border:1px solid transparent;border-radius:6px}

body.vt table.lines input.in:hover,
body.vt table.lines select.in:hover{border-color:var(--line-soft)}

body.vt table.lines input.in:focus,
body.vt table.lines select.in:focus{background:var(--surface-2)}

body.vt table.lines .c-desc{width:34%}
body.vt table.lines .c-num{width:9%}
body.vt table.lines .c-unit{width:8%}
body.vt table.lines .c-vat{width:13%}
body.vt table.lines .c-x{width:34px}

body.vt table.lines td.amt{font-variant-numeric:tabular-nums;color:var(--muted);white-space:nowrap}

body.vt table.lines.ro td{color:var(--ink)}

body.vt .x{background:none;border:1px solid var(--line-soft);color:var(--faint);border-radius:6px;cursor:pointer;padding:3px 8px;font-size:12px}

body.vt .x:hover{color:var(--bad);border-color:var(--bad)}

body.vt .add-line{margin-top:10px}

body.vt .suggest-bar{display:flex;align-items:center;gap:12px;margin-top:10px;font-size:13px;color:var(--muted);flex-wrap:wrap}

body.vt .suggest-bar:empty{display:none}


/* editor footer / totals */
body.vt .foot{display:flex;flex-direction:column;gap:14px;margin-top:18px}

body.vt .foot-left{display:flex;flex-direction:column;gap:12px}

/* Notes box and totals box side by side as an equal-height pair, so their bottom edges line up
   (owner 2026-08-28). The row is as tall as the taller box; the Notes textarea grows to fill. */
body.vt .foot-pair{display:grid;grid-template-columns:1fr 340px;gap:18px;align-items:stretch}

body.vt .foot-pair>.fld{height:100%}

body.vt .foot-pair>.fld textarea{flex:1;min-height:96px}

body.vt .foot-totals{display:flex;flex-direction:column}

body.vt .foot-totals>.tp{flex:1;margin:0}

body.vt .adj{display:flex;gap:16px;flex-wrap:wrap}

body.vt .adj .fld.inline input{width:110px}

body.vt .adj .fld.inline select{width:auto}

body.vt .alert{background:rgba(242,122,122,.12);border:1px solid rgba(242,122,122,.4);color:#ffd9d9;padding:9px 12px;border-radius:8px;font-size:13px}


body.vt .tp{border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;display:flex;flex-direction:column}

body.vt .tp .tp-row.total{margin-top:auto}
   /* pin the Total row to the box bottom so it aligns with the Notes box */
body.vt .tp-row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:9px 14px;font-size:13.5px}

body.vt .tp-row span:first-child{color:var(--muted)}

body.vt .tp-row em{font-style:normal;font-family:var(--mono);font-size:10.5px;color:var(--faint)}

body.vt .tp-row code{font-size:11.5px}

body.vt .tp-row.disc span:last-child{color:var(--ok)}

body.vt .tp-row.total{background:rgba(42,224,224,.10);border-top:1px solid var(--line);padding:12px 14px}

body.vt .tp-row.total span:first-child{color:var(--ink);font-weight:700}

body.vt .tp-row.total b{color:var(--brand);font-size:18px;font-weight:800}

body.vt .tp-row.dep{border-top:1px dashed var(--line-soft);font-size:12.5px}


/* settings / forms */
body.vt .card{border:1px solid var(--line-soft);border-radius:var(--radius-sm);padding:16px 18px;margin-bottom:16px;background:var(--panel)}

body.vt .card>h3:first-child{margin-top:0}

/* left-anchor the action buttons: the save button's left edge stays put when its label changes
   (Save→Saving…) or the success notice appears beside it, so the button never shifts (owner 2026-09-15) */
body.vt .form-actions{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin-top:8px}

body.vt .notice{background:rgba(75,214,160,.12);border:1px solid rgba(75,214,160,.4);color:#bff3dd;padding:8px 12px;border-radius:8px;font-size:13px}
/* an empty success box must not render as a stray green pill next to Save/Add-rate/etc. (pre-existing) */
body.vt .notice:empty{display:none}

/* .notice is a SUCCESS box (green) — right for "invite emailed" or "generated 3 invoices", wrong for
   a statement of what a grid is filtered to. A 60+ overdue summary was being framed in green, which is
   the ladder saying "good" about the worst money on the screen. Neutral variant for informational panels;
   the ladder-toned figures inside carry the meaning. */
body.vt .notice.is-info{background:var(--surface-2);border-color:var(--line);color:var(--ink)}

body.vt .hint{font-size:11.5px;color:var(--faint);margin-top:2px}

body.vt .logo-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap}

body.vt .logo-prev{height:56px;width:auto;max-width:180px;border:1px solid var(--line-soft);border-radius:6px;background:#fff;padding:4px;object-fit:contain}

body.vt input.chk,
body.vt input[type=checkbox]{width:auto}

body.vt select{height:32px;box-sizing:border-box}   /* match input height (was 38px) — uniform controls */

/* jrn-hide is the app's "hide this field" utility (.jrn-hide{display:none} in aboo.css), but under
   body.vt the base `body.vt .fld{display:flex}` out-specifies it, so hidden fields (e.g. the VAT
   number when VAT-registered is off) stayed visible. Force the hide for tenant screens. */
body.vt .jrn-hide{display:none !important}

/* Branding colour pickers (Settings ▸ Branding & Comms): the generic body.vt input{width:100%}
   above out-specifies .bt-swatch{width:44px} (aboo.css), so the native colour input ballooned to
   a full-width black bar (empty value = #000000) and crushed the hex field beside it to ~22px.
   Re-own the row here so the swatch is a small square and the hex field fills the rest. */
body.vt .bt-swatch{flex:0 0 44px;width:44px;height:38px;padding:2px}
body.vt .bt-hex{flex:1 1 auto;width:auto;min-width:0}


/* client detail — key/values, notes, documents (F5) */
body.vt .kv{display:grid;grid-template-columns:1fr 1fr;gap:10px 22px}

body.vt .kv>div{display:flex;flex-direction:column;gap:3px}

body.vt .kv span{font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--faint);font-weight:700}

body.vt .kv b{font-weight:600}

body.vt .notes-list{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}

body.vt .note-row{border:1px solid var(--line-soft);border-radius:var(--radius-sm);padding:8px 12px;font-size:13.5px}

body.vt .note-row .note-date{color:var(--faint);font-size:11px;margin-bottom:3px}

body.vt .docs-list{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}

body.vt .doc-row{display:flex;align-items:center;gap:10px;border:1px solid var(--line-soft);border-radius:var(--radius-sm);padding:7px 11px;font-size:13.5px}

body.vt .doc-row a{color:var(--brand);text-decoration:none;font-weight:600}

body.vt .doc-row a:hover{text-decoration:underline}

body.vt .doc-row .doc-meta{color:var(--faint);font-size:11.5px}

body.vt .doc-row .spacer{flex:1}


/* detail */
body.vt .detail-grid{display:grid;grid-template-columns:1fr 300px;gap:20px;margin-top:6px}

body.vt .journal .acct small{display:block;color:var(--faint);font-size:11px;font-family:var(--sans)}

body.vt .journal tfoot td{border-top:1px solid var(--line);font-weight:700;padding:9px 13px}

body.vt .bal.ok{color:var(--ok)}
body.vt .bal.bad{color:var(--bad)}
body.vt .bal.warn{color:var(--warn)}


/* invoice paper — client-facing SARS tax invoice (white/ink, print-ready) */
body.vt .invoice-paper{background:#fff;color:#1a2b2b;border-radius:var(--radius-sm);padding:34px 38px;max-width:820px;margin:0 auto;
  box-shadow:0 8px 30px rgba(0,0,0,.35);font-size:13px;line-height:1.5}

body.vt .doc-head{display:flex;justify-content:space-between;gap:24px;border-bottom:2px solid #1a2b2b;padding-bottom:18px;margin-bottom:18px}

body.vt .doc-supplier{max-width:60%}

body.vt .doc-logo{max-height:60px;max-width:220px;object-fit:contain;margin-bottom:8px;display:block}

body.vt .doc-org-name{font-size:20px;font-weight:800;letter-spacing:-.01em;margin-bottom:6px}

body.vt .doc-address{color:#3a4d4d;margin-bottom:6px}

body.vt .doc-meta-line{color:#3a4d4d;font-size:12px}

body.vt .doc-title-block{text-align:right;min-width:230px}

body.vt .doc-title{font-size:24px;font-weight:800;letter-spacing:.04em;color:#0d6b6b;margin-bottom:10px}

body.vt .doc-meta{margin-left:auto;font-size:12px;width:auto}

body.vt .doc-meta td{padding:2px 0;border:none}

body.vt .doc-meta td:first-child{color:#6a7d7d;text-align:right;padding-right:12px;text-transform:uppercase;font-size:10px;letter-spacing:.08em}

body.vt .doc-meta td:last-child{text-align:right;font-weight:600}

body.vt .doc-bill-to{margin-bottom:18px}

body.vt .doc-label{font-size:10px;text-transform:uppercase;letter-spacing:.12em;color:#6a7d7d;font-weight:700;margin-bottom:4px}

body.vt .doc-client-name{font-size:15px;font-weight:700}

body.vt .doc-lines-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:18px}

body.vt .doc-lines-tbl{width:100%;border-collapse:collapse;font-size:12.5px}

body.vt .doc-lines-tbl thead th{background:#f0f5f5;color:#4a5d5d;text-transform:uppercase;font-size:10px;letter-spacing:.06em;
  text-align:left;padding:8px 10px;border-bottom:2px solid #d5e0e0}

body.vt .doc-lines-tbl tbody td{padding:8px 10px;border-bottom:1px solid #e6eeee;color:#1a2b2b;vertical-align:top}

body.vt .doc-lines-tbl .c-desc{width:44%}

body.vt .doc-lines-tbl .ta-c{text-align:center}

body.vt .doc-summary{display:flex;justify-content:space-between;gap:24px;align-items:flex-start}

body.vt .doc-notes{max-width:55%;font-size:11.5px;color:#3a4d4d}

body.vt .doc-statement{margin-top:10px;font-size:10.5px;color:#6a7d7d;font-style:italic}

body.vt .doc-abridged{color:#0d6b6b}

body.vt .doc-totals{min-width:270px;margin-left:auto}

body.vt .dt-row{display:flex;justify-content:space-between;gap:20px;padding:5px 0;font-size:13px;color:#3a4d4d}

body.vt .dt-row.muted{color:#8aa0a0}

body.vt .dt-row .num{color:#1a2b2b;font-variant-numeric:tabular-nums}

body.vt .dt-total{border-top:2px solid #1a2b2b;margin-top:6px;padding-top:9px;font-size:16px;font-weight:800;color:#0d6b6b}

body.vt .dt-total .num{color:#0d6b6b;font-weight:800}

body.vt .dt-dep{border-top:1px dashed #c5d2d2;font-size:12px}

body.vt .pre{white-space:pre-line}

body.vt .paid-stamp{display:inline-block;margin-top:8px;border:2.5px solid #1a9d6b;color:#1a9d6b;font-weight:800;letter-spacing:.14em;padding:2px 12px;border-radius:6px;transform:rotate(-4deg);font-size:15px}

body.vt .void-stamp{display:inline-block;margin-top:8px;border:2.5px solid #c23b3b;color:#c23b3b;font-weight:800;letter-spacing:.14em;padding:2px 12px;border-radius:6px;transform:rotate(-4deg);font-size:15px}

body.vt .muted-block{color:var(--muted);padding:14px;border:1px dashed var(--line);border-radius:var(--radius-sm)}

body.vt .is-hidden{display:none!important}

body.vt .accounting{margin-top:26px}


/* payments panel */
body.vt .ok{color:var(--ok)}

body.vt .pay-summary{display:flex;gap:26px;flex-wrap:wrap;margin:4px 0 12px;font-size:14px;color:var(--muted)}

body.vt .pay-summary b{margin-left:6px;color:var(--ink)}

body.vt .pay-form{display:flex;gap:14px;align-items:flex-end;flex-wrap:wrap;margin-top:12px;padding:12px 14px;border:1px solid var(--line-soft);border-radius:var(--radius-sm);background:var(--panel)}

body.vt .pay-form .fld.inline{flex-direction:column;align-items:flex-start;gap:5px}

body.vt .pay-form .fld.inline input{width:150px}

body.vt .pay-journal{margin-top:10px}

body.vt .pay-journal .muted{margin-bottom:4px;font-size:12px}

body.vt .pay-online{display:flex;flex-direction:column;gap:6px;margin-top:14px;padding-top:12px;border-top:1px dashed var(--line-soft)}

body.vt .center-card{max-width:460px;margin:40px auto;text-align:center}

body.vt .center-card h1{font-size:22px;margin:12px 0 6px}


/* activity trail */
body.vt .activity{border:1px solid var(--line-soft);border-radius:var(--radius-sm);overflow:hidden}

body.vt .act-row{display:flex;align-items:center;gap:12px;padding:8px 12px;border-bottom:1px solid var(--line-soft);font-size:13px}

body.vt .act-row:last-child{border-bottom:none}

body.vt .act-date{color:var(--faint);font-size:11.5px}

body.vt .act-tag{font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:700;padding:2px 8px;border-radius:999px;background:rgba(143,177,176,.16);color:var(--muted)}

body.vt .act-tag.act-reminder{background:rgba(230,180,90,.16);color:var(--warn)}

body.vt .act-tag.act-void{background:rgba(242,122,122,.16);color:var(--bad)}

body.vt .act-tag.act-approve{background:rgba(75,214,160,.16);color:var(--ok)}

body.vt .act-tag.act-decline{background:rgba(242,122,122,.16);color:var(--bad)}

body.vt .approval-box{margin-bottom:16px}

body.vt .approval-head{display:flex;align-items:center;gap:10px;margin-bottom:6px}


/* modal overlay (ui.js) — AI draft confirm, approvals, onboarding preview */
body.vt .modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;z-index:100;padding:18px}

body.vt .modal-panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);max-width:560px;width:100%;max-height:88vh;overflow:auto;padding:20px 22px;box-shadow:0 20px 60px rgba(0,0,0,.5)}

body.vt .modal-panel:focus{outline:none}

body.vt .modal-title{font-size:16px;font-weight:800;margin-bottom:10px}

body.vt .modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:16px}

body.vt .ai-livebar{display:flex;align-items:center;gap:12px;margin:8px 0}

body.vt .ai-draft-live{min-height:60px}

body.vt .ai-draft .eyebrow{display:block;margin-bottom:8px}

/* embedded AI dictation control (in the client form + invoice/quote editor) */
body.vt .ai-embed{margin-bottom:14px}

body.vt .ai-dictate{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:9px 12px;border:1px solid var(--brand);border-radius:var(--radius-sm);background:rgba(42,224,224,.06)}

body.vt .ai-dictate .ai-cmd{flex:1;min-width:220px}

body.vt .ai-dictate [data-status]{white-space:nowrap;color:var(--brand)}

body.vt .ai-embed [data-ai-revert]{margin-left:10px}


/* customer-facing surface (customer.js) — simulated emailed link, no admin nav */
body.vt .cust{min-height:100vh;display:flex;flex-direction:column}

body.vt .cust-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 22px;border-bottom:1px solid var(--line);background:var(--panel)}

body.vt .cust-sub{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.14em;color:var(--brand)}

body.vt .cust-main{flex:1;max-width:820px;width:100%;margin:0 auto;padding:28px 22px}

body.vt .cust-card{border:1px solid var(--line-soft);border-radius:var(--radius);padding:24px 26px;background:var(--panel)}

body.vt .cust-card h1{font-size:22px;font-weight:800;margin-bottom:8px}

body.vt .cust-token{font-size:12.5px;color:var(--muted);margin-top:10px}

body.vt .cust-foot{padding:14px 22px;text-align:center;font-size:11.5px;color:var(--faint);border-top:1px solid var(--line-soft)}


/* dashboard */
body.vt .kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:18px}

body.vt .kpi{border:1px solid var(--line-soft);border-radius:var(--radius-sm);padding:14px 16px;background:var(--panel)}

body.vt .kpi-label{font-size:10.5px;text-transform:uppercase;letter-spacing:.1em;color:var(--faint);font-weight:700}

body.vt .kpi-value{font-size:20px;font-weight:800;margin-top:6px;letter-spacing:-.01em}

body.vt .kpi-sub{font-size:11.5px;color:var(--muted);margin-top:2px}

body.vt .kpi-brand .kpi-value{color:var(--brand)}

body.vt .kpi-bad .kpi-value{color:var(--bad)}

body.vt .kpi-warn .kpi-value{color:var(--warn)}

body.vt .kpi-ok .kpi-value{color:var(--ok)}

body.vt .dash-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}

body.vt .dash-sub{margin:16px 0 7px;font-size:11px;text-transform:uppercase;letter-spacing:.09em;color:var(--faint);font-weight:700}

body.vt .age-bar{display:flex;height:16px;border-radius:999px;overflow:hidden;background:var(--surface-2);margin:6px 0 12px}

body.vt .age-seg{height:100%}

body.vt .age-cur{background:var(--ok)}
body.vt .age-1{background:var(--warn)}
body.vt .age-2{background:var(--warn-2)}
body.vt .age-3{background:var(--bad)}

body.vt .age-legend{display:flex;flex-wrap:wrap;gap:14px;font-size:12.5px;color:var(--muted)}

body.vt .age-legend span{display:inline-flex;align-items:center;gap:6px}

body.vt .age-legend i{width:10px;height:10px;border-radius:2px;display:inline-block}

body.vt .age-legend i.age-cur{background:var(--ok)}
body.vt .age-legend i.age-1{background:var(--warn)}
body.vt .age-legend i.age-2{background:var(--warn-2)}
body.vt .age-legend i.age-3{background:var(--bad)}

body.vt .age-legend b{color:var(--ink)}

body.vt .recent{display:flex;flex-direction:column;gap:2px;margin-bottom:12px}

body.vt .recent-row{display:grid;grid-template-columns:1fr auto auto;align-items:center;gap:12px;padding:8px 10px;border-radius:var(--radius-sm);cursor:pointer}

body.vt .recent-row:hover{background:rgba(42,224,224,.06)}

body.vt .recent-amt{color:var(--ink)}


/* print — only the paper survives */
@media print{

  body.vt .topbar,
body.vt .no-print{display:none !important}

  body.vt{background:#fff}

  body.vt .page{padding:0;max-width:none;margin:0}

  body.vt .invoice-paper{box-shadow:none;max-width:none;border-radius:0;margin:0;padding:0}

  body.vt .doc-lines-wrap{overflow:visible}

}


@media (max-width:820px){

  body.vt .grid2{grid-template-columns:1fr 1fr}

  body.vt .kv{grid-template-columns:1fr}

  body.vt .foot-pair,
body.vt .detail-grid{grid-template-columns:1fr}

  body.vt .kpis{grid-template-columns:1fr 1fr}

  body.vt .dash-grid{grid-template-columns:1fr}

  body.vt .doc-head,
body.vt .doc-summary{flex-direction:column}

  body.vt .doc-title-block{text-align:left}

  body.vt .doc-meta{margin-left:0}

  body.vt .doc-notes,
body.vt .doc-totals{max-width:none;min-width:0;width:100%}

  body.vt .invoice-paper{padding:22px 20px}

}


/* ── from src/styles-owner.css ── */
/* Styles for the 2026-07-28 owner-feedback round (Andre + Lloyd review).
   Kept in its own file so the round is easy to review/revert; loaded after styles.css.
   Sections are owned by one screen each — see plan/owner-feedback-2026-07-28.md. */

/* ── App shell: side-nav drawer (transcript ruling — Lloyd: "even with sleek, we don't deal with
      tabs like that, we're dealing with this BAR"; Callan: "so you guys want the side nav";
      Andre: "try it"). Structure mirrors the real app's src/admin/shell.js: drawer + scrim +
      app-bar + ☰. Static column from 901px; slide-over drawer with a scrim below that. ───────── */
/* THE CONTENT AREA SCROLLS, NOT THE WINDOW (owner 2026-07-29: clicking a side-panel button "must not
   jump to the top like that, it is jarring on the eyes"). When the window was the scroller, leaving a
   screen from halfway down snapped the whole page to 0 on arrival. Now the offset belongs to
   `.shell-main`, which render() rebuilds on every navigation — so the new screen is simply born at the
   top and nothing visibly moves. The drawer and app bar become physically static rather than
   sticky-tracking a moving page. `100dvh` keeps it right on mobile browser chrome (vh first as the
   fallback). @media print undoes all of it — a clipped overflow container would truncate a printed
   invoice. */
body.vt .shell-layout{ display: flex; height: 100vh; height: 100dvh; overflow: hidden; }

body.vt .shell-main{ overflow-y: auto; overscroll-behavior: contain; }

/* position:static keeps the tenant drawer an IN-FLOW flex item at desktop (>=900px) so
   .shell-main sits BESIDE it. Without this, the global admin rule aboo.css `.drawer{position:fixed}`
   leaks onto the tenant drawer (this rule set flex:0 0 232px but not position), taking it out of
   flow — so .shell-content filled the row from x=0 and its left 232px rendered BEHIND the drawer.
   The <=900px off-canvas rule below re-sets position:fixed, so mobile is unaffected. */
body.vt .drawer{ position: static; width: 232px; flex: 0 0 232px; box-sizing: border-box; background: var(--panel);
  border-right: 1px solid var(--line); padding: 14px 10px 24px;
  height: 100%; overflow-y: auto; }

body.vt .drawer-brand{ padding: 2px 10px 14px; border-bottom: 1px solid var(--line); margin-bottom: 8px; }

body.vt .nav-section{ font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .12em;
  color: var(--faint); padding: 16px 10px 5px; }

body.vt .nav-item{ display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px;
  cursor: pointer; color: var(--muted); font-size: 13.5px; user-select: none; }

body.vt .nav-item:hover{ background: var(--surface-2); color: var(--ink); }

body.vt .nav-item.active{ background: var(--surface-2); color: var(--brand); font-weight: 600; }

body.vt .nav-ico{ width: 18px; text-align: center; opacity: .8; }

body.vt .nav-lbl{ flex: 1; }

body.vt .shell-main{ flex: 1; min-width: 0; display: flex; flex-direction: column; }

body.vt .shell-content{ flex: 1; min-width: 0; }

body.vt .app-bar-left{ align-items: center; }

body.vt .app-title{ font-weight: 700; font-size: 15px; }

body.vt .hamb{ display: none; }

body.vt .scrim{ display: none; }

/* Desktop: the bottom mobile nav is not shown. */
body.vt .mobile-nav{ display: none; }

/* Company name + page title is a real button that navigates to the dashboard on tap
   (owner Andre 2026-09-17: "tapping on the company name at the top should always go back
   to the dashboard"). Styled as text but with a subtle hover so it reads as tappable. */
body.vt .app-title-home{ display: flex; flex-direction: column; align-items: flex-start;
  background: transparent; border: 0; padding: 0; margin: 0; cursor: pointer;
  color: inherit; line-height: 1.1; text-align: left; }
body.vt .app-title-home:hover .app-company{ color: var(--brand, #0BA8A0); }
body.vt .app-title-home:focus-visible{ outline: 2px solid var(--brand, #0BA8A0); outline-offset: 4px;
  border-radius: 6px; }

@media (max-width: 900px){

  /* Mobile: no left-slide drawer, no bottom sheet — the persistent horizontal-scroll bar
     BELOW is the primary nav (owner Andre 2026-09-17). The desktop drawer + scrim + hamburger
     all hide, freeing space at the top for the title. */
  body.vt .drawer,
  body.vt .scrim,
  body.vt .btn.hamb{ display: none !important; }

  /* Persistent bottom nav — horizontally scrollable, safe-area-padded. Light surface with a
     hairline top border matches the tenant theme; the active item wears the brand fill so it
     stands out at a glance. (Tenant scope re-defines --brand-ink to white, so we don't use it
     as a background here; the desktop-style dark drawer background isn't the tenant look.) */
  body.vt .mobile-nav{ display: block; position: fixed; left: 0; right: 0; bottom: 0;
    z-index: 70; background: var(--surface, #fff); color: var(--ink);
    border-top: 1px solid var(--line, #e5e7eb);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    box-shadow: 0 -6px 20px rgba(0,0,0,.06); }
  body.vt .mobile-nav-scroll{ display: flex; gap: 2px; overflow-x: auto;
    scroll-snap-type: x proximity; scroll-behavior: smooth;
    padding: 6px 8px; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; }
  body.vt .mobile-nav-scroll::-webkit-scrollbar{ display: none; }
  body.vt .mobile-nav-item{ flex: 0 0 auto; display: flex; flex-direction: column;
    align-items: center; gap: 3px; padding: 8px 12px; border: 0; background: transparent;
    color: var(--muted, #6b7280); border-radius: 10px; cursor: pointer; scroll-snap-align: center;
    min-width: 64px; position: relative; }
  body.vt .mobile-nav-item .mobile-nav-ico{ display: inline-flex; align-items: center;
    justify-content: center; width: 24px; height: 24px; opacity: .95; }
  body.vt .mobile-nav-item .mobile-nav-lbl{ font-size: 11px; font-weight: 600; letter-spacing: .01em;
    white-space: nowrap; }
  body.vt .mobile-nav-item:hover{ color: var(--ink); }
  body.vt .mobile-nav-item.active{ background: var(--brand-faint, rgba(11, 168, 160, .12));
    color: var(--brand, #0BA8A0); }
  body.vt .mobile-nav-item .mobile-nav-dot{ position: absolute; top: 4px; right: 8px;
    background: var(--bad, #dc2626); color: #fff; font-size: 10px; font-weight: 800;
    min-width: 16px; height: 16px; line-height: 16px; border-radius: 999px; padding: 0 4px;
    text-align: center; }

  /* Reserve room at the bottom of scrollable content so the bar never covers the last row.
     .shell-main already scrolls internally (vt.css line 859), so this padding lives there. */
  body.vt .shell-main{ padding-bottom: calc(60px + env(safe-area-inset-bottom, 0px)); }

  /* Top bar squeezes down on mobile — no hamburger any more, and the user name / role badge /
     Sign out that used to crowd the right side move elsewhere (Sign out lives in the drawer's
     desktop footer; on mobile it's reachable from Settings). */
  body.vt .topbar{ padding: 8px 12px; gap: 6px; }
  body.vt .topbar .row-actions{ gap: 4px; }
  body.vt .role-switch{ gap: 4px; }
  body.vt .role-switch .user-name,
  body.vt .role-switch .role-badge,
  body.vt .role-switch > .btn.sm,
  body.vt .role-switch > .confirm-inline{ display: none; }

  /* Icon buttons stay small (34×34) — three still fit next to the freed-up title. */
  body.vt .theme-toggle{ padding: 0; width: 34px; height: 34px; min-width: 34px;
    display: inline-flex; align-items: center; justify-content: center; }

  /* Global search collapses to an icon-only trigger on mobile. */
  body.vt [data-gsearch-cell]{ display: none; }

  /* Title panel takes what's LEFT after the right icons; ellipsis kicks in rather than pushing
     them off. Parent .row-actions.app-bar-left also needs `min-width:0` so its child shrinks. */
  body.vt .topbar .row-actions.app-bar-left{ flex: 1 1 auto; min-width: 0; }
  body.vt .app-title-wrap{ flex: 1 1 auto; min-width: 0; overflow: hidden; padding-left: 4px; }
  body.vt .app-company{ font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  body.vt .app-title{ font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}

/* Printing must escape the fixed-height scroll container, or a long invoice is clipped to one screen. */
@media print{

  body.vt .drawer,
body.vt .scrim,
body.vt .app-bar{ display: none !important; }

  body.vt .shell-layout{ display: block; height: auto; overflow: visible; }

  body.vt .shell-main,
body.vt .shell-content{ overflow: visible; display: block; }

}

@media (prefers-reduced-motion: reduce){
 body.vt .drawer{ transition: none; }
 }


/* ── Setup guide (Xero pattern Andre asked us to steal) ─────────────────────────── */
body.vt .setup-guide{ margin-bottom: 18px; }

body.vt .setup-head{ display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }

body.vt .setup-head h3{ margin: 0; }

body.vt .setup-count{ color: var(--muted); font-size: 13px; }

body.vt .setup-head .btn{ margin-left: auto; }

body.vt .setup-cards{ display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; margin-top: 12px; }

body.vt .setup-card{ background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; text-align: left; cursor: pointer; display: flex; flex-direction: column; gap: 6px; }

body.vt .setup-card:hover{ border-color: var(--brand); }

body.vt .setup-card.done{ opacity: .62; }

body.vt .setup-card-title{ font-weight: 600; color: var(--brand); display: flex; align-items: center; gap: 6px; }

body.vt .setup-card-desc{ color: var(--muted); font-size: 12.5px; line-height: 1.45; flex: 1; }

body.vt .setup-meter{ height: 5px; border-radius: 3px; background: var(--line); overflow: hidden; }

body.vt .setup-meter > i{ display: block; height: 100%; background: var(--brand); border-radius: 3px; }

body.vt .setup-card.done .setup-meter > i{ background: var(--ok); }

body.vt .setup-pct{ font-size: 11.5px; color: var(--muted); }


/* ── Dashboard grids with their own search + status tabs + sortable columns ──────── */
/* styles.css:313 declares `.dash-grid{grid-template-columns:1fr 1fr}`. A grid item's automatic minimum
   is min-content, which IGNORES an inner `.tbl-wrap` scroll container — so a wide table stretches its
   cell and pushes the page sideways (a long-standing dashboard bug, ~6px here, 140px with a wide grid).
   `minmax(0, …)` lets the cell shrink so the table scrolls inside its own wrapper instead. */
body.vt .dash-grid{ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

@media (max-width: 820px){
 body.vt .dash-grid{ grid-template-columns: minmax(0, 1fr); }
 }


/* Collapsible grid panels — <details>/<summary>, so the browser handles show/hide + keyboard. */
body.vt .dash-grid-panel{ margin-bottom: 22px; }

body.vt .dash-grid-panel:last-child{ margin-bottom: 0; }

body.vt .dash-grid-summary{ cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px;
  padding: 4px 0; border-radius: 7px; }

body.vt .dash-grid-summary::-webkit-details-marker{ display: none; }
   /* replaced by our own chevron */
body.vt .dash-grid-summary::before{ content: '▸'; color: var(--muted); font-size: 12px; transition: transform .15s ease; }

body.vt .dash-grid-panel[open] > .dash-grid-summary::before{ transform: rotate(90deg); }

body.vt .dash-grid-summary:hover .dash-sub{ color: var(--brand); }

body.vt .dash-grid-summary .dash-sub{ margin: 0; }

body.vt .grid-count{ margin-left: 8px; font-size: 11.5px; font-weight: 600; color: var(--muted);
  background: var(--surface-2); border-radius: 999px; padding: 1px 8px; }

body.vt .dash-grid-panel .list-toolbar{ margin: 6px 0 10px; }

body.vt .dash-grid-panel .search{ max-width: 260px; }

body.vt .dash-grid-panel .segs{ flex-wrap: wrap; }


/* ── Aging legend (the bar itself is gone — the column chart replaced it, and a bucket now
      deep-links to the filtered invoice grid rather than expanding a drill table) ──────────── */
body.vt .age-legend span[role="button"]{ cursor: pointer; }

body.vt .age-legend span[role="button"]:hover b{ text-decoration: underline; }

body.vt .age-healthy{ color: var(--ok); font-size: 12px; }


/* ── Contacts: You owe / They owe + customer/supplier badges ────────────────────── */
body.vt .contact-tags{ display: inline-flex; gap: 4px; }

/* Customer / Supplier badges — filled soft chips, no outline border. Slightly bolder
   letterforms so a small chip still reads (owner Andre 2026-09-17 pass to drop border
   chrome across the contacts screen). */
body.vt .tag-cust,
body.vt .tag-supp{ font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px; border: 0; }

body.vt .tag-cust{ background: var(--brand-faint, rgba(11,168,160,.14)); color: var(--brand, #0BA8A0); }

body.vt .tag-supp{ background: rgba(120,140,140,.14); color: var(--muted, #6b7280); }
   /* a type, not a warning */
body.vt .tag-incomplete{ font-size: 10.5px; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--bad); color: var(--bad); }


/* ── Inline "create contact" row inside a search-selector popup ─────────────────── */
body.vt .ss-create{ border-top: 1px solid var(--line); color: var(--brand); font-weight: 600; }


/* ── Approvals queue (universal document approvals) ─────────────────────────────── */
body.vt .appr-doc{ display: flex; flex-direction: column; gap: 2px; }

body.vt .appr-type{ font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

body.vt .nav-badge{ display: inline-block; min-width: 17px; padding: 0 5px; margin-left: 5px; border-radius: 999px;
             /* warn, not bad: a queue of things to do is attention, not a failure (ladder in styles.css). */
             background: var(--warn); color: var(--brand-ink); font-size: 11px; font-weight: 700; text-align: center; line-height: 17px; }


/* ("+ New" document menu removed from the top bar 2026-09-15 — owner; styles deleted with it) */


/* ── Sales / Purchases overview: Xero's 4-tile status strip ─────────────────────── */
/* `.kpis` in styles.css is repeat(5,1fr); the overview strip has exactly 4 tiles. The mobile guard
   must repeat here, otherwise this rule outranks the 640px 2-column rule and 375px overflows. */
body.vt .kpis.kpis-4{ grid-template-columns: repeat(4, 1fr); }

@media (max-width: 640px){
 body.vt .kpis.kpis-4{ grid-template-columns: 1fr 1fr; }
 }

body.vt .kpi.clickable{ cursor: pointer; }

body.vt .kpi.clickable:hover{ border-color: var(--brand); }


/* Statuses that had no colour rule (quotes + purchase orders) — they rendered as bare grey pills
   on the overview and dashboard tables. Mapped onto the existing semantic colours. */
/* Same ladder as the invoice pills in styles.css — green good, yellow waiting, red bad, grey inert.
   `sent`/`open` were an off-ladder blue and `converted` was sharing a yellow with `part_received`,
   which said "needs attention" about a document that had already done its job. */
body.vt .pill-sent,
body.vt .pill-open{ background: rgba(230,180,90,.15); color: var(--warn); }
        /* awaiting a reply */
body.vt .pill-part_received{ background: rgba(230,180,90,.15); color: var(--warn); }
           /* half delivered */
body.vt .pill-accepted,
body.vt .pill-received{ background: rgba(75,214,160,.15); color: var(--ok); }
  /* good outcome */
body.vt .pill-declined,
body.vt .pill-cancelled{ background: rgba(242,122,122,.15); color: var(--bad); }

body.vt .pill-converted{ background: var(--neutral-bg); color: var(--neutral); }
               /* done its job — inert */

/* ── Per-list action row (Xero puts New X ▾ / New Repeating / New Credit Note here) ─ */
body.vt .list-actions{ margin-bottom: 12px; }


/* ── List toolbar: date range + created-by ──────────────────────────────────────── */
body.vt .filter-dates{ display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

body.vt .filter-dates label{ display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--muted); }

body.vt .filter-dates input[type="date"]{ padding: 5px 7px; }

body.vt .by-cell{ font-size: 12px; color: var(--muted); }


/* (Expenses reuses the existing .cell-sub / .pill / .tbl-wrap styles — no new CSS needed.) */

/* ── Bill dropzone (drag & drop a supplier invoice) ─────────────────────────────── */
body.vt .dropzone{ border: 2px dashed var(--line); border-radius: 12px; padding: 22px 16px; text-align: center; color: var(--muted); cursor: pointer; }

body.vt .dropzone.over{ border-color: var(--brand); color: var(--brand); background: var(--surface-2); }

body.vt .dropzone-title{ font-weight: 600; color: var(--ink); margin-bottom: 4px; }

body.vt .dropzone-hint{ font-size: 12.5px; }


/* ── Editor line grid: the new Account column (Xero parity) ─────────────────────── */
/* Rebalanced from styles.css:149 (desc 34% / num 9% / unit 8% / vat 13%) to make room. */
body.vt table.lines .c-desc{ width: 27%; }

body.vt table.lines .c-acct{ width: 12%; }

body.vt table.lines .c-vat{ width: 11%; }

body.vt table.lines .c-unit{ width: 7%; }


/* ── Form action rows must wrap on phones ───────────────────────────────────────── */
/* styles.css declares .form-actions as flex with the default nowrap. The credit-note / RTS pickers
   put three buttons + an error span in one row ("Credit the full ZAR 71 300,00 instead" is ~263px
   alone), which dragged the invoice detail 250px past a 375px viewport. Wrapping is the correct
   mobile-first behaviour for every action row, so this is a global override, not a picker-only one. */
body.vt .form-actions{ flex-wrap: wrap; }


/* ── Repeat-last-document prompt in the editor ──────────────────────────────────── */
body.vt .repeat-prompt{ display: flex; align-items: center; gap: 10px; flex-wrap: wrap; background: var(--surface-2);
                 border: 1px solid var(--brand); border-radius: 9px; padding: 9px 12px; margin-bottom: 10px; font-size: 13px; }

body.vt .repeat-prompt .grow{ flex: 1; }


@media (max-width: 640px){

  body.vt .setup-cards{ grid-template-columns: 1fr; }

  /* styles.css:120 sets `.fld.inline > span { white-space: nowrap }`. On a phone a long inline label
     (e.g. the GRNI toggle) then has a min-content wider than the column and drags the whole .grid2
     past the viewport — a real 235px overflow on Settings. Let those labels wrap on small screens;
     desktop keeps the tidy single-line look. */
  body.vt .fld.inline > span{ white-space: normal; }

}


/* ── Charts (src/charts.js): Xero-style column chart + compact trend strip ───────── */
/* Owner, 2026-07-28 — our stacked aging bar is "just not familiar, what I'm familiar with";
   Xero's home charts are "a more acceptable… familiar is the word", and useful because
   "you can say, gee, our sales have fallen this month". Plain divs, no chart library.
   Bar heights are stamped by charts.js after render (same trick as .age-seg widths). */

/* The chart scrolls INSIDE its own box: 12 monthly columns × 64px minimum cannot fit a
   375px phone, and a card must never drag the page sideways. */
body.vt .chart-scroll{ overflow-x: auto; overflow-y: hidden; padding-bottom: 2px; }

body.vt .chart-col-plot{ display: flex; align-items: flex-end; gap: 6px; min-width: min-content; padding: 4px 2px 0; }

/* 64px = the width a full money() value needs before it ellipsizes; also a fair touch target. */
body.vt .chart-col-item{ flex: 1 1 0; min-width: 64px; display: flex; flex-direction: column; gap: 4px; }

/* Definite height — this is what the bar's percentage resolves against. Bottom-aligned, so the
   value label rides directly on top of the bar and moves with it. */
body.vt .chart-col-track{ height: 132px; display: flex; flex-direction: column; justify-content: flex-end; gap: 3px; }

body.vt .chart-col-value{ flex: 0 0 auto; font-size: 10.5px; line-height: 1.1; color: var(--muted);
  text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

body.vt .chart-col-bar{ flex: 0 0 auto; min-height: 3px; border-radius: 5px 5px 2px 2px; background: var(--brand); }

body.vt .chart-col-bar-zero{ background: var(--line); }
        /* zero / all-zero → flat baseline */
body.vt .chart-col-bar[role="button"]{ cursor: pointer; }

body.vt .chart-col-bar[role="button"]:hover{ filter: brightness(1.2); }

body.vt .chart-col-bar:focus-visible{ outline: 2px solid var(--brand); outline-offset: 2px; }

body.vt .chart-col-label{ font-size: 10.5px; color: var(--faint); text-align: center; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; border-top: 1px solid var(--line); padding-top: 4px; }


/* Per-bar accents a caller can pass as `cls` (declared after .chart-col-bar so they win). */
body.vt .chart-bar-ok{ background: var(--ok); }

body.vt .chart-bar-warn{ background: var(--warn); }

body.vt .chart-bar-bad{ background: var(--bad); }

body.vt .chart-bar-muted{ background: var(--muted); }

body.vt .chart-bar-warn2{ background: var(--warn-2); }
   /* escalation step between warn and bad */

/* miniTrend — sparkline strip + latest value + period-on-period change. */
body.vt .chart-mini{ display: flex; flex-direction: column; gap: 5px; }

body.vt .chart-mini-bars{ display: flex; align-items: flex-end; gap: 3px; height: 34px; }

body.vt .chart-mini-bar{ flex: 1 1 0; min-width: 3px; min-height: 2px; border-radius: 2px; background: var(--brand); opacity: .55; }

body.vt .chart-mini-bar-last{ opacity: 1; }
                    /* the period being talked about */
body.vt .chart-mini-bar-zero{ background: var(--line); opacity: 1; }

body.vt .chart-mini-foot{ display: flex; align-items: baseline; gap: 8px; font-size: 11.5px; color: var(--muted); flex-wrap: wrap; }

body.vt .chart-mini-last{ color: var(--ink); font-weight: 600; }

body.vt .chart-mini-delta{ font-weight: 700; }

/* Reads correctly for money coming IN; a money-going-OUT caller can flip it with its own class. */
body.vt .chart-mini-up{ color: var(--ok); }

body.vt .chart-mini-down{ color: var(--bad); }

body.vt .chart-mini-flat{ color: var(--muted); }


body.vt .chart-empty{ color: var(--muted); font-size: 12px; padding: 10px 0; }


@media (max-width: 640px){

  body.vt .chart-col-track{ height: 108px; }

  body.vt .chart-col-value{ font-size: 10px; }

}


/* ── Dashboard tables: NO horizontal scrollbar, everything visible ────────────────── */
/* Owner, 2026-07-28: "the grids on the dashboard must not have a scroll bar, they must all be fully
   visible." So inside dashboard cards we (a) drop the scroll container, and (b) let cells WRAP so the
   columns compress to fit instead of being clipped. Document-list screens keep their scrolling tables —
   this override is scoped to the dashboard only. */
body.vt .page .card .tbl-wrap{ overflow-x: visible; }

body.vt .page .card table.list{ table-layout: auto; width: 100%; }

/* Wrapping is what makes "fit without scrolling" possible — `nowrap` cells are the thing that forces
   a table wider than its container, so they must yield inside a dashboard card. */
body.vt .page .card table.list th,
body.vt .page .card table.list td,
body.vt .page .card table.list td.nowrap,
body.vt .page .card table.list th.nowrap{ white-space: normal; overflow-wrap: anywhere; }

body.vt .page .card table.list th,
body.vt .page .card table.list td{ padding-left: 8px; padding-right: 8px; }

/* Action buttons stack rather than forcing a wide column. */
body.vt .page .card table.list td.nowrap .btn{ margin: 1px 0; }


/* Under 760px a 7-column table cannot be readable AND fully visible, so each row restacks into
   label: value pairs — nothing hidden, nothing scrolls. Labels come from the `data-label` that
   dashGrid stamps on every cell. */
@media (max-width: 760px){

  body.vt .dash-grid-panel table.list thead{ position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap; }
         /* visually hidden, still read by screen readers */
  body.vt .dash-grid-panel table.list,
body.vt .dash-grid-panel table.list tbody,
body.vt .dash-grid-panel table.list tr,
body.vt .dash-grid-panel table.list td{ display: block; width: auto; }

  body.vt .dash-grid-panel table.list tr{ border: 1px solid var(--line); border-radius: 9px;
    padding: 8px 10px; margin-bottom: 8px; background: var(--surface-2); }

  body.vt .dash-grid-panel table.list td{ display: flex; gap: 10px; align-items: baseline;
    border: 0; padding: 3px 0; text-align: left !important; }

  body.vt .dash-grid-panel table.list td::before{ content: attr(data-label); flex: 0 0 40%;
    font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
    color: var(--faint); }

  body.vt .dash-grid-panel table.list td.ta-r{ justify-content: flex-start; }

  body.vt .dash-grid-panel table.list td:empty{ display: none; }
   /* e.g. an empty Action cell */
}


/* ZAR-only (owner 2026-07-29: "this app must be only ZAR for now"). The editor's currency picker
   became a fixed readout, so it needs to look like a settled value rather than a disabled input. */
/* A read-only field value that must sit on the same line and height as the sibling inputs so a field
   row stays aligned (owner 2026-08-28). Single line, vertically centred; any detail rides inline as a
   faint suffix rather than a second line that would make this field taller than its neighbours. */
body.vt .fixed-val{display:flex;align-items:center;gap:7px;padding:0 2px;font-weight:600;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

body.vt .fixed-val .fv-sub{font-weight:400;font-size:11.5px;color:var(--faint);overflow:hidden;text-overflow:ellipsis}

/* #9: the document currency is locked to the selected contact's currency (display-only) */
body.vt .fixed-val.cur-locked{color:var(--ink)}
body.vt .fixed-val .cur-lock{font-size:11px;opacity:.5;font-weight:400}


/* Totals strip under a deep-linked grid's banner (owner 2026-07-29: "provide totals for both … it can
   be just below the show all invoices message"). The figure the chart was built from is highlighted so
   the reconciliation is unambiguous — sales matches Total invoiced, aging matches Amount due. */
body.vt .focus-totals{ display: flex; flex-wrap: wrap; gap: 8px 22px; align-items: flex-end;
  margin-top: 11px; padding-top: 11px; border-top: 1px solid var(--line); }

body.vt .focus-total{ display: flex; flex-direction: column; gap: 2px; }

body.vt .focus-total small{ color: var(--faint); font-size: 11px; }

body.vt .focus-total b{ font-size: 15px; }

/* "This is the chart's figure" is EMPHASIS, not meaning — so it gets weight and a ring, never a
   colour. Colour on these figures is reserved for the ladder below, or the 60+ bucket's overdue debt
   ends up painted brand-teal (reads as green) while being the worst number on the screen. */
body.vt .focus-total.is-match{ padding: 2px 9px 3px; margin: -2px -3px -3px; border-radius: 8px;
  background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--line); }

body.vt .focus-total.is-match small{ font-weight: 700; }

body.vt .focus-total.is-match b{ font-weight: 800; }


/* Semantic tones — the same ladder as the pills and the aging chart (see styles.css). Untoned cells
   (Total invoiced, Credited, the invoice count) are neutral facts and stay the default ink. */
body.vt .focus-total.tone-ok b{ color: var(--ok); }

body.vt .focus-total.tone-warn b{ color: var(--warn); }

body.vt .focus-total.tone-warn2 b{ color: var(--warn-2); }

body.vt .focus-total.tone-bad b{ color: var(--bad); }

/* Heading + the way out, on one line above the figures (owner 2026-07-29: "remove the copy around
   the totals, just show the totals"). */
body.vt .focus-head{ display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

body.vt .focus-head b{ font-size: 14px; }

body.vt .focus-head .btn{ margin-left: auto; }


/* ── "History and notes" (owner 2026-07-29) ───────────────────────────────────────────────────────
   Xero's shape, captured from the owner's org: a collapsible panel at the foot of every document AND
   of a contact — chevron + title on the left, "Add note" on the right, then Date · User · Action ·
   Detail newest-first. Action tones follow the ladder in styles.css: bad news red, good news green,
   everything else a neutral fact. */
body.vt .hn-card{ margin-top: 18px; }

body.vt .hn-head{ display: flex; align-items: center; gap: 12px; }

body.vt .hn-toggle{ display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }

body.vt .hn-toggle:hover b{ color: var(--brand); }

body.vt .hn-chev{ color: var(--muted); font-size: 12px; width: 10px; }

body.vt .hn-head .btn{ margin-left: auto; }

body.vt .hn-compose{ margin-top: 12px; }

body.vt .hn-compose textarea{ width: 100%; box-sizing: border-box; }

body.vt .hn-followup{ display: flex; flex-direction: column; gap: 4px; margin-top: 8px; max-width: 220px; font-size: 12.5px; }
body.vt .hn-followup > span{ font-weight: 600; }
body.vt .hn-followup sleek-date{ max-width: 200px; }

body.vt .hn-table{ margin-top: 10px; }

body.vt .hn-date{ white-space: nowrap; }

body.vt .hn-action{ font-weight: 600; }

body.vt .hn-action.hn-bad{ color: var(--bad); }

body.vt .hn-action.hn-ok{ color: var(--ok); }

body.vt .hn-intro{ margin: 10px 0 2px; font-size: 12.5px; }

/* Products & services grouped into category sections */
body.vt .cat-row td{ font-weight: 700; color: var(--brand); border-top: 2px solid var(--line); padding-top: 12px; }

body.vt .cat-row .grid-count{ color: var(--muted); }

/* Inline category rename (Categories tab) + the "N items → Uncategorised" delete warning. */
body.vt .cat-rename{ padding: 5px 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); font: inherit; width: 100%; max-width: 240px; }

body.vt .cat-del-warn{ color: var(--warn); font-size: 12px; margin-right: 6px; white-space: normal; }

/* "Investigate" — the accounting/ledger deep-dive tucked under a magnifying-glass toggle */
body.vt .investigate{ margin-top: 18px; }

body.vt .investigate > .investigate-summary{ cursor: pointer; user-select: none; display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 15px; padding: 10px 0; list-style: none; }

body.vt .investigate-summary::-webkit-details-marker{ display: none; }

body.vt .investigate-summary::marker{ content: ''; }

body.vt .investigate-summary .mag{ font-size: 18px; }

body.vt .investigate-summary small{ font-weight: 400; color: var(--muted); }

body.vt .investigate-summary:hover{ color: var(--brand); }

body.vt .investigate[open] > .investigate-summary{ border-bottom: 1px solid var(--line); margin-bottom: 8px; }

/* Investigate flags issues BY the control, coloured by severity — orange = attention, red = a real
   error. Neutral (muted) when the document is clean, so it's always available without alarming. */
body.vt .investigate--warn > .investigate-summary,
body.vt .investigate--warn > .investigate-summary .mag{ color: var(--warn); }

body.vt .investigate--bad > .investigate-summary,
body.vt .investigate--bad > .investigate-summary .mag{ color: var(--bad); }

body.vt .investigate--warn > .investigate-summary:hover,
body.vt .investigate--bad > .investigate-summary:hover{ filter: brightness(1.08); }

body.vt .inv-flag{ font-weight: 700; font-size: 12px; padding: 2px 10px; border-radius: 999px; }

body.vt .inv-flag--warn{ color: var(--warn); background: rgba(230,180,90,.16); }

body.vt .inv-flag--bad{ color: var(--bad); background: rgba(242,122,122,.16); }

/* The issues themselves, listed at the top of the panel and highlighted red/orange by seriousness */
body.vt .inv-issues{ display: flex; flex-direction: column; gap: 8px; margin: 2px 0 14px; }

/* Each issue is a BUTTON — clicking it jumps to + flashes the grid that evidences it. */
body.vt .inv-issue{ display: flex; flex-direction: column; gap: 3px; width: 100%; text-align: left; cursor: pointer;
  padding: 10px 12px; border: 1px solid transparent; border-left-width: 3px; border-radius: 6px; font: inherit; font-size: 13px; }

body.vt .inv-issue:hover{ filter: brightness(1.06); }

body.vt .inv-issue:focus-visible{ outline: 2px solid var(--brand); outline-offset: 2px; }

body.vt .inv-issue-hd{ font-weight: 700; }

body.vt .inv-issue-hint{ font-weight: 400; opacity: .85; }

body.vt .inv-issue-go{ font-weight: 700; font-size: 12px; opacity: .9; }

body.vt .inv-issue--warn{ color: var(--warn); background: rgba(230,180,90,.10); border-color: rgba(230,180,90,.35); border-left-color: var(--warn); }

body.vt .inv-issue--bad{ color: var(--bad); background: rgba(242,122,122,.10); border-color: rgba(242,122,122,.35); border-left-color: var(--bad); }

/* The chip target: scroll it in and flash a ring + tint, coloured by the issue's severity. */
body.vt .acc-sec{ scroll-margin-top: 16px; border-radius: 8px; }

body.vt .acc-flash{ animation: accFlashWarn 2.6s ease-out; }

body.vt .acc-flash.acc-flash--bad{ animation: accFlashBad 2.6s ease-out; }

@keyframes accFlashWarn{ 0%, 55% { box-shadow: 0 0 0 2px var(--warn) inset; background: rgba(230,180,90,.12); } 100% { box-shadow: 0 0 0 0 rgba(0,0,0,0) inset; background: transparent; } }

@keyframes accFlashBad{ 0%, 55% { box-shadow: 0 0 0 2px var(--bad) inset;  background: rgba(242,122,122,.12); } 100% { box-shadow: 0 0 0 0 rgba(0,0,0,0) inset; background: transparent; } }

/* Plain-language bad-debt explanation sitting above its ledger entries. */
body.vt .baddebt-explain{ margin: 8px 0 10px; padding: 10px 12px; border-radius: 6px; background: rgba(230,180,90,.08); border-left: 3px solid var(--warn); font-size: 13px; line-height: 1.5; }

/* Accounts-Receivable trail: the running "owed" balance of one invoice from raise → payments → write-off. */
body.vt .ar-trail th,
body.vt .ar-trail td{ font-size: 12.5px; }

body.vt .ar-trail td:last-child{ font-weight: 600; }

body.vt .ar-trail .ar-raise td{ color: var(--ink); }

body.vt .ar-trail .ar-writeoff td{ background: rgba(230,180,90,.14); color: var(--warn); font-weight: 700; }

/* Second grid inside the one Bad-debt block — the write-off double entry, sitting under the AR trail. */
body.vt .wo-subhead{ margin-top: 16px; padding-top: 12px; border-top: 1px dashed var(--line); }

/* Bad debt is a loss, so every figure in its one ledger grid reads red (owner 2026-08-31). */
body.vt .pay-journal[data-look="baddebt"] .num{ color: var(--bad); }

body.vt .wo-confirm{ display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }

body.vt .wo-confirm .num{ color: var(--bad); }


/* Approval-state banner on a draft document (editor) + the rejection reason in the Approvals grid. */
body.vt .appr-banner{ display: flex; gap: 12px; align-items: flex-start; margin: 0 0 16px; padding: 12px 14px; border-radius: 8px; border: 1px solid var(--line); }

body.vt .appr-banner .appr-banner-icon{ font-size: 20px; line-height: 1.1; }

body.vt .appr-banner-reason{ margin: 3px 0; font-style: italic; color: var(--ink); }

body.vt .appr-banner-hint{ font-size: 12.5px; color: var(--muted); }

body.vt .appr-banner--rejected{ border-color: var(--bad); background: rgba(242,122,122,.10); }

body.vt .appr-banner--rejected > .appr-banner-icon,
body.vt .appr-banner--rejected b{ color: var(--bad); }

body.vt .appr-banner--pending{ border-color: var(--warn); background: rgba(230,180,90,.10); }

body.vt .appr-banner--pending > .appr-banner-icon,
body.vt .appr-banner--pending b{ color: var(--warn); }

body.vt .appr-reason{ display: inline-block; max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; color: var(--bad); }

/* On-document approve/reject decision (owner 2026-08-31: a pending doc is decided on the document, not
   only from the Approvals list). Sits in the pending banner (editor) and in a card (detail/quote). */
body.vt .appr-decide{ display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }

body.vt .appr-decide-row{ display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }

body.vt .appr-decide-lbl{ font-weight: 600; font-size: 12.5px; color: var(--muted); }

/* The submitter's context note — why the document was created — shown to the approver before they decide. */
body.vt .appr-note{ font-size: 13px; line-height: 1.5; }

body.vt .appr-note-lbl{ display: block; font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-bottom: 2px; }

body.vt .appr-note-body{ font-style: italic; color: var(--ink); }

body.vt .appr-note--empty{ color: var(--faint); font-style: italic; }

body.vt .appr-decide-card{ border-left: 3px solid var(--warn); }

body.vt .appr-decide-card .appr-decide{ margin-top: 10px; }


/* Key/value details panel — the expense (claim) detail view reached by click-through from Approvals /
   Expenses. A two-column grid: label, then value; collapses to one column on a phone. */
body.vt .kv-list{ display: grid; grid-template-columns: max-content 1fr; gap: 7px 20px; margin-top: 8px; align-items: baseline; }

body.vt .kv-k{ color: var(--muted); font-size: 13px; }

body.vt .kv-v{ color: var(--ink); }

@media (max-width: 560px){

  body.vt .kv-list{ grid-template-columns: 1fr; gap: 2px; }

  body.vt .kv-k{ margin-top: 8px; font-weight: 600; }

}


/* The expense form's full-width "note for the approver" field, sitting between the grid and the rebill row. */
body.vt .fld-wide{ margin-bottom: 16px; }

body.vt .fld-wide textarea{ min-height: 52px; }

/* The submitter's note echoed on an Expenses list row so the approver sees the "why" without opening it. */
body.vt .cell-note{ display: block; color: var(--muted); font-size: 11.5px; font-style: italic; margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 62ch; }


/* "Viewed by customer" line under an invoice/quote title — grey until opened, green once the customer opens it. */
body.vt .seen-line{ font-size: 12px; color: var(--muted); margin-top: 3px; }

body.vt .seen-line.seen-yes{ color: var(--ok); font-weight: 600; }

/* Customer-facing invoice pay panel. */
body.vt .pay-due{ font-size: 15px; margin-bottom: 10px; }

body.vt .pay-due .num{ color: var(--ink); font-size: 18px; }


/* Guided empty state with a one-click CTA (first-run Invoices / Quotes). */
body.vt .empty-cta{ text-align: center; padding: 30px 16px; }

body.vt .empty-cta .empty-title{ font-size: 16px; font-weight: 700; color: var(--ink); margin-bottom: 4px; }

body.vt .empty-cta .muted{ margin: 0 auto 14px; max-width: 42ch; }


/* Dashboard "Needs your attention" worklist — the day's next actions in one place. */
body.vt .attention-card{ border-left: 3px solid var(--brand); }

body.vt .attention-list{ display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }

body.vt .attention-row{ display: flex; align-items: center; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--line); }

body.vt .attention-row:last-child{ border-bottom: none; }

body.vt .attention-ico{ font-size: 18px; width: 22px; text-align: center; }

body.vt .attention-text{ flex: 1; }

@media (max-width: 560px){

  body.vt .attention-row{ flex-wrap: wrap; }

  body.vt .attention-text{ flex-basis: 100%; }

}


/* Quick due/expiry-date presets under the date field in the editor. */
body.vt .date-presets{ display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }

body.vt .chip-btn{ border: 1px solid var(--line); background: var(--surface-2); color: var(--muted);
  border-radius: 999px; padding: 3px 10px; font-size: 11.5px; cursor: pointer; line-height: 1.4; }

body.vt .chip-btn:hover{ border-color: var(--brand); color: var(--brand); }


/* ── Inline "add a contact" popover (Xero's flow, captured 2026-07-29) ───────────────────────────
   Anchored under the contact selector on every document, so adding a customer never takes you off the
   document you are writing. Cancel/Save sit on the head row, as they do in Xero. */
body.vt .cqa{ margin-top: 10px; max-width: 620px; }

body.vt .cqa-head{ display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }

body.vt .cqa-head b{ font-size: 14px; }

body.vt .cqa-actions{ margin-left: auto; display: flex; gap: 6px; }

body.vt .cqa .fld{ margin-bottom: 8px; }

body.vt .cqa textarea{ resize: vertical; }

body.vt .cqa-banner{ display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 14px; }

body.vt .cqa-banner-actions{ margin-left: auto; }

/* The chip that survives "Later" — the only way back to a dismissed completion prompt. */
body.vt .cqa-chip{ margin-top: 6px; border-color: var(--warn); color: var(--warn); }

body.vt .cqa-chip:hover{ border-color: var(--warn); filter: brightness(1.15); }


/* ── Attachments ("Files") on every document + contact ──────────────────────────────────────────── */
body.vt .att-card{ margin-top: 18px; }

body.vt .att-zone{ margin-top: 10px; }

body.vt .att-table{ margin-top: 10px; }

body.vt .att-table a{ color: var(--brand); }


/* ── App-bar notification shortcuts (owner 2026-07-29): Expenses + Approvals next to + New ────────── */
body.vt .app-notifs{ display: inline-flex; gap: 6px; align-items: center; }

body.vt .app-notif{ display: inline-flex; align-items: center; gap: 6px; position: relative; }

body.vt .app-notif .nav-ico{ opacity: .85; }

body.vt .app-notif .nav-badge{ margin-left: 2px; }

/* the count badge already uses the ladder's warn colour via .nav-badge (a queue = attention, not failure) */
@media (max-width: 640px){
 body.vt .app-notif-lbl{ display: none; }
 }
   /* icon + count only on a phone */

/* ================================================================================================
   UI pass 2026-08-31 — commercial-product patterns (Xero/QuickBooks/Stripe/FreshBooks/Gmail).
   One merged section: each parallel builder reported its CSS; reconciled + appended here.
   ================================================================================================ */

/* Undo-capable notices (Gmail pattern) — flex added here (base .app-notice in styles.css has none). */
body.vt .app-notice{ display: flex; align-items: center; gap: 10px; }

body.vt .notice-undo{ background: none; border: 1px solid currentColor; border-radius: 6px; color: inherit;
  font: inherit; font-size: 12px; padding: 2px 10px; cursor: pointer; opacity: .9; }

body.vt .notice-undo:hover{ opacity: 1; }


/* Customer pay-page hero (Stripe hosted-invoice pattern). */
body.vt .cust-hero{ display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }

body.vt .cust-hero-logo{ max-height: 42px; max-width: 180px; object-fit: contain; }

body.vt .cust-hero-num{ font-size: 13px; opacity: .75; }

body.vt .cust-hero-amt{ font-size: 30px; font-weight: 700; letter-spacing: .2px; }

body.vt .cust-hero-amt small,
body.vt .cust-hero-due{ font-size: 13px; opacity: .7; font-weight: 400; }

body.vt .cust-trust{ font-size: 12px; opacity: .65; margin-top: 2px; }


/* Compose-before-send dialog (Xero pattern). .fld display comes from the global rule in styles.css —
   only the between-field spacing is added for modals (per the builder caveat, no display override). */
body.vt .mailc-preview{ margin-top: 10px; max-height: 260px; overflow: auto; border: 1px solid var(--line);
  border-radius: 8px; background: #fff; padding: 4px; }
   /* emails are light-themed HTML — stays white */
body.vt .mailc-preview-label{ font-size: 12px; opacity: .7; margin-top: 10px; }

body.vt .modal-panel .fld{ margin-bottom: 8px; }


/* Money-in strip above the invoices list (QuickBooks money bar) — each chip drives the status tabs. */
body.vt .money-strip{ display: flex; gap: 8px; flex-wrap: wrap; margin: 2px 0 10px; }

body.vt .mchip{ display: inline-flex; align-items: center; gap: 8px; padding: 7px 12px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--surface-2); color: var(--ink); cursor: pointer;
  font: inherit; font-size: 12.5px; }

body.vt .mchip:hover{ border-color: var(--brand); }

body.vt .mchip b{ font-size: 13.5px; }

body.vt .mchip.is-overdue b{ color: var(--bad); }

body.vt .mchip.is-soon b{ color: var(--warn); }

body.vt .mchip.is-draft b{ color: var(--muted); }


/* Document lifecycle strip (Stripe/FreshBooks): Issued → Sent → Viewed → Paid, dates on done steps. */
body.vt .lifec{ display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; font-size: 12px; }

body.vt .lifec-step{ display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px;
  border: 1px solid var(--line); border-radius: 999px; color: var(--muted); }

body.vt .lifec-step.done{ color: var(--ok); border-color: var(--ok); }

body.vt .lifec-step.now{ font-weight: 700; }

body.vt .lifec-step small{ font-weight: 400; opacity: .8; margin-left: 2px; }


/* Editor sticky action bar (Xero keeps Save/Approve visible) + autosave chip + money-input alignment. */
body.vt .editor-stickybar{ position: sticky; bottom: 0; z-index: 5; display: flex; align-items: center;
  justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 14px; padding: 10px 14px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 -6px 18px rgba(0, 0, 0, .18); }

body.vt .editor-stickybar .row-actions{ display: flex; gap: 8px; flex-wrap: wrap; }

body.vt .sticky-total{ font-size: 13px; color: var(--muted); }

body.vt .sticky-total b{ color: var(--ink); font-size: 15px; margin-left: 4px; }

body.vt .autosave-chip{ font-size: 12px; color: var(--ok); }

body.vt .editor input.num-input{ text-align: right; font-variant-numeric: tabular-nums; }
/* The Rate (incl.) / Amount cells clipped the last digit of 5-6-figure values: the line cells
   carried a generous 13px side padding, leaving the right-aligned input only ~44px of text room.
   Tighten the line-cell side padding (and the input's own) so a six-figure rate shows in full, and
   suppress the number spin-buttons that otherwise crowd the right edge. (Callan 2026-09-14) */
body.vt table.lines td,
body.vt table.lines th{ padding-left: 7px; padding-right: 7px; }
body.vt table.lines input.in,
body.vt table.lines select.in{ padding-left: 6px; padding-right: 6px; }
body.vt table.lines input[type=number].in{ -moz-appearance: textfield; }
body.vt table.lines input[type=number].in::-webkit-outer-spin-button,
body.vt table.lines input[type=number].in::-webkit-inner-spin-button{ -webkit-appearance: none; margin: 0; }

body.vt .editor-stickybar .sticky-left{ display: flex; align-items: baseline; gap: 10px; min-width: 0; }


/* ================================================================================================
   Document-page pass 2026-08-31 — Xero/Stripe/QuickBooks patterns on the detail pages.
   ================================================================================================ */

/* Xero-style "⋯" overflow menu (actionsmenu.js) */
body.vt .actions-menu{ display: inline-flex; }

body.vt .am-pop{ position: fixed; z-index: 70; min-width: 200px; background: var(--surface); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.35); padding: 5px; display: flex; flex-direction: column; }

body.vt .am-item{ text-align: left; background: none; border: 0; color: var(--ink); font: inherit; font-size: 13px;
  padding: 8px 12px; border-radius: 7px; cursor: pointer; }

body.vt .am-item:hover{ background: var(--surface-2); }

body.vt .am-item:focus-visible{ background: var(--surface-2); outline: 1px solid var(--brand); }

body.vt .am-item.danger{ color: var(--bad); }

body.vt .am-item[disabled]{ opacity: .45; cursor: default; }


/* Stripe/Xero two-column document layout: paper left, sticky rail right; stacks under 1100px. */
body.vt .detail-cols{ display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 16px; align-items: start; }

body.vt .detail-main{ min-width: 0; }

/* top clears the sticky .topbar (its live height is published as --topbar-h by shellWrap; the 62px
   fallback is the desktop single-row height, so the first paint is right before the observer fires). */
body.vt .detail-side{ position: sticky; top: calc(var(--topbar-h, 62px) + 10px); display: flex; flex-direction: column; gap: 12px; }

body.vt .side-card{ margin: 0; }

body.vt .side-card h3{ margin-top: 0; }

body.vt .side-card input[readonly]{ width: 100%; font-size: 12px; margin-bottom: 8px; }

@media (max-width: 1100px){
 body.vt .detail-cols{ grid-template-columns: 1fr; }
 body.vt .detail-side{ position: static; }
 }

@media print{
 body.vt .detail-cols{ display: block; }
 }


/* Balance card (Xero's amount-due panel + QuickBooks "Receive payment") */
body.vt .balance-card .bc-due{ font-size: 14px; color: var(--muted); display: flex; align-items: baseline;
  justify-content: space-between; gap: 8px; }

body.vt .balance-card .bc-due b{ font-size: 22px; color: var(--ink); }

body.vt .bc-rows{ display: flex; flex-direction: column; gap: 4px; margin: 8px 0; font-size: 13px; color: var(--muted); }

body.vt .bc-rows span{ display: flex; justify-content: space-between; gap: 8px; }

body.vt .bc-rows b{ color: var(--ink); }

/* align-items:stretch resets the flex-end inherited from .pay-form (styles.css) so the fields fill the
   card width and their labels/values left-align (owner 2026-09-01). */
body.vt .side-payform{ display: flex; flex-direction: column; align-items: stretch; gap: 8px; margin-top: 8px;
  padding-top: 10px; border-top: 1px solid var(--line); }

body.vt .side-payform .fld{ align-items: stretch; text-align: left; }

body.vt .side-payform .btn[type=submit]{ width: 100%; }

body.vt .bc-markpaid{ margin-top: 8px; width: 100%; }


/* Contextual banners (QuickBooks/FreshBooks) */
body.vt .hint-banner{ border: 1px solid var(--line); background: var(--surface-2); color: var(--muted);
  border-radius: 10px; padding: 9px 13px; font-size: 13px; margin-bottom: 12px; }

body.vt .ovd-banner{ border: 1px solid var(--bad); background: color-mix(in srgb, var(--bad) 12%, transparent);
  color: var(--ink); border-radius: 10px; padding: 9px 13px; font-size: 13px; margin-bottom: 12px;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }


/* "How to pay" banking block on the invoice paper */
body.vt .doc-paybox{margin-top:18px;padding-top:12px;border-top:1px solid #d5e0e0;break-inside:avoid}

body.vt .doc-pay-tbl{border-collapse:collapse}

body.vt .doc-pay-tbl td{font-size:12px;padding:2px 12px 2px 0;border:none;color:#1a2b2b}

body.vt .doc-pay-tbl td:first-child{color:#6a7d7d;text-transform:uppercase;font-size:10px;letter-spacing:.08em;padding-right:14px}

body.vt .doc-pay-note{font-size:10.5px;color:#6a7d7d;font-style:italic;margin-top:6px}


/* Wave 2 (2026-08-31): Xero's derived "Expired" quote state — same alarm tone as overdue. */
body.vt .pill-expired{color:var(--bad);background:rgba(242,122,122,.15)}


/* Wave 3 (2026-08-31): Xero contact-screen aging chips on the customer statement card. */
body.vt .aging-chips{ display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 10px; }

body.vt .aging-chip{ display: inline-flex; flex-direction: column; gap: 1px; padding: 5px 10px; border: 1px solid var(--line);
  border-radius: 8px; background: var(--surface-2); }

body.vt .aging-chip small{ font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

body.vt .aging-chip b{ font-size: 13px; color: var(--ink); }

body.vt .aging-chip--warn b{ color: var(--warn); }

body.vt .aging-chip--bad b{ color: var(--bad); }


/* ── Status-visibility pass 2026-09-01 — the user is never left guessing ─────────────────────────── */
/* Amber cousin of .ovd-banner: urgent-but-not-broken (quote expiring soon, delivery late). */
body.vt .warn-banner{ border: 1px solid var(--warn); border-left-width: 4px; background: var(--surface-2);
  color: var(--ink); border-radius: 10px; padding: 10px 14px; margin: 10px 0; font-size: 13px; }

/* Sent/Viewed micro-line under a list row's status pill (Xero's list indicators). 12px = a11y floor. */
body.vt .sent-line{ font-size: 12px; color: var(--muted); margin-top: 3px; white-space: nowrap; cursor: default; }

body.vt .sent-line.sl-ok{ color: var(--ok); }

body.vt .sent-line.sl-warn{ color: var(--warn); }

body.vt .sent-line.sl-bad{ color: var(--bad); font-weight: 650; }


/* Client activity feed (dashboard) — rows echo the attention-row anatomy; the ROW is the click target */
body.vt .cact-list{ display: flex; flex-direction: column; }

body.vt .cact-row{ display: flex; align-items: center; gap: 10px; padding: 8px 6px;
  border-top: 1px solid var(--line); border-radius: 6px; cursor: pointer; }

body.vt .cact-row:first-child{ border-top: 0; }

body.vt .cact-row:hover,
body.vt .cact-row:focus-visible{ background: var(--surface-2); }

body.vt .cact-head{ display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }

body.vt .cact-head h3{ margin: 0; flex: 0 1 auto; }

body.vt .cact-head [data-cact-actions]{ margin-left: auto; display: inline-flex; align-items: center; gap: 6px; }

/* Filter rows under the header (owner 2026-09-01): the STATUS tabs sit directly below the header —
   "below the header where all documents is" — with the document-type tabs on their own row beneath;
   the search moved up beside the title ("put the search next to the header"). */
body.vt .cact-subhead{ display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 8px; }

body.vt .cact-search{ max-width: 250px; font-size: 12.5px; padding: 5px 10px; }


/* Collapsible dashboard blocks (owner 2026-09-01: "make client activity and needs your attention
   blocks collapsable") — the header is the toggle; folded cards keep a small count so they still
   say how much they hide. State persists per browser (sleek-dash-coll). */
body.vt .coll-toggle{ cursor: pointer; user-select: none; }

body.vt .coll-ico{ display: inline-block; width: 16px; color: var(--muted); font-size: 12px; }

body.vt .coll-n{ display: none; color: var(--muted); font-weight: 500; font-size: 12.5px; margin-left: 8px; }

body.vt .dash-coll.is-collapsed .coll-n{ display: inline; }

body.vt .dash-coll.is-collapsed .coll-body,
body.vt .dash-coll.is-collapsed .cact-search,
body.vt .dash-coll.is-collapsed [data-cact-actions]{ display: none !important; }

body.vt .dash-coll.is-collapsed .cact-head,
body.vt .dash-coll.is-collapsed h3{ margin-bottom: 0; }

body.vt .segs-inline{ margin: 0; }

body.vt .segs-inline .seg{ padding: 3px 9px; font-size: 12px; }

body.vt .cact-confirm{ font-size: 12px; color: var(--muted); margin-right: 2px; }

body.vt .cact-scroll{ max-height: 430px; overflow-y: auto; }

body.vt .cact-foot{ display: flex; align-items: center; gap: 10px; padding-top: 8px; color: var(--muted); font-size: 12px; }

body.vt .cact-ico{ flex: 0 0 auto; width: 1.6em; text-align: center; }

body.vt .cact-text{ flex: 1 1 auto; min-width: 0; color: var(--ink); }

body.vt .cact-when{ flex: 0 0 auto; color: var(--muted); font-size: 12px; white-space: nowrap; }

/* Per-row status cluster: the doc's current pill + the LIFECYCLE-STRIP capsules (● done / ○ pending,
   owner 2026-09-01) so a row filtered to one kind still shows what else was fulfilled — compacted to
   fit the 38px feed rows, and never wrapping. */
body.vt .cact-badges{ display: flex; flex-wrap: nowrap; align-items: center; gap: 6px; flex: 0 0 auto; white-space: nowrap; margin-top: 0; }

body.vt .cact-badges .lifec-step{ padding: 1px 8px; white-space: nowrap; }

body.vt .lifec-step.bad{ color: var(--bad); border-color: var(--bad); }


/* Customer pay page: partial-payment amount next to Pay now (UX research 2026-09-01). */
body.vt .cust-payrow{ display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }

body.vt .cust-amt-fld{ max-width: 200px; margin: 0; }

body.vt .cust-amt-fld input{ text-align: right; font-weight: 600; }

body.vt .cust-amt-hint{ font-size: 12px; margin-top: 6px; }


/* ── List-UX pass 2026-09-01 (industry sweep: Xero/QBO/FreshBooks/Zoho/Stripe + NN/g) ───────────── */
/* Sticky header: the list tables scroll INSIDE a height-capped wrapper — position:sticky can never
   fire against the window once the wrapper has overflow-x:auto, so the wrapper becomes the scroller.
   Bonus: the pager and bulk bar stay on screen while the rows scroll. */
body.vt .tbl-sticky{ max-height: calc(100vh - 260px); min-height: 260px; overflow: auto; }

body.vt .tbl-sticky thead th{ position: sticky; top: 0; background: var(--surface-2, var(--surface)); z-index: 5;
  box-shadow: 0 1px 0 var(--line); }


/* Compact density (Xero shipped exactly this on its newest list, Oct 2025). 12px stays the floor. */
body.vt .page.dense .list th,
body.vt .page.dense .list td{ padding: 5px 8px; }

body.vt .page.dense .list{ font-size: 12.5px; }

body.vt .page.dense .tbl-sticky{ max-height: calc(100vh - 220px); }


/* Keyboard list navigation (j/k/Enter/x — the Gmail/Stripe/Linear convention). */
body.vt .list tbody tr.kb-focus{ outline: 2px solid var(--brand); outline-offset: -2px; }

body.vt .list tbody tr.kb-focus td{ background: rgba(42, 224, 224, .06); }


/* Cell subtexts: overdue-days on the invoices Due-date column, expiry countdown on quotes.
   12px (not the base 11px) — the a11y floor. */
body.vt .cell-sub.sub-bad{ color: var(--bad); font-size: 12px; font-weight: 600; }

body.vt .cell-sub.sub-warn{ color: var(--warn); font-size: 12px; font-weight: 600; }


/* Row-level actions: contextual primary + ⋯ overflow, right-aligned, never wrapping. */
body.vt td.row-act .btn{ margin-left: 6px; }

body.vt td.row-act .btn:first-child{ margin-left: 0; }

body.vt .btn.sm.am-trigger{ padding: 2px 9px; font-weight: 700; }


/* Wide list screens (owner 2026-09-01: "show the full page if the browser allows, users must not
   need to scroll left and right") — the document LISTS get a wider cap so their tables fit; detail
   pages keep the 1000px reading width. Horizontal scroll remains only when the window is small. */
body.vt .page.page-wide{ max-width: 1440px; }


/* Uniform list stack (owner): toolbar → money strip → status tabs → table, with air between the
   tabs and the table header on every list (invoices, quotes, bills — tabs were touching the grid). */
body.vt .segs.segs-list{ margin: 4px 0 14px; }

body.vt .money-strip{ margin-bottom: 10px; }


/* Scan-a-document chooser: the kind tabs and the Choose-a-file button were sitting flush against
   the dropzone (owner). */
body.vt .scan-chooser .segs{ margin: 10px 0 12px; }

body.vt .dropzone ~ .row-actions{ margin-top: 10px; }


/* The Apply-credit button sat flush under "Mark paid in full" (owner). */
body.vt .bc-applycredit{ margin-top: 8px; }


/* Feed rows: the status pill rides the END of the description with a little air (owner 2026-09-01:
   "make a slight space between the OVERDUE … and the status pins — maybe add it to the end of the
   description"), leaving the lifecycle capsules as their own right-hand cluster. */
body.vt .cact-pill{ margin-left: 8px; vertical-align: middle; }


/* ══ List-UX sweep №2 (2026-09-01, owner: "implement all") ══════════════════════════════════════ */

/* Money-strip: the GOOD half of the picture (QBO) — open-but-fine and recently-received tones. */
body.vt .mchip.is-open b{ color: var(--ink); }

body.vt .mchip.is-in b{ color: var(--ok); }


/* Advanced filters panel (Xero's expandable search / FreshBooks Advanced Search). */
body.vt .adv-panel{ display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; margin: -6px 0 14px;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2); }

body.vt .adv-panel.adv-hide{ display: none; }

body.vt .adv-panel .fld.inline{ display: flex; flex-direction: column; gap: 4px; margin: 0; }

body.vt .adv-panel .fld.inline > span{ font-size: 12px; color: var(--muted); }

body.vt .adv-panel input,
body.vt .adv-panel select{ min-width: 130px; }

body.vt .btn.adv-on{ border-color: var(--brand); color: var(--brand); }


/* Expected payment date (Xero's Awaiting-Payment column) — a quiet inline date the row can carry. */
body.vt .exp-input{ background: transparent; border: 1px solid transparent; border-radius: 6px; color: var(--ink);
  font: inherit; font-size: 12.5px; padding: 2px 4px; max-width: 130px; }

body.vt .exp-input:hover,
body.vt .exp-input:focus{ border-color: var(--line); background: var(--surface); }


/* Saved views: ★ tabs after the built-ins + the ☆ save control (Zoho custom views / Polaris). */
body.vt .seg.seg-view{ border-style: dashed; }

body.vt .seg.seg-save{ color: var(--muted); border-style: dashed; }

body.vt .seg .seg-x{ margin-left: 7px; opacity: .7; font-size: 11px; }

body.vt .seg .seg-x:hover{ opacity: 1; color: var(--bad); }


/* Compose-dialog extras: attach checkbox + Send later row (Xero schedule send). */
body.vt .mail-extra{ display: flex; align-items: center; gap: 8px; font-size: 12.5px; margin: 8px 0 0; }

body.vt .mail-later-row{ gap: 12px; }

body.vt .mail-later-row .mail-date{ max-width: 160px; }


/* Batch payment dialog (Xero "Deposit"): the settled-in-full list + shared fields. */
body.vt .bp-hint{ font-size: 12.5px; margin: 0 0 10px; }

body.vt .bp-list{ max-height: 200px; overflow-y: auto; margin-bottom: 12px; }

body.vt .bp-fields{ display: flex; gap: 12px; flex-wrap: wrap; }

body.vt .bp-fields .fld{ flex: 1 1 140px; }


/* Activity peek (QBO/Invoice Ninja slide-over): right panel, timeline rows, meta grid. */
body.vt .peek-overlay{ position: fixed; inset: 0; z-index: 90; background: rgba(0,0,0,.35); opacity: 0; transition: opacity .16s ease; }

body.vt .peek-overlay.open{ opacity: 1; }

body.vt .peek-panel{ position: absolute; top: 0; right: 0; height: 100%; width: 430px; max-width: 92vw;
  background: var(--surface); border-left: 1px solid var(--line); box-shadow: -14px 0 40px rgba(0,0,0,.3);
  padding: 18px 20px 22px; overflow-y: auto; transform: translateX(100%); transition: transform .18s ease; outline: none; }

body.vt .peek-overlay.open .peek-panel{ transform: translateX(0); }

body.vt .peek-head{ position: relative; padding-right: 34px; margin-bottom: 10px; }

body.vt .peek-title{ font-size: 17px; font-weight: 700; display: flex; align-items: center; gap: 10px; }

body.vt .peek-title .pill{ font-size: 11px; }

body.vt .peek-sub{ color: var(--muted); font-size: 12.5px; margin-top: 3px; }

body.vt .peek-x{ position: absolute; top: -2px; right: -4px; background: none; border: none; color: var(--muted);
  font-size: 15px; cursor: pointer; padding: 6px; border-radius: 8px; }

body.vt .peek-x:hover{ background: var(--surface-2); color: var(--ink); }

body.vt .peek-meta{ border: 1px solid var(--line); border-radius: 10px; padding: 4px 12px; margin: 12px 0; }

body.vt .peek-meta-row{ display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }

body.vt .peek-meta-row:last-child{ border-bottom: none; }

body.vt .peek-meta-label{ color: var(--muted); }

body.vt .peek-meta-val{ font-weight: 600; text-align: right; }

body.vt .peek-events{ margin: 12px 0 4px; }

body.vt .peek-ev{ display: flex; gap: 10px; padding: 8px 2px; border-top: 1px solid var(--line); font-size: 12.5px; }

body.vt .peek-ev:first-child{ border-top: none; }

body.vt .peek-ev-ico{ flex: 0 0 20px; text-align: center; }

body.vt .peek-ev-body{ flex: 1 1 auto; min-width: 0; }

body.vt .peek-ev-label{ font-weight: 600; }

body.vt .peek-ev-sub{ color: var(--muted); margin-top: 1px; overflow-wrap: anywhere; }

body.vt .peek-ev-when{ flex: 0 0 auto; color: var(--muted); font-size: 12px; white-space: nowrap; }

body.vt .peek-ev-bad .peek-ev-label{ color: var(--bad); }

body.vt .peek-foot{ display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }


/* Column chooser popover (colchooser.js) — .am-pop's look for a checkbox list. */
body.vt .col-menu{ position: fixed; z-index: 70; min-width: 220px; background: var(--surface);
  border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.35);
  padding: 6px; display: flex; flex-direction: column; }

body.vt .col-head{ font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); padding: 6px 10px 5px; border-bottom: 1px solid var(--line); margin-bottom: 4px; }

body.vt .col-row{ display: flex; align-items: center; gap: 9px; padding: 7px 10px; border-radius: 7px;
  font-size: 13px; color: var(--ink); cursor: pointer; user-select: none; }

body.vt .col-row:hover{ background: var(--surface-2); }

body.vt .col-row input[type="checkbox"]{ margin: 0; flex: none; accent-color: var(--brand); }

body.vt .col-row input[type="checkbox"]:disabled{ opacity: .55; cursor: default; }

body.vt .col-row input[type="checkbox"]:disabled + span{ color: var(--muted); }

body.vt .col-menu .btn.col-reset{ margin: 6px 4px 2px; align-self: flex-start; }


/* Saved-views name dialog (savedviews.js). */
body.vt .view-hint{ font-size: 12px; margin: 6px 0 0; }


/* Statement + delivery-note papers (paperextras.js) — paper palette literals match the paper's own. */
body.vt .stmt-aging{ display: flex; gap: 10px; margin: 16px 0 6px; }

body.vt .stmt-aging .stmt-bucket{ flex: 1; border: 1px solid #e6eeee; border-radius: 8px; padding: 8px 10px; text-align: right; }

body.vt .stmt-aging .num{ font-size: 13px; font-weight: 600; }

body.vt .statement-paper .doc-meta-line{ font-size: 12px; }

body.vt .dn-signoff{ display: flex; gap: 28px; margin-top: 44px; }

body.vt .dn-signoff .dn-sign{ flex: 1; }

body.vt .dn-signoff .dn-rule{ height: 34px; border-bottom: 1px solid #1a2b2b; margin-bottom: 6px; }


/* Statement picker dialog (reports → Statement: print/email upgrades). */
body.vt .stmt-tools{ display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin: 10px 0 14px; }


/* Printing a document page must yield THE PAPER ONLY — banners, the sticky rail, receiving grids
   and every list control were leaking into PDFs (and Chrome's print emulation made pages look
   full-bleed "landscape" while the dialog was open). */
@media print{

  body.vt .sidebar,
body.vt aside,
body.vt .detail-side,
body.vt .warn-banner,
body.vt .ovd-banner,
body.vt .segs,
body.vt .money-strip,
body.vt .list-toolbar,
body.vt .bulk-bar,
body.vt .pager,
body.vt .list-actions,
body.vt .attention-card,
body.vt .scan-chooser,
body.vt .adv-panel,
body.vt .peek-overlay{ display: none !important; }

  body.vt .detail-main > :not(.invoice-paper){ display: none !important; }

  body.vt .detail-cols{ display: block !important; }

}


/* Keyboard-shortcut sheet (the "?" overlay). */
body.vt .kbd-sheet{ max-width: 440px; }

body.vt .kbd-sheet h3{ margin: 0 0 10px; }

body.vt .kbd-table{ width: 100%; border-collapse: collapse; }

body.vt .kbd-table td{ padding: 7px 8px; border-top: 1px solid var(--line); font-size: 13px; }

body.vt .kbd-table td:first-child{ white-space: nowrap; }

body.vt kbd{ display: inline-block; padding: 1px 6px; border: 1px solid var(--line); border-bottom-width: 2px;
  border-radius: 5px; background: var(--surface-2, var(--surface)); font-family: inherit; font-size: 12px; }



