@font-face{
  font-family:'Inter';
  font-style:normal;
  font-display:swap;
  font-weight:100 900;
  src:url('fonts/inter.woff2') format('woff2');
}
:root{
  /* The sidebar's width is draggable between these two bounds and remembered per browser. A token,
     not a literal in the grid rule, so the shell, the drag handle and the JS clamp all read ONE
     source. --sidebar-w is overwritten inline on <html> by the head script and by the drag. */
  --sidebar-w:260px;
  --sidebar-w-min:200px;
  --sidebar-w-max:294px;
  /* iOS standalone draws content UNDER the status bar and home indicator, because
     apple-mobile-web-app-status-bar-style is black-translucent. These resolve to 0 on any device
     without a notch or home indicator, so they are safe to apply unconditionally - and they only
     resolve at all because the viewport meta carries viewport-fit=cover. */
  /* Minimum comfortable touch target. Applied on COARSE pointers only, so dense desktop
     tables and toolbars keep their current density (framework section 9 / section 13). */
  --tap:44px;
  --safe-t:env(safe-area-inset-top,0px);
  --safe-r:env(safe-area-inset-right,0px);
  --safe-b:env(safe-area-inset-bottom,0px);
  --safe-l:env(safe-area-inset-left,0px);

  --bg:#06010f;
  --bg-2:#0b0418;
  --panel:rgba(255,255,255,.06);
  --panel-2:rgba(255,255,255,.09);
  --border:rgba(255,255,255,.13);
  --text:#f3f1ff;
  --soft:rgba(255,255,255,.84);
  --mute:rgba(255,255,255,.72);
  --indigo:#6366f1;
  --violet:#8b5cf6;
  --rose:#ef4444;
  --amber:#f59e0b;
  --cyan:#06b6d4;
  --lime:#10f981;
  --amount-pos:#10f981;  /* balances/totals/indicators: positive */
  --amount-neg:#ff9d9d;  /* balances/totals/indicators: negative */
  --grad:linear-gradient(120deg,#6366f1,#8b5cf6,#ef4444,#f59e0b); /* @kind color */
  /* Brand for ACTIONS: a calm 2-stop indigo→violet (no red/amber). The full --grad is
     reserved for brand moments (logo, active nav highlight) - never plain buttons. Rule:
     one primary action per view; everything else is secondary or ghost. */
  --brand:linear-gradient(135deg,#6366f1,#7c5cf0); /* @kind color */
  --brand-hover:linear-gradient(135deg,#7178f4,#8a6bf5); /* @kind color */
  --font:Inter,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  /* Surfaces / overlays - one fill scale, flipped per theme (was scattered white-overlay literals). */
  --surf-1:rgba(255,255,255,.04);      /* subtle card fills (detail/event/timeline items, public cards) */
  --surf-2:rgba(255,255,255,.06);      /* raised controls, default buttons, badges */
  --field:#332f42;       /* form field fill - visibly lighter than the panel so
                                          every input box reads as a box (user decision 2026-07-05) */
  --hover:rgba(255,255,255,.09);       /* interactive hover fill */
  --divider:rgba(255,255,255,.08);     /* hairline dividers (nav groups, table cells, panel foot) */
  --border-soft:rgba(255,255,255,.1);  /* softer card border than --border */
  /* Status hues as RGB triplets so one source feeds badges, flash, danger, etc. */
  --ok-rgb:16,249,129;
  --info-rgb:6,182,212;
  --warn-rgb:245,158,11;
  --err-rgb:239,68,68;
  --accent-rgb:139,92,246;
  --brand-rgb:99,102,241;
  /* Categorical chart palette. Ten hues that stay apart from each other on both themes and in print,
     for a chart that names more than a handful of things - the expense donut shows nine categories
     plus a catch-all. The status tokens above carry MEANING (ok, warn, err) and reusing them here
     said a category was good or bad because of where it happened to sort; these say nothing except
     "a different one". Slot 10 is deliberately grey: it is where "Everything else" lands. */
  --chart-1-rgb:99,102,241;   --chart-2-rgb:245,158,11;  --chart-3-rgb:16,185,129;
  --chart-4-rgb:14,165,233;   --chart-5-rgb:244,63,94;   --chart-6-rgb:168,85,247;
  --chart-7-rgb:20,184,166;   --chart-8-rgb:234,88,12;   --chart-9-rgb:132,204,22;
  --chart-10-rgb:148,163,184;
  /* Radii scale - replaces ad-hoc 6/7/8/10/14/999px literals. */
  --r-sm:6px; --r:8px; --r-lg:10px; --r-xl:14px; --r-pill:999px;
  /* Elevation - shadow-1 subtle raise, shadow-2 popovers/modals */
  --shadow-1:0 1px 2px rgba(0,0,0,.18);
  --shadow-2:0 12px 32px rgba(0,0,0,.45);
}
*{box-sizing:border-box}
html{overflow-x:hidden}
body{
  margin:0;
  min-height:100vh;
  color:var(--text);
  background:
    radial-gradient(circle at 16% 18%,rgba(var(--brand-rgb),.24),transparent 32rem),
    radial-gradient(circle at 90% 8%,rgba(var(--err-rgb),.18),transparent 30rem),
    radial-gradient(circle at 54% 90%,rgba(var(--info-rgb),.16),transparent 34rem),
    var(--bg);
  font-family:var(--font);
  line-height:1.5;
  overflow-x:hidden;
}
a{color:inherit}
button,input,select,textarea{font:inherit;min-width:0}
img,svg{max-width:100%}
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible{
  outline:3px solid rgba(var(--info-rgb),.82);
  outline-offset:3px;
}
.shell{
  width:min(1440px,100%);
  margin:0 auto;
  padding:calc(20px + var(--safe-t)) calc(20px + var(--safe-r)) calc(20px + var(--safe-b)) calc(20px + var(--safe-l));
  display:grid;
  grid-template-columns:var(--sidebar-w) minmax(0,1fr);
  gap:18px;
}
/* Hidden by choice on desktop: the column goes, and the topbar hamburger appears to bring it back
   (it is the same control mobile already uses, so there is one way to reopen the menu, not two). */
.shell.nav-hidden{grid-template-columns:minmax(0,1fr)}
.shell.nav-hidden>.sidebar{display:none}
.content{
  display:grid;
  gap:18px;
  align-content:start;
  min-width:0;
}
.sidebar{
  position:sticky;
  top:20px;
  height:calc(100vh - 40px);
  display:flex;
  flex-direction:column;
  border:1px solid var(--border);
  border-radius:var(--r);
  background:rgba(6,1,15,.72);
  backdrop-filter:blur(18px);
  padding:14px;
}
.brand{
  display:flex;
  gap:12px;
  align-items:center;
  text-decoration:none;
  padding:8px;
  border-radius:var(--r);
  min-width:0;
}
.brand-mark{
  width:42px;
  height:42px;
  display:grid;
  place-items:center;
  border-radius:var(--r-lg);
  background:var(--grad);
  font-weight:800;
}
/* An uploaded entity logo fills the tile (clipped to its rounded shape), replacing the initials. */
.brand-mark.has-logo{background:none;overflow:hidden}
.brand-logo{width:100%;height:100%;object-fit:cover;border-radius:inherit;display:block}
.brand strong,.brand small{display:block;overflow-wrap:anywhere}
.brand small{color:var(--mute);margin-top:2px}
.nav{
  display:grid;
  align-content:start;
  gap:4px;
  margin-top:14px;
  flex:1 1 auto;
  min-height:0;
  overflow-y:auto;
  overflow-x:hidden;
  scrollbar-width:thin;
}
.nav-title{
  display:block;
  padding:0 12px 6px;
  color:var(--cyan);
  font-size:12px;
  font-weight:900;
  letter-spacing:.08em;
}
.nav-subtitle{
  margin-top:12px;
  padding-top:14px;
  border-top:1px solid var(--divider);
}
.nav a{
  padding:9px 12px;
  border-radius:var(--r);
  color:var(--soft);
  text-decoration:none;
  font-weight:700;
  min-width:0;
  overflow-wrap:anywhere;
}
.nav a:hover,.nav a.active,.nav a.soft-active{background:var(--hover);color:#fff}
.nav a.active{
  border:1px solid rgba(255,255,255,.16);
  background:linear-gradient(120deg,rgba(var(--brand-rgb),.26),rgba(var(--info-rgb),.14));
}
/* Collapsible "Settings" sub-section (native <details>): a labelled, divided group inside the nav.
   The <details> stays block-level - display:grid/flex on it breaks the native open/close. The links
   live in an inner grid so they stack like the Work links and hide when the group is collapsed. */
.nav-group{
  margin-top:10px;
  padding-top:10px;
  border-top:1px solid var(--divider);
}
.nav-group-items{
  display:grid;
  gap:4px;
  margin-top:4px;
}
/* Author styles beat the UA "hide when closed" rule, so hide the items explicitly when
   collapsed - otherwise the grid stays painted outside the closed <details> box. */
.nav-group:not([open]) .nav-group-items{display:none}
.nav-group>summary.nav-title{
  display:flex;
  align-items:center;
  justify-content:space-between;
  cursor:pointer;
  list-style:none;
  user-select:none;
  margin:0;
}
.nav-group>summary::-webkit-details-marker{display:none}
.nav-group>summary.nav-title::after{
  content:"";
  width:7px;
  height:7px;
  border-right:2px solid currentColor;
  border-bottom:2px solid currentColor;
  transform:rotate(-45deg);
  opacity:.7;
  transition:transform .15s ease;
  margin-bottom:6px;
}
.nav-group[open]>summary.nav-title::after{transform:rotate(45deg)}
.main{display:grid;gap:18px;min-width:0}
.topbar{
  min-height:72px;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:14px;
  border:1px solid var(--border);
  border-radius:var(--r);
  padding:12px 14px;
  background:rgba(6,1,15,.62);
  backdrop-filter:blur(18px);
  /* Panels also create stacking contexts (backdrop-filter); without a z-index here the
     topbar's dropdowns (notifications, user menu) paint BENEATH later page sections.
     30 keeps them above content and below the mobile nav overlay (55/60). */
  position:relative;
  z-index:30;
}
.topbar-crumbs{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:6px;
  margin:0 0 2px;
  font-size:12px;
  font-weight:800;
  letter-spacing:.04em;
}
.topbar-crumbs a{color:var(--mute);text-decoration:none}
.topbar-crumbs a:hover{color:var(--text);text-decoration:underline}
.topbar-crumbs [aria-current="page"]{color:var(--cyan)}
.topbar-crumbs .crumb-sep{color:var(--mute);opacity:.55;font-weight:600}
.topbar-actions{
  display:flex;
  gap:8px;
  align-items:center;
  flex-wrap:wrap;
  justify-content:flex-end;
}
.user-menu{
  position:relative;
  display:inline-block;
}
.user-menu-toggle{
  min-height:32px;
  display:flex;
  align-items:center;
  gap:6px;
  border:0;
  border-radius:var(--r-sm);
  padding:5px 8px;
  color:var(--soft);
  background:transparent;
  font-family:inherit;
  font-size:12px;
  font-weight:800;
  cursor:pointer;
}
.user-menu-toggle:hover,
.user-menu-toggle[aria-expanded="true"]{
  color:#fff;
  background:var(--surf-2);
}
.topbar-user{
  color:inherit;
  font-size:.8rem;
  line-height:1;
  white-space:nowrap;
  max-width:160px;
  overflow:hidden;
  text-overflow:ellipsis;
}
.user-menu-caret{
  color:var(--mute);
  flex:0 0 auto;
}
.user-menu-panel{
  display:none;
  position:absolute;
  top:calc(100% + 6px);
  right:0;
  z-index:20;
  min-width:210px;
  border:1px solid rgba(148,163,184,.24);
  border-radius:var(--r-lg);
  padding:6px;
  background:#1e293b;
  box-shadow:var(--shadow-2);
}
.user-menu-head{
  display:grid;
  gap:2px;
  padding:8px 10px 9px;
  border-bottom:1px solid #334155;
  margin-bottom:6px;
}
.user-menu-head strong{
  color:#fff;
  font-size:.8rem;
  line-height:1.2;
}
.user-menu-head span{
  color:#94a3b8;
  font-size:.75rem;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}
.user-menu-panel a,
.user-menu-panel button{
  width:100%;
  min-height:38px;
  display:flex;
  align-items:center;
  border:0;
  border-radius:var(--r-sm);
  padding:8px 12px;
  color:#cbd5e1;
  background:transparent;
  font:inherit;
  font-size:.8rem;
  font-weight:700;
  text-align:left;
  text-decoration:none;
  cursor:pointer;
}
.user-menu-panel a:hover,
.user-menu-panel button:hover{
  color:#fff;
  background:#334155;
}
.user-menu-panel form{
  margin:0;
  padding-top:4px;
  border-top:1px solid #334155;
}
.user-menu-panel form button{
  color:#f87171;
}
.user-menu-panel form button:hover{
  background:#3f0f0f;
}
.auth-main{
  width:min(720px,100%);
  min-height:100vh;
  margin:0 auto;
  padding:24px;
  display:grid;
  align-content:center;
  gap:18px;
}
/* Legacy class retained for older markup if any cached view renders it. */
.user-box{
  display:none;
  gap:8px;
  margin-top:18px;
  padding:12px;
  border:1px solid var(--border);
  border-radius:var(--r);
  background:var(--surf-1);
}
.user-box strong,.user-box small{display:block}
.user-box small{color:var(--mute)}
.page-head{
  display:flex;
  justify-content:space-between;
  align-items:flex-end;
  gap:18px;
  border:1px solid var(--border);
  border-radius:var(--r);
  padding:22px;
  background:var(--panel);
}
.eyebrow{
  margin:0 0 8px;
  color:var(--cyan);
  font-size:12px;
  font-weight:800;
  letter-spacing:.08em;
}
h1,h2{
  margin:0;
  line-height:1.05;
  letter-spacing:0;
}
h1{font-size:clamp(28px,3.2vw,42px)}
h2{font-size:24px}
.lede,.panel p{color:var(--soft);max-width:104ch}
/* Who owns a record (and who entered it, when they differ). Quieter than the lede it follows -
   it is provenance, not description - and never wrapped mid-name. */
.record-ownership{margin-top:.25rem;font-size:.875rem;color:var(--mute)}
/* States that a printed bill is our entry of a supplier's invoice, not the supplier's original.
   Must survive printing - it is the whole point of the line. */
.doc-recorded-note{margin:.35rem 0 0;font-size:.8125rem;color:var(--mute)}
/* A value that must stay on ONE line - a document reference, a code - the way dates already do
   (§12). One rule, used wherever a broken value would read as a typo. */
.nowrap{white-space:nowrap}
.record-code{
  display:inline-flex;
  gap:6px;
  align-items:center;
  margin:10px 0 0;
  color:var(--mute);
  font-size:13px;
  font-weight:800;
}
.record-code strong{
  color:var(--cyan);
  letter-spacing:0;
}
.record-link{
  color:var(--cyan);
  font-weight:800;
  text-decoration:none;
}
.record-link:hover{text-decoration:underline}
.stats-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(112px,1fr));
  gap:6px;
}
/* Finance dashboard synthesis (2026-07-24): hero tiles, two-column rows, spend pulse, category
   donut and label/value line lists - all reusing the stat/panel shells and colour tokens. */
/* Two tiles since net worth moved into its own panel (31-07-2026). auto-fit rather than a fixed
   count, so the row does not have to be re-tuned the next time a tile joins or leaves it. */
.dash-hero{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
/* Net worth at the head of the panel that explains it: the figure and its month movement on one
   baseline, with the trend line under them. */
.dash-networth{display:flex;flex-wrap:wrap;align-items:baseline;gap:10px;margin:0 0 2px}
.dash-networth strong{font-size:1.6rem;font-weight:800;line-height:1.2}
.dash-networth small{font-size:12px;font-weight:700}
.dash-networth .amount{display:inline}
/* A row's currency tag sits BESIDE its account name, never wrapped under it (framework §5: a label
   stays on one line). .dash-lines small is block elsewhere in this panel. */
.dash-lines li > span > small{display:inline;font-size:11px;margin-left:4px}
/* A day's header inside a dated list, and the items that hang under it. The date is stated once on
   the header with that day's net; the children indent so the grouping reads without a second
   border. */
.dash-lines li.dash-lines-group{font-weight:700;border-bottom:0;padding-bottom:2px}
.dash-lines li.dash-lines-group time{font-weight:700}
.dash-lines li.dash-lines-child{padding-left:18px}
.dash-lines li.dash-lines-child > span{display:flex;gap:6px;align-items:baseline;min-width:0}
.dash-hero .stat strong{font-size:1.35rem}
.dash-hero .stat small{white-space:nowrap}
/* money_signed()'s .amount span is display:block app-wide; inside the delta line it must sit inline
   so "▲ +€310 this month" reads as one line. */
.dash-hero .stat small .amount{display:inline}
.dash-spark{width:100%;height:36px;margin-top:4px;display:block}
/* The sparkline's own caption: a chart without a period named is a shape, not a fact. 11px matches
   .month-kpi's .stat-suffix, the app's existing size for a figure's footnote. It overrides the
   nowrap above deliberately - this one is a sentence, and on a narrow tile it must wrap rather
   than push the tile wider. */
.dash-spark-caption{display:block;font-size:11px;margin-top:2px;white-space:normal}
/* One invisible hover band per month-end, each carrying an SVG <title> with its month and figure.
   Transparent until pointed at, so the line reads clean and the whole column is the target. */
.dash-spark-band{fill:transparent;cursor:help;outline:none}
.dash-spark-band:hover,.dash-spark-band:focus-visible{fill:rgba(var(--brand-rgb),.18)}
/* The floating figure for a hovered/tapped/focused band. Same shell as .row-menu-list (one look for
   anything that floats over the page); position:fixed and set from JS, so it is never clipped by a
   panel's overflow. pointer-events:none keeps it from stealing the hover that summoned it. */
.spark-tip{position:fixed;z-index:70;pointer-events:none;padding:6px 9px;background:var(--bg-2);
  border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow-2);
  color:var(--soft);font-size:12px;font-weight:700;white-space:nowrap}
.dash-cols{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:14px 0}
.dash-cols .panel{margin:0;min-width:0}
/* A single-panel row: full width rather than half a two-column grid with a hole beside it. */
.dash-cols-single{grid-template-columns:1fr}
.dash-pulse-label{margin:10px 0 4px;font-size:.8rem;color:var(--mute)}
.dash-pulse{height:10px;border-radius:5px;background:var(--surf-2);overflow:hidden}
.dash-pulse-fill{height:100%;background:rgb(var(--warn-rgb));border-radius:5px}
.dash-pulse-fill.is-over{background:rgb(var(--err-rgb))}
.dash-pulse-note{margin:4px 0 0;font-size:.8rem;color:var(--mute)}
.dash-donut-row{display:flex;gap:16px;align-items:center;flex-wrap:wrap}
/* The donut carries the panel, so it takes the room the panel has: it grows with the column and
   stops before it dwarfs its own legend. At 96px it sat in a corner of a mostly empty card. */
.dash-donut{width:clamp(120px,26%,190px);height:auto;aspect-ratio:1;flex:0 0 auto;transform:rotate(0deg)}
.dash-donut-legend{list-style:none;margin:0;padding:0;font-size:.85rem;line-height:2;min-width:0;flex:1 1 220px}
/* Label left, amount right, on ONE line. Inline text let the amount wrap under its own label as soon
   as the legend column narrowed - which it does on paper - and a figure sitting alone on the next
   line reads as a different row entirely (framework 12: money is aligned, never adrift). */
.dash-donut-legend li{display:flex;align-items:baseline;gap:6px}
.dash-donut-legend li .dash-legend-label{min-width:0;overflow-wrap:anywhere}
.dash-donut-legend li .dash-legend-amount{margin-inline-start:auto;white-space:nowrap}
.dash-dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:var(--mute);vertical-align:baseline;margin-inline-end:2px}
.dash-dot.tone-warn{background:rgb(var(--warn-rgb))}
.dash-dot.tone-overdue,.dash-dot.tone-err{background:rgb(var(--err-rgb))}
.dash-dot.tone-info{background:rgb(var(--info-rgb))}
.dash-dot.tone-ok{background:rgb(var(--ok-rgb))}
.dash-lines{list-style:none;margin:0;padding:0;font-size:.9rem}
.dash-lines li{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:7px 0;border-bottom:1px solid var(--divider)}
.dash-lines li:last-child{border-bottom:0}
.dash-lines li > span{min-width:0}
.dash-lines strong{white-space:nowrap}
.dash-lines-foot span{font-size:.8rem}
/* Analyst layer: health gauge + sub-score bars beside the verdict lines. */
.dash-analyst{display:grid;grid-template-columns:minmax(220px,1fr) 2fr;gap:20px;align-items:start}
/* Radial health gauge: a semicircle arc whose fill and colour track the 0-100 score, number centred. */
.dash-gauge-arc{position:relative;width:100%;max-width:200px;margin:0 auto}
.dash-gauge-arc svg{display:block;width:100%;height:auto}
.dash-arc-track{stroke:var(--surf-2)}
.dash-arc-val{transition:stroke-dasharray .5s ease}
.dash-arc-val.tone-ok{stroke:rgb(var(--ok-rgb))}
.dash-arc-val.tone-warn{stroke:rgb(var(--warn-rgb))}
.dash-arc-val.tone-err{stroke:rgb(var(--err-rgb))}
/* UNKNOWN is not a bad score: while the history the score needs is still building, the dial
   stays empty and grey rather than painting an unearned amber. Same for a gated sub-bar and
   the dash that replaces the number. */
.dash-arc-val.tone-unknown{stroke:var(--surf-2)}
.dash-gauge-num strong.is-unknown{color:var(--mute);font-size:1.6rem}
.dash-subbar span.is-unknown{background:transparent}
.dash-gauge-num{position:absolute;left:0;right:0;bottom:2px;text-align:center;line-height:1}
.dash-gauge-num strong{font-size:2.1rem}
.dash-gauge-num span{font-size:.9rem;color:var(--mute);font-weight:600}
.dash-gauge-label{display:block;text-align:center;font-size:.8rem;color:var(--mute);margin:2px 0 4px}
.dash-gauge-note{display:block;text-align:center;font-size:.72rem;color:var(--mute);margin:0 0 10px;line-height:1.4}
.dash-subscores{list-style:none;margin:0;padding:0;font-size:.78rem}
.dash-subscores li{display:grid;grid-template-columns:minmax(70px,auto) 1fr auto;gap:8px;align-items:center;padding:2px 0}
.dash-subscores li > span:first-child{color:var(--mute);white-space:nowrap}
.dash-subbar{display:block;height:6px;border-radius:3px;background:var(--surf-2);overflow:hidden}
.dash-subbar > span{display:block;height:100%;border-radius:3px}
.dash-subbar > span.is-ok{background:rgb(var(--ok-rgb))}
.dash-subbar > span.is-warn{background:rgb(var(--warn-rgb))}
.dash-subbar > span.is-err{background:rgb(var(--err-rgb))}
.dash-verdict li{border-bottom:0;padding:4px 0}
.dash-verdict .amount{display:inline}
/* MoM/YoY chips + budget rows inside the This-month panel. */
.dash-chips{margin:10px 0 0;font-size:.8rem;display:flex;flex-wrap:wrap;gap:6px 10px;align-items:baseline}
.dash-chip{border:1px solid var(--border-soft);border-radius:var(--r);padding:1px 7px;white-space:nowrap}
.dash-chip .amount{display:inline}
.dash-budget-row{display:grid;grid-template-columns:minmax(90px,auto) 1fr auto;gap:10px;align-items:center;padding:3px 0}
.dash-budget-row .dash-pulse{display:block}
.dash-budget-row .dash-pulse-fill{display:block}
.dash-budget-row .dash-pulse-fill.is-ok{background:rgb(var(--ok-rgb))}
.dash-budget-name{font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Composition stacked bar + 30/60/90 cash bands. */
.dash-stack{display:flex;height:14px;border-radius:7px;overflow:hidden;background:var(--surf-2);margin-bottom:10px}
.dash-stack > span{display:block;height:100%}
.dash-bands{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-bottom:10px}
.dash-band{border:1px solid var(--border-soft);border-radius:var(--r);padding:6px 9px;display:flex;flex-direction:column;gap:1px;font-size:.78rem}
.dash-band strong{font-size:.9rem}
.dash-band .amount{display:inline}
@media (max-width:840px){.dash-cols{grid-template-columns:1fr}.dash-hero{grid-template-columns:1fr 1fr}.dash-analyst{grid-template-columns:1fr}}
@media (max-width:520px){.dash-hero{grid-template-columns:1fr}.dash-bands{grid-template-columns:1fr}}

/* Health/vital tiles flag a bad value: warn (amber) for concerning, bad (red) for a real problem. */
.stat.is-warn strong{color:rgb(var(--warn-rgb))}
.stat.is-bad strong{color:rgb(var(--err-rgb))}
/* Tiles whose value is a long string (e.g. a migration filename), not a number: let it wrap and
   break inside the tile instead of overflowing the fixed-width track. */
.stat.is-text strong{white-space:normal;overflow-wrap:anywhere;line-height:1.2}
/* A badge inside a tile LABEL (the setup checklist's "Done") has to hug its text: the `.stat span`
   rule sets display:block on EVERY span in a tile, which stretched the badge into a full-width bar. */
.stat span .badge{display:inline-block;width:auto;margin-inline-start:.35em}
/* Date/text tiles carry a longer value than a bare count, so give them a wider track (they span two
   of the auto-fit columns) - there is room, and it keeps dates and filenames on one tidy line. */
.stat.is-wide{grid-column:span 2}
/* Monthly summary: 3 columns (income / expense / net) × rows for actual, committed,
   variance - wider cards so larger amounts fit. */
.month-stats{grid-template-columns:repeat(3,minmax(0,1fr))}
/* Monthly summary top line: the three actuals (expense / income / net) on one row, each a single
   line with the label left and the amount right (beats .stat span/strong block rules). */
.month-kpi{grid-template-columns:repeat(3,minmax(0,1fr))}
.month-kpi .stat{display:flex;align-items:baseline;justify-content:space-between;gap:10px;flex-wrap:wrap}
.month-kpi .stat span{overflow-wrap:normal}
.month-kpi .stat strong{margin-top:0;text-align:right;white-space:nowrap}
/* A suffix riding inside a tile's figure line (the projected row's "+120.00" and surplus/shortfall
   verdict): inline with the number, smaller and quiet, never a second line - the strong's nowrap
   keeps the pair together (user decision 2026-07-30: one line per tile). */
.month-kpi .stat strong .stat-suffix{display:inline;font-size:11px;font-weight:700;color:var(--mute);margin:0}
/* A KPI tile can also carry a COUNT under the figure ("1 donation"). The flex row above makes every
   child a flex item, so `.stat strong + small{display:block}` no longer starts a new line and the
   count sat beside the amount and overflowed the tile - clipped to "1 d". Give it the full width so
   it drops below, where the block rule always intended it. */
.month-kpi .stat > small{flex-basis:100%;text-align:end;margin-top:.15rem}
/* Monthly "pending this month" cluster: drafts, recurring to generate, instalments, partner debts,
   transfers, depreciation. SIX tiles, so six columns - it was five, which left the last one alone on
   a second row looking like an afterthought rather than the sixth of a set. Count the tiles when one
   is added here. */
.month-indicators{grid-template-columns:repeat(6,minmax(0,1fr));margin-top:6px}
/* Six across needs the room for it. Below ~1200px the sidebar leaves about 700px of content, which
   would give each tile ~108px - too narrow for "Recurring to generate" above three amount lines - so
   they go 3 + 3 rather than six cramped columns. The <=980 rule further down takes it to two. */
@media (max-width:1200px){
  .month-indicators{grid-template-columns:repeat(3,minmax(0,1fr))}
}
.stat.indicator{padding:8px 10px}
.indicator-pair{display:flex;flex-direction:column;gap:3px;margin-top:5px}
/* .stat span{display:block} would otherwise flatten these rows - scope beats it (0,2,0). */
.indicator .ind-line{display:flex;align-items:baseline;justify-content:space-between;gap:8px;min-width:0}
.indicator .ind-line em{font-style:normal;color:var(--mute);font-size:11px;font-weight:700;overflow-wrap:anywhere}
.indicator .ind-line b{font-size:14px;font-weight:800;font-variant-numeric:tabular-nums;white-space:nowrap}
.indicator .ind-line b.muted{font-weight:700;opacity:.5}
/* Indicator tile header carrying its pending COUNT on the same line, right-aligned
   ("Drafts to confirm   12"). */
.indicator > span.with-count{display:flex;align-items:center;justify-content:space-between;gap:8px}
.indicator > span.with-count b{font-size:14px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}
.indicator > span.with-count b.muted{opacity:.5}
.stat,.panel{
  border:1px solid var(--border);
  border-radius:var(--r);
  background:var(--panel);
  backdrop-filter:blur(18px);
}
/* backdrop-filter makes each panel its own stacking context, so an in-flow dropdown (.ss-list /
   .combo-list, z-index:50) is trapped inside its panel and a LATER panel paints over it. While a
   field in the panel is focused (its list open), lift the whole panel above later sibling panels.
   Stays within the content area, below the topbar (z-index:30 in the outer context). */
.panel:focus-within{position:relative;z-index:20}
.stat{padding:8px 10px;min-width:0}
.stat span{display:block;color:var(--mute);font-size:11px;font-weight:800;line-height:1.15;overflow-wrap:anywhere}
/* The label rule above also catches money_signed()'s .amount span inside the value and
   muted its colour (0,2,0 beats 0,1,0) - restore the signed colours inside tile values. */
.stat strong .amount{display:inline;color:var(--ink);font-size:inherit;font-weight:inherit;line-height:inherit}
.stat strong .amount-pos{color:var(--amount-pos)}
.stat strong .amount-neg{color:var(--amount-neg)}
.stat strong{display:block;margin-top:3px;font-size:18px;line-height:1.05;white-space:nowrap;font-variant-numeric:tabular-nums}
.stat-link{
  display:inline-block;
  margin-top:12px;
  color:var(--cyan);
  font-size:13px;
  font-weight:800;
  text-decoration:none;
}
/* A whole TILE that is a link (a dashboard number opening the list it counts) keeps the tile's own
   typography - the value is data, not a link label - and shows it can be opened by lifting on
   hover/focus. Recolouring every value accent-blue would make a grid of ten tiles shout (§11:
   the brand colour is an accent, not the default). */
.stat.stat-link{display:block;margin-top:0;color:inherit;font-size:inherit;font-weight:inherit}
/* A tile that OPENS something looks different at rest, not only under the pointer: in a grid where
   some tiles are links and some are plain figures, a hover-only affordance is invisible until you
   have already guessed which is which - and invisible on a touch screen entirely, where there is no
   hover at all (user request 2026-07-29). A raised surface and a chevron say "this opens"; the value
   keeps its own typography, because it is data rather than a link label. */
.stat.stat-link{background:var(--hover);border-color:var(--border-soft);position:relative;padding-inline-end:22px}
.stat.stat-link::after{
  content:"";position:absolute;inset-inline-end:9px;top:12px;width:6px;height:6px;
  border-inline-end:2px solid var(--mute);border-block-start:2px solid var(--mute);
  transform:rotate(45deg);opacity:.8;
}
[dir="rtl"] .stat.stat-link::after{transform:rotate(225deg)}
.stat.stat-link:hover,.stat.stat-link:focus-visible{background:rgba(var(--brand-rgb),.16);border-color:var(--cyan)}
.stat.stat-link:hover::after,.stat.stat-link:focus-visible::after{border-color:var(--cyan);opacity:1}
.panel{padding:18px}
.panel-head{
  display:flex;
  justify-content:space-between;
  gap:14px;
  align-items:flex-start;
  margin-bottom:14px;
}
/* Collapsible panels (.panel.js-collapsible): a labelled toggle BAR (injected after the head) folds
   the list away - "Show N entries ▾" / "Hide entries ▴", so a hidden list is obvious. Processed
   sections can default collapsed (server adds .collapsed); state persists per panel. Empty sections
   are not made collapsible (the normal empty message shows instead). */
.panel-toggle{
  width:100%;display:flex;align-items:center;justify-content:center;gap:8px;
  padding:9px 12px;
  border:1px solid var(--border);border-radius:var(--r);
  background:var(--surf-1);color:var(--soft);cursor:pointer;
  font-family:inherit;font-weight:600;font-size:13px;
}
.panel-toggle:hover{background:var(--hover);color:var(--text)}
.panel-toggle::after{
  content:"";width:7px;height:7px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:translateY(-2px) rotate(45deg);transition:transform .15s ease;
}
.panel.js-collapsible:not(.collapsed) .panel-toggle::after{transform:translateY(2px) rotate(-135deg)}
.panel.js-collapsible:not(.collapsed) .panel-toggle{margin-bottom:14px}
.panel.js-collapsible.collapsed > .panel-head{margin-bottom:14px}
.panel.js-collapsible.collapsed > :not(.panel-head):not(.panel-toggle){display:none}
/* Tabs (sub-pages): for multi-section pages where pagination would break running totals/balances -
   organise into tabs instead of one long scroll. Active tab persists in the URL hash. */
/* Segmented control: a visible holder (filled bar) with a clearly highlighted active tab. */
/* Underline tabs: a strip with a baseline the content sits under, active tab marked by a brand
   underline (not a filled pill) so tabs read as navigation, not action buttons. */
/* Real bordered tabs: each tab is a bordered shape with rounded top corners sitting on the bar's
   baseline; the active one is accent-tinted and reads as selected. Tab titles use the accent colour
   (not body text) so a tab reads as navigation, not content. One rule drives every tab set. */
/* Spacing between a tab bar, its toolbar and the panel comes from ONE source - the container grid
   gap (.main, or .tabs which mirrors it) - never element margins, so it can't double up. */
.tabs{display:grid;gap:18px;min-width:0}
.tab-bar{display:flex;flex-wrap:wrap;gap:4px;max-width:100%;border-bottom:1px solid var(--border)}
.tab{
  appearance:none;margin-bottom:-1px;
  background:var(--surf-1);border:1px solid var(--border);border-bottom-color:transparent;
  color:rgb(var(--accent-rgb));font:inherit;font-weight:600;padding:9px 16px;cursor:pointer;
  border-radius:var(--r-sm) var(--r-sm) 0 0;text-decoration:none;
  transition:background .15s,color .15s,border-color .15s;
}
.tab:hover{background:var(--panel-2)}
/* Active tab is FILLED with the brand (same as primary buttons) + white text - the current tab is
   unmistakable, and white-on-brand is already tuned to read in both dark and light modes. */
.tab.active{color:#fff;background:var(--brand);border-color:transparent;font-weight:700}
/* An active tab-panel stacks its children (toolbar, panel/s) with the same single-source gap as
   .tabs/.main, so a filter toolbar sits OUTSIDE the list card with a consistent gap (and stacked
   panels - e.g. profile + contacts - are spaced too). */
.tab-panel{display:grid;gap:18px;min-width:0}
.tab-panel[hidden]{display:none}
.actions,.row-actions,.form-actions{
  display:flex;
  gap:8px;
  flex-wrap:wrap;
  align-items:center;
}
/* Section-head actions: one row, pinned to the panel's right edge, the same in every
   section (§7) - the title text shrinks instead of the buttons wrapping. */
/* The page head's actions hold their row, exactly as the panel head's do. Only .panel-head had
   this: on .page-head the buttons kept their default shrink, so a long lede squeezed them and
   they stacked one per line from ~1280px down - Point of sale showed it worst. The text side
   shrinks instead, which is what it is for. */
.panel-head>.actions,.page-head>.actions{flex-wrap:nowrap;flex-shrink:0;justify-content:flex-end}
.page-head>div:first-child{min-width:0}
/* Button placement (framework §11): only NAVIGATION (back/cancel links) sits at the start corner;
   every SAVE action groups together at the far end - the primary at the very end with secondary
   saves (e.g. "Save as draft") right beside it. The row is end-aligned; the nav link's
   `margin-inline-end:auto` pushes just that link to the start, keeping the save group adjacent (a
   single auto margin, so no gap splits the saves). DOM order matches this (Cancel → secondary save →
   primary) so TAB order follows the visual reading order, and the Enter-key default is the first
   submit - the safe "Save as draft" on multi-save forms. flex `order` just mirrors the DOM. On
   mobile buttons go full-width and stack. */
.form-actions{justify-content:flex-end}
/* Review screen: secondary (Back to edit) on the left, primary (Confirm) on the right. */
.form-actions.form-actions-split{justify-content:space-between}
.form-actions > a,
.form-actions > .js-cancel-dirty{order:1;margin-inline-end:auto}
.form-actions > button:not(.primary),
.form-actions > input[type="submit"]:not(.primary){order:2}
.form-actions > .primary{order:3}
.button{
  min-height:40px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--border);
  border-radius:var(--r);
  padding:9px 13px;
  background:var(--surf-2);
  color:var(--text);
  text-decoration:none;
  font-weight:800;
  cursor:pointer;
  text-align:center;
  max-width:100%;
  white-space:nowrap; /* a button label is one action - it never breaks into lines */
  line-height:1.2;
}
.button{transition:background .15s,border-color .15s,box-shadow .15s,transform .06s}
.button:hover{background:var(--hover)}
.button:active{transform:translateY(1px)}
.button:focus-visible{outline:2px solid rgba(var(--brand-rgb),.75);outline-offset:2px}
.button.primary:hover{filter:brightness(1.07);box-shadow:0 4px 14px rgba(var(--brand-rgb),.32)}
.button:disabled,button:disabled{opacity:.45;cursor:not-allowed}
/* §5 applies to fields too: a derived/locked value must READ as not-yours-to-type. */
/* A DERIVED value (calculated, never typed): dashed outline + muted ink reads as "output". */
.js-base-amount:disabled{border-style:dashed;background:transparent;color:var(--mute);opacity:1}
.button:disabled:hover,button:disabled:hover{background:var(--surf-2)}
/* Hierarchy: .primary (one per view) > default (secondary) > .ghost (tertiary). */
.button.primary{background:var(--brand);border-color:transparent;color:#fff}
.button.primary:hover{background:var(--brand-hover)}
.button.ghost{background:transparent;border-color:transparent;color:var(--soft);font-weight:700}
.button.ghost:hover{background:var(--hover)}
.button.danger{color:#ffd7d7;border-color:rgba(var(--err-rgb),.28)}
.button.ghost.danger{color:#ff9d9d}
.button.ghost.danger:hover{background:rgba(var(--err-rgb),.12)}
.button.small{min-height:40px;padding:8px 10px;font-size:12px}
/* Exchange-rate "Use fetched rate" sits inline beside the Source badge. */
.rate-clear-form{display:inline-block;margin-left:8px;vertical-align:middle}
.rate-clear-form .button.small{min-height:0;padding:2px 8px}

/* Row overflow menu (§11): one inline action + a "⋯" trigger that opens a fixed-position
   popover (fixed so the table's overflow can't clip it). JS in layout.php positions it. */
.row-menu{position:relative;display:inline-flex}
.row-menu-trigger{min-height:20px;min-width:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--border);border-radius:var(--r);background:var(--surf-2);color:var(--soft);cursor:pointer;font-weight:800;font-size:18px;line-height:1;letter-spacing:1px}
.row-menu-trigger:hover,.row-menu-trigger[aria-expanded="true"]{background:var(--hover)}
/* Labeled Options trigger (§13): reads as a normal button, not the round ⋯ chip - and it is where
   the statement and print live, so it carries the accent as TEXT AND BORDER to be findable at a
   glance (user request 2026-07-30). Tinted, not filled: it is a secondary action, and §11 keeps one
   primary per view - a filled brand button here would compete with the page's own. */
.row-menu-trigger.is-options{
  min-width:0;width:auto;height:auto;border-radius:var(--r);padding:8px 12px;font-size:13px;gap:6px;
  color:rgb(var(--accent-rgb));
  font-weight:800;
  border-color:rgba(var(--accent-rgb),.42);
  background:rgba(var(--accent-rgb),.12);
}
.row-menu-trigger.is-options:hover,
.row-menu-trigger.is-options[aria-expanded="true"]{
  background:rgba(var(--accent-rgb),.2);
  border-color:rgba(var(--accent-rgb),.6);
}
/* The palette is authored dark-first, so the accent needs a darker counterpart on a light surface:
   the dark-theme violet measures 4.33:1 there, and 13px bold needs 4.5:1. Same lesson as the badge
   tones - a colour that reads on near-black is not a colour that reads on white. */
:root[data-theme="light"] .row-menu-trigger.is-options{
  color:#6d28d9;
  border-color:rgba(109,40,217,.5);
  background:rgba(109,40,217,.1);
}
:root[data-theme="light"] .row-menu-trigger.is-options:hover,
:root[data-theme="light"] .row-menu-trigger.is-options[aria-expanded="true"]{
  background:rgba(109,40,217,.18);
  border-color:rgba(109,40,217,.7);
}
.row-menu-list{position:fixed;z-index:60;min-width:172px;display:flex;flex-direction:column;gap:2px;padding:6px;background:var(--bg-2);border:1px solid var(--border);border-radius:var(--r-lg);box-shadow:var(--shadow-2)}
.row-menu-list form{width:100%;margin:0}
.row-menu-item{display:block;width:100%;text-align:left;padding:9px 11px;border:0;border-radius:var(--r);background:transparent;color:var(--soft);font:inherit;font-weight:700;cursor:pointer;text-decoration:none}
.row-menu-item:hover{background:var(--hover)}
.row-menu-item.danger{color:#ff9d9d}
.row-menu-item.danger:hover{background:rgba(var(--err-rgb),.14)}

/* Modal dialog (native <dialog>): centered card + dimmed backdrop. */
dialog.modal{border:1px solid var(--border);border-radius:var(--r-xl);background:var(--bg-2);color:var(--text);padding:0;max-width:min(440px,92vw);width:100%;box-shadow:var(--shadow-2);max-height:calc(100dvh - var(--safe-t) - var(--safe-b) - 32px);margin-top:calc(var(--safe-t) + 16px);margin-bottom:calc(var(--safe-b) + 16px)}
/* Wide variant for content-bearing modals (the review-before-post dry run with its Dr/Cr table).
   The fetched panels keep their own .panel chrome; the modal scrolls if they run long. */
dialog.modal.modal-wide{max-width:min(720px,94vw)}
dialog.modal.modal-wide .js-preview-body{overflow-y:auto}
/* A table inside a modal never forces the page-table minimum. tables carry min-width:760px so a
   data table keeps its columns readable on a wide page - but a modal is NARROWER than that, so every
   table in one overflowed and had to be scrolled sideways to read. This rule existed for the
   .modal-wide preview only; the review overlay next to it had the same problem and no rule, so the
   transfer review's "Resulting posting" could not be read without scrolling (user report
   2026-07-29). One rule for every modal instead of one per overlay. */
dialog.modal table{min-width:0}
/* Wider modal for the review-in-place overlay (carries the transfer summary + its posting). */
dialog.modal.modal-lg{max-width:min(880px,94vw)}
dialog.modal.modal-lg .review-body .panel{border:none;background:transparent;padding:0}
dialog.modal.modal-lg .review-body .panel-head{margin-bottom:12px}
dialog.modal::backdrop{background:rgba(2,1,10,.64)}
.modal-card{padding:20px;display:flex;flex-direction:column;gap:12px}
.modal-card h2{margin:0;font-size:18px}
.modal-sub{margin:0;color:var(--soft);font-weight:700}
.modal-card textarea{width:100%}
.modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}
/* Stepped-flow actions: secondary/back on the LEFT, primary on the RIGHT (opposite corners). */
.modal-actions-split{justify-content:space-between}
.flash{
  border:1px solid rgba(var(--ok-rgb),.3);
  background:rgba(var(--ok-rgb),.1);
  color:#c7ffdf;
  border-radius:var(--r);
  padding:12px 14px;
}
/* Error variant - a form-level message (e.g. failed review validation) as a full-width banner. */
.flash-error{
  border-color:rgba(var(--err-rgb),.4);
  background:rgba(var(--err-rgb),.12);
  color:#ffd9d9;
}
:root[data-theme="light"] .flash-error{color:#b4232a}
/* Login-page "why you were signed out" banners. Security alert reuses the error tone; the neutral
   variant is a quiet note. */
.auth-alert{margin:0 0 16px;text-align:left;padding:12px 14px;border:1px solid var(--border);border-radius:var(--r-lg)}
.auth-alert.flash-error{display:flex;flex-direction:column;gap:6px}
.auth-alert strong{font-weight:800}
.auth-note{background:var(--surf-1);color:var(--soft);font-size:.9rem}
.action-link{
  display:grid;
  gap:8px;
}
.action-link span{
  font-size:12px;
  font-weight:800;
  letter-spacing:.06em;
}
.action-link code{overflow-wrap:anywhere}
.form-note{
  border:1px solid var(--border);
  border-radius:var(--r);
  padding:12px;
  color:var(--soft);
  background:var(--surf-1);
}
/* Toned variants of the same note, for a finding that needs to read as a finding (a member locked
   out, a reset link that expired unused). Existing status tokens only - the shared note keeps its
   shape and only its tone changes, so nothing needs a bespoke callout. */
.form-note.tone-warn{border-color:rgba(var(--warn-rgb),.48);background:rgba(var(--warn-rgb),.10)}
.form-note.tone-err{border-color:rgba(var(--err-rgb),.48);background:rgba(var(--err-rgb),.10)}
.form-note.tone-ok{border-color:rgba(var(--ok-rgb),.45);background:rgba(var(--ok-rgb),.09)}
.timeline-note-form{
  display:grid;
  gap:12px;
  margin-bottom:18px;
}
.timeline{
  display:grid;
  gap:10px;
}
.timeline-item{
  border:1px solid var(--border-soft);
  border-radius:var(--r);
  padding:12px;
  background:var(--surf-1);
}
.timeline-item p{margin:8px 0 0}
.timeline-meta{
  display:flex;
  flex-wrap:wrap;
  gap:8px;
  align-items:center;
  color:var(--mute);
  font-size:12px;
  font-weight:800;
}
.stage-pipeline{
  display:flex;
  flex-wrap:wrap;
  gap:8px;
}
.stage-step{
  min-height:40px;
  border:1px solid var(--border);
  border-radius:var(--r-pill);
  padding:7px 11px;
  background:var(--surf-2);
  color:var(--soft);
  font-weight:800;
  cursor:pointer;
}
.stage-step:hover{background:var(--hover);color:#fff}
.stage-step.active{
  color:#fff;
  border-color:transparent;
  background:var(--grad);
}
.stage-settings-table input{
  min-width:120px;
}
.stage-settings-table .compact-input{
  width:90px;
  min-width:90px;
}
.inline-check{
  display:flex;
  align-items:center;
  grid-template-columns:none;
  gap:8px;
  min-height:42px;
}
.inline-check input{
  width:18px;
  min-height:18px;
  accent-color:var(--lime);
}
.share-field{
  border:1px solid var(--border);
  border-radius:var(--r);
  padding:14px;
}
.share-field legend{
  padding:0 6px;
  font-weight:900;
}
.share-field p{
  margin:0 0 12px;
  color:var(--mute);
}
.check-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
  gap:8px;
}
.check-inline{
  display:flex;
  align-items:center;
  gap:8px;
}
/* Lowest price / best quote in the RFQ comparison matrix. */
.cell-best{
  background:var(--ok-bg,rgba(34,197,94,.12));
  font-weight:600;
}
.stage-create-form{
  margin-top:18px;
  padding-top:18px;
  border-top:1px solid var(--divider);
}
.email-template-list{
  display:grid;
  gap:8px;
}
.template-editor{
  border:1px solid var(--border-soft);
  border-radius:var(--r-lg);
  background:var(--surf-1);
}
.template-editor>summary{
  list-style:none;
  cursor:pointer;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  padding:12px 14px;
}
.template-editor>summary::-webkit-details-marker{display:none}
.template-editor>summary::after{content:"▾";color:var(--mute);flex:0 0 auto;font-size:12px}
.template-editor[open]>summary::after{content:"▴"}
.template-editor>summary:hover{color:var(--text)}
.template-editor-title{
  display:flex;
  align-items:center;
  gap:10px;
  min-width:0;
  flex-wrap:wrap;
}
.template-editor-title strong{font-size:15px}
.template-editor-body{
  display:grid;
  gap:12px;
  padding:4px 14px 14px;
}
/* A closed <details> child with an explicit display can leak open in some browsers - force it shut. */
.template-editor:not([open]) .template-editor-body{display:none}
/* Template category groups (collapsed by default) - scan a few headers instead of every template. */
.template-group>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:8px;padding:10px 2px;font-weight:500;font-size:15px}
.template-group>summary::-webkit-details-marker{display:none}
.template-group>summary::after{content:"▾";color:var(--mute);margin-left:auto;font-size:12px}
.template-group[open]>summary::after{content:"▴"}
.template-group>summary:hover{color:var(--text)}
.template-group-count{color:var(--mute);font-weight:400;font-size:13px}
.template-group-body{display:grid;gap:8px;padding:0 0 10px 12px}
.template-group:not([open]) .template-group-body{display:none}
.auth-card{
  width:min(560px,100%);
  margin:8vh auto;
  border:1px solid var(--border);
  border-radius:var(--r);
  padding:26px;
  background:var(--panel);
  backdrop-filter:blur(18px);
}
.public-portfolio-body{
  min-height:100vh;
}
.public-portfolio{
  width:min(1120px,100%);
  margin:0 auto;
  padding:24px;
  display:grid;
  gap:18px;
}
.public-hero{
  min-height:56vh;
  display:flex;
  justify-content:space-between;
  align-items:flex-end;
  gap:24px;
  border:1px solid var(--border);
  border-radius:var(--r);
  padding:32px;
  background:
    linear-gradient(120deg,rgba(6,1,15,.2),rgba(6,1,15,.7)),
    radial-gradient(circle at 78% 20%,rgba(var(--ok-rgb),.2),transparent 24rem),
    var(--panel);
  backdrop-filter:blur(18px);
}
.public-hero h1{
  max-width:780px;
  font-size:clamp(34px,6vw,64px);
}
.public-headline{
  max-width:760px;
  color:var(--soft);
  font-size:22px;
  font-weight:700;
}
.public-meta{
  display:flex;
  flex-wrap:wrap;
  gap:8px;
  margin-top:14px;
}
.public-meta span{
  display:inline-flex;
  border:1px solid var(--border);
  border-radius:var(--r-pill);
  padding:5px 9px;
  color:var(--soft);
  font-size:12px;
  font-weight:800;
}
.public-contact{
  display:flex;
  flex-wrap:wrap;
  gap:8px;
  justify-content:flex-end;
}
.public-section{
  border:1px solid var(--border);
  border-radius:var(--r);
  padding:24px;
  background:var(--surf-2);
  backdrop-filter:blur(18px);
}
.public-section > p{
  color:var(--soft);
  max-width:820px;
}
.public-section-head{
  display:flex;
  justify-content:space-between;
  gap:14px;
  align-items:center;
  margin-bottom:14px;
}
.public-project-grid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:14px;
}
.public-card{
  border:1px solid var(--border-soft);
  border-radius:var(--r);
  padding:16px;
  background:var(--surf-1);
}
.public-card h3{
  margin:10px 0 6px;
  font-size:21px;
  line-height:1.15;
}
.public-card,.public-section,.panel,.stat,.page-head,.topbar,.auth-card,.timeline-item,.template-editor{
  min-width:0;
}
.public-card p,.panel p,.lede,.empty,.muted,td,th,label,code{
  overflow-wrap:anywhere;
}
.public-card p{
  color:var(--soft);
}
.public-card-subtitle{
  margin:0;
  color:#fff;
  font-weight:800;
}
.public-project-card{
  display:grid;
  gap:14px;
}
.public-project-card img{
  width:100%;
  aspect-ratio:16 / 9;
  object-fit:cover;
  border-radius:var(--r);
  border:1px solid var(--border-soft);
  background:var(--surf-2);
}
.public-skill-list{
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:10px;
}
.public-skill{
  display:grid;
  gap:3px;
  border:1px solid var(--border-soft);
  border-radius:var(--r);
  padding:12px;
  background:var(--surf-1);
}
.public-skill span,.public-skill small{
  color:var(--mute);
  font-size:12px;
  font-weight:800;
}
.public-timeline{
  display:grid;
  gap:12px;
}
.login-form{
  display:grid;
  gap:14px;
  margin-top:22px;
}
.empty{color:var(--mute)}
.table-wrap{overflow:auto}
/* Dashboard "recent" widgets: a quiet right-aligned footer linking to the full lists. */
.panel-foot{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:14px;margin-top:12px;padding-top:12px;border-top:1px solid var(--border)}
.panel-foot a{color:var(--soft);font-weight:700;font-size:13px;text-decoration:none}
.panel-foot a:hover{color:var(--text)}
/* Report account trees: indent leaves by depth; parent rows (with a subtotal) read as headers. */
.coa-acct{display:inline-block;padding-left:calc(var(--coa-depth, 0) * 18px)}
.coa-group-row td{font-weight:700}
/* Shared pagination control (list pages): size selector + status + page links. */
.pagination{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;margin-top:14px}
.pagination-size{display:flex;flex-wrap:wrap;align-items:center;gap:12px}
.pagination-show{display:inline-flex;align-items:center;gap:6px;color:var(--mute);font-size:13px;margin:0}
.pagination-show select{width:auto;min-height:34px;padding:4px 26px 4px 8px}
.pagination-status{color:var(--mute);font-size:13px}
.pagination-nav{display:flex;flex-wrap:wrap;gap:4px}
.pagination-nav .button.small{min-width:36px;justify-content:center}
.pagination-nav [aria-disabled="true"]{opacity:.45;cursor:not-allowed}
.pagination-nav [aria-current="page"]{pointer-events:none}
.check-col{width:1%;white-space:nowrap;text-align:center}
.check-col input{cursor:pointer;width:16px;height:16px}
/* The .row-actions class also styles button groups (display:flex, line ~492). On a table cell that
   flex would drop it out of the row - and on the empty header cell it broke the header's bottom
   border after the last column. Force both header and body action cells back to table-cell. */
th.row-actions,td.row-actions{display:table-cell;white-space:nowrap}
/* Grouped section tables (monthly review): clear column heads, a group header row that
   carries the group name + its subtotal, and a section-net footer. */
td.num{text-align:right;white-space:nowrap}
th.num{text-align:right}
thead th{vertical-align:middle;border-top:1px solid var(--border);border-bottom:1px solid var(--border);font-size:11px;font-weight:800;color:var(--mute);padding:10px 12px 8px}
.col-date{white-space:nowrap}
/* A tile's second line: the same figure in the base currency, labelled so it is not mistaken for a
   stray number. Its own row beneath the primary amount, muted, and never wrapped mid-value. */
.stat strong + small,.statement-summary-cell strong + small{display:block;margin-top:.15rem;white-space:nowrap;font-weight:400}
.stat strong + small .amount,.statement-summary-cell strong + small .amount{display:inline}
/* Currency tick list on the exchange-rates page: wraps into columns, one per line on mobile. */
.currency-pick{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:.35rem 1rem;margin:.5rem 0}
.currency-pick .check-inline{display:flex;align-items:center;gap:.5rem}
/* A reference code is an identifier - never break one across lines, same rule as a date. */
.col-ref{white-space:nowrap}
.record-link{white-space:nowrap} /* a reference code is one token - never breaks (screen; print wraps via stmt rules) */
.col-wrap{white-space:normal;word-break:break-word;max-width:34ch}
table.grouped{min-width:0}
tbody td{padding-top:4px;padding-bottom:4px}
table.grouped thead th{font-size:11px;font-weight:800;color:var(--mute);border-top:1px solid var(--border);border-bottom:1px solid var(--border);padding:10px 12px 8px}
thead th{white-space:nowrap}
table.grouped tr.group-row th{background:var(--panel);text-align:left;font-size:13px;font-weight:800;color:var(--text);padding:9px 12px;border-top:1px solid var(--border);border-bottom:1px solid var(--border)}
table.grouped tr.group-row .group-sub{color:var(--mute);font-weight:700}
tr.empty-row td{color:var(--mute);font-style:italic}
table.grouped tfoot td{border-top:2px solid var(--border);font-weight:800;font-size:14px;padding-top:12px}
.section-net{font-weight:800}
.section-net.pos,.amount-pos{color:var(--amount-pos)}
.section-net.neg,.amount-neg{color:var(--amount-neg)}
.check-list{list-style:none;margin:0 0 14px;padding:0;display:flex;flex-direction:column;gap:8px}
.check-list li{display:flex;gap:10px;align-items:flex-start;color:var(--soft)}
.check-list .ck{flex:0 0 auto;font-weight:800}
.check-list .ck.ok{color:var(--lime)}
.check-list .ck.no{color:#ffb27a}
table{
  width:100%;
  border-collapse:collapse;
  min-width:760px;
}
th,td{
  padding:12px;
  border-bottom:1px solid var(--divider);
  text-align:left;
  vertical-align:middle;
  overflow-wrap:break-word;
}
th{
  color:var(--mute);
  font-size:12px;
  letter-spacing:.06em;
}
.badge{
  display:inline-flex;
  align-items:center;
  min-height:26px;
  border:1px solid var(--border);
  border-radius:var(--r-pill);
  padding:4px 8px;
  color:var(--soft);
  font-size:12px;
  font-weight:800;
  line-height:1.15;
  background:var(--surf-2);
}
/* A draft's blank reference placeholder in a page-head h1: the "—" sits on the text baseline while
   the Draft badge box is taller, which reads misaligned - centre both on the same midline. */
.ref-blank{display:inline-block;vertical-align:middle}
.ref-blank + .badge{vertical-align:middle}
/* A partner's PRIMARY role among several - a brand-tinted badge so the main role reads first. */
.badge-role-primary{
  border-color:rgba(var(--brand-rgb),.5);
  background:rgba(var(--brand-rgb),.18);
  color:var(--text);
}
.badge-status-paid,
.badge-status-active,
.badge-status-accepted,
.badge-status-completed,
.badge-status-shortlisted,
.badge-status-offer,
.badge-status-featured,
.badge-stage-offer,
.badge-signin-success,
.badge-signin-recovery-code-used,
.badge-signin-reset-requested,
.badge-priority-low{
  color:#c8ffe1;
  border-color:rgba(var(--ok-rgb),.34);
  background:rgba(var(--ok-rgb),.14);
}
.badge-status-interesting,
.badge-status-in-progress,
.badge-status-sent,
.badge-status-applied,
.badge-stage-applied,
.badge-stage-screening,
.badge-event-email,
.badge-event-call,
.badge-event-note,
.badge-event-admin-notification,
.badge-type-application,
.badge-type-opportunity,
.badge-type-course{
  color:#c7f5ff;
  border-color:rgba(var(--info-rgb),.36);
  background:rgba(var(--info-rgb),.14);
}
.badge-status-partial,
.badge-priority-medium,
.badge-status-planned,
.badge-status-researching,
.badge-status-invited,
.badge-stage-draft,
.badge-signin-form-expired,
.badge-signin-no-such-account,
.badge-signin-reset-no-match,
.badge-signin-reset-account-not-active,
.badge-event-task,
.badge-event-field-changed,
.badge-event-password-reset-generated,
.badge-event-invite-generated{
  color:#ffefbf;
  border-color:rgba(var(--warn-rgb),.38);
  background:rgba(var(--warn-rgb),.15);
}
.badge-priority-high,
.badge-event-interview,
.badge-event-follow-up,
.badge-event-deadline,
.badge-event-reminder,
.badge-event-request,
.badge-status-due{
  color:#ffe0c2;
  border-color:rgba(var(--warn-rgb),.48);
  background:rgba(var(--warn-rgb),.2);
}
.badge-status-overdue,
.badge-status-rejected,
.badge-status-dropped,
.badge-status-inactive,
.badge-stage-rejected,
.badge-signin-bad-password,
.badge-signin-locked-out,
.badge-signin-2fa-failed,
.badge-signin-reset-email-failed,
.badge-status-failed,
.badge-event-error{
  color:#ffd7d7;
  border-color:rgba(var(--err-rgb),.42);
  background:rgba(var(--err-rgb),.15);
}
.badge-status-archived,
.badge-status-paused,
.badge-status-withdrawn,
.badge-stage-withdrawn,
.badge-status-skipped,
.badge-role-admin,
.badge-role-owner,
.badge-role-user,
.badge-rate-triangulated{
  color:#ded9ff;
  border-color:rgba(var(--accent-rgb),.34);
  background:rgba(var(--accent-rgb),.14);
}
/* Exchange-rate source badges: fetched (ok) / manual (info) already covered by the groups above;
   these add the two rate-specific values. */
.badge-rate-fetched{color:#c8ffe1;border-color:rgba(var(--ok-rgb),.34);background:rgba(var(--ok-rgb),.14)}
.badge-rate-manual{color:#c7f5ff;border-color:rgba(var(--info-rgb),.36);background:rgba(var(--info-rgb),.14)}
.badge-rate-needs-manual{color:#ffefbf;border-color:rgba(var(--warn-rgb),.38);background:rgba(var(--warn-rgb),.15)}
/* GL/ledger account-type badges (asset/liability/equity/income/expense); other 'type' values fall
   back to the neutral base .badge. */
.badge-acct-asset{color:#c8ffe1;border-color:rgba(var(--ok-rgb),.34);background:rgba(var(--ok-rgb),.14)}
.badge-acct-income{color:#c7f5ff;border-color:rgba(var(--info-rgb),.36);background:rgba(var(--info-rgb),.14)}
.badge-acct-expense{color:#ffefbf;border-color:rgba(var(--warn-rgb),.38);background:rgba(var(--warn-rgb),.15)}
.badge-acct-liability{color:#ffd7d7;border-color:rgba(var(--err-rgb),.42);background:rgba(var(--err-rgb),.15)}
.badge-acct-equity{color:#ded9ff;border-color:rgba(var(--accent-rgb),.34);background:rgba(var(--accent-rgb),.14)}
/* A badge that navigates (e.g. GL account -> its bank account). */
a.badge.badge-link{cursor:pointer;text-decoration:none}
a.badge.badge-link:hover{border-color:rgba(var(--accent-rgb),.5)}
.muted{
  display:block;
  color:var(--mute);
  margin-top:4px;
  font-size:12px;
}
.field-note{
  display:block;
  color:var(--mute);
  margin-top:2px;
  font-size:12px;
  font-weight:400;
}
.base-amount-hint{display:block;margin-top:6px;font-size:13px;font-weight:800;color:var(--soft)}
.base-amount-hint:empty{display:none}
/* Field help: a "?" after a label opens a floating bubble with on-demand help. */
.field-help{position:relative;display:inline-flex;vertical-align:middle;margin-left:4px}
.field-help-btn{
  width:16px;height:16px;flex:none;padding:0;
  display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--border);border-radius:var(--r-pill);
  background:var(--surf-2);color:var(--mute);
  font:800 11px/1 var(--font);cursor:pointer;
}
.field-help-btn:hover{color:var(--text);border-color:var(--border-soft)}
.field-help-btn[aria-expanded="true"]{color:var(--cyan);border-color:var(--cyan)}
.help-bubble{
  position:absolute;z-index:50;top:24px;left:0;
  width:min(300px,78vw);
  padding:10px 12px;
  background:var(--bg-2);border:1px solid var(--border);
  border-radius:var(--r);box-shadow:var(--shadow-2);
  color:var(--text);font:400 13px/1.6 var(--font);
  text-align:left;white-space:normal;cursor:default;
}
.help-bubble strong{display:block;margin-bottom:4px;font-weight:800}
.help-bubble .help-b{display:block;position:relative;padding-left:14px;margin-top:6px}
.help-bubble .help-b::before{content:"\2022";position:absolute;left:3px;color:var(--mute)}
.help-bubble .help-note{display:block;margin-top:8px;color:var(--mute)}
tr.row-inactive td{opacity:.55}
tr.row-inactive td.row-actions{opacity:1}
.coa-group{border:1px solid var(--border);border-radius:var(--r);background:var(--panel);overflow:hidden}
.coa-group>summary{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:13px 16px;cursor:pointer;list-style:none;font-weight:800}
.coa-group>summary::-webkit-details-marker{display:none}
.coa-group>summary::before{content:"\25B8";font-size:11px;color:var(--mute);transition:transform .15s ease}
.coa-group[open]>summary::before{transform:rotate(90deg)}
.coa-group>summary:hover{background:var(--panel-2)}
.coa-root-code{color:var(--mute);font-variant-numeric:tabular-nums}
/* min-content, not 0: the group name is a single word ("Expenses", "Liabilities"), so a flex
   basis of 0 squeezed the box to ~24px and the word - which has nothing to wrap at - spilled
   straight out of it on a phone. Holding the longest word makes the summary WRAP instead. */
.coa-root-name{flex:1 1 auto;min-width:min-content}
.coa-root-count{color:var(--mute);font-size:12px;font-weight:600;white-space:nowrap}
.coa-group .table-wrap{margin:0;border-top:1px solid var(--border)}
.coa-group>.empty{padding:13px 16px;margin:0}
/* A level-3 account sits indented under its sub-account, with a tree connector, so the hierarchy reads. */
.coa-row-child td.coa-name-cell{padding-left:30px;position:relative}
.coa-tree-mark{position:absolute;left:11px;color:var(--mute);font-size:13px;line-height:1}
.locked-field{display:flex;gap:8px;align-items:center}
.locked-field>select,.locked-field>input,.locked-field>textarea{flex:1;min-width:0}
/* A locked field's control + pencil must stay clickable even inside a checkbox label, whose
   pointer-events the "click only the control" rule (pointer:fine, below) zeroes out. */
.locked-field,.locked-field *{pointer-events:auto}
/* A locked boolean keeps the checkbox + its pencil inline (not a full-width flex column) at natural
   size - in the generic settings form and the bespoke organisation forms alike. */
.locked-field:has(> input[type="checkbox"]){display:inline-flex;flex:none}
.locked-field>input[type="checkbox"]{flex:none}
.lock-edit{flex:none;width:38px;min-height:40px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--border);border-radius:var(--r);background:var(--panel);color:var(--mute);cursor:pointer}
.lock-edit:hover{color:var(--text)}
.more-options{grid-column:1 / -1;border-top:1px solid var(--border);padding-top:6px}
.more-options>summary{cursor:pointer;list-style:none;font-weight:800;color:var(--mute);padding:8px 0;display:inline-flex;align-items:center;gap:8px}
.more-options>summary::-webkit-details-marker{display:none}
.more-options>summary::before{content:"\25B8";font-size:11px;transition:transform .15s ease}
.more-options[open]>summary::before{transform:rotate(90deg)}
.more-options>.form-grid{margin-top:12px}
.month-switcher{display:grid;grid-template-columns:1fr auto 1fr;align-items:stretch;gap:10px;margin-bottom:18px}
/* Quick-add bar: one inline draft-entry form per group (Expenses / Income) on the monthly view.
   Labels sit ABOVE their field (the app-wide label grid), so the required * and ? help mirror the
   full entry form. Fields bottom-align so the inputs and the Add button share a baseline. Wraps to
   stacked on mobile. */
.quick-add{display:flex;flex-wrap:wrap;align-items:flex-end;gap:8px 10px;margin:0;padding:12px;border:1px solid var(--border);border-radius:var(--r-lg);background:var(--bg-2)}
.quick-add label{margin:0}
.quick-add .quick-add-cat{flex:1 1 200px}
.quick-add .quick-add-amount{flex:0 1 150px}
.quick-add .quick-add-details{flex:1 1 200px}
.quick-add select,.quick-add input{width:100%}
/* The add form lives in a full-width row at the foot of its group; the cell just frames it. */
.quick-add-row > td{padding:8px;background:var(--bg)}
@media (max-width:680px){
  .quick-add{flex-direction:column;align-items:stretch}
  .quick-add label,.quick-add .quick-add-cat,.quick-add .quick-add-amount,.quick-add .quick-add-details{flex:1 1 auto}
  .quick-add button{width:100%}
}

.month-nav{display:flex;flex-direction:column;justify-content:center;gap:3px;padding:12px 16px;border:1px solid var(--border);border-radius:var(--r);background:var(--panel);color:var(--mute);text-decoration:none}
.month-nav:hover{color:var(--text);background:var(--panel-2)}
.month-nav.next{align-items:flex-end;text-align:right}
.month-nav-dir{font-size:11px;font-weight:800}
.month-nav-label{font-size:14px;font-weight:800}
/* The centre of the switcher IS the month/year jump - a violet-accented card holding the two
   selects and a Go, so there is one control instead of a static label above a separate jump row. */
.month-current{display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 14px;border:1px solid var(--violet);border-radius:var(--r);background:var(--panel-2);margin:0}
.month-current label{margin:0;min-width:0}
.month-current select{min-width:0;font-weight:800}
.month-current .button{flex:0 0 auto}
@media (max-width:560px){
  .month-current{padding:6px 8px;gap:6px;flex-wrap:wrap}
  .month-nav{padding:10px 12px}
  .month-nav-label{font-size:12px}
  .month-nav-dir{font-size:10px}
}
/* Required marker (user decision 2026-07-05, framework §5): ANY label whose control is
   required gets a red asterisk automatically - one CSS rule, no per-field markup, so no
   form can forget it. Works for plain inputs, selects, textareas, the date picker and
   comboboxes (the real control keeps the required attribute inside the label). */
.form-grid label:has([required]) > span:first-child::after,
.form-grid label:has([data-required]) > span:first-child::after,
label:has(> [required]) > span:first-child::after,
label:has([data-required]) > span:first-child::after{
  content:" *";
  color:rgb(var(--err-rgb));
  font-weight:800;
}

.form-grid{
  display:grid;
  grid-template-columns:repeat(12,minmax(0,1fr));
  gap:14px 16px;
  align-items:start; /* cells size to content + top-align, so inputs line up across a row even
                        when one field carries a note or a longer label underneath */
}
/* Field span on a 12-col grid. Default is half-width; short fields auto-size to a compact
   span by their control type (via :has) so small values don't get long boxes - this covers
   bespoke and config-driven forms with no per-field markup. .field-* are explicit escape
   hatches; .wide / a textarea span the full row. Collapses to one column on small screens. */
.form-grid > label{grid-column:span 6}
.form-grid > label:has(.js-money),
.form-grid > label:has(.js-base-amount),
.form-grid > label:has(.js-currency-select),
.form-grid > label:has(.js-pay-via),
.form-grid > label:has(.date-picker),
.form-grid > label:has(input[type="number"]){grid-column:span 3}
.form-grid > label:has(textarea),
.form-grid > .wide{grid-column:1 / -1}
/* Explicit widths OUTRANK the :has() auto-sizing (label.field-* ties :has specificity,
   later source order wins) - a declared width is a decision, not a hint. */
.form-grid > label.field-sm,.form-grid > .field-sm{grid-column:span 3}
.form-grid > label.field-md,.form-grid > .field-md{grid-column:span 4}
.form-grid > label.field-lg,.form-grid > .field-lg{grid-column:span 6}
.form-grid > .field-xl{grid-column:span 8}
/* Grouped form sections (fieldsets) read as soft rounded cards, not the browser's square groove. */
.panel fieldset{
  border:1px solid var(--border);
  border-radius:var(--r-lg);
  background:var(--surf-1);
  padding:14px 16px;
  margin:0;
  min-width:0;
}
.panel fieldset > legend{
  padding:0 8px;
  font-size:12px;
  font-weight:800;
  color:var(--mute);
}
/* Monthly-view group box: wraps related sections (Income & expenses, Bank transfers) in one
   labelled box like the transfer form's field groups. Inner panels go flat so it reads as a
   single box holding subsections, not boxes nested in a box. */
.month-group{
  border:1px solid var(--border);
  border-radius:var(--r-lg);
  background:var(--surf-1);
  padding:6px 16px 14px;
  margin:0 0 18px;
  min-width:0;
}
.month-group > legend{
  padding:0 8px;
  font-size:12px;
  font-weight:800;
  color:var(--mute);
}
.month-group > .panel{
  background:none;
  border:0;
  border-radius:0;
  box-shadow:none;
  backdrop-filter:none;
  padding:10px 0;
  margin:0;
}
.month-group > .panel + .panel{border-top:1px solid var(--border)}
/* Schedule End date/Repeats pair, revealed by the "Set an end…" toggle - its own compact flex row. */
.js-end-fields{display:flex;flex-wrap:wrap;gap:14px 16px;align-items:start;margin-top:10px}
.js-end-fields > label{flex:0 1 200px;min-width:0}
.js-end-toggle{align-self:start;margin-top:2px}
/* A group of related fields kept together on their own line (e.g. the money block:
   Amount · Currency · [Exchange rate] · Amount in base). Flexbox so widths can differ and
   still fill the row with no gaps: fields grow equally by default; .field-narrow (the rate)
   is slimmer and .field-wide (amount in base) grows more so its note fits on one line.
   Wraps on small screens. */
.form-grid > .field-row{
  grid-column:1 / -1;
  display:flex;
  flex-wrap:wrap;
  gap:14px 16px;
  align-items:start;
}
.field-row > label{flex:1 1 150px;min-width:0}
/* Inside a flex field-row the grid span classes don't apply - map the sm width to a compact,
   content-sized flex basis (e.g. the Type picker beside Category on the recurring forms). */
.field-row > label.field-sm{flex:0 1 10rem}
.detail-grid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:12px;
}
/* Preview groups: the fields in a group box sit on ONE line, sharing the width, and wrap only when
   the row gets too narrow (mobile). Used by the posting-preview screens. */
.preview-line{display:flex;gap:12px;flex-wrap:wrap}
.preview-line > .detail-item{flex:1 1 140px;min-width:0}
/* The date shown inline with a panel title. */
.panel-head .head-meta{color:var(--mute);font-size:13px;font-weight:700;white-space:nowrap}
.detail-item{
  display:grid;
  gap:6px;
  padding:12px;
  border:1px solid var(--border);
  border-radius:var(--r);
  background:var(--surf-1);
  min-width:0;
}
.detail-item.wide{grid-column:1/-1}
/* Bare label/value detail rows (POS receipt + shift, appointment view): the label sits on its own
   line above the value, muted - without this the two inline spans render mashed ("CustomerJordan
   Ellis"), worst on mobile. Distinct from the bordered .detail-item card. */
.detail-label{display:block;color:var(--mute);font-size:.8rem;margin-bottom:1px}
/* Document header meta (invoice/RFQ: the number + date opposite the issuer). Aligns to the end edge
   (right in LTR, left in RTL) beside the issuer on desktop; on mobile the header stacks, so it aligns
   to the start edge to sit under the issuer instead of floating off to the side. */
.doc-head-meta{text-align:end}
/* Compact variant: small boxes sized to their content (an arrangement's terms), packing several per
   row instead of two big cards. Auto-fill so short values don't stretch into oversized boxes. */
.detail-grid.compact{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:8px}
.detail-grid.compact .detail-item{padding:7px 10px;gap:2px}
.detail-grid.compact .detail-item span{font-size:11px}
.detail-grid.compact .detail-item strong{font-size:13px;line-height:1.3}
/* Plan terms: reference (compact) + partner (fills the line) on row 1, then the four short terms on
   row 2. Four equal columns; the partner cell spans the three after the reference. */
.detail-grid.plan-terms{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}
.detail-grid.plan-terms .detail-item{padding:7px 10px;gap:2px}
.detail-grid.plan-terms .detail-item span{font-size:11px}
.detail-grid.plan-terms .detail-item strong{font-size:13px;line-height:1.3}
.detail-grid.plan-terms .col-partner{grid-column:span 3}
.detail-item span{
  color:var(--mute);
  font-size:12px;
  font-weight:800;
}
.detail-item strong{
  color:var(--soft);
  font-size:14px;
  line-height:1.45;
  overflow-wrap:anywhere;
}
.event-list{
  display:grid;
  gap:10px;
}
.event-item{
  display:grid;
  gap:6px;
  padding:12px;
  border:1px solid var(--border);
  border-radius:var(--r);
  background:var(--surf-1);
}
.event-item div{
  display:flex;
  justify-content:space-between;
  gap:12px;
  flex-wrap:wrap;
}
.event-item strong{color:var(--soft)}
.event-item span{color:var(--cyan);font-size:13px;font-weight:800}
.event-item p{margin:0}
.setup-steps{
  display:grid;
  gap:10px;
  margin:0;
  padding:0;
  list-style:none;
}
.setup-steps li{
  display:flex;
  justify-content:space-between;
  align-items:center;
  gap:14px;
  flex-wrap:wrap;
  padding:12px;
  border:1px solid var(--border);
  border-radius:var(--r);
  background:var(--surf-1);
}
.setup-steps li > div{min-width:0}
.setup-steps strong{color:var(--soft);display:block}
.setup-steps p{margin:4px 0 0;color:var(--mute);font-size:13px}
.setup-steps .step-done{color:var(--lime);font-weight:800;font-size:13px}
.setup-steps .step-wait{color:var(--mute);font-size:13px}
.rate-cell{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}
/* An explicit display (.button / .rate-edit-form / label{display:grid}) would otherwise
   defeat [hidden]; enforce the HTML semantic globally. */
[hidden]{display:none !important}
.list-toolbar{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
.list-toolbar-label{color:var(--mute);font-size:12px;font-weight:800}
.list-toolbar-field{display:flex;align-items:center;gap:8px;margin:0}
.list-toolbar-field > span{color:var(--mute);font-size:12px;font-weight:800;white-space:nowrap}
.list-toolbar select{width:auto;min-width:130px}
/* One-row budget: dropdowns and dates take fixed slices, search flexes into the rest,
   the button never wraps to its own line. */
.list-toolbar .combo,.list-toolbar .ss{flex:0 1 190px;min-width:140px}
/* Currency values are 3-letter codes - a narrow combo keeps the toolbar on one row so the Clear
   button fits without wrapping. */
.list-toolbar-field:has([name="currency"]) .combo{flex:0 1 110px;min-width:88px}
.list-toolbar input[type=search]{width:auto;flex:1 1 150px;min-width:130px;max-width:340px}
/* Clearable search box: an × sits inside the box once a term is searched; suppress the browser's
   own native clear widget so there's only one. */
.list-toolbar-search{position:relative;display:flex;align-items:center;flex:1 1 150px;min-width:130px;max-width:340px}
.list-toolbar-search input[type=search]{flex:1;width:100%;min-width:0;max-width:none;padding-right:30px}
.search-clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;color:var(--mute);text-decoration:none;font-size:15px;font-weight:700;line-height:1}
.search-clear:hover{background:var(--panel-2);color:var(--text)}
input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none;display:none}
.list-toolbar input[type=date]{width:126px;min-width:0}
/* A readonly field that a pencil button unlocks for editing (e.g. a bank account's opening balance). */
.field-lock{display:flex;gap:8px;align-items:stretch}
.field-lock input{flex:1 1 auto;min-width:0}
.field-lock-toggle{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:40px;background:transparent;border:1px solid var(--border);border-radius:var(--r-lg);color:var(--mute);cursor:pointer}
.field-lock-toggle:hover{color:var(--text);border-color:var(--cyan)}
/* Plain-text email preview (partner notification review). */
.email-preview{white-space:pre-wrap;word-break:break-word;font:13px/1.6 ui-monospace,"SF Mono",Menlo,Consolas,monospace;background:var(--panel);border:1px solid var(--border);border-radius:var(--r-lg);padding:14px 16px;margin:0 0 16px;color:var(--soft);overflow-x:auto}
.rate-edit{
  display:inline-flex;align-items:center;justify-content:center;
  width:26px;height:26px;padding:0;
  border:1px solid var(--border);border-radius:var(--r-sm);
  background:var(--panel);color:var(--mute);cursor:pointer;
}
.rate-edit:hover{color:var(--text);border-color:rgba(255,255,255,.24)}
.rate-edit:focus-visible{outline:none;border-color:var(--cyan);box-shadow:0 0 0 3px rgba(var(--info-rgb),.12)}
.rate-edit-form{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}
.rate-edit-form input[name=rate]{width:140px}
/* The rate's effective date sits beside it in the inline editor; the shared picker is used so
   dates read and parse the same here as everywhere else (§12). */
.rate-edit-form .rate-date-field .date-picker{width:150px}
.date-picker{
  position:relative;
  display:block;
}
.date-picker .js-date-display{
  padding-right:44px;
}
.date-picker .js-date-native{
  position:absolute;
  right:0;
  bottom:0;
  width:1px;
  height:1px;
  opacity:0;
  pointer-events:none;
}
/* Touch devices: the native date input covers the whole field so a TAP opens the OS date picker
   directly (a button → showPicker() is unreliable on iOS PWAs). It's transparent, so the formatted
   dd-mm-yyyy text shows through beneath it. */
@media (pointer: coarse){
  .date-picker .js-date-native{
    inset:0;
    width:100%;
    height:100%;
    pointer-events:auto;
    z-index:2;
    cursor:pointer;
    -webkit-appearance:none;
    appearance:none;
    /* iOS won't open the picker for an opacity:0 input, so keep it RENDERED (opacity 1) but make it
       invisible via transparent text/background/border - the dd-mm-yyyy text shows through beneath. */
    opacity:1;
    background:transparent;
    border:0;
    color:transparent;
  }
  .date-picker .js-date-native::-webkit-date-and-time-value{color:transparent}
  /* Keep the locked (readonly) date field un-tappable so a computed End date can't be changed. */
  .js-locked .date-picker .js-date-native{pointer-events:none;z-index:0}
}
.date-picker-button{
  position:absolute;
  right:6px;
  top:50%;
  width:32px;
  height:32px;
  min-height:32px;
  transform:translateY(-50%);
  border:0;
  border-left:1px solid var(--border);
  background:transparent;
  color:var(--cyan);
  cursor:pointer;
}
.date-picker-button::before{
  content:"";
  position:absolute;
  left:50%;
  top:50%;
  width:15px;
  height:14px;
  transform:translate(-50%,-45%);
  border:2px solid currentColor;
  border-radius:3px;
}
.date-picker-button::after{
  content:"";
  position:absolute;
  left:50%;
  top:9px;
  width:11px;
  height:2px;
  transform:translateX(-50%);
  background:currentColor;
  box-shadow:-4px -3px 0 -1px currentColor,4px -3px 0 -1px currentColor;
}
.date-picker-button:hover{color:var(--soft)}
.date-picker-button:focus-visible{
  outline:2px solid var(--cyan);
  outline-offset:2px;
  border-radius:var(--r-sm);
}
label{display:grid;gap:7px;color:var(--mute);font-size:12px;font-weight:800}
label span{letter-spacing:.05em}
/* A field label always sits on ONE line (framework §5): the label text, its (optional) hint and the
   ? help icon never wrap onto a second line - a field is widened to fit its label instead of letting
   the label break. Covers both the label caption (label > span) and standalone group captions
   (.field-label, e.g. "Lines", "Requested items"). The ? help bubble is absolutely positioned and
   white-space:normal, so its own text still wraps normally. Checkbox/radio label text is exempt (it
   can be a longer sentence next to the box) - those live in .check-field / .check-inline / .role-row. */
label > span:first-child,
.field-label{white-space:nowrap}
/* .muted is display:block globally, which would push a label's "(optional)" hint onto its own line
   despite the nowrap - inside a label caption it stays inline on the same single line. */
label > span:first-child .muted,
label > span:first-child small{display:inline;margin-top:0}
label small{color:var(--mute);font-weight:600;line-height:1.4;text-transform:none;letter-spacing:0}
.check-field{
  display:flex;
  align-items:center;
  gap:10px;
  min-height:42px;
  padding:10px 0;
}
/* Partner categories (roles): a tidy list of role rows - each a checkbox + a "Primary" radio. */
.role-grid{display:flex;flex-direction:column;gap:8px;margin-top:6px;max-width:520px}
.role-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:4px 12px;border:1px solid var(--border);border-radius:var(--r);background:var(--surf-1)}
.role-row .role-name{margin:0;flex:1 1 auto}
.role-primary{display:inline-flex;align-items:center;gap:6px;color:var(--mute);font-size:12px;font-weight:600;white-space:nowrap}
.role-primary input{width:16px;min-height:16px;accent-color:var(--lime)}
/* Primary only matters for a ticked role - dim it until the role is selected. */
.role-row:has(.role-name input:not(:checked)) .role-primary{opacity:.4}
.check-field input{width:18px;min-height:18px;accent-color:var(--lime)}
/* Form controls, app-wide: comfortable size, brand-green tick, clear keyboard focus. */
input[type="checkbox"],input[type="radio"]{width:17px;height:17px;accent-color:var(--lime);cursor:pointer}
input[type="checkbox"]:focus-visible,input[type="radio"]:focus-visible{outline:2px solid rgba(var(--brand-rgb),.75);outline-offset:2px}
/* A selectable row (packages, partner roles) lifts on hover and highlights when ticked. */
.role-row{transition:border-color .15s,background .15s}
.role-row:hover{border-color:var(--soft)}
.role-row:has(> input[type="checkbox"]:checked),.role-row:has(> .role-name > input[type="checkbox"]:checked){border-color:rgba(var(--ok-rgb),.45);background:rgba(var(--ok-rgb),.08)}
/* A checkbox/radio toggles ONLY when its own control is clicked with a MOUSE - not the label text or
   the surrounding row (user preference 2026-07-07). The label stays associated (screen readers +
   keyboard Space still work); pointer-events just narrows the mouse hit area to the control, while the
   inline help button stays clickable. On touch devices the whole label stays tappable (an 18px target
   is too small for a finger). */
@media (pointer: fine){
  .check-field,label:has(> input[type="checkbox"]),label:has(> input[type="radio"]){pointer-events:none}
  .check-field > input,.check-field .field-help-btn,
  label:has(> input[type="checkbox"]) > input,label:has(> input[type="radio"]) > input{pointer-events:auto}
}
/* When a checkbox shares a grid row with a taller field (e.g. Active beside Details), centre it
   vertically rather than letting it float at the top. Harmless when it's alone on its row. */
.form-grid > label.check-field{align-self:center}
input,select,textarea{
  width:100%;
  min-height:42px;
  border:1px solid var(--border);
  border-radius:var(--r);
  background:var(--field);
  color:var(--text);
  padding:10px 11px;
  outline:none;
}
textarea{resize:vertical}
/* Auto-growing textarea: starts one line, grows to fit its content (JS sets the height). */
textarea.js-autogrow{resize:none;overflow:hidden}
/* Inline "(optional)" marker that stays on the label line (unlike .muted, which is block). */
.field-optional{font-weight:600;color:var(--mute);font-size:11px;letter-spacing:0}
/* Searchable select (combobox): a filter input over a floating option list. */
.combo{position:relative}
.combo-input{width:100%}
.combo-list{
  position:absolute;z-index:50;top:calc(100% + 4px);left:0;right:0;
  margin:0;padding:4px;list-style:none;max-height:240px;overflow:auto;
  background:var(--bg-2);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow-2);
}
.combo-option,.combo-empty{padding:8px 10px;border-radius:var(--r-sm);font-size:14px}
.combo-option{cursor:pointer}
.combo-option:hover,.combo-option.active{background:var(--hover)}
.combo-empty{color:var(--mute);cursor:default}
/* Searchable native <select> (enhanced in place - the real select stays the source of truth,
   visually hidden, while this overlay provides type-to-filter). */
.ss{position:relative;flex:1 1 auto;min-width:0}
.locked-field>.ss{flex:1}
.ss-native{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}
.ss-input{width:100%}
.ss-disabled .ss-input{opacity:.6;cursor:not-allowed;background:var(--surf-1)}
/* Dropdown chevron so a searchable combobox still reads as a "choose from a list" field (like a
   native select) - masked SVG takes the muted token colour, adapting to dark/light. Text clears it
   via padding-right. Hidden while a combo is locked/disabled (nothing to drop down yet). */
.combo::after,.ss::after{
  content:"";position:absolute;right:11px;top:50%;width:11px;height:7px;
  transform:translateY(-50%);pointer-events:none;background-color:var(--mute);
  -webkit-mask:no-repeat center/contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  mask:no-repeat center/contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
}
.combo-input,.ss-input{padding-right:30px}
.ss-disabled::after{display:none}
.ss-list{
  position:absolute;z-index:50;top:calc(100% + 4px);left:0;right:0;
  margin:0;padding:4px;list-style:none;max-height:240px;overflow:auto;
  background:var(--bg-2);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow-2);
}
.ss-option{padding:8px 10px;border-radius:var(--r-sm);font-size:14px;cursor:pointer}
.ss-option:hover,.ss-option.active{background:var(--hover)}
/* Touch devices: comfortable tap targets for the searchable-dropdown rows, the "?" help and
   the row "⋯" trigger (kept compact on mouse/pointer-fine devices). */
@media (pointer:coarse){
  .combo-option,.combo-empty,.ss-option,.row-menu-item{min-height:var(--tap);display:flex;align-items:center}
  .field-help-btn{width:24px;height:24px;font-size:13px}
  /* These sat below the target the block itself already used: the row overflow trigger at 36px,
     the pencil that unlocks a locked field at 38x40, and small buttons at 40px. */
  .row-menu-trigger{min-height:var(--tap);min-width:var(--tap)}
  .lock-edit{width:var(--tap);min-height:var(--tap)}
  .button.small{min-height:var(--tap)}
  .check-field{min-height:var(--tap)}
  /* A checkbox/radio itself, not just its row - the label is tappable, the control should be too. */
  input[type="checkbox"],input[type="radio"]{min-width:22px;min-height:22px}
}
/* Native selects: force the field fill over any UA dark-scheme painting. */
select{background-color:var(--field)}
/* iOS paints a grey box over any tapped element. Suppress it and give tappable things their own
   pressed state, so the feedback is intentional rather than a UA artefact - or missing. */
html{-webkit-tap-highlight-color:transparent}
.button:active,.row-menu-item:active,.row-menu-trigger:active,.lock-edit:active,
.combo-option:active,.ss-option:active,.month-nav:active,.record-link:active{
  filter:brightness(.92);
}
input:focus,select:focus,textarea:focus{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(var(--info-rgb),.12)}
input:disabled,select:disabled,textarea:disabled{opacity:.6;cursor:not-allowed;background:var(--surf-1)}
/* A locked (readonly) rate field reads the same as a locked currency field - it still
   submits its value, the pencil unlocks it for a manual override. */
input.js-rate[readonly]{opacity:.6;cursor:not-allowed;background:var(--surf-1)}
/* The derived (system-computed) half of a linked pair - e.g. End date ↔ Repeats - reads as locked. */
.js-locked input{opacity:.6;cursor:not-allowed;background:var(--surf-1)}
.js-locked .date-picker-button{opacity:.6;cursor:not-allowed}
.inline-amount{max-width:130px;min-height:36px;padding:6px 9px;display:inline-block;text-align:right}
.wide{grid-column:1 / -1}
code{
  padding:2px 6px;
  border-radius:var(--r-sm);
  background:var(--hover);
}
/* App shell: the sidebar is a fixed column on desktop and an off-canvas drawer on mobile
   (toggled by the topbar hamburger). The Career/Finance toggle lives at the sidebar top. */
.topbar-lead{display:flex;align-items:center;gap:12px;min-width:0}
.nav-toggle{
  display:none;align-items:center;justify-content:center;
  width:40px;height:40px;flex:0 0 auto;
  border:1px solid var(--border);border-radius:var(--r);
  background:var(--surf-1);color:var(--text);cursor:pointer;
}
.nav-toggle:hover{background:var(--hover)}
.sidebar .module-toggle{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:14px}
.sidebar .module-toggle .button{width:100%;justify-content:center}
.sidebar .admin-zone{display:inline-flex;align-items:center;gap:7px;margin-top:14px;padding:7px 11px;border:1px solid var(--border);border-radius:var(--r);background:var(--surf-2);color:var(--soft);font-size:12px;font-weight:700}
.sidebar .admin-zone svg{color:rgb(var(--brand-rgb));flex:0 0 auto}
/* DRAG HANDLE. Sits on the sidebar's inline-end edge, inside the gap, so it never overlaps
   content. Keyboard-operable (it is a real button in the tab order): arrows nudge, Home resets. */
.sidebar{position:sticky}
.sidebar-resize{
  position:absolute;top:0;bottom:0;inset-inline-end:-10px;
  width:10px;border:0;padding:0;background:none;
  cursor:col-resize;touch-action:none;
  display:block;
}
.sidebar-resize::before{
  content:"";position:absolute;top:50%;inset-inline-end:3px;
  width:3px;height:38px;transform:translateY(-50%);
  border-radius:999px;background:var(--border);
  transition:background .15s ease,height .15s ease;
}
.sidebar-resize:hover::before,.sidebar-resize:focus-visible::before{background:rgb(var(--brand-rgb));height:64px}
.sidebar-resize:focus-visible{outline:2px solid rgb(var(--brand-rgb));outline-offset:2px;border-radius:var(--r)}
/* While dragging, kill text selection and pointer noise across the whole page. */
body.is-resizing-nav{user-select:none;cursor:col-resize}
body.is-resizing-nav iframe{pointer-events:none}
/* The hide control lives with the menu it hides. */
.sidebar-hide{
  display:inline-flex;align-items:center;justify-content:center;
  width:28px;height:28px;flex:0 0 auto;margin-inline-start:auto;
  border:1px solid var(--border);border-radius:var(--r);
  background:var(--surf-1);color:var(--soft);cursor:pointer;
}
.sidebar-hide:hover{background:var(--hover);color:var(--text)}
.nav-backdrop{display:none}

@media (min-width:981px){
  .shell.nav-hidden .nav-toggle{display:inline-flex}
}
@media (max-width:980px){
  /* Below this the sidebar is an off-canvas drawer at a fixed width: dragging a drawer's edge
     means nothing, and "hidden" is its normal resting state. */
  .sidebar-resize,.sidebar-hide{display:none}
  .shell.nav-hidden{grid-template-columns:1fr}
  .shell.nav-hidden>.sidebar{display:flex}
  .shell{grid-template-columns:1fr;padding:calc(16px + var(--safe-t)) calc(16px + var(--safe-r)) calc(16px + var(--safe-b)) calc(16px + var(--safe-l))}
  .nav-toggle{display:inline-flex}
  .sidebar{
    position:fixed;top:0;left:0;bottom:0;
    width:min(280px,84vw);height:100%;
    padding-top:var(--safe-t);padding-bottom:var(--safe-b);padding-left:var(--safe-l);
    border:0;border-right:1px solid var(--border);border-radius:0;
    z-index:60;overflow-y:auto;
    transform:translateX(-100%);transition:transform .22s ease;
    box-shadow:var(--shadow-2);
  }
  .shell.nav-open .sidebar{transform:translateX(0)}
  .nav-backdrop{
    display:block;position:fixed;inset:0;z-index:55;
    background:rgba(2,0,8,.55);backdrop-filter:blur(2px);
    opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease;
  }
  .shell.nav-open .nav-backdrop{opacity:1;visibility:visible}
  .nav{grid-template-columns:1fr}
  .stats-grid{grid-template-columns:repeat(auto-fit,minmax(90px,1fr))}
  .month-stats{grid-template-columns:repeat(2,minmax(0,1fr))}
  .month-indicators{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:680px){
  /* Keep the safe-area insets from the >980px rule: a flat pad here would slide the topbar under
     the iOS status bar / notch in standalone PWA mode (status bar is black-translucent). */
  .shell{padding:calc(10px + var(--safe-t)) calc(10px + var(--safe-r)) calc(10px + var(--safe-b)) calc(10px + var(--safe-l));gap:12px}
  .content,.main,.tabs,.tab-panel{gap:12px}
  .sidebar{padding:calc(10px + var(--safe-t)) 10px 10px calc(10px + var(--safe-l))}
  .brand{padding:6px}
  .brand-mark{width:36px;height:36px;border-radius:var(--r);flex:0 0 auto}
  .nav{gap:5px;margin-top:12px}
  .nav a{padding:10px;font-size:13px}
  .nav-title{padding:0 10px 4px}
  .auth-main{padding:14px;align-content:start}
  .auth-card{margin:2vh auto;padding:18px}
  .panel,.page-head,.topbar,.public-section{padding:14px}
  .public-portfolio{padding:12px;gap:12px}
  .actions,.form-actions{justify-content:flex-start;width:100%}
  .panel-head>.actions,.page-head>.actions{flex-wrap:wrap;justify-content:flex-start}
  .panel-head>.actions form{width:100%}
  .actions .button,.form-actions .button,.form-actions a{width:100%}
  h1{font-size:30px;line-height:1.08}
  h2{font-size:21px}
  .lede{font-size:14px}
  .stat{padding:9px}
  .stat strong{font-size:17px}
  .button{width:100%}
  .button.small{min-height:40px}
  .public-hero{min-height:auto;align-items:flex-start;flex-direction:column;padding:18px}
  .public-hero h1{font-size:clamp(34px,10vw,48px)}
  .public-headline{font-size:18px}
  .public-contact{justify-content:flex-start}
  .public-contact .button{width:auto}
  .public-project-grid,.public-skill-list{grid-template-columns:1fr}
  .page-head,.panel-head{align-items:flex-start;flex-direction:column}
  .panel-head .button,.page-head .button{width:100%}
  .nav,.stats-grid,.form-grid,.detail-grid{grid-template-columns:1fr}
  .detail-grid.plan-terms{grid-template-columns:1fr}
  .detail-grid.plan-terms .col-partner{grid-column:auto}
  /* Kill the desktop span rules (field-sm/md/lg + the :has() auto-sizing): an item spanning 3 of a
     1-column grid forces implicit extra columns, which squeezed fields side by side OUT of the
     form's order (Paid via beside Amount) at ~half width. !important because the span rules carry
     higher specificity; this is the terminal mobile override. One column, full width, DOM order. */
  .form-grid > *{grid-column:1 / -1 !important}
  .form-grid > .field-row{flex-direction:column}
  .field-row > label{flex-basis:auto}
  .field-row > label.field-sm{flex-basis:auto}
  /* Help bubble: on narrow screens anchor it to the field (not the inline "?") so it spans the
     field width and can't clip the screen edge. */
  .form-grid label:has(.field-help){position:relative}
  .form-grid label:has(.field-help) .field-help{position:static}
  .form-grid label:has(.field-help) .help-bubble{left:0;right:0;width:auto}
  .stage-pipeline{display:grid;grid-template-columns:1fr;gap:8px}
  .stage-pipeline form,.stage-step{width:100%}
  .template-editor-head{flex-direction:column}
  .template-editor .inline-check{width:100%}
  .table-wrap{overflow:visible}
  table{min-width:0}
  table,thead,tbody,tr,th,td{display:block}
  thead{display:none}
  tr{
    border:1px solid var(--border-soft);
    border-radius:var(--r);
    margin-bottom:10px;
    background:var(--surf-1);
    overflow:hidden;
  }
  td{
    display:grid;
    grid-template-columns:minmax(100px,42%) minmax(0,1fr);
    align-items:start;
    gap:14px;
    border-bottom:1px solid var(--divider);
    padding:10px;
    min-width:0;
  }
  td:last-child{border-bottom:0}
  td::before{
    content:attr(data-label);
    color:var(--mute);
    font-size:12px;
    font-weight:800;
    min-width:0;
  }
  td.check-col:empty,td.row-actions:empty,td.num:empty{display:none}
  /* width:auto undoes the desktop .check-col{width:1%} - a TABLE sizing trick (shrink to fit)
     that keeps applying once the cell is a stacked flex row, where 1% of the row is ~20px.
     The label and the tick box are deliberately non-shrinking below, so they spilled out of
     it: 111px of content in a 20px box, measured at 375px. */
  td.check-col{display:flex;align-items:center;gap:10px;width:auto}
  /* The cell becomes a flex row on mobile - its data-label sits beside the box. Without
     flex:none the input is a shrinkable flex item and the label squeezes it to ZERO WIDTH,
     so the tick box was invisible and untappable on a phone: measured 0x42 at 375px.
     Sized up from the desktop 16px as well, since this is a touch target (§9). */
  td.check-col::before{flex:0 0 auto}
  td.check-col input{flex:0 0 auto}
  /* One touch size for EVERY table checkbox on mobile, not just the selection column. The
     contribution-import rows put theirs in a plain grid cell, which never had the shrink bug
     but did stay at the 16px desktop size - so the same phone showed two different targets. */
  td input[type="checkbox"]{width:22px;height:22px}
  td.row-actions{
    display:grid;
    grid-template-columns:1fr;
    align-items:stretch;
    justify-content:stretch;
  }
  td.row-actions::before{grid-column:1}
  td.row-actions .button,td.row-actions form,td.row-actions button,td form .button{width:100%}
  .row-menu-trigger{min-height:40px;min-width:40px}
  td form{width:100%}
  /* Document totals footers (invoice / POS receipt / RFQ): a row is a "label ... amount" pair, the
     label spanning via colspan and exactly one amount after it. The generic cell-stacking above gave
     each an empty data-label gutter and split the pair onto stacked half-width boxes. Render the pair
     as one clean right-aligned line instead. Scoped to a single-amount colspan row via
     :not(:has(td.num + td.num)), so multi-amount closing rows (statement debit/credit/balance) and
     multi-column footers (aging report) keep the default stacking. */
  tfoot tr:has(td[colspan]):not(:has(td.num + td.num)){display:flex;justify-content:space-between;
    align-items:baseline;gap:12px;background:none;border:0;border-bottom:1px solid var(--divider);
    border-radius:0;margin:0}
  tfoot tr:has(td[colspan]):not(:has(td.num + td.num)) td{display:block;border:0;padding:7px 10px;min-width:0}
  tfoot tr:has(td[colspan]):not(:has(td.num + td.num)) td::before{display:none}
  tfoot tr:has(td[colspan]):not(:has(td.num + td.num)) td[colspan]{text-align:left}
  tfoot tr:has(td[colspan]):not(:has(td.num + td.num)) td.num{text-align:right;white-space:nowrap}
  /* Stacked document header: the number/date block sits under the issuer, aligned to the start edge
     instead of floating to the far side of a narrow screen. */
  .doc-head-meta{text-align:start}
}
@media (max-width:420px){
  .shell{padding:calc(8px + var(--safe-t)) calc(8px + var(--safe-r)) calc(8px + var(--safe-b)) calc(8px + var(--safe-l))}
  .nav a{font-size:12px;padding:9px}
  .panel,.page-head,.topbar,.public-section,.auth-card{padding:12px}
  h1{font-size:27px}
  .public-hero h1{font-size:34px}
  td{grid-template-columns:1fr;gap:6px}
  td::before{display:block}
  .public-meta span{width:100%;justify-content:center}
}

/* ============================================================================
   Light theme for ManagerDS / Bawtag app
   ----------------------------------------------------------------------------
   PASTE THIS BLOCK AT THE END of design-system/styles.css
   (and mirror it into public/assets/app.css - they are the same file deployed).

   Activated by  <html data-theme="light">.  Absence of the attribute, or
   data-theme="dark", keeps the shipped dark theme - no change to current
   behaviour. See repo-patch/README.md for the toggle button + no-flash script.

   How it works: the shipped CSS bakes tokens dark in :root and hardcodes white
   overlays (rgba(255,255,255,.0x)) and near-black surfaces in many rules. This
   block (a) redefines the tokens for light and (b) re-states every hardcoded
   color with a dark-on-light equivalent.
   ============================================================================ */

:root[data-theme="light"]{
  --bg:#f3f1fa;
  --bg-2:#ffffff;
  --panel:rgba(20,16,48,.04);
  --panel-2:rgba(20,16,48,.07);
  --border:rgba(20,16,48,.12);
  --text:#191430;
  --soft:rgba(25,20,48,.80);
  --mute:rgba(25,20,48,.56);
  --amount-pos:#15803d;  /* darker green/red for legibility on the light surface */
  --amount-neg:#dc2626;
  /* accent hues (indigo/violet/cyan/amber/rose/lime) read on light - unchanged */
  /* Surface scale flips dark-on-light; most per-rule light overrides below become redundant. */
  --surf-1:rgba(20,16,48,.035);
  --surf-2:rgba(20,16,48,.05);
  --field:#ffffff;                     /* light theme: white fields on the soft grey surface */
  --hover:rgba(20,16,48,.09);
  --divider:rgba(20,16,48,.08);
  --border-soft:rgba(20,16,48,.1);
  /* Softer elevation on the light base */
  --shadow-1:0 1px 2px rgba(20,16,48,.1);
  --shadow-2:0 12px 32px rgba(20,16,48,.18);
}

/* Body backdrop: same colored glows, lower alpha so they read on a light base */
:root[data-theme="light"] body{
  background:
    radial-gradient(circle at 16% 18%,rgba(var(--brand-rgb),.14),transparent 32rem),
    radial-gradient(circle at 90% 8%,rgba(var(--accent-rgb),.10),transparent 30rem),
    radial-gradient(circle at 54% 90%,rgba(var(--info-rgb),.10),transparent 34rem),
    var(--bg);
}

/* Frosted shells that hardcode a near-black fill */
:root[data-theme="light"] .sidebar{background:rgba(255,255,255,.70)}
:root[data-theme="light"] .topbar{background:rgba(255,255,255,.62)}

/* Sidebar nav */
:root[data-theme="light"] .nav a:hover,
:root[data-theme="light"] .nav a.active,
:root[data-theme="light"] .nav a.soft-active{background:rgba(20,16,48,.06);color:var(--text)}
:root[data-theme="light"] .nav a.active{
  border-color:rgba(var(--brand-rgb),.28);
  background:linear-gradient(120deg,rgba(var(--brand-rgb),.16),rgba(var(--info-rgb),.10));
}

/* Topbar user menu trigger (the menu popover itself stays dark slate - fine) */
:root[data-theme="light"] .user-menu-toggle:hover,
:root[data-theme="light"] .user-menu-toggle[aria-expanded="true"]{color:var(--text);background:rgba(20,16,48,.06)}

/* Buttons - keep .primary (brand gradient) and .ghost (transparent) intact */
:root[data-theme="light"] .button:not(.primary):not(.ghost){background:rgba(20,16,48,.05)}
:root[data-theme="light"] .button:not(.primary):not(.ghost):hover{background:rgba(20,16,48,.09)}
:root[data-theme="light"] .button:disabled:hover,
:root[data-theme="light"] button:disabled:hover{background:rgba(20,16,48,.05)}
:root[data-theme="light"] .button.ghost:hover{background:rgba(20,16,48,.06)}
:root[data-theme="light"] .button.danger{color:#b3261e}
:root[data-theme="light"] .button.ghost.danger{color:#c0352c}

/* Row overflow menu */
:root[data-theme="light"] .row-menu-trigger{background:rgba(20,16,48,.05)}
:root[data-theme="light"] .row-menu-trigger:hover,
:root[data-theme="light"] .row-menu-trigger[aria-expanded="true"]{background:rgba(20,16,48,.10)}
:root[data-theme="light"] .row-menu-item:hover{background:rgba(20,16,48,.07)}

/* Flash / notices */
:root[data-theme="light"] .flash{color:#0a7d4a}

/* Misc card-ish surfaces with white overlays */
:root[data-theme="light"] .setup-steps li{background:rgba(20,16,48,.035)}

/* Pipeline stage chips */
:root[data-theme="light"] .stage-step{background:rgba(20,16,48,.05)}
:root[data-theme="light"] .stage-step:hover{background:rgba(20,16,48,.09);color:var(--text)}

/* Public portfolio surfaces */
:root[data-theme="light"] .public-hero{
  background:
    linear-gradient(120deg,rgba(255,255,255,.45),rgba(255,255,255,.82)),
    radial-gradient(circle at 78% 20%,rgba(16,185,129,.18),transparent 24rem),
    var(--panel);
}
:root[data-theme="light"] .public-card-subtitle{color:var(--text)}
:root[data-theme="light"] .public-project-card img{border-color:rgba(20,16,48,.10);background:rgba(20,16,48,.05)}

/* Badges: readable text on each colored variant (surfaces flip via tokens).
   EVERY tone defined above with pale text needs its counterpart here. Six did not have one - among
   them "overdue" - so on a light background, and therefore on PAPER, they rendered near-white text
   on a pale tint: a badge that printed as an empty coloured pill. Found 2026-07-28 on the dashboard,
   where "concentrated" appeared as a blank lozenge. qa.php now compares the two lists. */
:root[data-theme="light"] .badge-status-active,
:root[data-theme="light"] .badge-status-completed,
:root[data-theme="light"] .badge-status-shortlisted,
:root[data-theme="light"] .badge-status-offer,
:root[data-theme="light"] .badge-status-featured,
:root[data-theme="light"] .badge-signin-recovery-code-used,
:root[data-theme="light"] .badge-stage-offer,
:root[data-theme="light"] .badge-priority-low{color:#0a7d4a}
:root[data-theme="light"] .badge-status-interesting,
:root[data-theme="light"] .badge-status-in-progress,
:root[data-theme="light"] .badge-status-applied,
:root[data-theme="light"] .badge-stage-applied,
:root[data-theme="light"] .badge-stage-screening,
:root[data-theme="light"] .badge-event-email,
:root[data-theme="light"] .badge-event-call,
:root[data-theme="light"] .badge-event-note,
:root[data-theme="light"] .badge-event-admin-notification,
:root[data-theme="light"] .badge-type-application,
:root[data-theme="light"] .badge-type-opportunity,
:root[data-theme="light"] .badge-type-course{color:#0b6e85}
:root[data-theme="light"] .badge-priority-medium,
:root[data-theme="light"] .badge-status-planned,
:root[data-theme="light"] .badge-status-researching,
:root[data-theme="light"] .badge-status-invited,
:root[data-theme="light"] .badge-stage-draft,
:root[data-theme="light"] .badge-event-task,
:root[data-theme="light"] .badge-event-field-changed,
:root[data-theme="light"] .badge-event-password-reset-generated,
:root[data-theme="light"] .badge-event-invite-generated,
:root[data-theme="light"] .badge-status-partial,
:root[data-theme="light"] .badge-signin-form-expired,
:root[data-theme="light"] .badge-signin-no-such-account,
:root[data-theme="light"] .badge-signin-reset-no-match,
:root[data-theme="light"] .badge-signin-reset-account-not-active,
:root[data-theme="light"] .badge-rate-needs-manual,
:root[data-theme="light"] .badge-acct-expense{color:#8a5800}
:root[data-theme="light"] .badge-priority-high,
:root[data-theme="light"] .badge-event-interview,
:root[data-theme="light"] .badge-event-follow-up,
:root[data-theme="light"] .badge-event-deadline,
:root[data-theme="light"] .badge-event-reminder,
:root[data-theme="light"] .badge-event-request,
:root[data-theme="light"] .badge-status-due{color:#9a4510}
:root[data-theme="light"] .badge-status-rejected,
:root[data-theme="light"] .badge-status-dropped,
:root[data-theme="light"] .badge-status-inactive,
:root[data-theme="light"] .badge-stage-rejected,
:root[data-theme="light"] .badge-status-failed,
:root[data-theme="light"] .badge-event-error,
:root[data-theme="light"] .badge-status-overdue,
:root[data-theme="light"] .badge-signin-bad-password,
:root[data-theme="light"] .badge-signin-locked-out,
:root[data-theme="light"] .badge-signin-2fa-failed,
:root[data-theme="light"] .badge-signin-reset-email-failed,
:root[data-theme="light"] .badge-acct-liability{color:#b3261e}
:root[data-theme="light"] .badge-status-archived,
:root[data-theme="light"] .badge-status-paused,
:root[data-theme="light"] .badge-status-withdrawn,
:root[data-theme="light"] .badge-stage-withdrawn,
:root[data-theme="light"] .badge-status-skipped,
:root[data-theme="light"] .badge-role-admin,
:root[data-theme="light"] .badge-role-owner,
:root[data-theme="light"] .badge-role-user{color:#5b3fb8}

/* Inline code */

/* Mobile nav hamburger */

/* Rate inline-edit */
:root[data-theme="light"] .rate-edit:hover{border-color:rgba(20,16,48,.24)}

/* Inputs */
:root[data-theme="light"] input,
:root[data-theme="light"] select,
:root[data-theme="light"] textarea{background:#ffffff}
:root[data-theme="light"] input:disabled,
:root[data-theme="light"] select:disabled,
:root[data-theme="light"] textarea:disabled,
:root[data-theme="light"] input.js-rate[readonly]{background:rgba(20,16,48,.04)}

/* Modal backdrop */
:root[data-theme="light"] dialog.modal::backdrop{background:rgba(20,16,48,.34)}

/* Mobile stacked-table card rows (≤680px) re-use white overlays - fix them too */
@media (max-width:680px){
  :root[data-theme="light"] tr{border-color:rgba(20,16,48,.10);background:rgba(20,16,48,.035)}
  :root[data-theme="light"] td{border-bottom-color:rgba(20,16,48,.07)}
}

/* ============================================================================
   PRINT - print/save-as-PDF the document, not the app chrome. Always on white with
   black text (dark theme prints terribly), flat (no blur/shadow), full width. Hide
   navigation, toolbars, pagination, action buttons and other interactive bits; mark
   anything else page-specific with [data-no-print]. A .js-print button calls print().
   ============================================================================ */
.print-header{display:none}
/* Public partner statement of account - a standalone letterheaded document (screen + print). */
.statement-body{background:var(--bg)}
.statement-doc{max-width:900px;margin:0 auto;padding:28px 24px;display:flex;flex-direction:column;gap:16px}
.statement-masthead{border-bottom:2px solid var(--border);padding-bottom:12px}
/* Three columns so the logo sits in the true centre regardless of the side widths. */
.statement-masthead-top{display:grid;grid-template-columns:1fr auto 1fr;gap:20px;align-items:start}
.statement-from{min-width:0}
.statement-logo-wrap{grid-column:2;justify-self:center;align-self:start;padding-top:2px}
.statement-masthead-top > .statement-title{grid-column:3}
/* Full-width identity line under the top row: entity contact + reg/tax on one wrapping line. */
.statement-issuer-contact{margin:12px 0 0;padding-top:10px;border-top:1px solid var(--divider);color:var(--mute);font-size:.82rem;display:flex;flex-wrap:wrap;gap:4px 8px;align-items:baseline}
.statement-issuer-contact .statement-sep{color:var(--border-soft)}
.statement-org{font-size:1.35rem;font-weight:800;margin:0}
/* The seller block at the top of a counter receipt - the same letterhead pieces as every other
   document, separated from the sale details below it. */
.doc-seller{margin:0 0 14px;padding-bottom:12px;border-bottom:1px solid var(--divider)}
.statement-org-line{margin:2px 0 0;color:var(--mute);font-size:.85rem}
.statement-title{text-align:right;flex:0 0 auto}
.statement-title h1{font-size:1.25rem;margin:0}
.statement-title p{margin:2px 0 0;font-size:.85rem}
.statement-parties{display:flex;flex-direction:column;gap:8px}
/* Partner block gets 2/3, balance 1/3 - so even a long address stays within its column. */
.statement-parties-top{display:grid;grid-template-columns:2fr 1fr;gap:24px;align-items:start}
.statement-to{min-width:0}
.statement-partner-contact{margin-top:6px;padding-top:6px;font-size:.72rem;gap:2px 6px}
.statement-label{font-size:.8rem;font-weight:700;color:var(--mute);margin:0 0 4px}
.statement-party-name{font-size:1.05rem;font-weight:700;margin:0}
/* Balance box: two lines - the status tag on top, the amount under it. */
.statement-headline{text-align:right}
.statement-headline > p{margin:0}
.statement-balance{font-size:1.6rem;font-weight:800;margin:4px 0 0}
/* One equal column per tile, however many the document has: the statement and receipt run 4,
   the monthly/aging/asset documents 3. A hard repeat(4,1fr) left the 3-tile documents with a
   dead quarter-width column on screen AND on paper. */
.statement-summary{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:1px;background:var(--border-soft);border:1px solid var(--border-soft);border-radius:var(--r-lg);overflow:hidden}
.statement-summary-cell{background:var(--surf-1);padding:7px 14px;display:flex;flex-direction:column;gap:2px}
.statement-summary-cell span{font-size:.8rem;color:var(--mute)}
.statement-summary-cell strong{font-size:1.05rem}
.statement-summary-cell.is-closing{background:rgba(var(--brand-rgb),.08)}
/* Financial report sections as bordered cards: each statement (income & expense, income statement,
   balance sheet, cash flow) reads as its own block instead of one continuous stack. */
.statement-lines{border:1px solid var(--border-soft);border-radius:var(--r-lg);background:var(--surf-1);padding:12px 16px 14px;margin:16px 0}
.statement-lines > .statement-section-title{padding-bottom:8px;border-bottom:1px solid var(--divider);margin-bottom:12px}
.statement-section-title{font-size:1rem;margin:0 0 8px}
.statement-tx-title{white-space:nowrap}
.statement-tx-title .muted{display:inline;font-weight:400;font-size:.85rem}
.statement-table tfoot .statement-closing-row td{border-top:2px solid var(--border);font-weight:700}
.statement-bf td{color:var(--mute)}
.statement-foot{border-top:1px solid var(--divider);padding-top:12px;font-size:.8rem}
/* Footer as one line: disclaimer on the start edge, "Generated on" pushed to the far edge.
   justify-content:space-between + RTL flips the edges automatically (LTR right / AR left). */
.statement-foot,.statement-footnote{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 24px}
.statement-foot .foot-note,.statement-footnote .foot-note{flex:1 1 60%;min-width:0}
.foot-generated{flex:0 0 auto;white-space:nowrap}
.statement-actions{margin-top:4px}
.statement-logo{max-height:56px;max-width:200px;object-fit:contain;margin:0 0 10px;display:block}
.statement-balance-note{margin:6px 0 0;font-size:.82rem;color:var(--mute)}
/* "How to pay": a compact box with an orange left edge; bank details inline like a contact line. */
/* "How to pay" as a fieldset: the label rides the top border like the form field groups. */
.statement-payment{border:1px solid var(--border-soft);border-left:3px solid rgba(var(--warn-rgb),.8);border-radius:var(--r-lg);background:var(--surf-1);padding:2px 12px 8px;margin:0;min-width:0}
.statement-payment > legend{padding:0 6px;font-size:.72rem;font-weight:800;letter-spacing:.02em;color:var(--text);white-space:nowrap}
.statement-payment > legend .muted{font-weight:600;display:inline}
.statement-bank-inline{border-top:0;margin:0;padding-top:0;font-size:.8rem}
.statement-payment .statement-bank-inline + .statement-bank-inline{margin-top:3px}
/* Letterhead meta chips: each date/time is its own labelled tag, so a reader knows
   which value is which; the value itself stays one LTR unit. */
.doc-meta-tags{display:flex;gap:5px;align-items:baseline;justify-content:flex-end;flex-wrap:wrap;margin:2px 0}
.doc-tag{display:inline-flex;gap:5px;align-items:baseline;border:1px solid var(--border-soft);border-radius:var(--r);padding:1px 7px;white-space:nowrap}
.doc-tag small{color:var(--mute);font-size:.68rem;font-weight:700}
.doc-tag-value{direction:ltr;unicode-bidi:isolate;font-variant-numeric:tabular-nums}
/* A period range: two date chips with a word separator between, so the range split can't be
   mistaken for the intra-date hyphens. LTR-wrapped so from->to order holds on RTL documents. */
.doc-tag-group{display:inline-flex;gap:5px;align-items:baseline;flex-wrap:wrap}
.doc-tag-sep{color:var(--mute);font-size:.72rem}
/* A range in a section heading: chips sit at label size, not the heading's, so they read as a
   quiet date range beside the title rather than competing with it. */
.statement-section-title .doc-tag-group{font-size:.8rem;font-weight:400}
/* Aging summary reuses the pay-box shell with an informational (not call-to-action) edge. */
.statement-aging{border-left-color:rgba(var(--info-rgb),.45)}
/* The transactions box: same shell, brand-toned edge - the document's core content. */
.statement-transactions{border-left-color:rgba(var(--brand-rgb),.5)}
.statement-transactions .table-wrap{margin:2px 0 0}
/* Statement tables: bold column headers over a strong rule line (user decision 2026-07-18). */
.statement-table thead th{font-weight:800;border-bottom:2px solid var(--border)}
.statement-aging .table-wrap{margin:2px 0 0}
.statement-payment-body{margin:4px 0 0;font-size:.82rem;white-space:pre-line}
.statement-generated{font-size:.75rem;margin-top:6px}
@media (max-width:640px){.statement-summary{grid-auto-flow:row;grid-template-columns:repeat(2,1fr)}.statement-title,.statement-headline{text-align:left}
  .statement-masthead-top{grid-template-columns:1fr}.statement-logo-wrap{justify-self:start}.statement-masthead-top > .statement-title{grid-column:1}
  .statement-parties-top{grid-template-columns:1fr}}
/* Statement print refinements: full-bleed page with printer margins, keep the summary 4-up. */
@media print{
  /* No @page here: the app-wide print block below sets margin:12mm, and a later unnamed @page
     wins - the 14mm this block used to declare had never once applied. One margin, stated once. */
  .statement-doc{max-width:none;padding:0;gap:14px}
  /* Force the tiles back onto one row per document (the ≤640 rule stacks them into 2 columns, so
     printing from a phone would otherwise print the phone layout), one equal column per tile. */
  .statement-summary{grid-auto-flow:column !important;grid-auto-columns:1fr !important;grid-template-columns:none !important}
  /* The closing tile's screen highlight is a TRANSLUCENT brand tint, so it composites over
     whatever is behind it - which is the grid's own background, reset to #bbb for print. That
     turned the document's most important figure into a muddy grey block. Opaque token instead. */
  .statement-summary-cell.is-closing{background:var(--surf-2)}
  .statement-masthead{border-bottom:2px solid #000}
  .statement-table tr{break-inside:avoid}
}

@media print{
  /* A table spanning printed pages repeats its tfoot on EVERY page by default - section
     totals must print exactly once, at the end of the table. */
  tfoot{display:table-row-group}
  /* Row action menus are interactive chrome - meaningless on paper. */
  .row-menu,.row-actions .button{display:none !important}
  :root,:root[data-theme="light"]{
    --bg:#fff;--bg-2:#fff;--text:#000;--soft:#1a1a1a;--mute:#444;
    --border:#999;--border-soft:#bbb;--divider:#bbb;
    --panel:#fff;--panel-2:#f4f4f4;--surf-1:#f7f7f7;--surf-2:#f1f1f1;--hover:#eee;
    --amount-pos:#0a6b2f;--amount-neg:#b3261e;
  }
  html,body{background:#fff !important;color:#000}
  /* Compact print typography so section descriptions fit on one line like the wide desktop view
     (the print page is much narrower) and headings aren't oversized on paper. */
  body{font-size:10pt}
  h1{font-size:18pt}
  h2{font-size:13pt}
  h3{font-size:11pt}
  .lede,.panel p,.muted,.empty,p{font-size:10pt}
  .eyebrow{font-size:8.5pt}
  .sidebar,.nav-backdrop,.nav-toggle,.topbar-actions,.topbar-crumbs,.month-switcher,
  .list-toolbar,.pagination,.row-menu,.actions,.form-actions,.field-help,.tab-bar,
  .combo-list,.ss-list,.panel-toggle,.module-toggle,.brand,button,.button,[data-no-print]{
    display:none !important;
  }
  /* Inputs keep their DARK-theme fill on paper - the light-theme rules are keyed on
     :root[data-theme="light"], which a printed page does not carry. The tax report printed its
     provision and return forms as near-black boxes with near-black text inside (2026-07-28). The
     controls themselves are meaningless on paper, but their VALUES are not - a date already chosen,
     a note already typed - so they print as a plain outlined field rather than being hidden. */
  input,textarea,select{
    background:#fff !important;color:#000 !important;border:1px solid #999 !important;
    box-shadow:none !important;
  }
  ::placeholder{color:#777 !important}
  .shell{display:block;width:auto;margin:0;padding:0;gap:0}
  .content,.main{display:block;gap:0}
  .topbar{display:none !important}
  .panel,.stat,.public-card,.public-section,.timeline-item,.template-editor,.detail-item,.event-item{
    border:1px solid #bbb !important;background:#fff !important;backdrop-filter:none !important;box-shadow:none !important;
    break-inside:avoid;
  }
  /* KPI tiles: the screen auto-fit grid resolves to one cramped row on paper (9 tiles at ~90px),
     clipping the figures. Fixed roomy columns instead - three per row so every value prints in full,
     and the tile content can wrap rather than overflow. */
  .stats-grid{grid-template-columns:repeat(3,minmax(0,1fr)) !important;gap:8px !important}
  .stat{padding:8px 10px}
  .stat strong{white-space:normal;overflow-wrap:anywhere}
  /* Statement section cards: white fill + grey border like the panels, but they must be free to
     flow across pages - a tall section forced onto the next page as one block leaves the previous
     page half empty. Rows still avoid breaking (rule above), so nothing splits mid-line. A little
     extra vertical room between sections so the groups read apart even where a border meets a page
     edge. */
  .statement-lines{
    border:1px solid #bbb !important;background:#fff !important;backdrop-filter:none !important;box-shadow:none !important;
    break-inside:auto;margin:0 0 18px;
  }
  .panel{padding:10px 12px;margin-bottom:10px}
  /* A full-page document rendered as a single panel (the invoice/bill, the RFQ printable): let it
     flow across pages instead of the .panel break-inside:avoid pushing a long items list whole to the
     next page. Rows still avoid breaking and the header repeats, so it paginates cleanly. */
  .panel.doc-flow,.panel.document{break-inside:auto}
  /* Monthly-view groups (Income & expenses, Bank transfers): the fieldset IS the card and its inner
     panels are borderless on screen. The .panel print rule above forces a border back onto those
     inner panels, drawing a second border inside the group's border. Restore the screen intent - the
     group is the one card, inner panels divide with a hairline - and let the group flow across pages
     instead of jumping whole to the next one (which left the first page half empty). */
  .month-group{border:1px solid #bbb !important;background:#fff !important;break-inside:auto}
  .month-group > .panel{border:0 !important;background:none !important;box-shadow:none !important;break-inside:auto}
  .month-group > .panel + .panel{border-top:1px solid #bbb !important}
  a{color:#000;text-decoration:none}
  /* Collapsed sections print in full - you print everything, not the folded view. */
  .panel.js-collapsible.collapsed > :not(.panel-head):not(.panel-toggle){display:block !important}
  .panel-toggle{display:none !important}
  /* Print every tab's content (e.g. an arrangement's schedule AND transactions), not just the
     one on screen; the tab bar itself is hidden above. */
  .tab-panel{display:block !important}
  /* Fit tables to the portrait page: no scroll viewport, smaller text, wrap long values, and
     drop the interactive checkbox/row-action columns so the real columns aren't pushed off.
     Force a real table even when printing from a phone - the ≤680 rules stack rows into cards
     ("prints what's on screen"), so override the whole display chain + the data-label pseudo. */
  .table-wrap{overflow:visible !important}
  /* table-layout:auto proportions columns by content and collapses hidden/empty ones (so the
     dropped checkbox/action columns don't leave gaps); cells wrap (overflow-wrap below) so wide
     lists fit instead of running off the edge. */
  /* THE screen min-width (760px ≈ 201mm) is WIDER than a portrait page (~184mm printable);
     min-width beats max-width in CSS, so without this reset every printed table hangs off
     the right edge - the instalments Amount column was the visible casualty. */
  table{display:table !important;width:100% !important;min-width:0 !important;border-collapse:collapse;table-layout:auto !important}
  thead{display:table-header-group !important} /* real header row, repeated per printed page */
  tbody{display:table-row-group !important}
  tr{display:table-row !important}
  th,td{display:table-cell !important;font-size:8.5pt;padding:3px 4px;white-space:normal;overflow-wrap:anywhere;vertical-align:top}
  td::before,th::before{display:none !important} /* drop the mobile "data-label" prefixes */
  /* Only amounts stay unbreakable - dates and reference codes may wrap, so an auto-layout
     table can ALWAYS compress to the page width. A nowrap date/ref column used to hold the
     table wider than the paper; in RTL the overflow clipped the trailing columns entirely. */
  td.num,th.num{white-space:nowrap}
  table{max-width:100% !important}
  /* Badge text is authored PALE, for the dark theme. The light-theme override is keyed on
     :root[data-theme="light"], which print does not carry - the attribute is set by the theme
     toggle, and a printed page may have none - so the pale colour survived onto white paper and a
     badge came out as an empty coloured pill. On paper the tint and border carry the meaning; the
     text just has to be readable, so it is black, for every tone at once. */
  .badge{font-size:7.5pt;padding:1px 5px;min-height:auto;border:1px solid #999;color:#000 !important}
  .check-col,.row-actions{display:none !important}
  /* Wide statement ledgers (8-col partner/bank ledgers): force fixed layout AND hard-cap the table
     to a portrait printable width, so the last column (Balance/Status) can't run off the page even
     if the print scale or browser margins make the page box wider than expected. */
  /* Fixed layout at 100% of the real container - NEVER a hard mm cap: 180mm overshot the
     printable width (page margins + panel padding), silently clipping the right-most
     column (the instalments Amount printed as 'Am'). 100% can't overflow by construction. */
  .stmt-table{table-layout:fixed !important;width:100% !important;max-width:none !important}
  /* The actions column doesn't print: its cells AND header carry .row-actions (hidden
     above), and its <col> collapses so fixed layout gives the width back to real data. */
  .stmt-table col.col-actions{width:0 !important}
  /* A record code is an identifier: it must not wrap mid-code any more than a date may (§12).
     Its own width under fixed layout, so adding it didn't squeeze the free-text Details column. */
  .stmt-table .col-ref{width:15% !important;white-space:nowrap !important}
  /* Fixed layout splits the width EVENLY, so the free-text Details column was getting the same
     narrow share as Type and the money columns and wrapping to three lines. Pin the predictable
     columns to what they actually need and Details takes the rest (~27%), which is the point of
     giving the collapsed actions column its width back further up. Scoped to the bank statement so
     the partner/report ledgers keep their own tuning. */
  #bank-statement .stmt-table .col-date{width:11% !important}
  #bank-statement .stmt-table .col-type{width:11% !important}
  #bank-statement .stmt-table th.num,#bank-statement .stmt-table td.num{width:12% !important}
  .stmt-table td.num,.stmt-table th.num,.stmt-table .col-date{white-space:normal !important}
  .stmt-table a,.stmt-table .badge{white-space:normal;overflow-wrap:anywhere}
  .col-date{white-space:normal !important} /* the screen nowrap outranks the td reset above */
  /* Report statement tables (income statement, balance sheet, cash flow, project/fund P&L, the aging
     buckets) have only a label column + narrow money columns - no long free-text column like the
     bank/partner ledgers, which keep fixed layout. The wide-ledger fixed rule above split these into
     equal columns, wrapping a long name ("Utilities - electricity, gas, water & internet" / a partner
     company name) while the row still had room. Auto layout instead: the money columns shrink to their
     content and the label column takes all the space that's left. */
  .stmt-report{table-layout:auto !important}
  .stmt-report td.num,.stmt-report th.num{width:1%;white-space:nowrap !important}
  /* The party statement's commitments ledger is 8 columns on a portrait page (it keeps fixed layout -
     auto would overrun), so its longer money headers ("Committed", "Outstanding", "Per repeat") wrap
     in an equal column. Nowrap them a touch smaller so each header sits on one line; fixed layout
     means a header slightly wider than its column can't push the table off the page. */
  .stmt-commitments th,.stmt-commitments th.num{white-space:nowrap !important;font-size:7.5pt}
  /* Standalone public statement: drop its 860px <main> cap + inline side padding and fit the page. */
  body>main{max-width:185mm !important;padding:0 !important;margin:0 auto !important}
  tr,article{break-inside:avoid}
  h1,h2,h3{break-after:avoid}
  /* a hidden tab-panel stays hidden - you print the tab you're viewing */
  /* Our own header at the top of the document (brand · account · printed date). Turn the
     browser's own headers/footers off in the print dialog to avoid the duplicate date/URL. */
  @page{margin:12mm}
  .print-header{display:flex !important;justify-content:space-between;gap:16px;margin:0 0 10px;padding-bottom:6px;border-bottom:1px solid #bbb;font-size:9pt;color:#555}
  /* Per-section print (.js-print-section): the JS marks the target section (.print-target) and its
     ancestor path (.print-anc). At each level, hide the siblings that aren't on that path - so only
     the chosen section's subtree prints, and it paginates normally. The brand header is kept. */
  body.print-section .print-anc > *:not(.print-anc):not(.print-target):not(.print-header){display:none !important}
  /* A page that carries its own LETTERHEAD is a document: it already says who issued it, what it is
     and when. The app's own two identity blocks are then noise on paper - the screen page-head
     repeats the reference and status as a second heading, and the print-header puts the operating
     user's name ("Account: Sam Rivers") on a sheet that goes to a customer. Both go, on any page
     with a letterhead, stated once here rather than as a data-no-print attribute each document view
     has to remember (three did, the POS receipt did not, and it printed both). A page WITHOUT a
     letterhead - a journal entry, the monthly view - keeps them: there they are the only heading. */
  body:has(.statement-masthead, .doc-seller, .doc-head-meta) .page-head,
  body:has(.statement-masthead, .doc-seller, .doc-head-meta) .print-header{display:none !important}
}

/* ── Server-side SVG charts (P3.1) - tokens only ── */
.chart{width:100%;height:auto;display:block}
.chart-grid{stroke:var(--border);stroke-width:1}
.chart-text{fill:var(--mute);font-size:11px}
/* svg_bar_chart() turns each series KEY into a chart-fill-<key> class, so a series with no rule
   below inherits the SVG default - solid black bars and an invisible legend swatch, which is how
   the Business and NGO charts shipped. This fallback keeps a new series legible until it is given
   its own colour; it sits first so every specific rule below still wins (equal specificity). */
[class*="chart-fill-"]{fill:var(--soft);opacity:.85}
.chart-fill-income{fill:var(--amount-pos);opacity:.85}
.chart-fill-expense{fill:var(--amount-neg);opacity:.85}
/* Business: money in vs money out, so it reads like the finance chart above. */
.chart-fill-sales{fill:var(--amount-pos);opacity:.85}
.chart-fill-purchases{fill:var(--amount-neg);opacity:.85}
/* NGO: donations and contributions are BOTH money in, so they are two categories rather than a
   positive and a negative - the same two colours the donut leads with. */
.chart-fill-donations{fill:var(--indigo);opacity:.85}
.chart-fill-contributions{fill:var(--violet);opacity:.85}
.chart-line{fill:none;stroke:var(--indigo);stroke-width:2.5;stroke-linejoin:round}
.chart-dot{fill:var(--indigo)}
.chart-donut-wrap{display:flex;gap:1.2rem;align-items:center;flex-wrap:wrap}
.chart-donut{max-width:200px;flex:0 0 auto}
.chart-slice{fill:none;stroke-width:26}
.chart-slice-0{stroke:var(--indigo)}.chart-slice-1{stroke:var(--violet)}
.chart-slice-2{stroke:var(--cyan)}.chart-slice-3{stroke:var(--amber)}
.chart-slice-4{stroke:var(--lime)}.chart-slice-5{stroke:var(--rose)}
.chart-slice-6{stroke:var(--soft)}.chart-slice-7{stroke:var(--mute)}
span.chart-swatch.chart-slice-0{background:var(--indigo)}span.chart-swatch.chart-slice-1{background:var(--violet)}
span.chart-swatch.chart-slice-2{background:var(--cyan)}span.chart-swatch.chart-slice-3{background:var(--amber)}
span.chart-swatch.chart-slice-4{background:var(--lime)}span.chart-swatch.chart-slice-5{background:var(--rose)}
span.chart-swatch.chart-slice-6{background:var(--soft)}span.chart-swatch.chart-slice-7{background:var(--mute)}
.chart-legend{display:flex;gap:1rem;flex-wrap:wrap;margin-top:.5rem}
.chart-legend-stack{flex-direction:column;gap:.35rem;margin-top:0}
.chart-legend-item{display:inline-flex;gap:.45em;align-items:center;color:var(--soft);font-size:.85rem}
.chart-legend-value{color:var(--mute)}
.chart-swatch{width:.75em;height:.75em;border-radius:3px;display:inline-block}
/* Same fallback-then-specific order as the bar fills above: a legend swatch with no colour of its
   own is transparent, which reads as a missing legend rather than a series. */
.chart-swatch[class*="chart-fill-"]{background:var(--soft)}
.chart-swatch.chart-fill-income{background:var(--amount-pos)}
.chart-swatch.chart-fill-expense{background:var(--amount-neg)}
.chart-swatch.chart-fill-sales{background:var(--amount-pos)}
.chart-swatch.chart-fill-purchases{background:var(--amount-neg)}
.chart-swatch.chart-fill-donations{background:var(--indigo)}
.chart-swatch.chart-fill-contributions{background:var(--violet)}
/* ── Topbar notifications (P3.2) ── */
.notice-menu{position:relative}
.notice-menu summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:.35em;position:relative}
.notice-menu summary::-webkit-details-marker{display:none}
.notice-count{position:absolute;top:-6px;right:-8px;background:var(--rose);color:#fff;border-radius:999px;font-size:.62rem;line-height:1;padding:.22em .45em;min-width:1.5em;text-align:center}
.notice-panel{position:absolute;right:0;top:calc(100% + .5rem);width:min(340px,86vw);background:var(--bg-2);border:1px solid var(--border);border-radius:12px;box-shadow:0 18px 40px rgba(0,0,0,.45);padding:.5rem;z-index:60;display:flex;flex-direction:column;gap:.25rem}
.notice-item{display:flex;flex-direction:column;gap:.15rem;padding:.55rem .6rem;border-radius:8px;text-decoration:none;color:var(--text)}
.notice-item:hover{background:var(--panel-2)}
.notice-item span{color:var(--mute);font-size:.82rem}
.notice-item time{color:var(--mute);font-size:.72rem}
.notice-empty{color:var(--mute);padding:.6rem;margin:0;font-size:.85rem}
.notice-readall{border-top:1px solid var(--border);padding-top:.4rem;margin-top:.2rem;display:flex;justify-content:flex-end}
/* Bell dropdown: a "Needs attention" section header, an attention row with a count pill, and a
   footer link into the full center. Tone accents reuse the status rgb tokens. */
.notice-head{margin:.3rem .3rem .1rem;font-size:.68rem;font-weight:800;letter-spacing:.04em;text-transform:none;color:var(--mute)}
.notice-attention{border-left:3px solid rgba(var(--info-rgb),.7)}
.notice-attention strong{display:flex;align-items:center;gap:.4em}
.notice-pill{background:rgba(var(--info-rgb),.18);color:var(--text);border-radius:999px;font-size:.72rem;font-weight:700;padding:.05em .5em;min-width:1.6em;text-align:center}
.notice-seeall{display:block;text-align:center;border-top:1px solid var(--border);margin-top:.3rem;padding-top:.5rem;color:var(--brand);font-size:.82rem;text-decoration:none;font-weight:600}
.notice-seeall:hover{text-decoration:underline}
/* Notifications center: live attention cards. Each is a count + title + summary, tone-accented. */
.attention-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px}
.attention-card{display:flex;gap:12px;align-items:flex-start;padding:14px;border:1px solid var(--border);border-left-width:4px;border-radius:var(--r-lg);background:var(--surf-1);text-decoration:none;color:var(--text)}
.attention-card:hover{background:var(--panel-2)}
.attention-count{font-size:1.5rem;font-weight:800;line-height:1;min-width:1.4em;text-align:center}
.attention-body{display:flex;flex-direction:column;gap:.2rem;min-width:0}
.attention-body strong{font-size:.95rem}
.attention-body span{color:var(--mute);font-size:.82rem}
/* Tone accents shared by the cards, bell rows and the type badge. */
.tone-err{border-left-color:rgba(var(--err-rgb),.75)}
.tone-warn{border-left-color:rgba(var(--warn-rgb),.8)}
.tone-ok{border-left-color:rgba(var(--ok-rgb),.7)}
.tone-info{border-left-color:rgba(var(--info-rgb),.7)}
.attention-card.tone-err .attention-count{color:rgb(var(--err-rgb))}
.attention-card.tone-warn .attention-count{color:rgb(var(--warn-rgb))}
.attention-card.tone-ok .attention-count{color:rgb(var(--ok-rgb))}
.attention-card.tone-info .attention-count{color:rgb(var(--info-rgb))}
.badge.tone-err{background:rgba(var(--err-rgb),.14);border-color:rgba(var(--err-rgb),.4)}
.badge.tone-warn{background:rgba(var(--warn-rgb),.14);border-color:rgba(var(--warn-rgb),.4)}
.badge.tone-ok{background:rgba(var(--ok-rgb),.14);border-color:rgba(var(--ok-rgb),.4)}
.badge.tone-info{background:rgba(var(--info-rgb),.14);border-color:rgba(var(--info-rgb),.4)}
small.block{display:block}
/* Notification feed: a vertical list (not a table) - each item is a leading type icon + title/
   snippet/relative-time, with a tone-coloured left edge and an unread dot. Time-bucketed by
   Today / Yesterday / Earlier headers. Reuses the shared .tone-* left-edge colours + .row-menu. */
.notif-feed{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.notif-group-head{font-size:.78rem;font-weight:700;letter-spacing:.02em;color:var(--mute);padding:12px 2px 2px}
/* Inline tax-return settle control (account + date + button) - wraps on narrow screens. */
.tax-settle-form{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:4px}
.tax-settle-form select{max-width:14rem}
.notif-group-head:first-child{padding-top:2px}
.notif-item{display:flex;align-items:stretch;gap:4px;border:1px solid var(--border-soft);border-left-width:3px;border-radius:var(--r-md);background:var(--surf-1);transition:background .15s}
.notif-item:hover{background:var(--panel-2)}
.notif-item.is-unread{background:rgba(var(--brand-rgb),.06)}
.notif-item.is-unread:hover{background:rgba(var(--brand-rgb),.1)}
.notif-open{display:flex;gap:12px;align-items:flex-start;flex:1;min-width:0;padding:12px 6px 12px 12px;text-decoration:none;color:var(--text)}
.notif-icon{flex:none;display:flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:var(--r-sm);background:var(--surf-2);color:var(--mute)}
.notif-item.tone-err .notif-icon{color:rgb(var(--err-rgb));background:rgba(var(--err-rgb),.12)}
.notif-item.tone-warn .notif-icon{color:rgb(var(--warn-rgb));background:rgba(var(--warn-rgb),.12)}
.notif-item.tone-ok .notif-icon{color:rgb(var(--ok-rgb));background:rgba(var(--ok-rgb),.12)}
.notif-item.tone-info .notif-icon{color:rgb(var(--info-rgb));background:rgba(var(--info-rgb),.12)}
.notif-body{display:flex;flex-direction:column;gap:.15rem;min-width:0}
.notif-title{display:flex;align-items:center;gap:8px;font-weight:600;font-size:.92rem}
.notif-item.is-unread .notif-title{font-weight:700}
.notif-dot{flex:none;width:8px;height:8px;border-radius:50%;background:rgb(var(--brand-rgb))}
.notif-snippet{color:var(--soft);font-size:.84rem;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.notif-meta{color:var(--mute);font-size:.76rem;margin-top:.1rem;white-space:nowrap}
/* Dates and datetimes never wrap onto two lines - a split date reads as broken (design framework
   §12). The datetime helper already glues its space; this covers the semantic <time> element. */
time{white-space:nowrap}
/* Maker-checker approval inbox: a proposal per card with a field-level diff and decide buttons. */
.approval-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}
.approval-item{border:1px solid var(--border-soft);border-radius:var(--r-lg);background:var(--surf-1);padding:14px 16px;display:flex;flex-direction:column;gap:8px}
.approval-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.approval-meta{font-size:.82rem;margin:0}
.approval-diff-note{margin:0;font-size:.88rem}
.approval-diff{width:100%;font-size:.86rem}
.approval-diff th{text-align:left;font-weight:600;color:var(--mute)}
.approval-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:2px}
.approval-actions .inline-form{display:inline}
.notif-snoozed{color:rgb(var(--warn-rgb))}
.notif-item .row-menu{align-self:center;padding-right:6px}
/* The unread/dismissed counter beside a status tab. */
.tab-count{display:inline-flex;align-items:center;justify-content:center;min-width:1.4em;height:1.4em;padding:0 .35em;margin-left:.15em;border-radius:999px;background:rgba(var(--brand-rgb),.22);color:inherit;font-size:.72rem;font-weight:700;vertical-align:middle}
.tab.active .tab-count{background:rgba(255,255,255,.25)}
/* A read-only value shown where an input would sit in a form grid (e.g. lifecycle-driven status). */
.static-field{display:flex;align-items:center;min-height:40px}
/* Draws the eye to a count that still needs the user to act (e.g. the monthly "To confirm" tile). */
.needs-action{color:rgb(var(--warn-rgb))}
/* Reports breakdown: a category table on the left, a filled donut (with the section name in its
   centre) on the right; stacks on narrow screens. */
.report-breakdown{display:grid;grid-template-columns:minmax(0,1fr) minmax(190px,240px);gap:24px;align-items:center}
.report-breakdown .table-wrap{overflow-x:auto}
.report-breakdown table{min-width:0}
.report-breakdown-donut{display:flex;justify-content:center}
.chart-donut-solid{max-width:230px;width:100%;height:auto}
.donut-center{fill:var(--text);font-size:15px;font-weight:800}
@media (max-width:680px){.report-breakdown{grid-template-columns:1fr}}
/* Income & expense analysis: sub-headings inside the one shared panel. */
.report-breakdown-title{margin:18px 0 8px;font-size:14px;font-weight:800}
.report-breakdown-title:first-of-type{margin-top:2px}
/* Compact Reports filter bar: a single wrapping row (period preset + dates + as-of + update),
   labels small and stacked, instead of the tall 6-col form grid. */
.report-filter-form{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px}
.report-filter-form > label,.report-custom-dates > label{display:flex;flex-direction:column;gap:4px;min-width:0;margin:0}
.report-filter-form > label > span,.report-custom-dates > label > span{font-size:11px;font-weight:800;color:var(--mute)}
.report-custom-dates{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px}
.report-custom-dates.is-hidden{display:none}
.report-scope-note{margin-top:8px;font-size:12px}
/* Right-aligned Print + Export toolbar for tabs without a panel-head to host them (Aging, Cash). */
.report-tab-tools{display:flex;justify-content:flex-end;gap:8px;margin:0 0 4px}
/* Cash projection header row: horizon pills (30/60/90) on the left, Print/Export on the right. */
.report-tab-head{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 6px}
.report-tab-head .report-tab-tools{margin:0}
.horizon-seg{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.horizon-seg .button{padding:6px 12px}
/* Admin entities: identity cell (logo + name), logo manager, and the invite form divider. */
.entity-cell{display:inline-flex;align-items:center;gap:.6em}
.entity-cell small{display:block}
.entity-mark{width:34px;height:34px;overflow:hidden;flex:0 0 auto}
.entity-mark img{width:100%;height:100%;object-fit:cover}
.entity-mark.lg{width:64px;height:64px}
.entity-logo-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.entity-logo-upload{display:flex;align-items:center;gap:.5em;flex-wrap:wrap}
.entity-invite{margin-top:14px;border-top:1px solid var(--border);padding-top:14px}
tr.is-unread td{font-weight:500}
tr.is-unread .record-link{font-weight:700}
/* RTL (P3.7): Arabic locale flips the document direction. Layout is mostly flex/grid so it
   mirrors automatically; these overrides catch the physical-side properties. */
[dir="rtl"] body{text-align:right}
[dir="rtl"] .sidebar{border-right:0;border-left:1px solid var(--border)}
[dir="rtl"] .nav a{text-align:right}
[dir="rtl"] .topbar-crumbs .crumb-sep{transform:scaleX(-1);display:inline-block}
[dir="rtl"] .form-actions{justify-content:flex-start}
/* DATA SURFACES STAY LTR (design rule, user decision 2026-07-05, framework §14):
   chrome (nav, headings, prose) mirrors for RTL locales, but tables, lists, pagination,
   charts and printed documents keep the SAME layout as every language - same column
   order, same alignment (§12: numbers right) - only their labels translate. Value
   tokens (amounts, codes, dates) are bidi-isolated so surrounding RTL text can never
   reorder them, and user-entered values self-determine direction (display as typed). */
[dir="rtl"] .table-wrap{direction:ltr}
[dir="rtl"] table{direction:ltr}
[dir="rtl"] table th, [dir="rtl"] table td{text-align:left}
[dir="rtl"] table th.num, [dir="rtl"] table td.num{text-align:right}
[dir="rtl"] .pagination, [dir="rtl"] .chart-wrap, [dir="rtl"] svg{direction:ltr}
[dir="rtl"] table td{unicode-bidi:plaintext}
[dir="rtl"] .stat strong, [dir="rtl"] .detail-item strong{unicode-bidi:plaintext}
/* A document letterhead is DATA (§14): an address, a phone number, an entity's own name. In an RTL
   paragraph a line STARTING with digits gets reordered - "1015 CJ Amsterdam, Netherlands" printed
   as "CJ Amsterdam, Netherlands 1015", moving the postcode to the end of the address. plaintext
   sets each line's direction from its OWN first strong character, so a Latin address reads LTR and
   an Arabic one reads RTL, without hard-coding either. */
/* The letterhead keeps its LTR geometry in EVERY language (user decision 2026-07-18):
   entity name + address stay LEFT, logo in the middle, the statement title block RIGHT -
   only the title block's TEXT runs RTL for Arabic. */
[dir="rtl"] .statement-masthead-top{direction:ltr}
[dir="rtl"] .statement-from{direction:ltr;text-align:left}
[dir="rtl"] .statement-title{direction:rtl}
/* In RTL flex-end flips to the visual LEFT - re-anchor the chip rows at the title's right edge. */
[dir="rtl"] .statement-title .doc-meta-tags{justify-content:flex-start}
[dir="rtl"] .statement-org, [dir="rtl"] .statement-org-line,
[dir="rtl"] .statement-party-name, [dir="rtl"] .statement-issuer-contact span,
[dir="rtl"] .statement-summary-cell strong{unicode-bidi:plaintext}
[dir="rtl"] .amount, [dir="rtl"] .record-link, [dir="rtl"] .num{direction:ltr;unicode-bidi:isolate}
/* Form values are data too: numeric, date, email and code inputs read LTR internally. */
[dir="rtl"] input[type="number"], [dir="rtl"] input[type="email"], [dir="rtl"] input[type="url"],
[dir="rtl"] input[type="date"], [dir="rtl"] input[inputmode="numeric"], [dir="rtl"] input[inputmode="decimal"],
[dir="rtl"] .js-date-display{direction:ltr;text-align:left}

/* --- RTL chrome fixes (walkthrough 2026-07-19) -----------------------------------------------
   Found by rendering real pages in Arabic, not by reading CSS. Chrome mirrors (§14), so anything
   anchored to a physical edge has to flip with it - a panel pinned right:0 hangs off the LEFT of
   the screen once the topbar moves.

   Deliberately NOT flipped: the nav-group and panel-toggle carets (border-right + rotate draws a
   DOWN chevron - direction-neutral), tables and pagination (§14 keeps data surfaces LTR), and the
   date-picker button inside date inputs, which stay LTR internally. */

/* Dropdown panels: measured at left:-139px and left:-54px in Arabic - both hung off-screen. */
[dir="rtl"] .notice-panel, [dir="rtl"] .user-menu-panel{right:auto;left:0}
/* Menu items are chrome copy, so they read from the right like every other label. */
[dir="rtl"] .user-menu-panel a, [dir="rtl"] .user-menu-panel button,
[dir="rtl"] .row-menu-item{text-align:right}
/* The help bubble opens from the field's reading-start edge. */
[dir="rtl"] .help-bubble{left:auto;right:0;text-align:right}
/* Gaps that sit AFTER their element in reading order. */
[dir="rtl"] .field-help{margin-left:0;margin-right:4px}
[dir="rtl"] .tab-count{margin-left:0;margin-right:.15em}
[dir="rtl"] .rate-clear-form{margin-left:0;margin-right:8px}
[dir="rtl"] .notif-item .row-menu{padding-right:0;padding-left:6px}
/* Tone accent edge belongs on the side the eye starts from. */
[dir="rtl"] .attention-card{border-left-width:1px;border-right-width:4px}
[dir="rtl"] .tone-err{border-left-color:var(--border);border-right-color:rgba(var(--err-rgb),.75)}
[dir="rtl"] .tone-ok{border-left-color:var(--border);border-right-color:rgba(var(--ok-rgb),.7)}
[dir="rtl"] .tone-warn{border-left-color:var(--border);border-right-color:rgba(var(--warn-rgb),.8)}
[dir="rtl"] .tone-info{border-left-color:var(--border);border-right-color:rgba(var(--info-rgb),.7)}

/* Screen-reader-only labels (a11y): visually removed, still announced. */
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* Per-subsection create action on grouped feeds: one aligned actions COLUMN (under
   Details), so buttons line up regardless of title length; primary small - the same
   look as every section-head action (§11). */
.group-row .group-row-action-cell{padding-top:6px;padding-bottom:6px}
.group-row .group-row-action-cell .button{margin:0}

/* Walkthrough: recycle bin + account/security polish (§1 no inline styles). */
.recycle-actions{display:flex;gap:var(--space-2,.5rem);justify-content:flex-end;flex-wrap:wrap}
.recycle-actions form{display:inline}
.twofa-key{font-size:1.05rem;letter-spacing:.12em}
/* Recovery codes in their one-and-only appearance. Monospace and generously spaced because these get
   copied by hand as often as by button, and a misread character is a code that never works. */
.recovery-codes{
  margin:0 0 14px;
  padding:12px 14px;
  border:1px solid var(--border);
  border-radius:var(--r);
  background:var(--surf-1);
  color:var(--text);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:1rem;
  line-height:1.9;
  letter-spacing:.08em;
  white-space:pre;
  overflow-x:auto;
  user-select:all;
}
.recovery-ack{
  display:flex;
  align-items:flex-start;
  gap:10px;
  margin:4px 0 14px;
  color:var(--soft);
  font-size:14px;
  cursor:pointer;
}
.recovery-ack input{margin-top:2px;flex:0 0 auto}
/* Printing the modal prints the CODES, not the page behind them. The script re-parents the dialog to
   <body> on open precisely so this selector can reach it - nested, it hid its own ancestors and
   printed a blank sheet. The buttons and the acknowledgement are screen furniture, not paper. */
@media print{
  body > *:not(#recovery-dialog){display:none !important}
  #recovery-dialog{position:static;display:block;border:0;box-shadow:none;max-width:none;margin:0}
  #recovery-dialog::backdrop{display:none}
  #recovery-dialog .modal-actions,#recovery-dialog .recovery-ack{display:none !important}
  .recovery-codes{border:1px solid #000;color:#000;background:#fff}
}
/* The setup QR. The white plate is NOT decoration and must not follow the theme: a scanner needs
   dark-on-light with a light quiet zone around it, so the code keeps its own background in dark mode
   and in print. Sized in ch-independent units and capped so it never overflows a narrow phone. */
.twofa-qr{
  display:inline-block;
  margin:4px 0 12px;
  padding:0;
  border-radius:var(--r);
  background:#fff;
  line-height:0;
}
.twofa-qr svg{width:100%;max-width:260px;height:auto;display:block}
.passkey-add-fields{margin-top:.6em}
.js-passkey-add-error{color:var(--err,#ff9d9d)}

/* Notifications preferences grid (account tab). */
.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}
.notif-grid th.notif-ch,.notif-grid td.notif-ch{text-align:center;width:6.5rem}
.notif-grid .notif-group-row th{background:var(--bg-hover,rgba(127,127,127,.08));font-weight:600;text-align:left;padding-top:.7rem}
.notif-grid td strong{display:block}
.notif-desc{display:block;font-size:.85em;margin-top:.15rem}
.notif-check{display:inline-flex;align-items:center;justify-content:center;margin:0}
.notif-check input{margin:0}
.notif-push-error{color:var(--err,#ff9d9d)}
@media (max-width:640px){
  .notif-grid th.notif-ch{width:auto}
  .notif-grid td.notif-ch{width:auto;text-align:right}
}

/* Clickable column-header sorting on generic engine lists (Date / Due date / Amount). Hardcoded
   module order is the default; ?sort/?dir override it. */
th .col-sort{color:inherit;text-decoration:none;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;cursor:pointer;border-radius:5px;padding:2px 5px;margin:-2px -5px;transition:background .12s,color .12s}
th .col-sort:hover{color:var(--text);background:var(--panel-2)}
th.num .col-sort{justify-content:flex-end}
.sort-ind{font-size:.95em;opacity:.6;line-height:1}
.col-sort:hover .sort-ind{opacity:.9}
.col-sort.is-sorted{color:var(--text)}
.col-sort.is-sorted .sort-ind{opacity:1;color:var(--indigo)}
/* Mobile sort control (the header row is hidden when the table stacks into cards). */
.list-sort-mobile{display:none}
@media (max-width:640px){
  .list-sort-mobile{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin:0 0 12px}
  .list-sort-mobile .list-sort-field{display:flex;flex-direction:column;gap:4px}
  .list-sort-mobile .list-sort-field span{font-size:12px;color:var(--mute)}
  .list-sort-mobile select{min-width:150px}
}

/* Read-only (viewer role) safety net: the backend denies every write route for viewers, and the
   common action buttons are gated server-side. This hides any residual write control - links to
   write routes, POST action forms, and bulk-selection checkboxes - so a viewer's UI is truly
   read-only everywhere, even on inline row/detail actions that aren't individually gated. */
.shell[data-viewer] a[href*="/new?"],
.shell[data-viewer] a[href$="/new"],
.shell[data-viewer] a[href*="/edit/"],
.shell[data-viewer] a[href*="/reverse/"],
.shell[data-viewer] a[href*="/dispose/"],
.shell[data-viewer] a[href*="/delete/"],
.shell[data-viewer] a[href*="/toggle/"],
.shell[data-viewer] a[href*="/correct/"],
.shell[data-viewer] a[href*="/convert/"],
.shell[data-viewer] a[href*="/review/"],
.shell[data-viewer] a[href*="/dispose"],
.shell[data-viewer] form[action*="/new"],
.shell[data-viewer] form[action*="/edit"],
.shell[data-viewer] form[action*="/store"],
.shell[data-viewer] form[action*="/update"],
.shell[data-viewer] form[action*="/delete"],
.shell[data-viewer] form[action*="/reverse"],
.shell[data-viewer] form[action*="/dispose"],
.shell[data-viewer] form[action*="/toggle"],
.shell[data-viewer] form[action*="/archive"],
.shell[data-viewer] form[action*="/correct"],
.shell[data-viewer] form[action*="/collections"],
.shell[data-viewer] form[action*="/recalc"],
.shell[data-viewer] form[action*="/notify"],
.shell[data-viewer] form[action*="/convert"],
.shell[data-viewer] form[action*="/send"],
.shell[data-viewer] form[action*="/accept"],
.shell[data-viewer] form[action*="/cancel"],
.shell[data-viewer] form[action*="/stage"],
.shell[data-viewer] form[action*="/post"],
.shell[data-viewer] form[action*="/generate"],
.shell[data-viewer] form[action*="/close"],
.shell[data-viewer] form[action*="/skip"],
.shell[data-viewer] form[action*="/discard"],
.shell[data-viewer] form[action*="/depreciate"],
.shell[data-viewer] .row-menu,
.shell[data-viewer] .check-col,
.shell[data-viewer] .js-check-all,
.shell[data-viewer] .js-row-check,
.shell[data-viewer] .js-bulk-submit,
.shell[data-viewer] .timeline-add {
  display: none !important;
}
/* A labelled sub-heading inside a form-grid, spanning the full width to group related fields. */
.form-section-label{grid-column:1/-1;margin:8px 0 0;font-size:.8rem;font-weight:700;color:var(--mute);text-transform:none}

/* ---------------------------------------------------------------------------------------------
   The combined public landing page (manager.bawtag.com, and finance./partners. on their own tab).
   PURE-CSS TABS: a radio per tab, then labels and panels that a sibling selector switches. The page
   is the front door for people who are not signed in, so it must work with scripting off entirely -
   the active tab is chosen server-side from the host and rendered as the checked radio.
   --------------------------------------------------------------------------------------------- */
.landing-body{margin:0}
.landing-wrap{max-width:960px;margin:0 auto;padding:48px 20px 64px}
.landing-head{margin-bottom:28px}
.landing-head h1{margin:.2em 0}
.landing-tab-input{position:absolute;opacity:0;pointer-events:none}
.landing-tab-bar{display:flex;flex-wrap:wrap;gap:6px;border-bottom:1px solid var(--border);margin-bottom:22px}
.landing-tab{padding:10px 16px;border-radius:var(--r) var(--r) 0 0;cursor:pointer;font-weight:700;
  color:var(--mute);border:1px solid transparent;border-bottom:0;white-space:nowrap}
.landing-tab:hover{color:var(--soft);background:var(--hover)}
/* Keyboard: the label is focusable, so the ring has to be visible on it (framework §9). */
.landing-tab:focus-visible{outline:2px solid rgb(var(--brand-rgb));outline-offset:-2px}
.landing-panel{display:none}
.landing-features{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin:22px 0}
.landing-features article{padding:16px;border:1px solid var(--border);border-radius:var(--r-lg);background:var(--bg-2)}
.landing-features h3{margin:0 0 6px;font-size:1rem}
.landing-features p{margin:0;color:var(--soft)}
.landing-actions{margin-top:26px}
.landing-foot{margin-top:48px;padding-top:18px;border-top:1px solid var(--border)}
/* One pair per tab: the checked radio lights its label and reveals its panel. Written out rather
   than generated, because the tab set is fixed and four explicit rules read better than a loop. */
#lt-finance:checked  ~ .landing-tab-bar label[for="lt-finance"],
#lt-business:checked ~ .landing-tab-bar label[for="lt-business"],
#lt-ngo:checked      ~ .landing-tab-bar label[for="lt-ngo"],
#lt-partners:checked ~ .landing-tab-bar label[for="lt-partners"]{
  color:var(--text);background:var(--bg-2);border-color:var(--border)}
#lt-finance:checked  ~ #lp-finance,
#lt-business:checked ~ #lp-business,
#lt-ngo:checked      ~ #lp-ngo,
#lt-partners:checked ~ #lp-partners{display:block}
/* On a phone the four tabs wrap to two rows and leave the last one orphaned under three - it works,
   but it reads as an afterthought rather than a set. A 2x2 grid keeps all four visible and evenly
   weighted, with no horizontal swipe to discover (checked on the live page at 375px, 31-07-2026). */
@media (max-width:430px){
  .landing-tab-bar{display:grid;grid-template-columns:1fr 1fr;gap:4px}
  .landing-tab{text-align:center;padding:11px 8px;border-radius:var(--r)}
}
