/* Frontend UX standard — application-level rules shared by tenant screens.
   Keep this sheet last in index.html. It owns layout decisions that should be consistent
   across screens; screen-specific styles stay with the screen's existing brand sheets. */

body.vt{
  --ux-space-1:4px;
  --ux-space-2:8px;
  --ux-space-3:12px;
  --ux-space-4:16px;
  --ux-space-5:24px;
  --ux-list-top-gutter:26px;
  --ux-card-pad-compact:var(--ux-space-3) 14px;
  --ux-card-pad-form:var(--ux-space-4);
  --ux-empty-pad:var(--ux-space-5) var(--ux-space-3);
}

/* Screen headings are navigation anchors, not hero copy. Keep them compact so the first useful
   control remains visible on a phone. */
body.vt .page-head h1,
body.vt h1.page-title{font-size:20px;line-height:1.2;font-weight:750;letter-spacing:-.015em;margin-top:0}
body.vt .eyebrow{display:none}
body.vt .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* One shared previous-place control. It uses the recorded route stack, so a report drill-down
   returns to the exact report/filter the user left rather than a hardcoded dashboard. */
body.vt .nav-pill{display:inline-flex;align-items:center;gap:4px;min-height:28px;padding:4px 9px;
  margin:0 0 8px;border:1px solid var(--line);border-radius:999px;background:var(--surface-2);
  color:var(--ink);font-size:12px;font-weight:650;line-height:1.2;cursor:pointer}
body.vt .nav-pill:hover{border-color:var(--brand);background:var(--brand-faint);color:var(--ink)}
body.vt .nav-pill:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

/* A card owns its internal spacing through .card-body. Removing an outer card inset avoids
   the current 6–8px card inset stacking with Tina4's 16px .card-body inset. */
body.vt .card{padding:0}
body.vt .card-body{padding:var(--ux-card-pad-compact)}
/* Contacts has an older, more specific page rule that adds a second 6–8px inset.
   Keep the same single-inset card contract as Service Items. */
body.vt .ct-page .card{padding:0}

/* `.alert` is the legacy error banner, but several forms correctly add `.alert-ok` after a
   successful save. The generic tenant alert rule was more specific and painted those successes
   red. Keep failures red and make every explicit success banner use the green semantic tone. */
body.vt .alert.alert-ok{background:color-mix(in srgb,var(--ok) 12%,transparent);border-color:var(--ok);color:var(--ok)}

/* Forms and rule editors need a little more breathing room around grouped controls, but still
   use one predictable inset rather than a nested card + body combination. */
body.vt .card-body.ct-edit,
body.vt .card-body.rule-form,
body.vt .card-body.set-form{padding:var(--ux-card-pad-form)}
/* sleek-date owns its inner input and calendar button. Styling the custom-element host as a second
   .form-control creates the double border seen in Batch Invoicing and shifts the icon. */
body.vt sleek-date.form-control{display:block;height:auto;min-height:0;padding:0;border:0;background:transparent;box-shadow:none}
body.vt .doc-page .page-head.with-action{margin-bottom:var(--ux-space-3)}
/* Service Items is the reference list surface. Other list screens opt into this contract rather
   than recreating top padding or action spacing in their templates. */
body.vt .ux-list-page{padding-top:var(--ux-list-top-gutter)}
/* Service Items and Batch Invoices share one list rhythm: action, count, optional form, list. */
body.vt .bt-list-page .page-head.with-action{padding-top:0;padding-bottom:0;margin:0 0 var(--ux-space-2)}
body.vt .bt-list-page .bt-list-tools{justify-content:flex-start}
body.vt .bt-list-page .bt-list-tools .row-actions{order:2}
body.vt .bt-new-card{margin-top:0;margin-bottom:var(--ux-space-3)}
body.vt .bt-new-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--ux-space-3)}
body.vt .bt-new-head h3{margin:0;font-size:16px}
body.vt .bt-new-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--ux-space-3)}
body.vt .bt-count{margin:0 0 var(--ux-space-4);align-self:flex-start}
body.vt .list-count{font-size:16px;line-height:1.2;color:var(--muted)}
body.vt .bt-card .bt-delete{margin-left:auto;display:inline-flex;align-items:center}
body.vt .inv-row .inv-row-delete{align-self:center;margin-left:auto}
body.vt .inv-toolbar{display:flex;flex-direction:column;align-items:flex-start;gap:var(--ux-space-2)}
body.vt .inv-toolbar>.inv-count{margin:0;align-self:flex-start}
/* The single shared list action row: search and primary Add action.  It is the only list
   element that sticks, which keeps Batch Templates, Service Items and Contacts compact. */
body.vt .ux-list-actions{
  display:flex;align-items:center;gap:var(--ux-space-2);width:100%;
  /* Match the landing-page gutter after the row becomes sticky; otherwise it visibly jumps
     upward against the app bar as soon as the list starts moving. */
  position:sticky;top:calc(var(--bar-h) + var(--ux-list-top-gutter));z-index:20;
  background:var(--bg);padding:0;margin:0;
}
body.vt .ux-search{
  display:block;flex:0 0 auto;max-width:320px;width:100%;height:34px !important;min-height:34px;
  box-sizing:border-box;padding:4px 10px 4px 34px !important;font-size:13px;line-height:24px;
}
body.vt .ux-list-actions .ux-search{
  flex:0 0 auto;width:100%;max-width:none;
  margin:0;padding:4px 10px 4px 34px !important;height:34px !important;min-height:34px;
  line-height:24px;box-sizing:border-box;
}
/* Contacts keeps its segment and tag filters in normal flow below the shared action row. */
body.vt .ct-page .ct-count{margin:var(--ux-space-2) 0}
body.vt .ct-page .doc-tabhead{display:flex;flex-direction:column;align-items:stretch;gap:var(--ux-space-2);margin-top:0}
body.vt .ct-page .doc-tabhead>.segs{margin:0;min-width:0}
/* Contacts previously zeroed its page gutter. Use the exact shared list gutter so its toolbar
   and table align with Batch Invoices and Service Items on wide screens. */
body.vt .page.ct-page{padding:26px 22px 80px}
body.vt .page.ct-page.ux-list-page{padding-top:26px}
/* Wide layouts keep the search and primary action together; filters and counts retain a second row. */
@media (min-width:768px){
  body.vt .ux-list-actions .ux-search{flex:1 1 auto;width:auto}
  body.vt .ux-list-actions>.btn.primary,
  body.vt .ux-list-actions .row-actions{flex:0 0 auto}
  body.vt .ux-list-actions .row-actions .btn{width:auto}
}
body.vt .inv-price-chip{margin-right:4px}
body.vt .inv-price-unit{opacity:.65}
body.vt .inv-empty{padding:24px;text-align:center}
body.vt .inv-row-code{font-weight:400}
body.vt .inv-form-title{margin:0}
body.vt .inv-form-intro{font-size:12px;margin-top:2px}
body.vt .inv-form-hint{font-size:12px}
body.vt .inv-prices-hint{font-size:12px;font-weight:400}
body.vt .inv-price-header-rate{text-align:right}
body.vt .inv-no-currencies{padding:16px;text-align:center;border:1px dashed var(--line);border-radius:8px}
body.vt .inv-price-tag.inherit{margin-left:6px}
body.vt .detail-placeholder{padding:32px;text-align:center}
body.vt .detail-placeholder-title{margin:0 0 8px}
body.vt .detail-placeholder-copy{margin:0;opacity:.75}
body.vt .cb-card-file{display:none}
body.vt .cb-vat-inline.is-hidden{display:none}

@media (min-width:768px){
  /* Keep the right side of every service-item card on the same columns: prices share a stable
     right-aligned slot and the destructive action owns the final 28px. */
  body.vt .inv-row-prices{flex:0 0 260px;max-width:260px;justify-content:flex-end}
  body.vt .inv-row-delete{flex:0 0 28px}
}

/* KPI cards already have a compact information hierarchy; preserve their established metric. */
body.vt .kpi .card-body{padding:14px 16px}

/* Dashboard: use the same compact, fluid panel grid as the other list surfaces. The legacy fixed
   five-column grid overflowed as soon as the sidebar reduced the available content width. */
body.vt .dash-page{padding-top:var(--ux-space-4)}
/* Every dashboard section uses the same 14px horizontal inset.  The prior dashboard cards
   inherited zero padding from the shared card reset, leaving each section's header and contents
   to establish their own (often mismatched) gutters. */
body.vt .dash-page .dash-coll>.coll-toggle{margin:0;padding:var(--ux-space-3) 14px;line-height:1.25}
body.vt .dash-page .dash-coll>.coll-body{padding:0 14px 14px}
body.vt .dash-page .dash-coll>.coll-body>.kpis{margin:0}
body.vt .dash-page .dash-coll>.coll-body>.btn{margin-top:var(--ux-space-3)}
body.vt .dash-page .kpis{grid-template-columns:repeat(auto-fit,minmax(155px,1fr));gap:var(--ux-space-3)}
/* Keep long currency figures inside their KPI tile. The auto-fit track is minmax(155px,1fr); a
   grid item's default min-width:auto let an unbreakable figure (e.g. R 16,960,399.55) push the
   track wider and bleed past the tile edge. Let the tile shrink, fluidly scale the value so big
   numbers fit on one line, and allow a wrap as a last resort — never clip a money figure. */
body.vt .dash-page .kpis>.kpi{min-width:0}
body.vt .dash-page .kpi-value{font-size:clamp(14px,1.15vw + 6px,20px);overflow-wrap:anywhere;hyphens:none}
body.vt .dash-page .dash-coll{margin-bottom:var(--ux-space-3)}
/* Financial-year turnover comparison: each fiscal month carries the prior-year and current-year
   turnover bars side by side. The bars are data-driven; layout, sizing and colours stay shared CSS. */
body.vt .dash-fy-chart{margin:0 0 var(--ux-space-3);overflow-x:auto}
body.vt .dash-fy-legend{display:flex;justify-content:flex-end;gap:var(--ux-space-3);margin:0 0 var(--ux-space-2);
  color:var(--muted);font-size:11px;font-weight:700}
body.vt .dash-fy-key{display:inline-flex;align-items:center;gap:5px}
body.vt .dash-fy-key::before{content:"";width:9px;height:9px;border-radius:2px;background:var(--muted)}
body.vt .dash-fy-key.current::before{background:var(--brand)}
body.vt .dash-fy-cols{display:grid;grid-template-columns:repeat(12,minmax(34px,1fr));gap:var(--ux-space-2);min-width:440px}
body.vt .dash-fy-col{display:grid;grid-template-rows:132px auto;gap:var(--ux-space-1);min-width:0}
body.vt .dash-fy-bars{display:flex;align-items:flex-end;justify-content:center;gap:3px;border-bottom:1px solid var(--line)}
body.vt .dash-fy-bar{display:block;width:min(16px,40%);min-height:0;border-radius:3px 3px 0 0;background:var(--muted)}
body.vt .dash-fy-bar.current{background:var(--brand)}
body.vt .dash-fy-bar.neg{background:var(--bad)}
body.vt .dash-fy-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;color:var(--muted);font-size:10px}
/* Shared bank-register hover: cashbook and dashboard allocation rows use the same response without
   turning a whole row into a deceptive click target. */
body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit){transition:background .14s ease,border-color .14s ease,box-shadow .14s ease}
body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit):hover{background:var(--surface-2)}

@media (max-width:1100px){
  /* The bank and activity tables become labeled panels before columns need horizontal scrolling. */
  body.vt .dash-page .dash-bank-table,
  body.vt .dash-page .dash-activity-table{overflow:visible}
  body.vt .dash-page .dash-bank-table table,
  body.vt .dash-page .dash-bank-table tbody,
  body.vt .dash-page .dash-activity-table table,
  body.vt .dash-page .dash-activity-table tbody{display:block;width:100%}
  body.vt .dash-page .dash-bank-table thead,
  body.vt .dash-page .dash-activity-table thead{display:none}
  body.vt .dash-page .dash-bank-table tbody,
  body.vt .dash-page .dash-activity-table tbody{display:grid;gap:var(--ux-space-2)}
  body.vt .dash-page .dash-bank-table tr,
  body.vt .dash-page .dash-activity-table tr{display:grid;gap:var(--ux-space-2);padding:var(--ux-space-3) 14px;
    border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface)}
  body.vt .dash-page .dash-bank-table tr{grid-template-columns:1fr auto}
  body.vt .dash-page .dash-activity-table tr{grid-template-columns:1fr auto}
  body.vt .dash-page .dash-bank-table td,
  body.vt .dash-page .dash-activity-table td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--ux-space-3);
    padding:0;border:0;text-align:left!important;min-width:0}
  body.vt .dash-page .dash-bank-table td:nth-child(2),
  body.vt .dash-page .dash-activity-table td:nth-child(2){grid-column:1/-1}
  body.vt .dash-page .dash-bank-table td::before,
  body.vt .dash-page .dash-activity-table td::before{content:attr(data-label);flex:0 0 auto;color:var(--faint);font-size:10px;
    font-weight:700;letter-spacing:.08em;text-transform:uppercase}
  body.vt .dash-page .dash-bank-table td:empty{display:none}
  body.vt .dash-page .dash-activity-table td:nth-child(2){align-items:flex-start;overflow-wrap:anywhere}
  /* These are cards at this breakpoint. The desktop list hover colours individual cells,
     which creates horizontal bars instead of one clear hover state. */
  body.vt .dash-page .dash-bank-table tr.clickable,
  body.vt .dash-page .dash-activity-table tr.clickable{transition:background .14s ease,border-color .14s ease,box-shadow .14s ease}
  body.vt .dash-page .dash-bank-table tr.clickable:hover,
  body.vt .dash-page .dash-activity-table tr.clickable:hover{background:var(--surface-2);border-color:var(--brand);box-shadow:0 0 0 1px var(--brand-faint)}
  body.vt .dash-page .dash-bank-table tr.clickable:hover td,
  body.vt .dash-page .dash-activity-table tr.clickable:hover td{background:transparent}
}

/* Empty states should explain the next action without taking a full viewport band. */
body.vt .empty{padding:var(--ux-empty-pad)}

/* Service item rows are interactive surfaces. The old fallback was a light-theme colour, which
   made a dark-theme hover flash white and reduced contrast. Use the tenant theme ladder instead. */
body.vt .inv-row:hover,
body.vt .inv-row:focus-visible{
  background:var(--surface-2);
  border-color:var(--brand);
  color:var(--ink);
}
body.vt .inv-row:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
body.vt .inv-row:hover .inv-row-name,
body.vt .inv-row:focus-visible .inv-row-name{color:var(--ink)}

/* Service item editor: the old `--muted-bg` fallback painted the pricing panel white in the
   dark tenant theme. Keep the editor on the same surface ladder as the rest of the application. */
body.vt .inv-form{
  background:var(--surface);
  border-color:var(--line);
  padding:var(--ux-card-pad-form);
}
body.vt .inv-form-close{display:none}
body.vt .inv-prices{
  background:var(--surface-2);
  border-color:var(--line);
  color:var(--ink);
  padding:12px;
}
body.vt .inv-price-row{border-bottom-color:var(--line-soft)}
body.vt .inv-price-tag.inherit{background:rgba(75,214,160,.16);color:var(--ok)}
body.vt .inv-price-row .ss{min-width:0;width:100%}
body.vt .inv-price-row .inv-tax-search{height:32px;box-sizing:border-box}
body.vt .inv-code-input{width:100%;max-width:none}

/* Form actions always read left-to-right as Cancel, then the primary commitment. This keeps
   forms consistent even when a screen's template order is accidentally reversed. */
body.vt .form-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap}
body.vt .form-actions>.btn.btn-primary,
body.vt .form-actions>.btn.primary{order:2}
body.vt .form-actions>.btn:not(.btn-primary):not(.primary){order:1}

/* Windows convention: primary commitment first, Cancel second. macOS and unknown platforms use
   the default above (Cancel first, primary second). Screen markup stays platform-neutral. */
html.platform-windows body.vt .form-actions>.btn.btn-primary,
html.platform-windows body.vt .form-actions>.btn.primary{order:1}
html.platform-windows body.vt .form-actions>.btn:not(.btn-primary):not(.primary){order:2}

@media (max-width:767px){
  body.vt .form-actions{align-items:stretch}
  body.vt .form-actions>.btn{flex:1 1 140px}
}

/* Desktop pricing grids become explicit cards on a phone. Each row gets its own boundary so
   currency, rate, unit and VAT remain grouped while the controls can use the full width. */
@media (max-width:720px){
  body.vt .inv-price-row{
    grid-template-columns:1fr 1fr;
    gap:8px;
    padding:10px;
    border:1px solid var(--line);
    border-radius:var(--radius-sm);
    background:var(--surface);
  }
  body.vt .inv-price-row + .inv-price-row{margin-top:8px}
  body.vt .inv-price-row .inv-price-ccy{margin-bottom:2px}
}

/* Primary create actions are full-width on mobile so the next step is obvious and easy to tap.
   Supporting counts stay secondary and align to the edge below the action. */
@media (max-width:767px){
  body.vt .app-bar .app-title{display:block;font-size:11px;line-height:1.1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  body.vt .bt-list-page .page-head.with-action{padding-top:0;margin-top:0}
  body.vt .bt-list-page>.page-head h1{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
  body.vt .bt-new-grid{grid-template-columns:1fr}
  body.vt .inv-form-close{display:inline-flex;align-items:center;justify-content:center}
  body.vt .inv-row{position:relative;padding-right:48px}
  body.vt .inv-row .inv-row-delete{position:absolute;top:8px;right:8px;margin:0;align-self:auto}
  body.vt .bt-card{position:relative;padding-right:48px}
  body.vt .bt-card .bt-delete{position:absolute;top:8px;right:8px;margin:0}
  body.vt .ct-addr-card{position:relative;padding-right:48px}
  body.vt .ct-addr-card .ct-addr-delete{position:absolute;top:8px;right:8px;margin:0}
  body.vt .doc-page .page-head.with-action .row-actions{width:100%}
  body.vt .doc-page .page-head.with-action .row-actions>.btn{width:100%;justify-content:center}
  body.vt .inv-toolbar{align-items:stretch;gap:8px}
  body.vt .ux-list-actions{flex-direction:column;align-items:stretch;gap:8px}
  body.vt .ux-list-actions>.btn.primary,
  body.vt .ux-list-actions .row-actions,
  body.vt .ux-list-actions .row-actions>.btn{width:100%;justify-content:center}
  body.vt .ux-search{flex:0 0 auto;max-width:320px;width:100%;height:34px !important;min-height:34px}
  body.vt .inv-toolbar>.inv-count{margin-left:0;align-self:flex-start}
}

/* Keep mobile content off the viewport edge while allowing dense cards to stay compact. */
@media (max-width:767px){
  body.vt .page{padding-left:12px;padding-right:12px}
  body.vt .ct-page{padding-left:12px !important;padding-right:12px !important}
}

/* Small-screen contact cards are self-contained surfaces; the card body rule must not add a
   second inset to the table-row cards. */
@media (max-width:767px){
  body.vt .ct-list tr{padding:var(--ux-space-3) 14px}
}

/* The bank register is an endless chronological list. At narrower app widths, keeping nine
   table columns forces every transaction into an unreadable horizontal grid. Render each row as
   a self-contained panel instead, while retaining the same table markup and controls. */
@media (max-width:1100px){
  body.vt .cb-register-wrap{overflow:visible}
  body.vt .cb-register-table,
  body.vt .cb-register-table tbody{display:block;width:100%;border:0;background:transparent}
  body.vt .cb-register-table thead{display:none}
  body.vt .cb-register-table tbody{display:grid;gap:var(--ux-space-2)}
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit){
    position:relative;display:grid;grid-template-columns:auto max-content minmax(0,1fr) auto;
    grid-template-areas:
      "select date ref actions"
      "description description description description"
      "received paid balance balance"
      "source source source source";
    gap:var(--ux-space-2);padding:var(--ux-space-3) 14px;
    border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);
  }
  /* Posted register lines keep an empty selection cell in the shared table markup.
     Remove that grid column on cards so the date aligns with the panel content. */
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit):has(.cb-th-sel:empty),
  body.vt .cb-register-table.bank-inbox-table tbody tr.cb-line:not(.cb-row-edit){
    grid-template-columns:max-content minmax(0,1fr) auto;
    grid-template-areas:
      "date ref actions"
      "description description description"
      "received paid balance"
      "source source source";
  }
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit):hover{border-color:var(--brand);box-shadow:0 0 0 1px var(--brand-faint)}
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit) td{
    display:block;padding:0;border:0;min-width:0;text-align:left;white-space:normal;
  }
  /* aboo.css colours every table cell on hover. These rows are cards, so preserve one
     consistent card surface and use the border as the hover cue. */
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit):hover td{background:transparent}
  /* The unallocated marker used to sit on the first table cell. Put it on the card edge
     now that the first visible cell is the date. */
  body.vt .cb-register-table tbody tr.cb-row-unalloc{border-left:3px solid #b45309}
  body.vt .cb-register-table tbody tr.cb-row-unalloc td:first-child{box-shadow:none}
  body.vt .cb-register-table .cb-th-sel{grid-area:select;align-self:center}
  body.vt .cb-register-table .cb-td-date{grid-area:date;align-self:center;font-weight:650}
  body.vt .cb-register-table .cb-td-ref{grid-area:ref;align-self:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  body.vt .cb-register-table .cb-td-desc{grid-area:description;overflow-wrap:anywhere;line-height:1.35}
  body.vt .cb-register-table .cb-td-act2{grid-area:actions;align-self:center;justify-self:end}
  body.vt .cb-register-table .cb-td-received{grid-area:received}
  body.vt .cb-register-table .cb-td-paid{grid-area:paid}
  body.vt .cb-register-table .cb-td-balance{grid-area:balance;justify-self:end}
  body.vt .cb-register-table .cb-td-source{grid-area:source}
  body.vt .cb-register-table .cb-td-received::before,
  body.vt .cb-register-table .cb-td-paid::before,
  body.vt .cb-register-table .cb-td-balance::before,
  body.vt .cb-register-table .cb-td-source::before{
    content:attr(data-label);display:block;margin-bottom:2px;color:var(--faint);font-size:10px;
    font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  }
  body.vt .cb-register-table .cb-row-opening{grid-template-areas:
    "date date date balance"
    "description description description description"
    "received paid source source"}
  body.vt .cb-register-table .cb-row-opening .cb-th-sel,
  body.vt .cb-register-table .cb-row-opening .cb-td-act2{display:none}
  /* a balance the co-op moved between its sub-accounts keeps its tint as a card */
  body.vt .cb-register-table tbody tr.cb-line.cb-row-move:not(.cb-row-edit){background:var(--brand-faint,rgba(25,196,196,.07))}
}

/* Inline "create account" from the bank GL allocation dropdown (owner 2026-09-21). */
body.vt .cb-drop-add{color:var(--brand);font-weight:600;cursor:pointer;border-top:1px solid var(--line-soft)}
body.vt .cb-create-acc{padding:4px 0}
body.vt .cb-create-row{display:flex;flex-direction:column;gap:3px;padding:5px 10px}
body.vt .cb-create-row label{font-size:11px;font-weight:700;color:var(--faint);text-transform:uppercase;letter-spacing:.05em}
body.vt .cb-create-actions{display:flex;gap:8px;justify-content:flex-end;padding:8px 10px 4px}

/* Register "Jump to statement" nav on the balance bar (owner 2026-09-21). */
body.vt .cb-jump{max-width:210px;height:var(--control-h);padding:2px 8px;font-size:12.5px}

/* Settings forms (Company Details and the other tabs built on .set-stack) use plain .card sections
   with no .card-body, so the card reset above (body.vt .card{padding:0}) left their headings and
   fields flush against the card edges. Give them the form inset. Every control in a settings row
   shares one height (CLAUDE.md rule 9): the plain <select>s (FY start day / month) rendered 32px with
   the native arrow beside 35px searchSelect inputs, so pin inputs, selects and searchSelects to
   --control-h and give the selects the same chevron as the rest of the app. A checkbox field sits on
   the inputs' baseline, not centred between a label and its input. */
body.vt .set-stack .card{padding:var(--ux-card-pad-form)}
body.vt .set-stack select,
body.vt .set-stack .ss-input,
body.vt .set-stack input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]){
  height:var(--control-h);box-sizing:border-box}
body.vt .set-stack select{appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%237A8F93' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 10px center}
body.vt .set-stack .fld.inline{align-self:end;min-height:var(--control-h)}

/* ---------------------------------------------------------------------------------------------
   PHONE FIT (owner 2026-10-19: "everything to fit inside the mobile window but for it to still
   look like functional ui"; "the desktop functionality must not break").
   ONE block, and it stays the LAST rule of this sheet: every phone-only fix of that pass lives
   inside it, so 768px and up is untouched by construction (test/mobile.test.js asserts it), and a
   check can switch the whole pass off by deleting this one rule. Wide tables keep the page fitting
   and scroll sideways inside their own box (owner's choice). Each screen's rules are scoped to
   that screen's own classes. Evidence: sleek-investigations/can-every-screen-fit-a-phone-without-
   changing-desktop/.
   --------------------------------------------------------------------------------------------- */
@media (max-width:767px){
  /* Your companies (src/tenant/home.js). .row-actions (aboo.css) is one row that neither wraps nor
     shrinks: with "Chart of accounts" and "New window" it measured 485-524px inside a 369px card at
     401px, the buttons ran off the card and the phone zoomed the page out to 555px. The actions take
     the card's width and WRAP, buttons share each line (as aboo.css MOBILE STACKING's
     .row-card > .btn), and an armed Reset/Delete confirm wraps on its own line. The "›" moves to
     the card's top-right beside the name (owner's choice): left in the wrapping row it fell onto a
     line of its own at some widths. top/right are the card's padding (12px 14px) and height the
     name's 24px line, so it centres on the name; the name stops 24px short of the edge, 8px clear
     of it. Scoped to .co-card: bare .row-actions is also the tenant app bar's (tshell.js). */
  .co-card{position:relative}
  .co-card > .row-main{padding-right:24px}
  .co-card > .row-actions{flex:1 1 100%;flex-wrap:wrap}
  .co-card > .row-actions > .btn{flex:1 1 auto}
  .co-card > .row-actions > .row-open{position:absolute;top:12px;right:14px;height:24px;display:flex;align-items:center}
  .co-card > .row-actions > .confirm-inline{flex:1 1 100%;flex-wrap:wrap}
  /* Bank rules (src/tenant/bank_rules.js:190). A rule's chips (match type, scope, Apply to existing),
     its edit chip and its delete sit in one .row-actions row only as wide as its content (aboo.css
     .row-actions flex:0 0 auto): at 320px the row measured 336px and ran 43px past the screen. Wrapping
     it inside the card (this pass's first fix) left the delete, and at 320px the edit too, on a line of
     their own under the chips (owner 2026-10-20: buttons stacked). On a phone the row's box steps aside
     (display:contents; vt.css:125 makes it a flex box, hence body.vt) so its buttons place in the card
     itself: the edit and delete at the top right beside the rule's name, the chips on the line below,
     8px apart as in the row. The name keeps all but 96px of the first line: room for the two icons
     (35 + 28px and two 8px gaps = 79px) and never for a chip too (17px left, the narrowest chip is
     50px). While a delete is being confirmed only the edit is up there, so the name keeps all but 60px;
     the "Delete? Yes / No" takes a line of its own at the right, under the chips. */
  .rule-card{column-gap:8px}
  .rule-card > .row-main{flex:1 1 calc(100% - 96px)}
  .rule-card:has(> .row-actions > .confirm-inline) > .row-main{flex-basis:calc(100% - 60px)}
  body.vt .rule-card > .row-actions{display:contents}
  .rule-card > .row-actions > .chip-btn{order:1}
  .rule-card > .row-actions > .chip-btn:has(+ .btn-row-del),
  .rule-card > .row-actions > .chip-btn:has(+ .confirm-inline){order:0}
  .rule-card > .row-actions > .confirm-inline{order:2;flex:1 1 100%;justify-content:flex-end}
  /* Contact detail (src/tenant/contacts.js:940). Its four tabs (.cb-tabs, aboo.css:837) stay on one
     line, which needs ~530px: at 401px "Communications" and "Attachments" ran out of the tab bar and
     the others squeezed until their labels wrapped inside them (62px tall). Letting them wrap (this
     pass's first fix) put each tab on a line of its own at 320px: four lines of tabs. On a phone the four
     are a 2x2 grid of equal tabs. Each draws its own underline, because the bar's line (aboo.css:837)
     would sit under the second row only; the open tab's is the accent, 4px as on desktop. The tabs are 6px
     apart and keep the desktop's 14px: touching (0px, the bar no longer draws the strip) they read as four
     controls jammed together, and 13px changed their type. "Communications (0)" takes two lines at 320px. */
  .ct-tabs{display:grid;grid-template-columns:1fr 1fr;gap:6px;border-bottom:0}
  .ct-tabs > .cb-tab{min-width:0;margin-bottom:0;padding:8px 4px;white-space:normal;border-bottom:2px solid var(--line)}
  .ct-tabs > .cb-tab.active{border-bottom:4px solid var(--aboo-accent,#19C4C4)}
  /* Sales and purchase lists (src/tenant/documents.js:165). In the table's narrow actions cell,
     "Clone" and "Repeat" wrapped and stacked touching (0px apart). Keep them on one line as on
     desktop, 6px further apart than the 3px a space gives. The table already scrolls sideways inside
     its own box (.tbl-wrap), so the wider cell costs nothing. */
  .doc-row-actions{white-space:nowrap}
  .doc-row-actions > .btn + .btn{margin-left:6px}
  /* Document editor (src/tenant/document_editor.js:123). The actions at its top (Create Credit Note,
     Clone, Repeat) are appended by script with no space between them, so the buttons touch. Lay them
     out as a wrapping row with a 6px gap. */
  .de-head-actions{display:flex;flex-wrap:wrap;gap:6px}
  /* Settings > Branding colours (src/tenant/settings.js:1269). The two colour rows share a .grid2
     line (two columns up to 820px, vt.css:815), and a column is never narrower than its content: the
     hex box's natural ~190px. At 401px the second box ran ~120px out of the card. Width 0 lets the
     box shrink to its share of the column; flex:1 1 auto (vt.css:522) grows it back to fill the row. */
  body.vt .bt-color-row > .bt-hex{width:0}
  /* Settings tabs (src/tenant/settings.js, the .set-stack forms). A checkbox label stays on one line
     (.fld.inline > span is nowrap) and vt.css:1208 lets it wrap only up to 640px, so from 641 to 767px
     "Allow bank accounts in other currencies (USD, EUR …)" widened its .grid2 column to 423px and
     pushed the checkbox out of the card and off the screen (127px past it at 641px). Let it wrap on
     every phone width. body.vt keeps this off the admin surface, which also uses .set-stack
     (admin/user_detail.js) but never has body.vt (admin/shell.js:84 removes it). */
  body.vt .set-stack .fld.inline > span{white-space:normal}
  /* Reports (src/tenant/reports.js:1316, every report). The PDF / Excel / CSV / Print buttons sit in a
     row that does not wrap (aboo.css:1491): at 320px they were squeezed to 303.5px inside a 296px
     header and Print stuck out 7.5px. Letting the row wrap (this pass's first fix) left Print alone on a
     second line at 320 and 375px. On a phone the four are a 2x2 grid of equal buttons across the line under the title
     (the header wraps there, aboo.css:593). */
  .rp-actions{display:grid;grid-template-columns:1fr 1fr;flex:1 1 100%}
  /* Income statement (src/tenant/reports.js:962). Each line is a grid of the label and two fixed
     140px money columns (aboo.css:2180) in a card padded 26px a side: at 375px the profit lines ran
     11px past the screen, at 320px 27px. A statement is a table, so it gets the owner's rule for wide
     tables: on a phone it scrolls sideways inside its own card. */
  .rp-is{overflow-x:auto}
  /* Recurring profiles, the "New / Edit recurring profile" modal (src/tenant/recurring.js:297). Its
     lines table (description, qty, price, disc %, account and tax pickers, remove) needs 438px and
     sits straight in the modal card with no scroll box: at 320px it ran 176px past the card, at
     401px 95px. A table, so the owner's wide-table rule: it scrolls sideways inside its own box.
     display:block makes the table element that box; its rows keep their table layout, and where it
     already fits (about 520px and up) its columns keep the same widths. */
  .rec-lines-tbl{display:block;overflow-x:auto}
  /* ...and its line inputs keep about their desktop widths (69-113px at 768-1280px). In the scrolling table they
     shrank to their minimum on a phone (aboo.css:3551 lets them): quantity 23px, price 34px, account 59px wide,
     too small to read what is typed. The table scrolls sideways in its own box instead. */
  .rec-lines-tbl tbody .form-control{min-width:70px}
  .rec-lines-tbl tbody td:first-child .form-control{min-width:110px}
  .rec-lines-tbl tbody .ss-input{min-width:90px}
  /* Dashboard (src/tenant/dashboard.js:1057). The Profit & Loss panel sets "This month" and "Year to date"
     side by side in a 1fr 1fr grid (aboo.css:1523), and a column cannot shrink below its unbroken figures
     ("R 155,292.50 ↑ R 155,292.50" is nowrap): at 320px the pair needed 475px of a 266px card and "Year to
     date" ran to 502px, 182px past the screen (72px at 430). On a phone the two sit side by side while they
     fit and stack when they do not; where they fit, each still takes half of the card. */
  .dash-pnl{display:flex;flex-wrap:wrap}
  .dash-pnl > .dash-pnl-col{flex:1 1 220px}
  /* VAT report, the VAT201 return (src/tenant/reports.js:847). Its boxes table (SARS reference, description,
     amount) needs 390px and sits straight in the return's padded card with no scroll box of its own (the
     transaction tables below it have one): at 320px it ran 97px past the screen and the whole page scrolled
     sideways. A table, so the owner's wide-table rule: it scrolls sideways inside its own box, as the
     recurring lines table above does. That box reaches 14px into the card's side padding (the card's phone
     padding, aboo.css:2262) and pads itself back by the same 14px, so the cells sit exactly where they did:
     at 430px, where the table already used that padding to fit, it still shows whole, with nothing hidden
     behind the scroll; and where it fits its card, its columns keep the same widths. body.vt: the tenant
     skin's body.vt table{width:100%} (vt.css) would otherwise keep the box from reaching into the padding. */
  body.vt .v201-fields{display:block;width:auto;overflow-x:auto;margin-left:-14px;margin-right:-14px;padding-left:14px;padding-right:14px}
  /* Document editor, the footer actions (src/tenant/document_editor.js:183). Cancel, "Email to contact",
     Save and Confirm share one row that does not wrap (aboo.css:3747): on a new draft at 320-401px Save and
     Confirm ran off the screen and were cut off (Confirm to 430px at 320). Letting the row wrap (this pass's
     first fix) split the buttons: Cancel alone above Save and Confirm at 320px, Confirm alone at 375-430px.
     On a phone the "Email to contact" switch takes the first line, and Cancel, Save and Confirm share the next
     in equal parts (on a sent document, Cancel and Send). The spacer that pushes them right on desktop is not
     shown, and a save error takes its own line below the buttons, so they do not move under the finger. */
  .de-actions{flex-wrap:wrap}
  .de-actions > .jrn-spacer{display:none}
  .de-actions > .de-send-toggle{order:-1;flex:1 1 100%;margin-right:0}
  .de-actions > .btn{flex:1 1 0}
  .de-actions > .row-err{order:9;flex:1 1 100%}
  /* Every typeahead dropdown: the account, contact and bank pickers of journals, the bank screens, the
     dashboard, reports and Settings > Chart of accounts. They open in ONE floating layer, a child of <body>
     (src/shared/floatroot.js), so no screen's class can reach them. .cb-dropdown{min-width:320px}
     (aboo.css:1151), and every screen places a dropdown at least 8px from the left (positionNear,
     journals.js:174, and the same line in cashbook, dashboard, reports and settings), so on a 320px phone
     each one ran from 8 to 328px: 8px past the screen. A dropdown now asks for at most the screen less
     16px, which is under 320px only below 336px; from 336px up it is 320px, exactly as before. */
  .cb-float-root > .cb-dropdown{min-width:min(320px,calc(100vw - 16px))}
  /* Found by looking at every screen at 320/375/401/430px in the owner's Chrome (owner 2026-10-19: "make sure
     the ui is correct on mobile"): nothing below ran off the screen, so the fit checks passed it, but each one
     looked broken on a phone. */
  /* Dashboard, Bank Accounts and Recent Activity (src/tenant/dashboard.js:982, :1074). Up to 1100px each row
     is a card of label/value pairs in two columns (ux.css, "1fr auto"), the second as wide as its widest value,
     so at 320px the first was ~99px: a date broke into "17/10/" + "2026" and an amount into "R", "862." and "50"
     (at 375px "R" + "2,415.00"). On a phone each pair takes a line of its own. In a Bank Accounts card the
     account cell breaks words anywhere (vt.css:1310, every dashboard table), so the account number broke
     inside itself ("(62999 001)" at 320px, a lone ")" at 375px): on a phone it breaks only between words. Whole,
     the number then stood 10-15px out of its card beside a longer name ("Invented Dollar Bank (62000000002)" at
     320px), so the cell wraps: the number takes the next line, at the right, when the name leaves it no room. */
  body.vt .dash-page .dash-activity-table tr{grid-template-columns:1fr}
  body.vt .dash-page .dash-bank-table table.list td:nth-child(2){overflow-wrap:normal}
  body.vt .dash-page .dash-bank-table table.list td:nth-child(2){flex-wrap:wrap;row-gap:2px}
  body.vt .dash-page .dash-bank-table table.list td:nth-child(2) > .muted{margin-left:auto}
  /* Bank screens, the register cards (src/tenant/cashbook.js). A posted line keeps an empty selection cell, and
     the card's three-column grid (ux.css, "date ref actions") has no area for it, so it was placed in two extra
     columns that took 44px from the middle one: at 320px the reference wrapped ("PRB-" / "L4") and a paid amount
     broke into "R" + "3,852.50", the number running under the balance. The empty cell is not shown. */
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit) > td.cb-th-sel:empty{display:none}
  /* Bank screens, "Email statements to import" (src/tenant/cashbook.js:1433). The label, the address and its
     copy button wrap as one row: at 430px the label and the address fit a line and the copy button fell onto the
     next on its own. The label takes its own line, so the address and its button stay together. */
  .cb-intake-row > .cb-intake-label{flex:1 1 100%}
  /* Balance sheet (src/tenant/reports.js:1001). Each line is a grid of an 86px code column, the account and the
     amount (aboo.css:2193): at 320px the account had ~90px, so "Machinery and Equipment - Cost" took four lines
     and amounts broke into "R" + "113,492.50", as did a group's total. The codes are 4 characters (~30px): on a
     phone the code column is 48px, and an amount stays on one line. */
  .rp-bs-row{grid-template-columns:48px 1fr auto}
  .rp-bs-row > .rp-bs-amt{white-space:nowrap}
  .rp-bs-grouphead > .mono{white-space:nowrap}
  /* Report filters (src/tenant/reports.js: general ledger, bank statement, cost codes, income statement,
     reconciliation, transaction listing, VAT). Each label and its field are siblings in one wrapping row
     (aboo.css:1458), so the row broke between a label and its field: "From [date] To" on one line and the "To"
     date alone on the next, at every phone width. On a phone each label sits beside its own field, every field
     takes the rest of its line, and the transaction listing's search box takes a line of its own. */
  .rp-filters:has(> .rp-flabel){display:grid;grid-template-columns:max-content minmax(0,1fr);align-items:center;gap:8px 10px}
  .rp-filters > .rp-glpick{max-width:none}
  .rp-filters > .rp-acct{min-width:0;max-width:none}
  .rp-filters > .rp-search{grid-column:1/-1;max-width:none}
  /* Settings, every tab (src/tenant/settings.js: company details, branding, the chart of accounts forms, API keys)
     and the expense form (src/tenant/expenses.js:303). Their fields sit in a .grid2 of two columns up to 820px
     (vt.css:815): at 320px each field was 125px, so values were cut ("VAT201 Test (ir"), a two-line label pushed
     a field below its neighbour, and the financial year's month box showed no month. On a phone the fields take
     one column. A settings tab is the pane after the tab strip (.set-tabs, settings.js:1769). */
  body.vt .set-tabs ~ * .grid2{grid-template-columns:1fr}
  body.vt .expense-form > .grid2{grid-template-columns:1fr}
  /* Document editor, the line cards (src/tenant/document_editor.js:228). On a phone each line is a card of label
     and field (aboo.css, "each line becomes a labeled card"): the typed fields fill their line, but the item, VAT
     and account pickers kept their natural ~206px, a ragged right edge, with the account cut to "0400 — Sales -
     Standarc". The pickers fill their line too. */
  body.vt .de-lines td > [data-tax-pick]{flex:1 1 auto;min-width:0}
  body.vt .de-lines td > [data-acct-pick]{flex:1 1 auto;min-width:0}
  body.vt .de-lines .de-item-wrap > span{align-self:stretch}
  body.vt .de-lines .ss-input{width:100%}
  /* ...and a line's remove cell (document_editor.js:234). The card's phone rule (aboo.css:3787) draws it as a rule
     across the card with the bin at its right end, but the table's 30px column width (aboo.css:3734) stayed: a 30px
     stub of a line with the bin under it at the left. It takes the card's width. */
  body.vt .de-lines .de-c-x{width:auto}
  /* The top bar of every company screen (src/tenant/tshell.js:370). On a phone its right group is the three 34px
     icons (switch company, governance, light/dark: the user name, role and Sign out are hidden below 900px,
     vt.css:961), and the company name is meant to take what is left and end in "…" (vt.css:973). But the group
     may shrink (vt.css:80, for the desktop's long user names), so a long company name squeezed it below its icons:
     with "Currency live check ZA (invented data)" the light/dark icon ran 17px past a 320px screen (1px at 375),
     and even "VAT201 Test (invented data)" pushed the icons 10px out of their group. Found 2026-10-20 by the
     currency companies' screens. On a phone the group keeps its width. */
  body.vt .topbar.app-bar > .app-bar-left + .row-actions{flex-shrink:0}
  /* Contacts list (src/tenant/contacts.js:197). On a phone each contact is a card, and its email and phone links
     stretch across it "so long emails don't truncate" (aboo.css:2748). But a link stays on one line (.ct-quick,
     aboo.css:2751, white-space:nowrap) and the name cell keeps 70px clear for the actions (aboo.css:2784), so a
     long address set the card's width: "usd01@currency-live-check.invalid" made the cards 371px wide on a 320px
     screen, and on a 375px one. Found 2026-10-20 by the currency companies' screens. On a phone a link's text
     wraps inside the link, whole, and the card fits. */
  body.vt .ct-list .ct-quick{white-space:normal;overflow-wrap:anywhere}
  /* -------- Round 2 (owner 2026-10-20: "chart of accounts in settings grid is broken and also on some pages the width
     of the buttons has not been adjusted"; "save/cancel should be next to each other and visible not stacked on top of
     each other"). Found by measuring, at 320/375/401/430px, every group of buttons that share a parent, as each screen
     first draws and after one click (another tab, Edit, New or Add, an armed delete): a group with one button per line,
     a button alone on a line, a field apart from its own button, or one line of mixed heights. -------- */
  /* Forms on every tenant screen (the shared .form-actions row). The stock phone rule gives each button a 140px basis
     (ux.css, "Form actions always read left-to-right"), so a row narrower than 288px put Cancel and Save on a line each:
     a new service item (262px at 320px) and a new batch template (266px). The notes form (src/shared/historynotes.js:273)
     keeps an empty error span of at least 120px in its row (aboo.css:270), which pushed "Save note" under Cancel at every
     phone width. On a phone the buttons share their line in equal parts and wrap only when their labels cannot fit side
     by side (min-width:auto undoes aboo.css:349's 96px, so a label is never cut). The spacer that pushes them apart on
     desktop is not shown. A message takes its own line under the buttons, and an empty one takes no room. body.vt keeps
     the admin surface's forms out of it. */
  body.vt .form-actions > .btn{flex:1 1 0;min-width:auto}
  body.vt .form-actions > .form-actions-spacer{display:none}
  body.vt .form-actions > .row-err{order:9;flex:1 1 100%;min-width:0}
  body.vt .form-actions > .row-err:empty{display:none}
  /* Search boxes on every tenant list (the shared .ux-search). The stock phone rule caps them at 320px (ux.css, "Primary
     create actions are full-width on mobile"), so from 375px each stopped 31px short of the line that the buttons under
     it fill. On a phone a search box fills its line. */
  body.vt .ux-search{max-width:none}
  /* Settings > Chart of accounts (src/tenant/settings.js:1220). The toolbar kept its desktop widths: at 320px the search,
     the 190px type filter and the 130px page-size select took a line each, 34, 32 and 44px tall, with "+ New account"
     beside the page size. On a phone the search fills the first line, the two selects share the second at one height (the
     type filter's own 32px height, from the tenant skin, gives way so it can stretch), and the create button fills the
     third, as ux.css asks of primary create actions. The templates toolbar (settings.js:1720) is the same bar holding only
     "+ New template". */
  body.vt .set-coa-bar{gap:8px}
  .set-coa-bar > .set-typef,
  .set-coa-bar > .per-select{flex:1 1 0;min-width:0;max-width:none;align-self:stretch;height:auto}
  .set-coa-bar > .jrn-spacer{display:none}
  .set-coa-bar > .btn{flex:1 1 100%}
  /* ...the year-end account (settings.js:1250). Its box keeps a 260px minimum (aboo.css:1718), so at every phone width Save
     fell onto the next line alone, 44px tall under a 38px box. The box takes the rest of the line, both are one height,
     and a message takes its own line. */
  .set-re{align-items:stretch}
  .set-re > .set-re-sel{flex:1 1 0;min-width:0;max-width:none}
  .set-re > .row-err{flex:1 1 100%}
  /* ...the accounts (settings.js:1259). Eight columns squeezed into a phone: descriptions wrapped word by word, and the
     edit and delete buttons sat past the right edge of the scroll box, out of sight. On a phone each account is a card, as
     on the read-only chart screen and the contacts list: the code, the description and the buttons on the first line, the
     group under them, then tax, bank, balance and transactions, each with its label (the cells' data-label). An empty cell
     is not shown, and the edit pencil is 44px like the delete beside it. A row is a flex container, so its cells are laid
     out as blocks. An armed delete ("Delete? Yes No", or a refusal) takes a line of its own, right-aligned under the name:
     beside it the name was squeezed into three lines at 375 and 401px. */
  body.vt .tbl-wrap:has(> .set-coa){border:0;border-radius:0;overflow:visible}
  body.vt .set-coa,
  .set-coa > tbody{display:block;width:100%}
  .set-coa > thead{display:none}
  .set-coa > tbody{display:flex;flex-direction:column;gap:8px}
  .set-coa > tbody > tr{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;padding:10px 12px;
    border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface)}
  body.vt .set-coa > tbody > tr > td{padding:0;border:0;background:transparent;text-align:left}
  .set-coa > tbody > tr > td:nth-child(n+3):not(.set-acts){order:1}
  .set-coa > tbody > tr > td:first-child:not([colspan]){white-space:nowrap;font-weight:650}
  .set-coa > tbody > tr > td:nth-child(2){flex:1 1 0;min-width:0}
  .set-coa > tbody > tr > td:nth-child(3){flex:1 1 100%}
  .set-coa > tbody > tr > td[data-label]{white-space:nowrap}
  .set-coa > tbody > tr > td[data-label]::before{content:attr(data-label);margin-right:6px;color:var(--faint);
    font-family:var(--font-body,inherit);font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
  .set-coa > tbody > tr > td[data-label]:empty{display:none}
  .set-coa .set-acts > .icon-btn{min-width:44px;min-height:44px}
  .set-coa > tbody > tr > td.set-acts:has(> .confirm-inline){flex:1 1 100%;justify-content:flex-end}
  /* Settings > Branding, the reminder days (settings.js:1402). The new-day box and its "+ Add" sit at the end of the chips
     (aboo.css:686): on a phone they made a line of their own, a 44px box beside a 35.5px button. They take the whole line,
     the box grows, and the button takes the box's height. */
  .remday-add{flex:1 1 100%;margin-left:0;align-items:stretch}
  .remday-add > .remday-in{flex:1 1 0;width:auto;min-width:0}
  /* Settings > API keys, the AI key (settings.js:1532). The key box and its two buttons (112px each, aboo.css) share a line
     that does not wrap: at 320px the box was 22px wide. On a phone the box takes its line and the buttons share the next. */
  .set-key-row{flex-wrap:wrap}
  .set-key-row > .form-control{flex:1 1 100%}
  .set-key-row > .set-key-act{flex:1 1 0;min-width:0}
  /* Settings, its Communications part and Governance: the tab strips (settings.js:1783 and :1758, src/tenant/
     governance.js:304). The tabs wrap onto two or three lines, each at its own width, so the lines end ragged and a tab
     can sit alone ("API Keys & MCP" at 320px when Forex shows). On a phone each line is filled. */
  .set-tabs > .seg,
  .cm-subtabs > .seg{flex:1 1 auto;justify-content:center}
  .gov-tabs > .seg{flex:1 1 auto;justify-content:center}
  /* Sales and purchase lists (src/tenant/documents.js:305). "+ New ▾" kept its desktop size at the left of the toolbar,
     and the type tabs wrapped ragged: three purchase tabs alone on a line each at 320px. On a phone the create button
     fills its line (ux.css: primary create actions are full-width on mobile), the search the next, and the tabs fill
     theirs. */
  .doc-toolbar > .ct-new-dd{flex:1 1 100%}
  .doc-toolbar > .ct-new-dd > .ct-new-btn{flex:1 1 auto}
  .doc-toolbar > .segs{flex:1 1 100%}
  .doc-toolbar > .segs > .seg{flex:1 1 auto;justify-content:center}
  /* Document editor, its top actions (Create Credit Note, Clone, Repeat; document_editor.js:124). Under the title they
     took only their own width, and at 320px Repeat sat alone on a second line. On a phone they take the line under the
     title and fill it (at 320px Repeat fills a second line). */
  .de-head > .de-head-actions{flex:1 1 100%}
  .de-head-actions > .btn{flex:1 1 auto}
  /* Reports, the quick ranges (src/tenant/reports.js:397 and :666). The label and the buttons wrap at their own widths:
     "All" sat alone on a line at 320 and 430px. On a phone the label takes its own line and each line of buttons is
     filled. */
  .rp-quick > .rp-quick-lbl{flex:1 1 100%}
  .rp-quick > .btn{flex:1 1 auto}
  /* ...the financial-year arrows (reports.js:655 and :659): "‹" (a character) was 34.9x35.5px and "›" (an icon)
     44x32px. Both are 44x36px. */
  .rp-fynav > .btn{width:44px;height:36px;padding:0}
  /* ...the trial balance's Balances / Movements switch (reports.js:631): at 320 and 375px "Movements (from – to)" fell
     under "Balances (as at)". On a phone the two share the line in equal halves, and a label wraps inside its half if it
     must. */
  .rp-filters-col > .rp-tb-mode{flex-wrap:nowrap;align-self:stretch}
  .rp-tb-mode > .login-kind-btn{flex:1 1 0;min-width:0;white-space:normal;padding:8px 10px}
  /* ...the totals cards (reports.js:921 and every report with totals): each card kept its 130px minimum width, two to a
     line with a ragged edge. On a phone each line of cards is filled. */
  .rp-cards > .rp-kpi{flex:1 1 140px}
  /* Bank screens, the balance bar (src/tenant/cashbook.js:1862). The phone rule meant to stop it sticking (aboo.css:123,
     position:static) loses to the bar's own later rule (aboo.css:809, position:sticky), so on a phone the bar, two or three
     lines tall, stayed pinned over the register. And its controls kept desktop widths: the 210px statement jump alone on
     a line at 320 and 375px, then "Fetch mail" and "Statements" each at its own width. On a phone the bar scrolls with the
     page and is a grid of two equal columns: the balance with the reconcile badge beside it (the badge wraps inside its
     half when it must: "Off by R 13,926.91" is 155px, wider than half the bar at 320px); the unallocated badge beside
     Re-run; Post (or its confirm) on a line; the jump on a line; then Fetch mail and Statements in equal halves. The
     badge wraps after "Off by" only: its amount (its own span, cashbook.js:1869) keeps one line, where it broke "R"
     over "13,926.91" at 320px. */
  .cb-summary{position:static;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:8px;align-items:center}
  .cb-summary > .cb-summary-bal{grid-column:1}
  .cb-summary > .cb-recon{grid-column:2;justify-self:end;white-space:normal;text-align:right}
  .cb-summary > .cb-recon > .cb-recon-amt{white-space:nowrap}
  .cb-summary > .cb-unalloc-badge{grid-column:1;justify-self:start}
  .cb-summary > .cb-unalloc-badge + .btn{grid-column:2}
  .cb-summary > .btn-primary,
  .cb-summary > .confirm-inline{grid-column:1 / -1}
  body.vt .cb-summary > .cb-jump{grid-column:1 / -1;max-width:none}
  .cb-summary > .cb-fetch-mail{grid-column:1;margin-left:0}
  .cb-summary > .cb-statements{grid-column:2}
  /* ...the page head (cashbook.js:1481): "Allocation rules" (35.5px) beside the Auto / Manual switch (29.5px). The switch
     takes the button's height. */
  .cb-head-actions{align-items:stretch}
  /* ...the register toolbar (cashbook.js:1898): the search kept its 260px cap (aboo.css:828) and the From and To dates
     their own widths, so the lines ended ragged (the search 84px short of the line at 320px). On a phone the search fills
     its line, From and To take a line each with the two dates lined up after their labels, and the checkboxes follow
     with the toolbar's own 12px gap (their stock 6px margins left 24px between the two checkbox lines at 320px). */
  .cb-auto-toolbar > .cb-auto-search{flex:1 1 100%;max-width:none}
  .cb-auto-toolbar > .cb-date-filter{flex:1 1 100%;display:grid;grid-template-columns:40px minmax(0,1fr);align-items:center}
  .cb-auto-toolbar > .check-line{margin:0}
  /* ...and an allocated line's chip in the register (cashbook.js:1800): the account code (or the contact), the
     account's name, the AI tag and the clear ✕ wrap as one row, so at 320px the ✕ fell onto a line of its own under
     the rest ("4300 Utilities - Commercial AI 95%"; and "Bright Cable Supplies (invented)", where the contact's empty
     name slot added a gap that tipped it over by 1px). On a phone the row stays on one line: the account's name wraps
     inside its own space, an empty name takes no room, the AI tag keeps its width, and a contact's chip gives way
     (an ellipsis) before the ✕ would leave the line. */
  .cb-alloc-line:has(> .cb-gl-desc){flex-wrap:nowrap;max-width:100%}
  .cb-alloc-line > .cb-gl-desc{min-width:0}
  .cb-alloc-line > .cb-gl-desc:empty{display:none}
  .cb-alloc-line:has(> .cb-gl-desc) > .cb-ai-tag{flex-shrink:0}
  .cb-alloc-line:has(> .cb-gl-desc) > .cb-via-tag{flex-shrink:0}
  .cb-alloc-line:has(> .cb-gl-desc) > .alloc-btn-matched{min-width:0;overflow:hidden;text-overflow:ellipsis}
  /* Journals, the entry's date, reference, amount and tax (src/tenant/journals.js:562). They kept their desktop widths
     (190, 130, 120 and 180px) in a wrapping row, so the lines ended ragged: at 320px the date sat alone with 106px to
     spare. On a phone they are a two-column grid: date and reference, then amount and tax. */
  .jrn-meta{display:grid;grid-template-columns:1fr 1fr}
  .jrn-meta > .jrn-ref,
  .jrn-meta > .jrn-amt,
  .jrn-meta > .jrn-tax-cell{max-width:none;width:100%}
  /* ...and its actions (journals.js:617): two switches, a spacer and "+ Add" in a row that wraps with an 18px gap. The
     button sat beside "Keep description" at 320-401px and alone at the right of a line of its own at 430px. On a phone
     it fills its own line under the switches, like the other add buttons ("+ Add rule" on Allocation rules), 12px below
     them. */
  .jrn-actions{row-gap:12px}
  .jrn-actions > .jrn-spacer{display:none}
  .jrn-actions > .btn-primary{flex:1 1 100%}
  /* Your companies, a card's buttons (src/tenant/home.js). "Activate billing · R199.00/mo" is long, so with it "Chart of
     accounts" and "New window" each took a line of their own at 320 and 375px, and "New window" sat alone on a second
     line at 401 and 430px. The billing button moves to the end and fills its own line; the other two share the first.
     At 320px those two need 260.5px of the card's 258 (128.1 + 123.4px and the row's 9px gap), so on a phone the card's
     buttons take 10px side padding, not 12: 252.5px, and they share the line there too. */
  .co-card > .row-actions > .btn-primary{order:1;flex:1 1 100%}
  .co-card > .row-actions > .btn{padding-left:10px;padding-right:10px}
  /* Expenses (src/tenant/expenses.js:311). "Choose a file" and "Enter manually" kept their content widths at the left of
     the drop box. On a phone they share its line in equal halves; at 320px the two labels (119.6 + 132.6px and the
     10px gap) need more than the box's 235px, so there each fills a line of its own. The status tabs under the search
     (expenses.js:446) wrapped ragged, three and three at 320px, four and two from 375px; they fill their lines, as the
     Sales and purchase tabs do. */
  .expense-drop > .btn{flex:1 1 0;min-width:auto}
  .expense-page > .list-toolbar > .segs{flex:1 1 100%}
  .expense-page > .list-toolbar > .segs > .seg{flex:1 1 auto;justify-content:center}
  /* Cards whose content touched their border (found by measuring every screen at 375px for a card with no inset whose
     headings and fields start on its edge). ux.css's card reset (body.vt .card{padding:0}, line 35) took their inset,
     as it once did the other Settings forms' and the API keys cards' (given back at ux.css:395 and aboo.css:1671,
     "fix the margins"). On a phone: in Settings, the Branding tab's five cards and Chart of accounts' year-end card
     (settings.js:1248, 1289-1432) take the form inset every other Settings card has (16px; the tab's pane follows the
     tab strip); the document editor's cards (document_editor.js:129-178, 543) take the compact inset of a card's body
     (the editor is a .ct-page, whose card reset at ux.css:39 is 0,3,1: .de-page matches it and wins by coming later).
     Desktop's cards are as flush; from 768px up nothing here applies. */
  body.vt .set-tabs ~ * .card{padding:var(--ux-card-pad-form)}
  body.vt .de-page .de-card{padding:var(--ux-card-pad-compact)}
  /* A card's heading and its grey note (vt.css:143: body.vt h3 is a flex row that does not wrap, the title then the
     <small> note). On a phone each squeezed the other into a narrow column: "Key / figures" beside a two-line note on
     the dashboard at every phone width, "Create a / company / MCP key" in Settings. On a phone the note goes under its
     title. Settings (settings.js, 20 headings with a note): the note takes its own line. Dashboard (dashboard.js:56,
     the folding cards): the chevron, then the title with the note under it, and a folded card's count at the right. */
  body.vt .set-tabs ~ * h3{flex-wrap:wrap;row-gap:2px}
  body.vt .set-tabs ~ * h3 > small{flex:1 1 100%}
  body.vt .dash-page .dash-coll > .coll-toggle{display:grid;grid-template-columns:auto minmax(0,1fr) auto;row-gap:2px}
  body.vt .dash-page .dash-coll > .coll-toggle > small{grid-column:2}
  body.vt .dash-page .dash-coll > .coll-toggle > .coll-n{grid-column:3;grid-row:1}
  /* Governance, VAT returns (src/tenant/governance.js:282). The From and To dates kept their 190px boxes and Preview
     its own width, so the period wrapped ragged: From alone, then To with Preview beside it (under it at 320px), the
     boxes short of the card's edge. On a phone From and To share a line in halves (a line each at 320px, where two
     140px halves do not fit) and Preview fills the line under them. The previewed return's four figures (Output VAT,
     Input VAT, the net, Lines) wrapped three and one, or two and two: a two-by-two grid. Filing (shown to a user who
     may govern, governance.js:228): the reference box and "File this return" fill a line each. */
  .gov-vat-period > .gov-vat-lbl{flex:1 1 140px}
  .gov-vat-period > .btn{flex:1 1 100%}
  .gov-vat-totals{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px}
  .gov-vat-file > .gov-vat-ref{flex:1 1 100%;min-width:0}
  .gov-vat-file > .btn{flex:1 1 100%}
  /* ...and its cards, a financial year or a submitted return (governance.js:144, 247): the lock, the year or period and
     its badges, then the dates, counts and actions, in a row that wraps. At 320px a return's period broke into four
     lines ("2026-" / "04-01 →" / ...) beside its badge and its reference; the reference takes a line of its own under
     them. The period gives up to 100px of the line (the lock, a badge and their gaps: 22 + 59 + 16px) so it wraps
     beside the lock and keeps its badge after it, instead of dropping to a line of its own (the lock left alone on the
     first, the badge on a third, at 320 and 375px); its two lines are balanced, so a date stays whole ("2026-04-01 →"
     over "2026-05-31", not "... → 2026-" over "05-31" at 375px). For a user who may govern (governance.js:107-135,
     262-272), an armed lock, unlock or reverse takes the card's width: its reason box (or the closing-journals option)
     on a line, then its two buttons side by side in halves. */
  .gov-year{flex-wrap:wrap}
  .gov-year > .gov-fy{max-width:calc(100% - 100px);text-wrap:balance}
  .gov-year > .gov-reason{flex:1 1 100%}
  .gov-act:has(> .gov-arm){flex:1 1 100%}
  .gov-act > .gov-arm{flex:1 1 100%}
  .gov-arm > .gov-reason-in,
  .gov-arm > .gov-close-opt{flex:1 1 100%;min-width:0}
  .gov-arm > .btn{flex:1 1 0}
  /* A page's title block with its action (aboo.css:130, .page-head.with-action) stays pinned under the top bar while
     the page scrolls ("keep the title + Add in reach", #9). On a phone that pin covered 107-192px of the screen for the
     whole scroll: 192px on every report (the title and the four download buttons), 130px on Your companies, Chart of
     accounts and Import, 107-126px on Governance, which has no action at all. On a phone it scrolls with the page; the
     top bar and the bottom bar stay. Company screens only: body.vt (the company shell) and .tenant-main (Your
     companies, Chart of accounts, Import, Billing); the admin surface has neither. */
  body.vt .page-head.with-action,
  .tenant-main .page-head.with-action{position:static}
  /* Add a company (home.js:250) and the billing return / cancel pages (billing.js:97, 117): one card in a padded wrap,
     three insets deep (the content's 16px, the wrap's 16px, the card's 26px): 202px of a 320px phone was left for the
     form, so "Create company" broke over two lines beside Cancel and Cancel stood 6px out of its row. On a phone the
     wrap adds no side inset of its own and the card's is 18px: 250px at 320. */
  .tenant-main .onboard-wrap{padding-left:0;padding-right:0}
  .tenant-main .onboard-wrap > .onboard-card > .card-body{padding-left:18px;padding-right:18px}
  /* Service items, a price row (src/tenant/inventory.js:250). The stock phone layout (aboo.css:1843: the currency,
     then rate and unit side by side, then the tax across the row) places the tax by `select`, but the tax is a search
     box in a <div data-item-tax> now (since fa319eb): it sat in the rate's column, half the row, "— None —" cut. And at
     320px the rate's half (103px) is too narrow for its hint, "— not priced —" (109px of text in 81). The tax takes
     the row again, and the rate gets two thirds of it, the unit ("hour", 34px) one: 115px for the hint at 320. 0,3,0
     and later beats the stock .inv-prices.no-tax .inv-price-row. */
  .inv-form .inv-prices > .inv-price-row{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}
  .inv-price-row > [data-item-tax]{grid-column:1 / -1}
  /* The forms of the screens outside the company shell (.tenant-main: Add a company, Import, Billing). Their
     .form-actions row (aboo.css:399) does not wrap, and its buttons' labels do: Import's mapping step ("Cancel",
     "Choose another file", "Import these accounts") squeezed into labels two and three lines tall (56-75px) at every
     phone width, Cancel 24px off the screen at 320px, and its last step's two buttons two lines tall at 320. As the
     company screens' rows above: the buttons share a line in equal parts and keep their labels on one line, a button
     wraps under the others only when the labels cannot fit side by side, and the empty spacer goes. A button's side
     padding is 14px (18 on desktop), so "Import another file" and "Back to home" (151 + 117px and the 8px gap) and
     Cancel and "Choose another file" share a 288px line at 320. */
  .tenant-main .form-actions{flex-wrap:wrap}
  .tenant-main .form-actions > .btn{flex:1 1 0;min-width:auto;white-space:nowrap;padding-left:14px;padding-right:14px}
  .tenant-main .form-actions > .form-actions-spacer{display:none}
  /* Values stay whole: a date, an amount or a reference never breaks over two lines. On a phone a table narrows its
     columns before it scrolls, down to the longest word in each, so a value broke at its space or its hyphen: the sales
     and purchase totals "R" over "212,750.00" (documents.js:288), the team list's dates "2026-" over "10-07"
     (team.js:121), and, with a company date format of yyyy-MM-dd or dd-MM-yyyy (formats.js:3, bank_helpers.js:280),
     the dates in approvals, expenses and recurring. These cells keep one line, as the bank and report tables' already
     do (aboo.css:2432), and the table scrolls sideways inside its own box (vt.css:149). */
  .doc-list td.mono{white-space:nowrap}
  .team-tbl td.mono{white-space:nowrap}
  .apv-tbl td.mono{white-space:nowrap}
  .expense-tbl td.mono{white-space:nowrap}
  .rec-tbl td.mono{white-space:nowrap}
  /* ...the VAT201 transactions (reports.js, v201Row): the reference, the third column (v201Head), broke at its hyphen
     once it outgrew the column's longest unbroken reference ("RCPT-12345" beside "INV00012"). */
  .v201-tx tr.v201-drill > td:nth-child(3){white-space:nowrap}
  /* ...inside a card the company skin lets every table cell wrap, even mid-word, and stops the table scrolling
     (vt.css:1295-1313, written for the narrow desktop dashboard cards). On a phone the dashboard's drafts broke
     "20/10/2026" and "R 2,500.00" in two, and its batches due and a batch's previous runs (batches.js:912) break a
     date or an amount the same way. Their words stay whole (the cells wrap between words, as everywhere else), their
     value cells keep one line, and the table scrolls inside its card. Keeping only the values whole squeezed the other
     columns to a letter a line ("Contact" stacked down the drafts at 320-401px). 0,4,3 and later, or 0,5,3, beats the
     skin's 0,4,3; 0,5,2 beats its 0,4,1. The dashboard's bank accounts and activity are cards already (ux.css:172). */
  body.vt .page .card table.drafts-list th,
  body.vt .page .card table.drafts-list td{overflow-wrap:normal}
  body.vt .page .card table.drafts-list td.mono,
  body.vt .page .card table.drafts-list td > .mono{white-space:nowrap}
  body.vt .page .card .tbl-wrap:has(> table.drafts-list){overflow-x:auto}
  body.vt .page .card table.batches-due-list th,
  body.vt .page .card table.batches-due-list td{overflow-wrap:normal}
  body.vt .page .card table.batches-due-list td.mono{white-space:nowrap}
  body.vt .page .card .tbl-wrap:has(> table.batches-due-list){overflow-x:auto}
  body.vt .page .card.bt-runs table.list th,
  body.vt .page .card.bt-runs table.list td{overflow-wrap:normal}
  body.vt .page .card.bt-runs table.list td.mono,
  body.vt .page .card.bt-runs table.list td.num{white-space:nowrap}
  body.vt .page .card.bt-runs .tbl-wrap{overflow-x:auto}
  /* ...the bank register cards (ux.css:315, the skin's own card layout up to 1100px). The amounts row gives Received,
     Paid and Balance a column each, and the cells wrap (ux.css:344), so a column narrower than its amount broke it ("€"
     over "-1,234,567.89" at 320px once an amount reaches seven digits). The reference broke at its hyphen ("LS-" over
     "1009"): the cells' wrap (0,4,4) beats the reference's own one line and "…" (ux.css:356, 0,3,0). A line has
     Received or Paid, never both: the empty one goes and the other takes both their columns. The amounts and the
     reference keep one line, and a long reference ends in "…", as the skin meant. */
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit) > td.cb-td-received:empty,
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit) > td.cb-td-paid:empty{display:none}
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit) > td.cb-td-received,
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit) > td.cb-td-paid{grid-column:received-start / paid-end;white-space:nowrap}
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit) > td.cb-td-balance,
  body.vt .cb-register-table tbody tr.cb-line:not(.cb-row-edit) > td.cb-td-ref{white-space:nowrap}
  /* Bank screens, a co-op statement's review (src/tenant/coop_review.js; plan/supplier-cashbooks.md §9). On a phone it
     takes the whole screen: the shared overlay's 24px padding and the modal's box (aboo.css .cb-stmt-overlay/-modal)
     only narrowed tables that scroll sideways inside their own box anyway. */
  .coop-overlay{padding:0;align-items:stretch}
  .coop-overlay > .coop-review{width:100%;max-height:none;border-radius:0}
  /* Bank screens, the page's message (src/tenant/cashbook.js toastView; aboo.css .cb-toast). Centred by left:50% and
     translateX(-50%), its box could only fill the half of the screen right of the centre: a failed import's message ran
     to seven lines in 188px at 375px (2026-10-26). It takes the width its words need, up to the screen less 16px a side. */
  .cb-toast{box-sizing:border-box;width:max-content;max-width:calc(100vw - 32px)}
}
