/* =====================================================================================
 *  HAALTD INVOICE  —  css/style.css
 *  A premium iOS / macOS–inspired design system layered over Bootstrap 5.
 *
 *  Structure:
 *    1. Design tokens (:root)          6. Auth screen
 *    2. Base / reset                   7. App shell (topbar, sidebar, main)
 *    3. Typography                     8. Components (buttons, forms, cards, KPI)
 *    4. Scrollbars                     9. Modals, toasts, connectivity pill
 *    5. Splash                        10. Bootstrap overrides & responsive
 * ===================================================================================== */

/* =====================================================================================
 *  1. DESIGN TOKENS
 * ===================================================================================== */
:root {
  /* ===================================================================================
   *  "LEDGER"  —  the entire visual layer lives here.
   *
   *  The existing variable NAMES are kept deliberately: every view already consumes
   *  them, so replacing the values re-skins the whole app at once instead of leaving
   *  eleven files to be hand-edited and half of them missed. New semantic names are
   *  added alongside as aliases for anything written from here on.
   * ================================================================================= */

  /* ---- SURFACES: warm off-white, not grey. The warmth is what keeps a finance UI
     from feeling clinical; a neutral grey canvas reads as a spreadsheet. --------- */
  --paper:           #FCFCFD;
  --bg:              #F7F7F5;   /* warm off-white: cards read as objects on a surface,
                                   not as slightly different white on white */
  --surface:         #FFFFFF;
  --surface-2:       #F7F8FA;
  --surface-sunken:  #F1F1EE;   /* warmed to match the off-white base */
  --sunken:          #F1F3F7;

  /* ---- INK: text is NAVY, never black. This single decision does more for the
     "financial institution" read than any accent choice. 15.8:1 on paper. ------- */
  --ink:             #0A1F44;
  --text:            #0A1F44;
  --ink-2:           #4A5878;
  --text-secondary:  #4A5878;
  --ink-3:           #7A87A3;
  --text-tertiary:   #7A87A3;
  --text-on-accent:  #FFFFFF;
  --on-accent:       #FFFFFF;

  /* ---- ACCENTS: desaturated and darkened against the iOS system palette.
     A ledger flags; it does not alarm. Consumer-bright makes money feel like a
     game, which is the opposite of what an invoice should feel like. ------------ */
  --accent:          #2C5CE6;
  --blue:            #2C5CE6;
  --accent-press:    #1E48C4;
  --blue-hover:      #1E48C4;
  --accent-wash:     rgba(44, 92, 230, .08);
  --blue-soft:       rgba(44, 92, 230, .08);

  --positive:        #0E7C5A;
  --green:           #0E7C5A;
  --positive-wash:   rgba(14, 124, 90, .10);

  --attention:       #B54708;
  --amber:           #B54708;
  --attention-wash:  rgba(181, 71, 8, .10);

  --critical:        #9F1239;
  --red:             #9F1239;
  --critical-wash:   rgba(159, 18, 57, .09);

  --purple:          #5B4BC4;
  --teal:            #0E7490;

  /* ---- LINES: navy-tinted, never neutral grey. A grey hairline on warm paper
     looks dirty; tinting it with the ink colour keeps the page coherent. -------- */
  /* ---- INVOICE STATUS RAMP (v2.11.1) -------------------------------------------
     Two tiers per status on purpose. The BAR tier is the vivid one you see at a
     glance; the INK tier is a deeper shade used for label text, because the vivid
     amber that reads beautifully as a 5px bar only manages ~3.9:1 against white and
     would fail contrast as small bold type. Same hue, different job.
     --------------------------------------------------------------------------- */
  --st-draft-bar:   #94A3B8;
  --st-draft-ink:   #5A6883;
  --st-due-bar:     #F59E0B;   /* amber — the standard for "payment expected"        */
  --st-due-ink:     #B45309;
  --st-late-bar:    #EF4444;   /* red — overdue, and only overdue                    */
  --st-late-ink:    #DC2626;
  --st-paid-bar:    #10B981;   /* emerald — brighter than --positive, which is teal  */
  --st-paid-ink:    #047857;
  --st-void-bar:    #A8B2C4;
  --st-void-ink:    #657189;   /* darkened from #7A87A3: muted is the intent, 3.6:1 was not */
  --st-rail:        rgba(10, 31, 68, .09);

  --line:            rgba(10, 31, 68, .10);
  --separator:       rgba(10, 31, 68, .10);
  --line-strong:     rgba(10, 31, 68, .17);
  --separator-strong:rgba(10, 31, 68, .17);
  --border:          rgba(10, 31, 68, .12);

  --glass-bg:        rgba(252, 252, 253, .78);
  --glass-blur:      saturate(180%) blur(20px);

  /* ---- RADII: tightened from 12–22px. Soft corners read consumer; these read
     instrument. The change is small in pixels and large in character. ---------- */
  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 10px;
  --r-xl: 14px;
  --r-pill: 999px;

  /* ---- ELEVATION: structure comes from borders. Shadow is reserved for things
     that genuinely float — menus, modals, toasts. ------------------------------ */
  --lift-1:     0 1px 2px rgba(10, 31, 68, .04);
  --shadow-sm:  0 1px 2px rgba(10, 31, 68, .04);
  --lift-2:     0 4px 12px -2px rgba(10, 31, 68, .08), 0 2px 4px -2px rgba(10, 31, 68, .05);
  --shadow-md:  0 4px 12px -2px rgba(10, 31, 68, .08), 0 2px 4px -2px rgba(10, 31, 68, .05);
  --lift-3:     0 16px 32px -8px rgba(10, 31, 68, .16);
  --shadow-lg:  0 16px 32px -8px rgba(10, 31, 68, .16);
  --shadow-glass: 0 1px 0 rgba(255,255,255,.7) inset, 0 1px 16px rgba(10,31,68,.05);

  /* ---- SPACE (4px base) ---------------------------------------------------- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 20px;
  --s6: 24px; --s7: 32px; --s8: 40px; --s9: 56px;

  --topbar-h: 60px;
  --sidebar-w: 232px;

  /* ---- MOTION -------------------------------------------------------------- */
  --ease:      cubic-bezier(.2, .8, .2, 1);     /* decelerating; things settle */
  --ease-out:  cubic-bezier(.16, 1, .3, 1);     /* longer tail, for entrances */
  --dur:       .2s;
  --dur-slow:  .45s;

  /* ---- TYPE: three roles, each with a job.
       display — Inter Tight, tighter tracking, for headings and figures with weight
       ui      — Inter, everything else
       ledger  — IBM Plex Mono, for amounts in tables so decimals align down a column */
  --font-display: "Inter Tight", "Inter", system-ui, -apple-system, sans-serif;
  --font:         "Inter", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-ui:      "Inter", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-ledger:  "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

/* =====================================================================================
 *  2. BASE / RESET
 * ===================================================================================== */
* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

body {
  overflow-x: hidden;
  /* iOS safe-area padding for notched devices */
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
           env(safe-area-inset-bottom) env(safe-area-inset-left);
}

[hidden] { display: none !important; }

a { color: var(--blue); text-decoration: none; }
a:hover { color: var(--blue-hover); }

:focus-visible {
  outline: 3px solid var(--blue-soft);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* =====================================================================================
 *  3. TYPOGRAPHY
 * ===================================================================================== */
h1,h2,h3,h4,h5 { font-weight: 600; letter-spacing: -0.02em; color: var(--text); }
.text-secondary-ios { color: var(--text-secondary); }
/* .mono already marks every money cell across the views, so it becomes the ledger
   column rather than introducing a second class that would have to be threaded through
   eleven files by hand. */
.mono {
  font-family: var(--font-ledger);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  font-size: .92em;
  letter-spacing: -.02em;
}

/* =====================================================================================
 *  4. SCROLLBARS (thin macOS style)
 * ===================================================================================== */
* { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.22) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-thumb {
  background: rgba(0,0,0,0.22);
  border-radius: var(--r-pill);
  border: 2px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.34); background-clip: content-box; }
*::-webkit-scrollbar-track { background: transparent; }

/* =====================================================================================
 *  5. SPLASH
 * ===================================================================================== */
.app-splash {
  position: fixed; inset: 0; z-index: 2000;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-6); background: var(--bg);
  transition: opacity var(--dur) var(--ease);
}
.app-splash.is-hidden { opacity: 0; pointer-events: none; }
.app-splash__logo { width: 68px; height: 68px; border-radius: var(--r-lg); box-shadow: var(--shadow-md); }
.app-splash__spinner {
  width: 26px; height: 26px; border-radius: 50%;
  border: 3px solid var(--separator); border-top-color: var(--blue);
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* =====================================================================================
 *  6. AUTH SCREEN
 * ===================================================================================== */
.auth-screen {
  min-height: 100vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-6); padding: var(--sp-6);
  background:
    radial-gradient(1200px 600px at 50% -10%, rgba(0,122,255,0.10), transparent 60%),
    var(--bg);
}
.auth-card {
  width: 100%; max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-8) var(--sp-6) var(--sp-6);
  text-align: center;
  animation: rise var(--dur) var(--ease);
}
@keyframes rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
.auth-card__logo { width: 60px; height: 60px; border-radius: var(--r-md); box-shadow: var(--shadow-sm); }
.auth-card__title { margin: var(--sp-4) 0 var(--sp-1); font-size: 1.5rem; }
.auth-card__subtitle { margin: 0 0 var(--sp-6); color: var(--text-secondary); font-size: 0.95rem; }
.auth-form { text-align: left; display: flex; flex-direction: column; gap: var(--sp-4); }
.field__label {
  display: block; font-size: 0.8rem; font-weight: 500;
  color: var(--text-secondary); margin-bottom: var(--sp-1); padding-left: 2px;
}
.auth-toggle { margin: var(--sp-5) 0 0; font-size: 0.9rem; color: var(--text-secondary); }
.auth-toggle a { font-weight: 500; margin-left: 4px; }
.auth-footer { font-size: 0.8rem; color: var(--text-tertiary); }
.auth-alert {
  text-align: left; font-size: 0.88rem; padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md); margin-bottom: var(--sp-4);
  background: rgba(255,59,48,0.10); color: var(--critical); border: 1px solid rgba(255,59,48,0.2);
}
.auth-alert.is-success { background: rgba(52,199,89,0.12); color: var(--positive); border-color: rgba(52,199,89,0.25); }

/* =====================================================================================
 *  7. APP SHELL
 * ===================================================================================== */
.app-shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  grid-template-rows: var(--topbar-h) 1fr;
  grid-template-areas:
    "topbar topbar"
    "sidebar main";
}

/* --- Top bar --- */
.app-topbar {
  grid-area: topbar;
  position: sticky; top: 0; z-index: 1030;
  display: flex; align-items: center; gap: var(--sp-4);
  height: var(--topbar-h); padding: 0 var(--sp-5);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--separator);
  box-shadow: var(--shadow-glass);
}
.topbar__menu-btn {
  display: none; border: 0; background: transparent; font-size: 1.4rem;
  color: var(--text); padding: 4px 8px; border-radius: var(--r-sm); cursor: pointer;
}
.topbar__menu-btn:hover { background: var(--surface-sunken); }
.topbar__brand { display: flex; align-items: center; gap: var(--sp-2); color: var(--text); font-weight: 600; }
.topbar__logo { width: 30px; height: 30px; border-radius: var(--r-md); box-shadow: var(--shadow-sm); }
.topbar__brand-text { letter-spacing: -0.02em; }

.topbar__search {
  position: relative; flex: 1; max-width: 460px; margin: 0 auto;
}
.topbar__search-icon {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--text-tertiary); font-size: 0.95rem; pointer-events: none;
}
.topbar__search-input {
  width: 100%; height: 38px; padding: 0 14px 0 36px;
  border: 1px solid transparent; border-radius: var(--r-pill);
  background: var(--surface-sunken); color: var(--text); font-size: 0.9rem;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.topbar__search-input::placeholder { color: var(--text-tertiary); }
.topbar__search-input:focus {
  outline: none; background: var(--surface);
  border-color: var(--blue); box-shadow: 0 0 0 4px var(--blue-soft);
}

.topbar__right { display: flex; align-items: center; gap: var(--sp-3); }

/* Connectivity pill */
.conn-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: var(--r-pill);
  background: rgba(52,199,89,0.12); font-size: 0.78rem; font-weight: 500; color: var(--positive);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.conn-pill__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); }
.conn-pill.is-offline { background: rgba(142,142,147,0.16); color: var(--text-secondary); }
.conn-pill.is-offline .conn-pill__dot { background: var(--text-tertiary); }
.conn-pill.is-syncing { background: rgba(255,159,10,0.16); color: var(--attention); }
.conn-pill.is-syncing .conn-pill__dot { background: var(--amber); animation: pulse 1s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }

/* User chip */
.user-chip {
  display: flex; align-items: center; gap: var(--sp-2);
  border: 0; background: transparent; cursor: pointer;
  padding: 4px 6px; border-radius: var(--r-pill);
}
.user-chip:hover { background: var(--surface-sunken); }
.user-chip__avatar {
  width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center; font-weight: 600; font-size: 0.85rem;
  color: var(--text-on-accent); background: linear-gradient(135deg, var(--blue), var(--purple));
}
.user-chip__meta { display: flex; flex-direction: column; line-height: 1.1; text-align: left; }
.user-chip__name { font-size: 0.85rem; font-weight: 600; color: var(--text); }
.user-chip__role { font-size: 0.7rem; color: var(--text-tertiary); text-transform: capitalize; }
.user-chip__caret { font-size: 0.7rem; color: var(--text-tertiary); }
.user-dropdown { border-radius: var(--r-md); border: 1px solid var(--border); box-shadow: var(--shadow-lg); padding: 6px; }
.user-dropdown__email { font-size: 0.8rem; color: var(--text-secondary); }
.user-dropdown .dropdown-item { border-radius: var(--r-sm); font-size: 0.9rem; display: flex; align-items: center; gap: 8px; }

/* --- Sidebar --- */
.app-sidebar {
  grid-area: sidebar;
  position: sticky; top: var(--topbar-h);
  height: calc(100vh - var(--topbar-h));
  display: flex; flex-direction: column;
  padding: var(--sp-4) var(--sp-3);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-right: 1px solid var(--separator);
  overflow-y: auto;
}
.sidebar__nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }
.nav-item {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 10px 12px; border-radius: var(--r-md);
  color: var(--text-secondary); font-size: 0.92rem; font-weight: 500;
  cursor: pointer; border: 0; background: transparent; width: 100%; text-align: left;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nav-item i { font-size: 1.1rem; width: 22px; text-align: center; }
.nav-item:hover { background: var(--surface-sunken); color: var(--text); }
.nav-item.is-active { background: var(--blue); color: var(--text-on-accent); box-shadow: var(--shadow-sm); }
.nav-item.is-active i { color: var(--text-on-accent); }
.sidebar__footer { padding: var(--sp-3) var(--sp-2) 0; border-top: 1px solid var(--separator); margin-top: var(--sp-3); }
.sidebar__version { font-size: 0.72rem; color: var(--text-tertiary); }

/* Sidebar head (Menu label + collapse toggle) */
.sidebar__head { display: flex; align-items: center; justify-content: space-between; padding: 2px 8px 10px; margin-bottom: 4px; }
.sidebar__label { font-size: 0.7rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-tertiary); }
.sidebar__collapse {
  border: 0; background: transparent; color: var(--text-tertiary);
  width: 28px; height: 28px; border-radius: var(--r-md); cursor: pointer;
  display: grid; place-items: center; font-size: 0.95rem;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.sidebar__collapse:hover { background: var(--surface-sunken); color: var(--text); }

/* Workspace chip in the footer */
.workspace-chip { display: flex; align-items: center; gap: 10px; padding: 6px; border-radius: var(--r-md); }
.workspace-chip__logo img { width: 30px; height: 30px; border-radius: var(--r-md); box-shadow: var(--shadow-sm); display: block; }
.workspace-chip__meta { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.workspace-chip__name { font-size: 0.85rem; font-weight: 600; color: var(--text); white-space: nowrap; }
.workspace-chip__sub { font-size: 0.72rem; color: var(--text-tertiary); }

/* Smooth width animation between expanded and collapsed */
.app-shell { transition: grid-template-columns var(--dur) var(--ease); }

/* ---- Collapsed (icon rail) mode ---- */
.app-shell.is-collapsed { grid-template-columns: 76px 1fr; }
.app-shell.is-collapsed .app-sidebar { padding-left: 10px; padding-right: 10px; }
.app-shell.is-collapsed .sidebar__label,
.app-shell.is-collapsed .nav-item span,
.app-shell.is-collapsed .workspace-chip__meta { display: none; }
.app-shell.is-collapsed .sidebar__head { justify-content: center; padding: 2px 0 10px; }
.app-shell.is-collapsed .nav-item { justify-content: center; padding-left: 0; padding-right: 0; }
.app-shell.is-collapsed .nav-item i { width: auto; }
.app-shell.is-collapsed .workspace-chip { justify-content: center; }
.app-shell.is-collapsed .sidebar__collapse i { transform: rotate(180deg); }
.app-shell.is-collapsed .topbar__brand-text { display: none; }

/* Tooltip on nav items when collapsed */
.app-shell.is-collapsed .nav-item { position: relative; }
.app-shell.is-collapsed .nav-item::after {
  content: attr(data-tip);
  position: absolute; left: calc(100% + 12px); top: 50%;
  transform: translateY(-50%) translateX(-4px);
  background: var(--text); color: #fff; font-size: 0.8rem; font-weight: 500;
  padding: 6px 10px; border-radius: var(--r-md); white-space: nowrap;
  opacity: 0; pointer-events: none; z-index: 1060; box-shadow: var(--shadow-md);
  transition: opacity 0.15s var(--ease), transform 0.15s var(--ease);
}
.app-shell.is-collapsed .nav-item:hover::after { opacity: 1; transform: translateY(-50%) translateX(0); }

/* On mobile the off-canvas drawer is used, so the collapse head is hidden */
@media (max-width: 991px) {
  .sidebar__head { display: none; }
  .app-shell.is-collapsed { grid-template-columns: 1fr; }
}

.sidebar-backdrop {
  position: fixed; inset: 0; z-index: 1020;
  background: rgba(0,0,0,0.35);
}

/* --- Main --- */
.app-main { grid-area: main; min-width: 0; padding: var(--sp-6); }
.view-root { max-width: 1200px; margin: 0 auto; animation: fade var(--dur) var(--ease); }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* View header + empty state (used by the placeholder and future views) */
.view-header { margin-bottom: var(--sp-6); }
.view-header__title { font-size: 1.7rem; margin: 0 0 4px; }
.view-header__subtitle { color: var(--text-secondary); margin: 0; font-size: 0.95rem; }

.empty-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: var(--sp-3);
  padding: var(--sp-10) var(--sp-6); min-height: 320px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
}
.empty-state__icon {
  width: 64px; height: 64px; border-radius: var(--r-lg);
  display: grid; place-items: center; font-size: 1.8rem;
  color: var(--blue); background: var(--blue-soft);
}
.empty-state__title { font-size: 1.15rem; margin: 0; }
.empty-state__text { color: var(--text-secondary); margin: 0; max-width: 380px; }

/* =====================================================================================
 *  8. COMPONENTS — buttons, forms, cards, KPI, segmented control, iOS switch
 * ===================================================================================== */

/* iOS-style buttons */
/* Gradient fill and a lift on hover. The gradient is shallow on purpose — two stops a
   few degrees apart in the same hue reads as depth; a rainbow reads as a toy, and this
   button moves money. The glow is the brand colour at low alpha rather than a grey
   shadow, so the button looks lit from within instead of pasted on. */
.btn-ios-primary {
  background: linear-gradient(135deg, var(--blue), var(--blue-hover));
  color: var(--text-on-accent);
  border: 0; border-radius: var(--r-md); padding: 11px 18px;
  font-weight: 600; font-size: 0.95rem;
  transition: transform .2s var(--ease), box-shadow .2s var(--ease), filter .2s var(--ease);
  box-shadow: 0 1px 2px rgba(10, 31, 68, .16), 0 6px 16px -6px color-mix(in srgb, var(--blue) 55%, transparent);
}
.btn-ios-primary:hover {
  color: var(--text-on-accent); transform: translateY(-1px); filter: saturate(1.06);
  box-shadow: 0 1px 2px rgba(10, 31, 68, .16), 0 12px 24px -8px color-mix(in srgb, var(--blue) 70%, transparent);
}
.btn-ios-primary:active { transform: translateY(0) scale(.985); }

/* Secondary stays deliberately quiet — if both buttons shout, neither leads. */
.btn-ios-secondary {
  transition: transform .2s var(--ease), background .2s var(--ease),
              border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.btn-ios-secondary:hover {
  transform: translateY(-1px);
  box-shadow: 0 1px 2px rgba(10, 31, 68, .06), 0 8px 18px -8px rgba(10, 31, 68, .22);
}
.btn-ios-secondary:active { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .btn-ios-primary, .btn-ios-secondary { transition: none !important; }
  .btn-ios-primary:hover, .btn-ios-secondary:hover { transform: none; }
}
.btn-ios-primary:disabled { opacity: 0.5; }

.btn-ios-secondary {
  background: var(--surface-sunken); color: var(--blue);
  border: 0; border-radius: var(--r-md); padding: 11px 18px; font-weight: 600; font-size: 0.95rem;
  transition: background var(--dur) var(--ease), transform 0.08s var(--ease);
}
.btn-ios-secondary:hover { background: var(--surface-sunken); color: var(--blue-hover); }
.btn-ios-secondary:active { transform: scale(0.98); }

.btn-ios-ghost {
  background: transparent; color: var(--blue); border: 0; padding: 9px 12px;
  border-radius: var(--r-md); font-weight: 500;
}
.btn-ios-ghost:hover { background: var(--blue-soft); }

.btn-ios-danger { background: var(--red); color: #fff; border: 0; border-radius: var(--r-md); padding: 11px 18px; font-weight: 600; }
.btn-ios-danger:hover { background: var(--critical); color: #fff; }

/* Cards */
.ios-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
  padding: var(--sp-5);
}

/* KPI banner */
.kpi-banner {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp-4); margin-bottom: var(--sp-6);
}
.kpi-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--sp-5); box-shadow: var(--shadow-sm);
}
.kpi-card__label { font-size: 0.8rem; color: var(--text-secondary); font-weight: 500; margin: 0 0 6px; }
.kpi-card__value { font-size: 1.7rem; font-weight: 700; letter-spacing: -0.02em; margin: 0; font-variant-numeric: tabular-nums; }
.kpi-card__delta { font-size: 0.8rem; font-weight: 600; margin-top: 4px; }
.kpi-card__delta.is-up { color: var(--green); }
.kpi-card__delta.is-down { color: var(--red); }

/* Segmented control (iOS) */
.segmented {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--surface-sunken); border-radius: var(--r-md);
}
.segmented__item {
  border: 0; background: transparent; padding: 7px 16px; border-radius: var(--r-md);
  font-size: 0.85rem; font-weight: 500; color: var(--text-secondary); cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.segmented__item.is-active { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }

/* iOS switch toggle (the signature control) */
.ios-switch { position: relative; display: inline-block; width: 51px; height: 31px; }
.ios-switch input { opacity: 0; width: 0; height: 0; }
.ios-switch__slider {
  position: absolute; inset: 0; cursor: pointer; border-radius: var(--r-pill);
  background: var(--surface-sunken); transition: background var(--dur) var(--ease);
}
.ios-switch__slider::before {
  content: ""; position: absolute; height: 27px; width: 27px; left: 2px; top: 2px;
  background: #fff; border-radius: 50%; box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease);
}
.ios-switch input:checked + .ios-switch__slider { background: var(--green); }
.ios-switch input:checked + .ios-switch__slider::before { transform: translateX(20px); }

/* Status badges (invoice states) */
/* --- Status badges: traffic-light, with a live status dot -------------------------
   Green = settled. Amber = money still owed. Red = overdue. Grey = not yet live.   */
.badge-ios {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: .01em;
  padding: 4px 11px 4px 8px; border-radius: var(--r-pill);
  border: 1px solid transparent; white-space: nowrap; line-height: 1.5;
}
.badge-ios::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: 0 0 auto; box-shadow: var(--lift-1);
}
.badge-pre_invoice   { background: rgba(142,142,147,0.14); color: var(--ink-2); border-color: rgba(142,142,147,0.28); }
.badge-due           { background: rgba(249,115,22,0.14); color: var(--attention); border-color: rgba(249,115,22,0.34); }
.badge-partially_paid{ background: rgba(249,115,22,0.16); color: var(--attention); border-color: rgba(249,115,22,0.38); }
.badge-overdue       { background: rgba(220,38,38,0.12);   color: var(--critical); border-color: rgba(220,38,38,0.32); }
.badge-overdue::before { animation: badgePulse 1.8s ease-in-out infinite; }
@keyframes badgePulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(1.3); } }
.badge-paid {
  background: linear-gradient(135deg, rgba(15,157,88,.16), rgba(52,199,89,.12));
  color: var(--positive); border-color: rgba(15,157,88,0.34);
}
.badge-cancelled { background: rgba(220,38,38,0.10); color: #991B1B; border-color: rgba(220,38,38,0.26); text-decoration: line-through; }

/* Dark mode: lift the badge colours so they stay vivid */
/* Status ramp, dark: bars stay vivid, ink tier lightens so it reads on a dark surface. */
html[data-theme="dark"] {
  --st-draft-bar: #94A3B8; --st-draft-ink: #A9B4C7;
  --st-due-bar:   #FBBF24; --st-due-ink:   #FCD34D;
  --st-late-bar:  #F87171; --st-late-ink:  #FCA5A5;
  --st-paid-bar:  #34D399; --st-paid-ink:  #6EE7B7;
  --st-void-bar:  #6B7688; --st-void-ink:  #8A94A6;
  --st-rail:      rgba(255, 255, 255, .13);
}

html[data-theme="dark"] .badge-pre_invoice   { background: rgba(142,142,147,0.20); color: var(--ink-3); }
html[data-theme="dark"] .badge-due           { background: rgba(245,158,11,0.18);  color: #FCD34D; border-color: rgba(245,158,11,0.35); }
html[data-theme="dark"] .badge-partially_paid{ background: rgba(251,146,60,0.18);  color: #FDBA74; border-color: rgba(251,146,60,0.35); }
html[data-theme="dark"] .badge-overdue       { background: rgba(248,113,113,0.18); color: #FCA5A5; border-color: rgba(248,113,113,0.35); }
html[data-theme="dark"] .badge-paid          { background: rgba(52,211,153,0.18);  color: #6EE7B7; border-color: rgba(52,211,153,0.35); }
html[data-theme="dark"] .badge-ios::before   { box-shadow: var(--lift-1); }
.badge-cancelled     { background: rgba(60,60,67,0.14);    color: #48484A; text-decoration: line-through; }

/* Toolbar (card header row) + section titles */
.toolbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--separator);
}
.section-title { font-size: 1.05rem; margin: 0; }

/* Data table (shared by dashboard / invoices / customers) */
.data-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.data-table thead th {
  text-align: left; font-weight: 600; font-size: 0.75rem; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--text-tertiary);
  padding: 10px var(--sp-5); background: var(--surface-2);
  border-bottom: 1px solid var(--separator); white-space: nowrap;
}
.data-table tbody td { padding: 13px var(--sp-5); border-bottom: 1px solid var(--separator); color: var(--text); vertical-align: middle; }
.data-table tbody tr:last-child td { border-bottom: 0; }
.data-table tbody tr { transition: background var(--dur) var(--ease); }
.data-table tbody tr:hover { background: var(--surface-sunken); }
.text-end { text-align: right !important; }

/* Skeleton loaders */
.skeleton {
  background: linear-gradient(90deg, var(--surface-sunken) 25%, #E9E9EF 37%, var(--surface-sunken) 63%);
  background-size: 400% 100%; border-radius: var(--r-sm); animation: shimmer 1.3s ease-in-out infinite;
}
.skeleton--label { height: 12px; width: 55%; margin-bottom: 12px; }
.skeleton--value { height: 26px; width: 78%; }
.skeleton--row   { height: 18px; width: 100%; margin: 14px 0; }
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

/* =====================================================================================
 *  9. MODALS, TOASTS
 * ===================================================================================== */
.ios-modal {
  border: 0; border-radius: var(--r-xl); box-shadow: var(--shadow-lg);
  background: var(--surface); overflow: hidden;
}
.ios-modal .modal-header { border-bottom: 1px solid var(--separator); padding: var(--sp-5); }
.ios-modal .modal-title { font-size: 1.1rem; }
.ios-modal .modal-body { padding: var(--sp-5); }
.ios-modal .modal-footer { border-top: 1px solid var(--separator); padding: var(--sp-4) var(--sp-5); gap: var(--sp-2); }
/* Modal backdrop — deliberately NOT blurred. The topbar and sidebar already use
   backdrop-filter glass; stacking another blur over them forces the browser to
   re-composite nested filter layers on every frame, which makes typing inside a
   modal feel sluggish. A plain scrim is just as good and costs nothing. */
.modal-backdrop.show { background: rgba(0,0,0,0.42); }

.toast-stack {
  position: fixed; z-index: 2000; right: var(--sp-5); bottom: var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-2); max-width: 360px;
}

/* =====================================================================================
 *  10. BOOTSTRAP OVERRIDES  &  RESPONSIVE
 * ===================================================================================== */
.form-control, .form-select {
  border: 1px solid var(--separator); border-radius: var(--r-md);
  padding: 10px 12px; font-size: 0.92rem; background: var(--surface); color: var(--text);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.form-control:focus, .form-select:focus {
  border-color: var(--blue); box-shadow: 0 0 0 4px var(--blue-soft); outline: none;
}
.form-control::placeholder { color: var(--text-tertiary); }

.btn-close:focus { box-shadow: 0 0 0 4px var(--blue-soft); }

.dropdown-menu { --bs-dropdown-border-color: var(--border); border-radius: var(--r-md); box-shadow: var(--shadow-lg); }

/* Bootstrap color utility remaps toward the iOS palette */
.text-danger { color: var(--red) !important; }
.text-success { color: var(--green) !important; }

/* Tablet / mobile: sidebar becomes an off-canvas drawer */
@media (max-width: 991px) {
  .app-shell {
    grid-template-columns: 1fr;
    grid-template-areas:
      "topbar"
      "main";
  }
  .topbar__menu-btn { display: inline-flex; align-items: center; }
  .topbar__brand-text { display: none; }
  .app-sidebar {
    position: fixed; top: 0; left: 0; z-index: 1040;
    height: 100vh; width: 84vw; max-width: 300px;
    padding-top: calc(var(--topbar-h) + var(--sp-3));
    transform: translateX(-100%);
    transition: transform var(--dur) var(--ease);
    box-shadow: var(--shadow-lg);
  }
  .app-sidebar.is-open { transform: translateX(0); }
}

@media (max-width: 575px) {
  .app-main { padding: var(--sp-4); }
  .topbar__search { display: none; }        /* free up room on phones */
  .user-chip__meta { display: none; }
  .conn-pill__label { display: none; }
  .conn-pill { padding: 6px; }
}

/* Respect reduced-motion preferences */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* =====================================================================================
 *  11. NATIVE APP SHELL — install banner + standalone hardening
 * ===================================================================================== */

/* Remove the grey tap flash everywhere (app-like feel) */
a, button, .nav-item, .user-chip, [role="button"] { -webkit-tap-highlight-color: transparent; }

/* Install / add-to-home banner */
.pwa-install {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 2500; display: flex; align-items: center; gap: 12px;
  width: calc(100% - 32px); max-width: 440px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-lg); padding: 12px 14px;
  animation: rise var(--dur) var(--ease);
}
.pwa-install__icon { width: 44px; height: 44px; border-radius: var(--r-lg); box-shadow: var(--shadow-sm); flex: 0 0 auto; }
.pwa-install__text { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.pwa-install__text strong { font-size: 0.92rem; color: var(--text); }
.pwa-install__text span { font-size: 0.8rem; color: var(--text-secondary); }
.pwa-install__cta { flex: 0 0 auto; padding: 8px 16px; }
.pwa-install__close { flex: 0 0 auto; border: 0; background: transparent; color: var(--text-tertiary); font-size: 1.05rem; cursor: pointer; padding: 4px; border-radius: var(--r-md); }
.pwa-install__close:hover { background: var(--surface-sunken); color: var(--text); }

/* =====================================================================================
 *  GLOBAL UI LOCK — the single biggest thing that separates a native app from a web page.
 *  Chrome, labels, buttons, nav and headings are NOT selectable: tapping, holding or
 *  dragging never highlights stray words. Applies everywhere (browser and installed),
 *  because the app should feel the same in both.
 *
 *  Two deliberate exceptions:
 *    1. INPUT  — anything the user types into must stay fully selectable/editable.
 *    2. DATA   — real business data (invoice numbers, amounts, emails, addresses,
 *                table cells, notes) stays copyable. Mark extra spots with
 *                class="selectable" or data-selectable.
 * =================================================================================== */
html, body { overscroll-behavior: none; }
body {
  -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;
  -webkit-touch-callout: none;          /* no long-press callout menu */
  -webkit-tap-highlight-color: transparent;
}

/* 1 — INPUT EXCEPTION (crucial: never lock what people type into) */
input, textarea, select, [contenteditable], [contenteditable] * {
  -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text;
  -webkit-touch-callout: default;
}

/* 2 — DATA EXCEPTION (business data must stay copyable) */
.data-table td,
.selectable, [data-selectable],
.inv-doc,
.notif-sub,
.ip-bank-grid b {
  -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text;
  -webkit-touch-callout: default;
}

/* Images and icons are never draggable — dragging a logo out of the window is a web tell */
img, svg, .bi, .workspace-chip__logo, .ab-hero__icon, .pwa-banner__icon {
  -webkit-user-drag: none; -khtml-user-drag: none; -moz-user-drag: none; user-drag: none;
}
img { pointer-events: auto; }

/* Native-feeling scroll + no text cursor over chrome */
button, .nav-item, .user-chip, [role="button"], .set-tab, .ver-chip { cursor: pointer; }

/* Installed mode: also kill the rubber-band and native context menu entirely */
html.is-standalone, html.is-standalone body { overscroll-behavior: none; overflow-x: hidden; }

/* =====================================================================================
 *  DARK MODE — a true dark palette (not an inversion). Applied by js/core/theme.js via
 *  <html data-theme="dark">. Every surface, hairline, glass and shadow is re-tuned for
 *  low light; brand colors are lifted slightly so they stay vivid on dark backgrounds.
 * =================================================================================== */
html[data-theme="dark"] {
  /* A variant, not an inversion. Surfaces stay blue-black so the navy identity
     survives; the accent lifts in luminance so it still reads on a dark field. */
  --paper:           #0A0F1C;
  --bg:              #0A0F1C;
  --surface:         #101827;
  --surface-2:       #141D2E;
  --surface-sunken:  #1A2437;
  --sunken:          #1A2437;

  --ink:             #EEF2F9;
  --text:            #EEF2F9;
  --ink-2:           #9AA8C4;
  --text-secondary:  #9AA8C4;
  --ink-3:           #6B7A99;
  --text-tertiary:   #6B7A99;

  --accent:          #6690FF;
  --blue:            #6690FF;
  --accent-press:    #7FA2FF;
  --blue-hover:      #7FA2FF;
  --accent-wash:     rgba(102, 144, 255, .14);
  --blue-soft:       rgba(102, 144, 255, .14);

  --positive:        #34D399;
  --green:           #34D399;
  --positive-wash:   rgba(52, 211, 153, .13);
  --attention:       #FBA94C;
  --amber:           #FBA94C;
  --attention-wash:  rgba(251, 169, 76, .13);
  --critical:        #FB7185;
  --red:             #FB7185;
  --critical-wash:   rgba(251, 113, 133, .13);
  --purple:          #A78BFA;
  --teal:            #22D3EE;

  --line:            rgba(238, 242, 249, .10);
  --separator:       rgba(238, 242, 249, .10);
  --line-strong:     rgba(238, 242, 249, .18);
  --separator-strong:rgba(238, 242, 249, .18);
  --border:          rgba(238, 242, 249, .12);

  --glass-bg:        rgba(16, 24, 39, .78);

  --lift-1:     0 1px 2px rgba(0,0,0,.40);
  --shadow-sm:  0 1px 2px rgba(0,0,0,.40);
  --lift-2:     0 4px 12px -2px rgba(0,0,0,.50);
  --shadow-md:  0 4px 12px -2px rgba(0,0,0,.50);
  --lift-3:     0 16px 32px -8px rgba(0,0,0,.60);
  --shadow-lg:  0 16px 32px -8px rgba(0,0,0,.60);
  --shadow-glass: 0 1px 0 rgba(255,255,255,.05) inset, 0 1px 20px rgba(0,0,0,.35);

  color-scheme: dark;
}

/* Form controls need explicit surfaces in dark */
html[data-theme="dark"] .form-control,
html[data-theme="dark"] .form-select {
  background-color: var(--surface-sunken);
  border-color: var(--border);
  color: var(--text);
}
html[data-theme="dark"] .form-control::placeholder { color: var(--text-tertiary); }
html[data-theme="dark"] .form-control:focus,
html[data-theme="dark"] .form-select:focus {
  background-color: var(--surface-sunken);
  color: var(--text);
}
/* Native pickers (date/time/color) render legibly */
html[data-theme="dark"] input[type="date"],
html[data-theme="dark"] input[type="datetime-local"],
html[data-theme="dark"] input[type="number"] { color-scheme: dark; }

/* Zebra rows / hover states */
html[data-theme="dark"] .data-table tbody tr:hover { background: var(--surface-2); }

/* The invoice preview is paper — it stays light in both themes */
html[data-theme="dark"] .inv-doc { box-shadow: var(--lift-3); }

/* Smooth the switch between themes */
html.theme-anim, html.theme-anim body,
html.theme-anim .app-shell, html.theme-anim .ios-card,
html.theme-anim .topbar, html.theme-anim .sidebar {
  transition: background-color .28s ease, color .28s ease, border-color .28s ease;
}
@media (prefers-reduced-motion: reduce) {
  html.theme-anim, html.theme-anim body, html.theme-anim .app-shell,
  html.theme-anim .ios-card, html.theme-anim .topbar, html.theme-anim .sidebar { transition: none; }
}

/* Derived "Amount paid" display in the invoice editor (payments ledger owns the value) */
.inv-paid-box {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-sunken); padding: 8px 8px 8px 14px;
}
.inv-paid-box__v { font-weight: 700; font-size: 1rem; color: var(--text); font-variant-numeric: tabular-nums; }
.inv-paid-box__btn { font-size: .82rem; font-weight: 600; white-space: nowrap; }

/* ===================================================================================
 *  PAYMENTS SECTION
 * ================================================================================= */
/* =====================================================================================
 *  PAYMENTS — KPI MODULES   (v2.12.0)
 *  Standalone cards, not boxes with a number in them. Each carries its own trend behind
 *  the figure so the number arrives with context, and the third does actual work rather
 *  than reporting: it summarises collection state and offers the follow-up.
 * =================================================================================== */
.pv-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(212px, 1fr));
  gap: 14px; margin-bottom: 20px;
}

.pv-kpi {
  --kpi: var(--accent);
  position: relative; overflow: hidden;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; padding: 16px 18px 18px;
  box-shadow: 0 1px 2px rgba(10, 31, 68, .05);
  transition: transform .2s var(--ease), box-shadow .2s var(--ease), border-color .2s var(--ease);
}
.pv-kpi:hover {
  transform: translateY(-2px);
  border-color: var(--line-strong);
  box-shadow: 0 1px 2px rgba(10, 31, 68, .05), 0 10px 26px -10px rgba(10, 31, 68, .22);
}
.pv-kpi--good   { --kpi: var(--st-paid-bar); }
.pv-kpi--accent { --kpi: var(--accent); }
.pv-kpi--state  { --kpi: var(--st-due-bar); }
.pv-kpi--muted  { --kpi: var(--text-tertiary); }
.pv-kpi--state.is-alert { --kpi: var(--st-late-bar); border-color: rgba(239, 68, 68, .28); }

/* The sparkline sits behind the content, bottom-aligned, so the figure stays readable
   and the trend is peripheral — glanceable, never competing with the number. */
.pv-kpi__spark {
  position: absolute; left: 0; right: 0; bottom: 0; height: 46px;
  color: var(--kpi); pointer-events: none;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 22%, #000 100%);
  mask-image: linear-gradient(90deg, transparent, #000 22%, #000 100%);
}
.pv-spark { width: 100%; height: 100%; display: block; }

.pv-kpi__h { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; position: relative; }
.pv-kpi__ico {
  width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center;
  font-size: .8rem; color: var(--kpi); background: color-mix(in srgb, var(--kpi) 12%, transparent);
}
.pv-kpi__l { font-size: .76rem; font-weight: 550; color: var(--text-secondary); letter-spacing: .002em; }
.pv-kpi__v {
  position: relative; margin: 0; font-family: var(--font-ledger);
  font-size: 1.5rem; font-weight: 600; letter-spacing: -.022em;
  color: var(--text); line-height: 1.1; font-variant-numeric: tabular-nums;
}
.pv-kpi__s { position: relative; margin: 4px 0 0; font-size: .74rem; color: var(--text-tertiary); }
.pv-kpi__s--ok { color: var(--st-paid-ink); font-weight: 550; display: flex; align-items: center; gap: 5px; }

/* ---- collection state: three counts, one of which must be impossible to miss ---- */
.pv-state { position: relative; display: flex; gap: 16px; margin: 2px 0 12px; }
.pv-state__i { display: flex; flex-direction: column; line-height: 1.15; }
.pv-state__i b {
  font-family: var(--font-ledger); font-size: 1.32rem; font-weight: 600;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.pv-state__i em { font-style: normal; font-size: .68rem; color: var(--text-tertiary); margin-top: 2px; }
.pv-state__i--paid b { color: var(--st-paid-ink); }
.pv-state__i--due  b { color: var(--st-due-ink); }
.pv-state__i--late b { color: var(--st-late-ink); }
/* Overdue is the only count that is allowed to shout. */
.pv-kpi--state.is-alert .pv-state__i--late b { text-shadow: 0 0 16px rgba(239, 68, 68, .35); }

.pv-followup {
  position: relative; width: 100%; display: inline-flex; align-items: center;
  justify-content: center; gap: 7px; padding: 9px 14px; border: 0; cursor: pointer;
  border-radius: 10px; font-size: .8rem; font-weight: 600; color: #fff;
  background: linear-gradient(135deg, #EF4444, #DC2626);
  box-shadow: 0 1px 2px rgba(220, 38, 38, .3), 0 6px 16px -6px rgba(220, 38, 38, .5);
  transition: transform .2s var(--ease), box-shadow .2s var(--ease), filter .2s var(--ease);
}
.pv-followup:hover {
  transform: translateY(-1px); filter: saturate(1.08);
  box-shadow: 0 1px 2px rgba(220, 38, 38, .3), 0 10px 22px -6px rgba(220, 38, 38, .62);
}
.pv-followup:active { transform: translateY(0); }

/* ---- segmented control: one indicator that moves ---- */
.pv-seg {
  position: relative; display: flex; gap: 2px; padding: 3px; margin: 12px 0 4px;
  background: var(--surface-sunken); border: 1px solid var(--line);
  border-radius: 11px; overflow-x: auto; scrollbar-width: none;
}
.pv-seg::-webkit-scrollbar { display: none; }
.pv-seg__ind {
  position: absolute; top: 3px; bottom: 3px; left: 0; width: 0; opacity: 0;
  border-radius: 8px; background: var(--surface); pointer-events: none;
  box-shadow: 0 1px 2px rgba(10, 31, 68, .10), 0 0 0 1px rgba(10, 31, 68, .05);
  transition: transform .24s var(--ease), width .24s var(--ease), opacity .18s var(--ease);
}
.pv-chip {
  position: relative; z-index: 1; flex: 0 0 auto; border: 0; background: transparent;
  padding: 7px 13px; border-radius: 8px; cursor: pointer; white-space: nowrap;
  font-size: .81rem; font-weight: 550; color: var(--text-secondary);
  transition: color .18s var(--ease);
}
.pv-chip:hover { color: var(--text); }
.pv-chip.is-on { color: var(--text); font-weight: 650; }

/* ---- row action menu: glass, because it floats over content ---- */
.pvg-more {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 8px;
  border: 1px solid transparent; background: transparent; cursor: pointer;
  color: var(--text-tertiary); display: grid; place-items: center; font-size: .9rem;
  transition: background .16s var(--ease), color .16s var(--ease), border-color .16s var(--ease);
}
.pvg-more:hover { background: var(--surface-sunken); border-color: var(--line); color: var(--text); }
.pvg-more[aria-expanded="true"] { background: var(--accent-wash); color: var(--accent); border-color: transparent; }

.pvg-menu {
  position: fixed; z-index: 5200; width: 208px; padding: 5px;
  border-radius: 12px; border: 1px solid rgba(255, 255, 255, .55);
  background: rgba(255, 255, 255, .78);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
  box-shadow: 0 1px 2px rgba(10, 31, 68, .06), 0 14px 34px -8px rgba(10, 31, 68, .26);
  opacity: 0; transform: translateY(-5px) scale(.97); transform-origin: top right;
  pointer-events: none; transition: opacity .16s var(--ease), transform .16s var(--ease);
}
.pvg-menu.is-open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.pvg-menu button {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  border: 0; background: transparent; cursor: pointer; padding: 9px 11px;
  border-radius: 8px; font-size: .82rem; font-weight: 500; color: var(--text);
  transition: background .14s var(--ease);
}
.pvg-menu button > i { color: var(--text-tertiary); font-size: .88rem; }
.pvg-menu button:hover { background: rgba(10, 31, 68, .06); }
.pvg-menu button:hover > i { color: var(--accent); }

html[data-theme="dark"] .pvg-menu {
  background: rgba(28, 28, 34, .82); border-color: rgba(255, 255, 255, .10);
  box-shadow: 0 14px 34px -8px rgba(0, 0, 0, .6);
}
html[data-theme="dark"] .pvg-menu button:hover { background: rgba(255, 255, 255, .07); }
html[data-theme="dark"] .pv-kpi:hover { box-shadow: 0 10px 26px -10px rgba(0, 0, 0, .6); }
html[data-theme="dark"] .pv-seg__ind { box-shadow: 0 1px 2px rgba(0,0,0,.4), 0 0 0 1px rgba(255,255,255,.08); }

@media (prefers-reduced-motion: reduce) {
  .pv-kpi, .pv-seg__ind, .pv-chip, .pvg-menu, .pvg-more, .pv-followup { transition: none !important; }
  .pv-kpi:hover, .pv-followup:hover { transform: none; }
}
.pv-filters { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.pv-search { position: relative; flex: 1; min-width: 200px; }
.pv-search > i { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-tertiary); pointer-events: none; }
.pv-search input { padding-left: 36px; }
.pv-count { font-size: .8rem; font-weight: 600; color: var(--text-secondary); white-space: nowrap; margin-left: auto; }
.pv-method { display: inline-flex; align-items: center; gap: 6px; font-size: .84rem; color: var(--text-secondary); }
.pv-method > i { color: var(--blue); }
.pv-amt { font-weight: 700; color: var(--green); }
.pv-picker { max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 7px; }
.pv-pick { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; text-align: left;
  border: 1px solid var(--border); background: var(--surface); border-radius: var(--r-md); padding: 11px 14px; cursor: pointer; transition: all .14s; }
.pv-pick:hover { border-color: var(--green); background: var(--surface-sunken); }
.pv-pick__l strong, .pv-pick__r strong { display: block; font-size: .9rem; color: var(--text); }
.pv-pick__l span, .pv-pick__r span { display: block; font-size: .76rem; color: var(--text-tertiary); }
.pv-pick__r { text-align: right; }
.pv-pick__r strong { color: var(--green); }

/* ===================================================================================
 *  RENDER PERFORMANCE
 *  Modals and popovers are isolated so their internal updates (a typing amount field,
 *  a moving progress bar) can never force layout or paint work on the page behind them.
 * ================================================================================= */
.modal-content.hm-modal { contain: layout paint; }
.notif-panel, .vup { contain: layout paint; }

/* The payment progress bar animates width; promote just that element to its own layer
   so the browser moves it on the GPU instead of repainting the modal. */
.pay-bar__new { will-change: width; }

/* Honour reduced-motion everywhere (also disables the decorative infinite loops). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ===================================================================================
 *  INVOICE TABS — Active / Completed / Cancelled / All
 * ================================================================================= */
/* =====================================================================================
 *  INVOICE TABS  —  lifecycle rail + one focused figure   (v2.10.0)
 *  Replaces four competing summary cards. The rail is a quiet segmented control; the
 *  number that matters sits under it and changes with the tab, so there is one thing
 *  to read rather than four to compare.
 * =================================================================================== */
.inv-tabs { margin-bottom: 18px; }

.inv-tabs__rail {
  display: flex; align-items: center; gap: 2px;
  padding: 3px; border-radius: var(--r-lg);
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.inv-tabs__rail::-webkit-scrollbar { display: none; }

.inv-tab {
  /* Justified: each tab takes an equal share so the rail reads as one complete
     control rather than a cluster of pills with dead space to the right. flex-basis 0
     with grow makes the shares equal regardless of label length; on a narrow screen
     the min-width floor kicks in and the rail scrolls instead of crushing. */
  flex: 1 1 0; min-width: max-content; justify-content: center;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 13px; border: 0; border-radius: calc(var(--r-lg) - 4px);
  background: transparent; color: var(--text-secondary);
  font-size: .85rem; font-weight: 550; white-space: nowrap; cursor: pointer;
  transition: background .18s var(--ease), color .18s var(--ease);
}
.inv-tab > i { font-size: .9rem; opacity: .75; }
.inv-tab:hover { background: rgba(10, 31, 68, .045); color: var(--text); }
.inv-tab.is-empty { opacity: .5; }
.inv-tab.is-empty:hover { opacity: .85; }

/* The active pill lifts onto its own surface — the Mercury move: one raised element,
   no borders shouting, colour carried by the count rather than the whole chip. */
.inv-tab.is-active {
  background: var(--surface); color: var(--text); font-weight: 650;
  box-shadow: 0 1px 2px rgba(10, 31, 68, .10), 0 0 0 1px rgba(10, 31, 68, .05);
  opacity: 1;
}
.inv-tab.is-active > i { opacity: 1; color: var(--accent); }
/* Tabs borrow the same ramp, so the tab you are on and the rows beneath it agree. */
.inv-tab--due.is-active > i       { color: var(--st-due-ink); }
.inv-tab--overdue.is-active > i   { color: var(--st-late-ink); }
.inv-tab--paid.is-active > i      { color: var(--st-paid-ink); }
.inv-tab--cancelled.is-active > i { color: var(--st-void-ink); }

.inv-tab__n {
  min-width: 19px; padding: 0 5px; height: 19px; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(10, 31, 68, .07); color: var(--text-secondary);
  font-size: .7rem; font-weight: 650; font-variant-numeric: tabular-nums;
}
.inv-tab.is-active .inv-tab__n { background: var(--accent-wash); color: var(--accent); }
.inv-tab--due.is-active .inv-tab__n      { background: rgba(245, 158, 11, .14); color: var(--st-due-ink); }
.inv-tab--overdue.is-active .inv-tab__n { background: rgba(239, 68, 68, .13);  color: var(--st-late-ink); }
.inv-tab--paid.is-active .inv-tab__n    { background: rgba(16, 185, 129, .14); color: var(--st-paid-ink); }

/* ---- the one figure that matters ---- */
.inv-tabs__summary {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
  padding: 14px 2px 0;
}
.inv-sum__figure {
  font-family: var(--font-ledger); font-size: 1.55rem; font-weight: 600;
  letter-spacing: -.02em; color: var(--text); font-variant-numeric: tabular-nums;
}
.inv-sum__figure--overdue { color: var(--st-late-ink); }
.inv-sum__figure--paid    { color: var(--st-paid-ink); }
.inv-sum__caption { font-size: .82rem; color: var(--text-tertiary); }

.inv-sum__nudge {
  margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px; border: 1px solid transparent;
  background: rgba(239, 68, 68, .11); color: var(--st-late-ink);
  font-size: .78rem; font-weight: 600; cursor: pointer;
  font-variant-numeric: tabular-nums; transition: border-color .16s;
}
.inv-sum__nudge:hover { border-color: var(--st-late-bar); }

/* =====================================================================================
 *  STATUS TRACK  —  Pre-invoice -> Due -> Paid          (recoloured v2.11.1)
 *  One colour family per track, set once via --tone on the wrapper. Amber means payment
 *  is expected, red means it is late, green means it is settled. Those three are the
 *  conventions people already read without being taught, so the track uses them and
 *  nothing else — the accent blue was decorative here and carried no meaning.
 *
 *  Two tiers per status: --tone for the bar, --ink for the label. The vivid amber that
 *  works at 5px would fail contrast as small bold text, so the label uses a deeper shade
 *  of the same hue rather than a different colour.
 * =================================================================================== */
/* Named --trk-* on purpose. These were --tone / --ink, and --ink is ALREADY a global
   token (the app's primary text navy). Setting it here quietly redefined it for every
   descendant of a track — harmless today because a track holds only a bar and a label,
   but a trap for whoever next puts something inside one. */
.inv-trk--draft { --trk-tone: var(--st-draft-bar); --trk-ink: var(--st-draft-ink); }
.inv-trk--due   { --trk-tone: var(--st-due-bar);   --trk-ink: var(--st-due-ink);   }
.inv-trk--late  { --trk-tone: var(--st-late-bar);  --trk-ink: var(--st-late-ink);  }
.inv-trk--paid  { --trk-tone: var(--st-paid-bar);  --trk-ink: var(--st-paid-ink);  }
.inv-trk--void  { --trk-tone: var(--st-void-bar);  --trk-ink: var(--st-void-ink);  }

/* NOTE: this rule deliberately does NOT declare --tone / --ink.
   It used to, and because it sits AFTER the .inv-trk--* modifiers and has identical
   specificity (one class each), it won — pinning every track to the grey draft tone no
   matter which modifier the markup carried. The fallbacks now live inside each var()
   below instead, so there is no competing declaration and source order cannot matter. */
.inv-trk {
  display: inline-flex; flex-direction: column; gap: 6px; min-width: 96px;
}

.inv-trk__bar { position: relative; display: flex; gap: 4px; }

.inv-trk__seg {
  position: relative; width: 22px; height: 5px; border-radius: 3px;
  background: var(--st-rail); overflow: hidden;
  transition: background .22s var(--ease), opacity .22s var(--ease);
}

/* Stages already passed: the same hue, stepped back, so the eye reads one journey
   rather than a row of unrelated chips. */
.inv-trk__seg.is-done { background: var(--trk-tone, var(--st-draft-bar)); opacity: .32; }
.inv-trk__seg.is-now  { background: var(--trk-tone, var(--st-draft-bar)); }
.inv-trk__seg.is-void { background: var(--st-void-bar); opacity: .30; }

.inv-trk__fill { position: absolute; inset: 0 auto 0 0; background: var(--st-paid-bar); opacity: .5; }

/* Cancelled: literally struck out. A line straight through the track and the label,
   in grey, so a voided invoice reads as voided from across the room. */
.inv-trk--void .inv-trk__bar::after {
  content: ""; position: absolute; left: -3px; right: -3px; top: 50%;
  height: 1.5px; border-radius: 1px; background: var(--st-void-ink);
  transform: translateY(-50%);
}

.inv-trk__label {
  font-size: .78rem; font-weight: 600; color: var(--trk-ink, var(--st-draft-ink)); white-space: nowrap;
  letter-spacing: -.005em;
}

/* =====================================================================================
 *  STATUS MOTION  (v2.11.2)
 *  -------------------------------------------------------------------------------------
 *  Motion here is doing a job, not decorating. Three rules kept it from becoming noise:
 *
 *    1. Entrance animations run ONCE per render and are short (<420ms all in). The list
 *       re-renders on every filter change, so anything longer would feel like lag.
 *    2. Only ONE thing loops — the overdue glow. If everything pulses, nothing stands
 *       out, and the whole point is that late money should catch your eye first.
 *    3. Everything animates transform/opacity only, so it runs on the compositor and
 *       cannot cause layout thrash on a long invoice table.
 * =================================================================================== */

@keyframes trkSegIn {
  from { transform: scaleX(.15); opacity: 0; }
  to   { transform: scaleX(1);   opacity: 1; }
}

/* Segments sweep in left-to-right, so the eye travels the lifecycle in the direction it
   actually runs. 45ms apart — enough to read as a sequence, quick enough not to wait. */
.inv-trk__seg {
  transform-origin: left center;
  animation: trkSegIn .34s cubic-bezier(.22, 1, .36, 1) backwards;
}
.inv-trk__seg:nth-child(1) { animation-delay: 0ms; }
.inv-trk__seg:nth-child(2) { animation-delay: 45ms; }
.inv-trk__seg:nth-child(3) { animation-delay: 90ms; }

/* ---- OVERDUE: the one thing that keeps moving ---------------------------------- */
@keyframes lateGlow {
  0%, 100% { box-shadow: 0 0 5px 0 rgba(239, 68, 68, .55); }
  50%      { box-shadow: 0 0 11px 2px rgba(239, 68, 68, .85); }
}
.inv-trk--late .inv-trk__seg.is-now {
  animation: trkSegIn .34s cubic-bezier(.22, 1, .36, 1) backwards,
             lateGlow 2.1s ease-in-out 420ms infinite;
}
/* A quiet halo behind the label too, so the row reads as urgent at a glance even when
   the bar is off to the side of a wide table. */
.inv-trk--late .inv-trk__label { text-shadow: 0 0 14px rgba(239, 68, 68, .28); }

/* ---- PAID: a single sheen, then stillness -------------------------------------- */
@keyframes paidSheen {
  from { transform: translateX(-120%); }
  to   { transform: translateX(320%); }
}
.inv-trk--paid .inv-trk__bar { position: relative; overflow: hidden; border-radius: 3px; }
.inv-trk--paid .inv-trk__bar::after {
  content: ""; position: absolute; inset: 0; width: 34%; pointer-events: none;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .78), transparent);
  animation: paidSheen 1.05s cubic-bezier(.4, 0, .2, 1) 260ms 1 backwards;
}
.inv-trk--paid .inv-trk__seg.is-now { box-shadow: 0 0 8px 0 rgba(16, 185, 129, .5); }

/* ---- PARTIAL: the fill grows to its true share --------------------------------- */
@keyframes fillGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.inv-trk__fill {
  transform-origin: left center;
  animation: fillGrow .52s cubic-bezier(.22, 1, .36, 1) 150ms backwards;
}

/* ---- the overdue nudge chip breathes in time with the tracks -------------------- */
@keyframes nudgeBreathe {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, .32); }
  50%      { box-shadow: 0 0 0 5px rgba(239, 68, 68, 0); }
}
.inv-sum__nudge { animation: nudgeBreathe 2.4s ease-in-out infinite; }
.inv-sum__nudge > i { animation: none; }

/* ---- tab counts acknowledge a change ------------------------------------------- */
@keyframes countPop {
  0%   { transform: scale(.72); opacity: .35; }
  60%  { transform: scale(1.08); }
  100% { transform: scale(1); opacity: 1; }
}
.inv-tab__n { animation: countPop .3s cubic-bezier(.22, 1, .36, 1); }

/* ---- the headline figure settles in -------------------------------------------- */
@keyframes figureIn {
  from { transform: translateY(5px); opacity: 0; }
  to   { transform: translateY(0);   opacity: 1; }
}
.inv-sum__figure { animation: figureIn .3s cubic-bezier(.22, 1, .36, 1); }

/* Honour the OS setting completely — including the looping glow, which is exactly the
   kind of thing motion sensitivity exists to stop. Colour still carries every meaning. */
@media (prefers-reduced-motion: reduce) {
  .inv-trk__seg,
  .inv-trk--late .inv-trk__seg.is-now,
  .inv-trk--paid .inv-trk__bar::after,
  .inv-trk__fill,
  .inv-sum__nudge,
  .inv-tab__n,
  .inv-sum__figure { animation: none !important; }
  .inv-trk--late .inv-trk__label { text-shadow: none; }
}
.inv-trk--void .inv-trk__label {
  text-decoration: line-through;
  text-decoration-color: var(--st-void-ink);
  text-decoration-thickness: 1.5px;
}

@media (prefers-reduced-motion: reduce) {
  .inv-tab, .inv-trk__seg { transition: none; }
}

/* Dark mode: the two hover/count washes above are hardcoded ink-on-light, so they
   vanish against a dark surface. Restate them rather than leaving invisible chips. */
html[data-theme="dark"] .inv-tab:hover      { background: rgba(255, 255, 255, .06); }
html[data-theme="dark"] .inv-tab__n         { background: rgba(255, 255, 255, .09); }
html[data-theme="dark"] .inv-tab.is-active  { box-shadow: 0 1px 2px rgba(0, 0, 0, .40), 0 0 0 1px rgba(255, 255, 255, .08); }
/* The rail colour is now a token (--st-rail) with its own dark value, so the blanket
   override that used to live here would win over the .is-done / .is-now states and
   flatten every track to grey. Removed deliberately — .is-dead no longer exists either. */

/* ===================================================================================
 *  INVOICE FILTERS + PAYMENT TIMELINE
 * ================================================================================= */
.toolbar--filters { flex-wrap: wrap; gap: 10px; }
.inv-filter-bar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; flex: 1; }
/* The selects, the search field and the date trigger are one row of peers, so they
   share one visual language: hairline border, 9px vertical rhythm, accent ring on
   focus. Previously each came from a different place (Bootstrap form-select, the
   topbar search, the picker) and it showed. */
.inv-f {
  max-width: 172px; font-size: .85rem; font-weight: 500;
  padding: 9px 30px 9px 13px;
  border: 1px solid var(--line); border-radius: var(--r-md);
  background-color: var(--surface); color: var(--text);
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.inv-f:hover { border-color: var(--line-strong); }
.inv-f:focus {
  border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); outline: none;
}
.inv-f.is-set {
  border-color: var(--accent); color: var(--accent); font-weight: 600;
  background-color: var(--accent-wash);
}

.inv-filter-bar .topbar__search-input {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); padding: 9px 13px 9px 36px; font-size: .85rem;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.inv-filter-bar .topbar__search-input:hover { border-color: var(--line-strong); }
.inv-filter-bar .topbar__search-input:focus {
  border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); outline: none;
}

.inv-clear {
  font-size: .8rem; font-weight: 600; color: var(--critical); white-space: nowrap;
  padding: 9px 12px; border-radius: var(--r-md);
  transition: background .16s var(--ease);
}
.inv-clear:hover { background: var(--critical-wash); }

html[data-theme="dark"] .inv-f:hover,
html[data-theme="dark"] .inv-filter-bar .topbar__search-input:hover { border-color: rgba(255,255,255,.22); }

.pay-tl { display: flex; flex-direction: column; gap: 2px; }
.pay-tl__empty {
  display: flex; align-items: center; gap: 8px; padding: 14px; border: 1px dashed var(--border);
  border-radius: var(--r-md); color: var(--text-tertiary); font-size: .83rem; margin: 0;
}
.pay-tl__item { display: flex; align-items: flex-start; gap: 12px; padding: 10px 4px; position: relative; }
.pay-tl__item::before {
  content: ""; position: absolute; left: 17px; top: 34px; bottom: -2px; width: 2px; background: var(--separator);
}
.pay-tl__item:last-child::before { display: none; }
.pay-tl__dot {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  background: rgba(52,199,89,.12); color: var(--green); font-size: .9rem; z-index: 1;
}
.pay-tl__body { flex: 1; min-width: 0; }
.pay-tl__top { display: flex; align-items: baseline; gap: 10px; }
.pay-tl__top strong { font-size: 1rem; color: var(--green); font-variant-numeric: tabular-nums; }
.pay-tl__amt-left { margin-left: auto; font-size: .76rem; color: var(--text-tertiary); }
.pay-tl__meta { font-size: .77rem; color: var(--text-secondary); margin-top: 1px; }
.pay-tl__note { font-size: .76rem; color: var(--text-tertiary); font-style: italic; margin-top: 2px; }

/* Payment timeline — richer metadata rows */
.pay-tl__badge {
  font-size: .68rem; font-weight: 700; padding: 2px 8px; border-radius: var(--r-pill);
  background: var(--surface-sunken); color: var(--text-secondary); letter-spacing: .01em;
}
.pay-tl__meta > i { font-size: .72rem; margin-right: 3px; opacity: .8; }
.pay-tl__meta--dim { color: var(--text-tertiary); font-size: .73rem; margin-top: 1px; }
.pay-tl__sep { opacity: .5; margin: 0 2px; }

/* Payments table — "when" cell showing business date, weekday and exact entry time */
.pv-when { display: flex; flex-direction: column; line-height: 1.35; }
.pv-when strong { font-size: .86rem; color: var(--text); }
.pv-when > span { font-size: .72rem; color: var(--text-tertiary); }
.pv-when__rec { font-variant-numeric: tabular-nums; opacity: .85; }

/* ===================================================================================
 *  PAYMENT HISTORY — grouped by invoice, transactions nested beneath
 * ================================================================================= */
.pvg { padding: 0; overflow: hidden; }
/* =====================================================================================
 *  PAYMENT HISTORY  —  rebuilt v2.12.1
 *  The progress bar is gone. A 4px stripe under a header is a dashboard cliché, and it
 *  was duplicating information the outstanding figure already states precisely. In its
 *  place: a vertical rail down the transaction list, so a run of payments reads as a
 *  sequence of events against one invoice — which is what it is.
 * =================================================================================== */
.pvg {
  border-radius: 16px; overflow: hidden;
  transition: transform .2s var(--ease), box-shadow .2s var(--ease), border-color .2s var(--ease);
}
.pvg:hover {
  transform: translateY(-2px); border-color: var(--line-strong);
  box-shadow: 0 1px 2px rgba(10, 31, 68, .05), 0 14px 32px -12px rgba(10, 31, 68, .24);
}

.pvg-head {
  display: flex; align-items: center; gap: 14px;
  padding: 18px 20px 16px; flex-wrap: nowrap;
}
.pvg-head__l { min-width: 0; flex: 1; }
.pvg-head__r { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; margin-left: auto; }

/* The old 4px stripe. Kept as a no-op rather than deleted so any stale markup during a
   cached-CSS window degrades to nothing instead of a naked coloured bar. */
.pvg-bar, .pvg-bar span { display: none; }

/* ---- the rail ---- */
.pvg-txns { position: relative; padding: 2px 20px 6px 20px; }
.pvg-txns::before {
  content: ""; position: absolute; left: 34px; top: 8px; bottom: 22px; width: 1.5px;
  background: linear-gradient(180deg, var(--line), transparent);
  border-radius: 1px;
}

.pvg-txn {
  position: relative; display: flex; align-items: flex-start; gap: 14px;
  padding: 12px 0; border-bottom: 0;
}
.pvg-txn + .pvg-txn { border-top: 1px solid var(--separator); }

.pvg-txn__ico {
  position: relative; z-index: 1; flex: 0 0 auto;
  width: 29px; height: 29px; border-radius: 50%;
  display: grid; place-items: center; font-size: .78rem;
  background: var(--surface); color: var(--st-paid-ink);
  border: 1.5px solid var(--st-paid-bar);
  box-shadow: 0 0 0 4px var(--surface);
  transition: transform .2s var(--ease), box-shadow .2s var(--ease);
}
.pvg-txn:hover .pvg-txn__ico {
  transform: scale(1.08);
  box-shadow: 0 0 0 4px var(--surface), 0 0 12px rgba(16, 185, 129, .45);
}

.pvg-txn__main { flex: 1; min-width: 0; }
.pvg-txn__l1 { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.pvg-txn__l1 strong {
  font-family: var(--font-ledger); font-size: 1.02rem; font-weight: 600;
  letter-spacing: -.015em; color: var(--text); font-variant-numeric: tabular-nums;
}
.pvg-txn__method {
  font-size: .66rem; font-weight: 600; padding: 2px 8px; border-radius: var(--r-pill);
  background: var(--surface-sunken); color: var(--text-secondary); letter-spacing: .01em;
}
.pvg-txn__ref { font-size: .71rem; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.pvg-txn__l2 { font-size: .73rem; color: var(--text-tertiary); margin-top: 3px; }
.pvg-txn__note { font-size: .75rem; color: var(--text-tertiary); font-style: italic; margin-top: 3px; }
.pvg-dot { opacity: .45; margin: 0 4px; }

.pvg-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: 13px 20px; background: var(--surface-sunken);
  border-top: 1px solid var(--line);
  font-size: .78rem; color: var(--text-tertiary);
}
.pvg-foot strong {
  font-family: var(--font-ledger); font-size: 1rem; font-weight: 600;
  color: var(--text); font-variant-numeric: tabular-nums; letter-spacing: -.015em;
}

.pvg-num {
  display: block; font-size: 1.02rem; font-weight: 650; color: var(--text);
  text-decoration: none; letter-spacing: -.015em;
  transition: color .16s var(--ease);
}
.pvg-num:hover { color: var(--accent); }
.pvg-cust { display: block; font-size: .8rem; color: var(--text-secondary); margin-top: 2px; }
.pvg-owed { font-size: .76rem; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }

html[data-theme="dark"] .pvg:hover { box-shadow: 0 14px 32px -12px rgba(0, 0, 0, .62); }

@media (prefers-reduced-motion: reduce) {
  .pvg, .pvg-txn__ico, .pvg-num { transition: none !important; }
  .pvg:hover { transform: none; }
  .pvg-txn:hover .pvg-txn__ico { transform: none; }
}

/* ===================================================================================
 *  PHASE 4 — deep search, date range, batch selection
 * ================================================================================= */
.toolbar--filters .topbar__search.is-busy .topbar__search-icon { animation: searchPulse .9s ease-in-out infinite; color: var(--blue); }
@keyframes searchPulse { 0%,100% { opacity: 1; } 50% { opacity: .3; } }

@keyframes fadeDown { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* Selection bar */
.inv-selbar { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  padding: 11px 16px; margin-top: 10px; border-radius: var(--r-md);
  background: var(--text); color: var(--surface); box-shadow: var(--shadow-md);
  animation: fadeDown .2s var(--ease); }
.inv-selbar__n { font-size: .86rem; }
.inv-selbar__n strong { font-size: 1rem; }
.inv-selbar__actions { display: flex; gap: 8px; flex-wrap: wrap; }
.inv-selbar .btn-ios-ghost { color: rgba(255,255,255,.7); }
.inv-selbar .btn-ios-ghost:hover { color: #fff; background: rgba(255,255,255,.12); }

/* Checkbox column */
.inv-ck { width: 38px; padding-right: 0 !important; }
.ck { position: relative; display: inline-flex; cursor: pointer; }
.ck input { position: absolute; opacity: 0; width: 18px; height: 18px; margin: 0; cursor: pointer; }
.ck span { width: 18px; height: 18px; border-radius: var(--r-xs); border: 1.5px solid var(--border);
  background: var(--surface); transition: all .14s; display: block; position: relative; }
.ck input:checked + span { background: var(--blue); border-color: var(--blue); }
.ck input:checked + span::after { content: ""; position: absolute; left: 5px; top: 1.5px;
  width: 5px; height: 10px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg); }
.ck input:indeterminate + span { background: var(--blue); border-color: var(--blue); }
.ck input:indeterminate + span::after { content: ""; position: absolute; left: 3.5px; top: 7px;
  width: 9px; height: 2px; background: #fff; border-radius: var(--r-xs); }
.inv-row.is-sel { background: var(--blue-soft) !important; }

/* Batch progress */
.inv-batch { margin-top: 8px; padding: 10px 14px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--shadow-sm); }
.inv-batch__bar { height: 6px; border-radius: var(--r-xs); background: var(--surface-sunken); overflow: hidden; }
.inv-batch__bar span { display: block; height: 100%; width: 0; border-radius: var(--r-xs);
  background: linear-gradient(90deg, var(--blue), var(--teal)); transition: width .25s ease; }
.inv-batch__t { font-size: .78rem; color: var(--text-secondary); margin-top: 6px; font-variant-numeric: tabular-nums; }

/* ===================================================================================
 *  ENTERPRISE TOOLBAR — the date range now lives in js/core/daterange.js (bespoke
 *  picker with its own scoped styles). The old preset buttons, dual native date
 *  inputs and period dropdown have been purged entirely.
 * ================================================================================= */
/* Selection command bar — a floating pill, not a slab */
.selbar { display: flex; align-items: center; gap: 14px; margin-top: 10px; padding: 10px 12px 10px 10px;
  border-radius: var(--r-xl); background: var(--surface); border: 1px solid var(--border);
  box-shadow: var(--lift-3);
  animation: selIn .22s var(--ease); flex-wrap: wrap; }
@keyframes selIn { from { opacity: 0; transform: translateY(-6px) scale(.99); } to { opacity: 1; transform: none; } }
.selbar__badge { flex: 0 0 auto; min-width: 40px; height: 40px; padding: 0 10px; border-radius: var(--r-xl);
  display: grid; place-items: center; font-size: 1.05rem; font-weight: 800; color: #fff;
  background: linear-gradient(135deg, var(--blue), var(--accent-press)); box-shadow: var(--lift-2);
  font-variant-numeric: tabular-nums; }
.selbar__meta { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.selbar__t { font-size: .82rem; font-weight: 700; color: var(--text); }
.selbar__s { font-size: .76rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.selbar__acts { display: flex; align-items: center; gap: 6px; margin-left: auto; flex-wrap: wrap; }
.selbar__btn { display: inline-flex; align-items: center; gap: 7px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text-secondary); font-size: .82rem; font-weight: 650;
  padding: 8px 13px; border-radius: var(--r-lg); cursor: pointer; transition: all .14s var(--ease); white-space: nowrap; }
.selbar__btn:hover { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); transform: translateY(-1px); }
.selbar__btn--go { background: linear-gradient(135deg, var(--blue), var(--accent-press)); border-color: transparent; color: #fff;
  box-shadow: var(--lift-2); }
.selbar__btn--go:hover { color: #fff; filter: brightness(1.07); border-color: transparent; }
.selbar__x { border: 0; background: transparent; color: var(--text-tertiary); width: 32px; height: 32px;
  border-radius: var(--r-md); cursor: pointer; display: grid; place-items: center; font-size: .78rem; }
.selbar__x:hover { background: var(--surface-sunken); color: var(--text); }
@media (max-width: 640px) { .selbar__btn span { display: none; } .selbar__btn { padding: 8px 11px; } }

/* ===================================================================================
 *  PREDICTIVE DATA ENTRY — the quiet hint shown when the app fills fields from history
 * ================================================================================= */
.pred-hint {
  grid-column: 1 / -1;
  display: flex; align-items: flex-start; gap: 11px;
  padding: 11px 12px 11px 14px; margin-top: 4px;
  background: linear-gradient(135deg, var(--blue-soft), rgba(88,86,214,.08));
  border: 1px solid rgba(37,99,235,.20); border-radius: var(--r-md);
  animation: predIn .26s cubic-bezier(.25,1,.5,1);
}
@keyframes predIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: none; } }
.pred-hint > i { color: var(--blue); font-size: .95rem; margin-top: 1px; flex: 0 0 auto; }
.pred-hint strong { display: block; font-size: .82rem; color: var(--text); font-weight: 650; }
.pred-hint span { display: block; font-size: .76rem; color: var(--text-secondary); line-height: 1.45; margin-top: 1px; }
.pred-x { margin-left: auto; border: 0; background: transparent; color: var(--text-tertiary);
  width: 24px; height: 24px; border-radius: var(--r-sm); cursor: pointer; flex: 0 0 auto; display: grid; place-items: center; }
.pred-x:hover { background: rgba(120,130,150,.14); color: var(--text); }

/* ===================================================================================
 *  PAYMENT EDITING — an explicit, always-visible action.
 *  This was previously hidden until hover, which made it undiscoverable: a correction
 *  tool you can't find is a correction tool that doesn't exist. It is now a labelled
 *  button that states plainly what it does.
 * ================================================================================= */
.pay-edit {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  border: 1px solid var(--border); background: var(--surface); color: var(--text-secondary);
  font-size: .74rem; font-weight: 650; padding: 5px 11px; border-radius: var(--r-md); cursor: pointer;
  white-space: nowrap; transition: all .15s var(--ease);
}
.pay-edit:hover { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); transform: translateY(-1px); }
.pay-edit:active { transform: translateY(0); }
.pay-edit > i { font-size: .72rem; }
@media (max-width: 560px) { .pay-edit span { display: none; } .pay-edit { padding: 6px 9px; } }

/* ===================================================================================
 *  NATURAL LANGUAGE SEARCH — the chips showing what the app understood
 * ================================================================================= */
.nl-chips { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; padding: 9px 12px; margin-top: 8px;
  background: linear-gradient(135deg, var(--blue-soft), rgba(88,86,214,.07));
  border: 1px solid rgba(37,99,235,.18); border-radius: var(--r-md);
  animation: nlIn .24s cubic-bezier(.25,1,.5,1); }
@keyframes nlIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.nl-chips__l { display: inline-flex; align-items: center; gap: 6px; font-size: .72rem; font-weight: 700;
  color: var(--blue); letter-spacing: .02em; text-transform: uppercase; }
.nl-chip { display: inline-flex; align-items: center; gap: 6px; font-size: .76rem; font-weight: 600;
  padding: 4px 11px; border-radius: var(--r-pill); background: var(--surface); color: var(--text);
  border: 1px solid rgba(37,99,235,.22); box-shadow: var(--lift-1); }
.nl-chip > i { font-size: .72rem; color: var(--blue); }
.nl-chips__x { margin-left: auto; border: 0; background: transparent; color: var(--text-tertiary);
  width: 24px; height: 24px; border-radius: var(--r-sm); cursor: pointer; display: grid; place-items: center; }
.nl-chips__x:hover { background: rgba(120,130,150,.14); color: var(--text); }

/* Pre-invoice expiry alerts */
.stale-alerts { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.stale { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid rgba(217,119,6,.28); box-shadow: var(--shadow-sm);
  animation: nlIn .26s cubic-bezier(.25,1,.5,1); }
.stale__dot { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  background: rgba(249,115,22,.14); color: var(--attention); font-size: .95rem; position: relative; }
.stale__dot::after { content: ""; position: absolute; inset: -3px; border-radius: 50%;
  border: 2px solid rgba(249,115,22,.35); animation: staleRing 2s ease-in-out infinite; }
@keyframes staleRing { 0%,100% { opacity: .8; transform: scale(1); } 50% { opacity: 0; transform: scale(1.25); } }
.stale__b { flex: 1; min-width: 0; }
.stale__t { font-size: .86rem; font-weight: 650; color: var(--text); }
.stale__s { font-size: .77rem; color: var(--text-secondary); }
.stale__go { border: 0; background: var(--attention); color: #fff; font-size: .78rem; font-weight: 650;
  padding: 7px 14px; border-radius: var(--r-md); cursor: pointer; white-space: nowrap;
  box-shadow: var(--lift-2); transition: filter .14s; }
.stale__go:hover { filter: brightness(1.07); }
.stale__x { border: 0; background: transparent; color: var(--text-tertiary); width: 28px; height: 28px;
  border-radius: var(--r-md); cursor: pointer; display: grid; place-items: center; }
.stale__x:hover { background: var(--surface-sunken); color: var(--text); }

/* ===================================================================================
 *  MULTI-CURRENCY — dashboard breakdown (presentation only, never converted)
 * ================================================================================= */
.cur-card { margin-bottom: 16px; }
.cur-card__h { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 14px; flex-wrap: wrap; }
.cur-card__h > span { font-size: .74rem; color: var(--text-tertiary); }
.cur-grid { display: flex; flex-direction: column; gap: 12px; }
.cur-row { display: flex; align-items: center; gap: 14px; }
.cur-badge { flex: 0 0 auto; width: 38px; height: 38px; border-radius: var(--r-lg); display: grid; place-items: center;
  background: var(--blue-soft); color: var(--blue); font-size: 1.05rem; font-weight: 800; }
.cur-b { flex: 1; min-width: 0; }
.cur-t { display: flex; align-items: baseline; gap: 8px; }
.cur-t strong { font-size: .88rem; color: var(--text); letter-spacing: .02em; }
.cur-t span { font-size: .74rem; color: var(--text-tertiary); }
.cur-bar { height: 5px; border-radius: var(--r-xs); background: var(--surface-sunken); overflow: hidden; margin-top: 5px; }
.cur-bar span { display: block; height: 100%; border-radius: var(--r-xs); background: linear-gradient(90deg, var(--green), var(--blue)); }
.cur-v { text-align: right; flex: 0 0 auto; }
.cur-v strong { display: block; font-size: .92rem; color: var(--text); font-variant-numeric: tabular-nums; }
.cur-v span { display: block; font-size: .73rem; color: var(--text-tertiary); }
.hm-hint { font-size: .72rem; color: var(--text-tertiary); margin: 5px 0 0; line-height: 1.4; }

/* ===================================================================================
 *  SEND TO ACCOUNTANT — bundle modal
 * ================================================================================= */
.acct-hero { display: flex; align-items: center; gap: 14px; padding: 14px 16px;
  background: var(--surface-sunken); border-radius: var(--r-lg); }
.acct-hero__n { flex: 0 0 auto; min-width: 46px; height: 46px; padding: 0 10px; border-radius: var(--r-xl);
  display: grid; place-items: center; font-size: 1.3rem; font-weight: 800; color: #fff;
  background: linear-gradient(135deg, var(--blue), var(--accent-press)); box-shadow: var(--lift-2);
  font-variant-numeric: tabular-nums; }
.acct-hero__t { font-size: .88rem; font-weight: 700; color: var(--text); }
.acct-hero__s { font-size: .78rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.acct-hero__zip { margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
  font-size: .72rem; font-weight: 700; color: var(--text-secondary);
  background: var(--surface); border: 1px solid var(--border); padding: 5px 11px; border-radius: var(--r-pill); white-space: nowrap; }
.acct-hero__zip > i { color: var(--blue); }
.acct-save { display: flex; align-items: center; gap: 9px; margin-top: 14px; cursor: pointer;
  font-size: .8rem; color: var(--text-secondary); }
.acct-save input { width: 16px; height: 16px; accent-color: var(--blue); cursor: pointer; }
@keyframes acctPulse { 0%,100% { opacity: .35; } 50% { opacity: 1; } }

/* Accountant modal — three routes, plainly compared */
.acct-routes { display: grid; gap: 7px; margin-top: 16px; }
.acct-route { display: flex; gap: 11px; align-items: flex-start; padding: 10px 12px;
  background: var(--surface-sunken); border-radius: var(--r-md); }
.acct-route > i { flex: 0 0 auto; width: 28px; height: 28px; border-radius: var(--r-md); display: grid; place-items: center;
  background: var(--surface); color: var(--blue); font-size: .82rem; box-shadow: var(--shadow-sm); }
.acct-route strong { display: block; font-size: .8rem; color: var(--text); font-weight: 700; }
.acct-route span { display: block; font-size: .74rem; color: var(--text-secondary); line-height: 1.45; margin-top: 1px; }
.acct-route.is-off { opacity: .5; }
.acct-route.is-off > i { color: var(--text-tertiary); }

.acct-more { margin-top: 14px; border-top: 1px solid var(--separator); padding-top: 12px; }
.acct-more > summary { display: flex; align-items: center; gap: 8px; cursor: pointer; list-style: none;
  font-size: .8rem; font-weight: 650; color: var(--text-secondary); padding: 5px 0;
  border-radius: var(--r-md); transition: color .15s var(--ease); }
.acct-more > summary::-webkit-details-marker { display: none; }
.acct-more > summary:hover, .acct-more[open] > summary { color: var(--blue); }
.acct-more > summary > i { color: var(--text-tertiary); }
.acct-more > summary::after { content: "\F282"; font-family: "bootstrap-icons"; margin-left: auto;
  font-size: .7rem; color: var(--text-tertiary); transition: transform .18s var(--ease); }
.acct-more[open] > summary::after { transform: rotate(180deg); }
.acct-more__b { padding-top: 10px; animation: predIn .2s cubic-bezier(.25,1,.5,1); }
.acct-cap { display: flex; gap: 7px; align-items: flex-start; font-size: .73rem; color: var(--text-tertiary);
  margin: 12px 0 0; line-height: 1.45; }
.acct-cap b { color: var(--text-secondary); }
.acct-primary { box-shadow: var(--lift-3); }
.w-100 { width: 100%; }

/* ===================================================================================
 *  SPOTLIGHT — the Payments search surfaces invoices you can act on
 * ================================================================================= */
.pv-search { position: relative; }
.spot { position: absolute; top: calc(100% + 7px); left: 0; right: 0; z-index: 200;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-xl); padding: 5px;
  box-shadow: var(--lift-3); max-height: 340px; overflow-y: auto;
  animation: acIn .16s cubic-bezier(.25,1,.5,1); contain: layout paint; }
.spot__h { font-size: .68rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-tertiary); padding: 8px 10px 6px; }
.spot__i { display: flex; align-items: center; gap: 11px; width: 100%; text-align: left; border: 0;
  background: transparent; padding: 9px 10px; border-radius: var(--r-lg); cursor: pointer; transition: background .12s; }
.spot__i:hover { background: var(--blue-soft); }
.spot__ic { flex: 0 0 auto; width: 32px; height: 32px; border-radius: var(--r-md); display: grid; place-items: center;
  background: var(--surface-sunken); color: var(--blue); font-size: .85rem; }
.spot__ic.is-late { background: rgba(220,38,38,.12); color: var(--red); }
.spot__b { flex: 1; min-width: 0; }
.spot__n { display: block; font-size: .85rem; font-weight: 700; color: var(--text); }
.spot__c { display: block; font-size: .74rem; color: var(--text-secondary); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.spot__r { text-align: right; flex: 0 0 auto; }
.spot__v { display: block; font-size: .86rem; font-weight: 700; color: var(--attention); font-variant-numeric: tabular-nums; }
.spot__s { display: block; font-size: .68rem; color: var(--text-tertiary); }
.spot__s.is-late { color: var(--red); font-weight: 700; }

/* ===================================================================================
 *  AVATARS — a face for every customer, no setup required
 * ================================================================================= */
.ava { display: inline-grid; place-items: center; flex: 0 0 auto; border-radius: 50%;
  font-weight: 700; letter-spacing: .01em; line-height: 1; overflow: hidden;
  box-shadow: var(--lift-1); user-select: none; }
.ava--img { background: var(--surface-sunken); }
.ava--img img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Customer form — avatar row */
.cf-ava-row { display: flex; align-items: center; gap: 16px; }
.cf-ava-b { flex: 1; min-width: 0; }

/* ===================================================================================
 *  RECORD A PAYMENT — inline, the first thing on the page
 * ================================================================================= */
/* =====================================================================================
 *  RECORD A PAYMENT  —  rebuilt v2.12.1
 *  The amount is the only field that matters; everything else is metadata about it. So
 *  the amount gets ledger type at display size and its own lit panel, and the rest sits
 *  quietly beneath. Treating all five inputs as equal peers is what made this read as a
 *  form rather than an instrument.
 * =================================================================================== */
.rec {
  /* Reverted from the dark experiment. Two things were wrong with it: it read as a
     separate application rather than part of this one, and the gradient background
     failed to apply in Chrome while the light `color` on the same rule did — leaving
     near-white text on a white card. Premium here comes from restraint and depth, not
     from inverting the palette. */
  position: relative; margin-bottom: 22px; border-radius: 20px; overflow: hidden;
  padding: 26px 28px 24px;
  background:
    linear-gradient(180deg, #FFFFFF 0%, #FCFCFB 100%);
  border: 1px solid var(--line);
  box-shadow:
    0 1px 2px rgba(10, 31, 68, .04),
    0 18px 40px -20px rgba(10, 31, 68, .20),
    inset 0 1px 0 #FFFFFF;
  animation: recPanelIn .5s cubic-bezier(.22, 1, .36, 1);
}
@keyframes recPanelIn {
  from { opacity: 0; transform: translateY(10px) scale(.994); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* Fields cascade in behind the panel so it assembles rather than appears. */
.rec .hm-field, .rec .rec__go {
  animation: recFieldIn .42s cubic-bezier(.22, 1, .36, 1) backwards;
}
@keyframes recFieldIn {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: translateY(0); }
}
.rec .rec__grid    .hm-field:nth-child(1) { animation-delay: 60ms; }
.rec .rec__grid    .hm-field:nth-child(2) { animation-delay: 100ms; }
.rec .rec__grid--2 .hm-field:nth-child(1) { animation-delay: 140ms; }
.rec .rec__grid--2 .hm-field:nth-child(2) { animation-delay: 175ms; }
.rec .rec__grid--2 .hm-field:nth-child(3) { animation-delay: 210ms; }
.rec .rec__grid--2 .hm-field:nth-child(4) { animation-delay: 245ms; }
.rec .rec__go { animation-delay: 290ms; }

/* With the receipt toggle gone the footer holds one thing, so let it hold it properly. */
.rec .rec__foot { justify-content: flex-end; }

@media (prefers-reduced-motion: reduce) {
  .rec, .rec .hm-field, .rec .rec__go { animation: none !important; }
}
/* A hairline of colour along the top edge — the Stripe tell. Enough to say "this panel
   does something" without a coloured header bar shouting it. */
.rec::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, var(--accent), var(--st-paid-bar) 65%, transparent);
  opacity: .85;
}

.rec__h {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 18px; flex-wrap: wrap;
}
.rec__hint { font-size: .76rem; color: var(--text-tertiary); }

.rec__grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 14px; }
.rec__grid--2 { grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); margin-top: 14px; }
.rec__find { min-width: 0; }
.rec__pick { min-width: 0; }

.rec__search { position: relative; }
.rec__search > i {
  position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
  color: var(--text-tertiary); pointer-events: none; font-size: .88rem;
  transition: color .2s var(--ease);
}
.rec__search:focus-within > i { color: var(--accent); }
.rec__search input { padding-left: 37px; }

/* ---- the hero: amount ---- */
.rec__amt {
  position: relative; display: flex; align-items: center; gap: 10px;
  padding: 14px 18px; border-radius: 14px;
  background: linear-gradient(180deg, var(--surface), var(--surface-sunken));
  border: 1.5px solid var(--line);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease), background .2s var(--ease);
}
.rec__amt:focus-within {
  border-color: var(--st-paid-bar); background: var(--surface);
  box-shadow: 0 0 0 4px rgba(16, 185, 129, .13), 0 8px 24px -12px rgba(16, 185, 129, .5);
}
.rec__amt > span {
  font-family: var(--font-ledger); font-size: 1.45rem; font-weight: 500;
  color: var(--text-tertiary); line-height: 1;
  transition: color .2s var(--ease);
}
.rec__amt:focus-within > span { color: var(--st-paid-ink); }
.rec__amt input {
  flex: 1; min-width: 0; border: 0; background: transparent; outline: none; padding: 0;
  font-family: var(--font-ledger); font-size: 1.7rem; font-weight: 600;
  letter-spacing: -.025em; color: var(--text); font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.rec__amt input::placeholder { color: var(--text-tertiary); font-weight: 500; opacity: .55; }
/* Spinners are hostile on a currency field — you do not nudge money by 1. */
.rec__amt input::-webkit-outer-spin-button,
.rec__amt input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.rec__amt input[type=number] { -moz-appearance: textfield; }

.rec__chips { display: flex; gap: 7px; flex-wrap: wrap; margin-top: 10px; }
.rec__chips button {
  border: 1px solid var(--line); background: var(--surface); cursor: pointer;
  padding: 6px 12px; border-radius: var(--r-pill);
  font-size: .74rem; font-weight: 600; color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  transition: transform .18s var(--ease), border-color .18s var(--ease),
              color .18s var(--ease), background .18s var(--ease);
}
.rec__chips button:hover {
  transform: translateY(-1px);
  border-color: var(--st-paid-bar); color: var(--st-paid-ink);
  background: rgba(16, 185, 129, .07);
}

.rec__sel {
  margin-top: 14px; padding: 13px 16px; border-radius: 12px;
  background: var(--accent-wash); border: 1px solid transparent;
  font-size: .84rem; color: var(--text);
  animation: recSelIn .28s cubic-bezier(.22, 1, .36, 1);
}
@keyframes recSelIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The chosen-invoice panel. Its sub-classes lost their styling when this block was
   rebuilt in v2.12.1 — restored here rather than left to inherit browser defaults,
   which is why the panel briefly rendered as unstyled divs. */
.rec__sel {
  display: flex; align-items: center; gap: 14px;
}
.rec__sel-b { flex: 1; min-width: 0; }
.rec__sel-t {
  font-size: .92rem; font-weight: 650; color: var(--text); letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rec__sel-s { font-size: .76rem; color: var(--text-secondary); margin-top: 2px; }
.rec__sel-r { text-align: right; flex: 0 0 auto; }
.rec__sel-v {
  font-family: var(--font-ledger); font-size: 1.12rem; font-weight: 600;
  letter-spacing: -.02em; color: var(--st-due-ink); font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.rec__sel-l {
  font-size: .66rem; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-tertiary); margin-top: 1px;
}
.rec__sel-x {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 9px; cursor: pointer;
  border: 1px solid transparent; background: transparent; color: var(--text-tertiary);
  display: grid; place-items: center; font-size: .78rem;
  transition: background .16s var(--ease), color .16s var(--ease), border-color .16s var(--ease);
}
.rec__sel-x:hover {
  background: var(--critical-wash); color: var(--critical); border-color: transparent;
}

.rec__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; margin-top: 20px; padding-top: 18px;
  border-top: 1px solid var(--separator); flex-wrap: wrap;
}
/* ---- fields inside the record card ----------------------------------------------
   Every input here shares one treatment so the block reads as a single instrument
   rather than four controls that happened to land next to each other. */
.rec .form-control,
.rec .form-select {
  padding: 11px 14px; border-radius: 11px;
  border: 1px solid var(--line); background: var(--surface);
  font-size: .88rem; font-weight: 500; color: var(--text);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease), background .2s var(--ease);
}
.rec .form-control:hover,
.rec .form-select:hover { border-color: var(--line-strong); }
.rec .form-control:focus,
.rec .form-select:focus {
  border-color: var(--accent); background: var(--surface);
  box-shadow: 0 0 0 4px var(--accent-wash); outline: none;
}
.rec .hm-label {
  font-size: .68rem; font-weight: 650; letter-spacing: .055em;
  text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 7px; display: block;
}
.rec .hm-opt { font-weight: 500; letter-spacing: 0; text-transform: none; opacity: .7; }
.rec .hm-field { display: flex; flex-direction: column; min-width: 0; }

/* ---- receipt: a statement with a veto, not a question --------------------------- */
.rcpt {
  display: flex; align-items: center; gap: 13px; min-width: 0; flex: 1;
  padding: 11px 14px; border-radius: 12px;
  background: rgba(16, 185, 129, .07);
  border: 1px solid rgba(16, 185, 129, .22);
  transition: background .22s var(--ease), border-color .22s var(--ease);
}
.rcpt.is-warn {
  background: rgba(245, 158, 11, .09);
  border-color: rgba(245, 158, 11, .3);
}

.rcpt__ico {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: 9px;
  display: grid; place-items: center; font-size: .92rem;
  background: var(--surface); color: var(--st-paid-ink);
  box-shadow: 0 1px 2px rgba(10, 31, 68, .07);
  transition: color .22s var(--ease), transform .22s var(--ease);
}
.rcpt.is-warn .rcpt__ico { color: var(--st-due-ink); }
/* A single soft pulse when the recipient resolves — acknowledgement, not decoration. */
.rcpt:not(.is-warn) .rcpt__ico { animation: rcptPing 2.6s ease-in-out infinite; }
/* Written in order for readability. The ring only appears for ~3% of the cycle, so it
   reads as an occasional heartbeat rather than something demanding attention. */
@keyframes rcptPing {
  0%, 88%  { box-shadow: 0 1px 2px rgba(10, 31, 68, .07); }
  91%      { box-shadow: 0 1px 2px rgba(10, 31, 68, .07), 0 0 0 0 rgba(16, 185, 129, .45); }
  94%      { box-shadow: 0 1px 2px rgba(10, 31, 68, .07), 0 0 0 5px rgba(16, 185, 129, 0); }
  100%     { box-shadow: 0 1px 2px rgba(10, 31, 68, .07); }
}

.rcpt__txt { min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.rcpt__txt b {
  font-size: .82rem; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rcpt__txt em { font-style: normal; font-size: .71rem; color: var(--text-tertiary); margin-top: 1px; }

/* ---- the switch ---- */
.rcpt__sw {
  flex: 0 0 auto; margin-left: auto; position: relative; cursor: pointer;
  width: 42px; height: 24px; border-radius: 999px; border: 0; padding: 0;
  background: var(--line-strong);
  transition: background .24s var(--ease), box-shadow .24s var(--ease), opacity .2s var(--ease);
}
.rcpt__sw > span {
  position: absolute; top: 3px; left: 3px; width: 18px; height: 18px;
  border-radius: 50%; background: #fff;
  box-shadow: 0 1px 3px rgba(10, 31, 68, .3);
  transition: transform .24s cubic-bezier(.22, 1, .36, 1);
}
.rcpt__sw.is-on {
  background: linear-gradient(135deg, #10B981, #059669);
  box-shadow: 0 0 0 3px rgba(16, 185, 129, .16);
}
.rcpt__sw.is-on > span { transform: translateX(18px); }
.rcpt__sw:disabled { opacity: .4; cursor: not-allowed; }

html[data-theme="dark"] .rcpt__ico { background: rgba(255, 255, 255, .07); }

@media (prefers-reduced-motion: reduce) {
  .rcpt, .rcpt__ico, .rcpt__sw, .rcpt__sw > span,
  .rec .form-control, .rec .form-select { transition: none !important; }
  .rcpt:not(.is-warn) .rcpt__ico { animation: none; }
}

/* The commit button. Green because it completes a transaction, not because green is
   nice — the primary blue is for navigation, this is money landing. */
.rec__go {
  padding: 12px 26px; font-size: .89rem; font-weight: 650; border: 0; cursor: pointer;
  border-radius: 12px; color: #fff;
  background: linear-gradient(135deg, #10B981, #059669);
  box-shadow: 0 1px 2px rgba(5, 150, 105, .28), 0 8px 20px -8px rgba(5, 150, 105, .55);
  transition: transform .2s var(--ease), box-shadow .2s var(--ease), filter .2s var(--ease);
}
.rec__go:hover:not(:disabled) {
  transform: translateY(-1px); filter: saturate(1.08);
  box-shadow: 0 1px 2px rgba(5, 150, 105, .28), 0 14px 28px -8px rgba(5, 150, 105, .68);
}
.rec__go:active:not(:disabled) { transform: translateY(0) scale(.985); }
.rec__go:disabled {
  opacity: .4; cursor: not-allowed; box-shadow: none; filter: grayscale(.35);
}

@media (max-width: 760px) { .rec__grid { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) {
  .rec__amt, .rec__chips button, .rec__go, .rec__search > i { transition: none !important; }
  .rec__sel { animation: none; }
  .rec__chips button:hover, .rec__go:hover:not(:disabled) { transform: none; }
}

/* History filter chips */
/* The .pv-chips / .pv-chip block that lived here has been removed. It was defined AFTER
   the segmented control at ~line 1002, so at equal specificity it won and repainted the
   active segment as a solid blue pill — the indicator was sliding underneath it,
   invisible. .pv-chips is no longer emitted at all; .pv-chip is styled with the control.

/* ===================================================================================
 *  ACCOUNTANT EXPORTS — how the PDFs travel
 * ================================================================================= */
.exp-modes, .acct-modes { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media (max-width: 560px) { .exp-modes, .acct-modes { grid-template-columns: 1fr; } }

.exp-mode { display: flex; flex-direction: column; gap: 3px; text-align: left; cursor: pointer;
  border: 2px solid var(--border); background: var(--surface); border-radius: var(--r-xl); padding: 14px;
  transition: all .16s var(--ease); }
.exp-mode:hover { border-color: var(--text-tertiary); transform: translateY(-2px); }
.exp-mode.is-on { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
.exp-mode > i { font-size: 1.25rem; color: var(--text-tertiary); margin-bottom: 4px; }
.exp-mode.is-on > i { color: var(--blue); }
.exp-mode__t { font-size: .88rem; font-weight: 700; color: var(--text); }
.exp-mode__s { font-size: .74rem; color: var(--text-tertiary); line-height: 1.45; }

.acct-modes { margin: 8px 0 4px; }
.acct-mode { display: flex; align-items: flex-start; gap: 9px; text-align: left; cursor: pointer;
  border: 1.5px solid var(--border); background: var(--surface); border-radius: var(--r-lg); padding: 10px 11px;
  transition: all .15s var(--ease); }
.acct-mode:hover { border-color: var(--text-tertiary); }
.acct-mode.is-on { border-color: var(--blue); background: var(--blue-soft); }
.acct-mode > i { font-size: .95rem; color: var(--text-tertiary); margin-top: 1px; flex: 0 0 auto; }
.acct-mode.is-on > i { color: var(--blue); }
.acct-mode span { display: flex; flex-direction: column; min-width: 0; }
.acct-mode strong { font-size: .78rem; color: var(--text); font-weight: 700; }
.acct-mode span > strong + * , .acct-mode span { font-size: .7rem; color: var(--text-tertiary); line-height: 1.4; }

/* =====================================================================================
 *  ROW ACTION BUTTONS  (invoice list, and any table that reuses .row-act)
 *  One geometry for every action button in a row, whether it holds a Bootstrap icon
 *  glyph or an inline SVG. Previously the WhatsApp button carried inline
 *  display:inline-flex + min-height:44px, which baseline-aligned it below its
 *  siblings and clipped it against the cell padding.
 *
 *  vertical-align:middle is the load-bearing line: an inline-flex box's baseline is
 *  its bottom margin edge, so without this it drops relative to inline-block siblings.
 *  The 44x44 touch target lives on an ::after overlay, so the column width stays sane.
 * ===================================================================================== */
.row-act {
  width: 36px; height: 36px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  vertical-align: middle; position: relative;
  border: 0; background: transparent; color: var(--blue);
  border-radius: var(--r-md); flex: 0 0 auto;
  transition: background var(--dur) var(--ease);
}
.row-act + .row-act { margin-left: 2px; }
.row-act::after {                      /* vertical-only hit expansion to 44px */
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  height: 44px; transform: translateY(-50%);
}                                      /* NOT expanded horizontally: at 36px pitch,
                                          44px-wide overlays would overlap and the
                                          later sibling would swallow its neighbour's taps. */
.row-act:hover:not(:disabled) { background: var(--blue-soft); }
.row-act:disabled { opacity: .3; cursor: default; }
/* Every action icon is a Bootstrap Icons glyph, so they share one optical weight and
   one grid. A hand-rolled SVG here (the WhatsApp mark used to be one) renders as a
   solid shape among outline glyphs and visually dominates the row. */
.row-act > i { font-size: 1rem; line-height: 1; display: block; }

/* Colour variants — kept here rather than as inline style attributes */
.row-act--green { color: var(--green); }
.row-act--red   { color: var(--red); }
.row-act--wa    { color: #25D366; }
.row-act--wa:hover:not(:disabled) { background: rgba(37, 211, 102, .12); }
.row-act--green:hover:not(:disabled) { background: rgba(52, 199, 89, .12); }
.row-act--red:hover:not(:disabled)   { background: rgba(255, 59, 48, .12); }

/* The actions cell must never be the thing that clips a button */
.data-table td.row-acts { white-space: nowrap; overflow: visible; }

/* =====================================================================================
 *  WHATSAPP CONFIRMATION DIALOG
 *  The message preview is deliberately shaped like a WhatsApp bubble — what you see
 *  in this box is byte-for-byte what the client receives.
 * ===================================================================================== */
.wa-cf__lead { margin: 0 0 14px; color: var(--text); font-size: .95rem; line-height: 1.45; }
.wa-cf__card {
  background: var(--surface-sunken); border: 1px solid var(--separator);
  border-radius: var(--r-md); padding: 12px 14px; margin-bottom: 16px;
  display: flex; flex-direction: column; gap: 9px;
}
.wa-cf__row { display: flex; align-items: flex-start; gap: 10px; font-size: .86rem; color: var(--text-secondary); line-height: 1.4; }
.wa-cf__row > i { color: var(--text-tertiary); font-size: .95rem; line-height: 1.35; flex: 0 0 auto; }
.wa-cf__row--warn { color: var(--amber, var(--attention)); }
.wa-cf__row--warn > i { color: currentColor; }
.wa-cf__label {
  font-size: .7rem; font-weight: 650; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-tertiary); margin-bottom: 7px;
}
.wa-cf__bubble {
  position: relative; background: #DCF8C6; color: #111;
  border-radius: 12px 12px 12px 4px; padding: 11px 14px;
  font-size: .88rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
}
.wa-cf__note {
  margin-top: 14px; font-size: .8rem; color: var(--text-secondary);
  display: flex; align-items: flex-start; gap: 8px; line-height: 1.45;
}
.wa-cf__note > i { flex: 0 0 auto; line-height: 1.4; }
.wa-cf__go.is-armed { background: #25D366; border-color: #25D366; }
.wa-cf__go.is-armed:hover { background: #1FB855; }

/* The green bubble is a WhatsApp convention, so it stays green in dark mode —
   only the text colour is pinned so it never goes light-on-light. */
html[data-theme="dark"] .wa-cf__bubble { background: #075E54; color: #E9EDEF; }

/* Bootstrap ships modal-sm / -lg / -xl but no explicit medium; this names the default
   width so `size: 'modal-md'` is a real instruction rather than an inert class. */
.modal-dialog.modal-md { max-width: 500px; }

/* Touch devices: the button itself becomes the full 44x44 target. */
@media (pointer: coarse) {
  .row-act { width: 44px; height: 44px; }
  .row-act + .row-act { margin-left: 0; }
}

/* =====================================================================================
 *  "VIEW ALL ACTIVITY" — the one deliberately black control on the dashboard.
 *  Everything else on this surface is blue or ghost, so black reads as a different
 *  KIND of action: leave this glance, go read the record. In dark mode it inverts to
 *  near-white, because a black button on a black card is just a hole.
 * ===================================================================================== */
.btn-allactivity {
  width: 100%; margin-top: 14px;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--ink); color: #fff; border: 0;
  border-radius: var(--r-md); padding: 12px 18px;
  font-size: .85rem; font-weight: 650; letter-spacing: .01em; cursor: pointer;
  box-shadow: var(--lift-2);
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), background .18s;
}
.btn-allactivity:hover { background: #23232B; box-shadow: var(--lift-3); }
.btn-allactivity:active { transform: scale(.985); }
.btn-allactivity > i { font-size: .82rem; transition: transform .22s var(--ease); }
.btn-allactivity:hover > i { transform: translateX(3px); }

html[data-theme="dark"] .btn-allactivity {
  background: var(--ink); color: var(--ink); box-shadow: var(--lift-2);
}
html[data-theme="dark"] .btn-allactivity:hover { background: #fff; }

@media (prefers-reduced-motion: reduce) {
  .btn-allactivity, .btn-allactivity > i { transition: none; }
  .btn-allactivity:active { transform: none; }
}

/* =====================================================================================
 *  LEDGER — TYPOGRAPHY ROLES & MOTION
 *
 *  Motion philosophy: every animation here moves ONLY transform and opacity, both of
 *  which the compositor handles without touching layout or paint. Anything that
 *  animates width, height, top or margin will jank on a mid-range phone, and this app
 *  gets used on phones in truck yards.
 *
 *  Restraint matters as much as the motion itself. Things settle into place, they don't
 *  bounce or slide across the screen. A ledger that flings its numbers around does not
 *  read as trustworthy.
 * ===================================================================================== */

h1, h2, h3, .view-header__title, .section-title, .kpi-card__value,
.dash-hero__amount, .metric__v {
  font-family: var(--font-display);
  letter-spacing: -.02em;
}

/* THE SIGNATURE — the ledger column.
   Amounts inside data tables are monospaced and right-aligned so decimal points stack
   down the column, exactly as they would in a ruled account book. Headline figures stay
   proportional, so this reads as a deliberate accounting convention rather than a font
   accident that escaped into the layout. */
.led,
.data-table td.num,
.data-table td.amount,
.data-table .money {
  font-family: var(--font-ledger);
  font-variant-numeric: tabular-nums;
  font-size: .92em;
  letter-spacing: -.02em;
  text-align: right;
}
.data-table th.num, .data-table th.amount { text-align: right; }

/* Invoice references read as identifiers, not prose. */
.ref, .inv-number, .data-table td .invoice-ref {
  font-family: var(--font-ledger);
  font-size: .93em;
  font-weight: 500;
  letter-spacing: -.02em;
}

/* Any large figure: tabular so digits don't shuffle width as values change. */
.fig, .kpi-card__value, .dash-hero__amount, .metric__v, .donut-legend__val, .top-row__val {
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
}

/* ------------------------------------------------------------------ REVEAL ---------
   Sections arrive rather than appearing. Applied by js/core/motion.js on scroll, so
   content below the fold animates when it's reached instead of all at once at load. */
.rv { opacity: 0; transform: translateY(14px); will-change: transform, opacity; }
.rv.rv-in {
  opacity: 1; transform: none;
  transition: opacity .5s var(--ease-out), transform .5s var(--ease-out);
  transition-delay: var(--rv-d, 0ms);
}
.rv.rv-done { will-change: auto; }

/* ------------------------------------------------------------------ HOVER ----------
   1px of lift. Enough to feel responsive, not enough to look like the card is escaping.
   Only on devices with a real pointer — on touch this fires on tap and looks broken. */
@media (hover: hover) and (pointer: fine) {
  .ios-card, .kpi-card, .chart-card {
    transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
  }
  .ios-card:hover, .kpi-card:hover, .chart-card:hover {
    transform: translateY(-1px);
    box-shadow: var(--lift-2);
    border-color: var(--line-strong);
  }
  .data-table tbody tr { transition: background var(--dur) var(--ease); }
}

/* Rows get a navy edge on hover — a ledger's ruled margin, reacting to the cursor. */
.data-table tbody tr { position: relative; }
.data-table tbody tr::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--accent); transform: scaleY(0); transform-origin: center;
  transition: transform .22s var(--ease);
}
.data-table tbody tr:hover::after { transform: scaleY(1); }

/* ------------------------------------------------------------------ BUTTONS -------- */
.btn-ios-primary, .btn-ios-secondary, .btn-ios-ghost, .btn-allactivity, .row-act {
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease),
              border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.btn-ios-primary:active, .btn-ios-secondary:active, .btn-allactivity:active { transform: scale(.975); }

/* Keyboard focus is visible and on-brand. Never removed — it's how the app is usable
   without a mouse, and a 2px navy ring is not a design compromise. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* ------------------------------------------------------------------ SKELETON -------
   A sweep across the surface rather than a pulsing block: it reads as "loading", not
   as "broken". background-position is cheap here because the element is a solid fill. */
.skeleton, .act-sk {
  background: linear-gradient(90deg, var(--surface-sunken) 25%, var(--surface-2) 37%, var(--surface-sunken) 63%);
  background-size: 400% 100%;
  animation: sweep 1.4s ease-in-out infinite;
  border-radius: var(--r-md);
}
@keyframes sweep { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* ------------------------------------------------------------------ ACCENTS --------
   Status badges get a dot in their own colour: scannable at a glance without relying
   on colour alone, which matters for anyone with a colour vision deficiency. */
.badge::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; display: inline-block; margin-right: 5px;
  vertical-align: middle; position: relative; top: -1px;
}

/* Sidebar: the active item earns a navy rule, not just a tint. */
.sidebar__link { position: relative; transition: background var(--dur) var(--ease), color var(--dur) var(--ease); }
.sidebar__link::before {
  content: ""; position: absolute; left: 0; top: 50%; height: 0; width: 3px;
  background: var(--accent); border-radius: 0 3px 3px 0;
  transform: translateY(-50%); transition: height .24s var(--ease);
}
.sidebar__link.is-active::before { height: 60%; }

/* Ring/donut draws itself once, so the collection rate lands rather than being there. */
@keyframes drawRing { from { stroke-dashoffset: var(--ring-len, 240); } }
.ring-anim { animation: drawRing 1s var(--ease-out) both; }

/* ------------------------------------------------------------ REDUCED MOTION -------
   Everything above is decoration on top of a layout that already works. Turning it all
   off must leave the app fully usable, not half-faded. */
@media (prefers-reduced-motion: reduce) {
  .rv { opacity: 1 !important; transform: none !important; }
  .skeleton, .act-sk { animation: none; }
  .ring-anim { animation: none; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}

/* =====================================================================================
 *  LEDGER — COMPONENT LAYER
 *
 *  The token swap alone only changed colours, radii and type. This block rebuilds the
 *  COMPONENTS to match the approved direction: structure carried by hairlines instead
 *  of shadows, tighter density, uppercase micro-labels on data, and a nav that marks
 *  the active item with a rule rather than a solid fill.
 *
 *  Appended deliberately: later rules of equal specificity win, so this overrides the
 *  original component definitions without editing 1,600 lines in place and risking a
 *  half-migrated stylesheet.
 * ===================================================================================== */

/* ------------------------------------------------------------------ TOP BAR -------- */
.app-topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  box-shadow: none;                       /* structure comes from the hairline */
  backdrop-filter: none;
}
.topbar__logo, .workspace-chip__logo img { border-radius: var(--r-md); box-shadow: none; }
.topbar__brand-text { font-family: var(--font-display); letter-spacing: -.015em; font-weight: 600; }
.topbar__search-input {
  background: var(--surface-sunken);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-size: .86rem;
}
.topbar__search-input:focus {
  background: var(--surface);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}

/* ------------------------------------------------------------------ SIDEBAR -------- */
.app-sidebar { background: var(--surface); border-right: 1px solid var(--line); }
.sidebar__label {
  font-size: .66rem; letter-spacing: .09em; font-weight: 600; color: var(--ink-3);
}

/* The active item gets a navy rule and a wash, not a solid blue slab. A filled pill is
   the heaviest thing on the page and pulls the eye away from the data, which is the
   thing that actually matters on every one of these screens. */
.nav-item {
  position: relative;
  border-radius: var(--r-md);
  padding: 9px 12px;
  font-size: .855rem;
  font-weight: 500;
  color: var(--ink-2);
  gap: 10px;
}
.nav-item i { font-size: 1rem; width: 20px; }
.nav-item:hover { background: var(--surface-sunken); color: var(--ink); }
.nav-item.is-active {
  background: var(--accent-wash);
  color: var(--accent);
  font-weight: 600;
  box-shadow: none;
}
.nav-item.is-active i { color: var(--accent); }
.nav-item::before {
  content: ""; position: absolute; left: -8px; top: 50%; width: 3px; height: 0;
  background: var(--accent); border-radius: 0 3px 3px 0;
  transform: translateY(-50%); transition: height .24s var(--ease);
}
.nav-item.is-active::before { height: 56%; }

.workspace-chip__name { font-family: var(--font-display); font-weight: 600; }

/* ------------------------------------------------------------------ CARDS ---------- */
/* Bordered, not floating. A page of drop-shadowed cards reads as a dashboard template;
   hairline-bounded panels read as a document. */
.ios-card, .chart-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: none;
}
.section-title {
  font-family: var(--font-display);
  font-size: .92rem; font-weight: 600; letter-spacing: -.01em;
}
.view-header__title { font-size: 1.55rem; font-weight: 600; letter-spacing: -.025em; }
.view-header__subtitle { font-size: .88rem; color: var(--ink-2); }

/* ---- KPI strip: one surface divided by hairlines, not six floating cards.
   Six cards imply six unrelated things; these are six readings of one business. ---- */
.kpi-banner {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  overflow: hidden;
  gap: 0;
}
.kpi-card {
  background: transparent;
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  box-shadow: none;
  padding: var(--sp-5);
}
.kpi-card:last-child { border-right: 0; }
.kpi-card__label { font-size: .72rem; color: var(--ink-3); font-weight: 500; letter-spacing: .01em; }
.kpi-card__value { font-size: 1.35rem; font-weight: 600; }
.kpi-card__delta { font-size: .72rem; font-weight: 500; color: var(--ink-3); }
.kpi-card__delta.is-up { color: var(--positive); }
.kpi-card__delta.is-down { color: var(--attention); }
@media (hover: hover) and (pointer: fine) {
  .kpi-card:hover { transform: none; box-shadow: none; background: var(--surface-2); }
}

/* ------------------------------------------------------------------ TABLES --------- */
/* Column headers become micro-labels: small, uppercase, tracked out, on a sunken bar.
   This is the single strongest signal that a table is a ledger and not a list. */
.data-table thead th {
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  padding: 10px var(--sp-5);
  white-space: nowrap;
}
.data-table tbody td {
  padding: 12px var(--sp-5);
  border-bottom: 1px solid var(--line);
  font-size: .855rem;
}
.data-table tbody tr:last-child td { border-bottom: 0; }
.data-table tbody tr:hover td { background: var(--surface-2); }

/* ------------------------------------------------------------------ BADGES --------- */
.badge-ios {
  border: 0;
  border-radius: var(--r-sm);
  font-size: .69rem;
  font-weight: 600;
  padding: 3px 9px;
  letter-spacing: .005em;
}
/* These are the LATER of two definitions of the same classes, so they win regardless
   of what the earlier block says — which is why Overdue kept rendering amber after the
   ramp was introduced, and why Cancelled was rendering in critical red rather than grey.
   Repointed at the status ramp so badges, tracks and tabs finally agree. */
.badge-pre_invoice    { background: var(--surface-sunken);        color: var(--st-draft-ink); }
.badge-due            { background: rgba(245, 158, 11, .13);      color: var(--st-due-ink); }
.badge-partially_paid { background: rgba(245, 158, 11, .13);      color: var(--st-due-ink); }
.badge-overdue        { background: rgba(239, 68, 68, .12);       color: var(--st-late-ink); }
.badge-paid           { background: rgba(16, 185, 129, .13);      color: var(--st-paid-ink); }
.badge-paid_in_full   { background: rgba(16, 185, 129, .13);      color: var(--st-paid-ink); }
.badge-cancelled      { background: var(--surface-sunken);        color: var(--st-void-ink); text-decoration: line-through; }
html[data-theme="dark"] .badge-pre_invoice,
html[data-theme="dark"] .badge-due,
html[data-theme="dark"] .badge-partially_paid,
html[data-theme="dark"] .badge-overdue,
html[data-theme="dark"] .badge-paid,
html[data-theme="dark"] .badge-cancelled { border: 0; }

/* ------------------------------------------------------------------ BUTTONS -------- */
.btn-ios-primary, .btn-ios-secondary, .btn-ios-ghost {
  border-radius: var(--r-md);
  font-size: .82rem;
  font-weight: 600;
  padding: 9px 15px;
  letter-spacing: .005em;
}
.btn-ios-primary { background: var(--accent); color: #fff; box-shadow: none; }
.btn-ios-primary:hover { background: var(--accent-press); }
.btn-ios-secondary {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line-strong);
}
.btn-ios-secondary:hover { background: var(--surface-2); color: var(--ink); border-color: var(--line-strong); }

/* ------------------------------------------------------------------ FORMS ---------- */
.form-control, .form-select {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  color: var(--ink);
  font-size: .875rem;
  padding: 9px 12px;
}
.form-control:focus, .form-select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
  background: var(--surface);
  color: var(--ink);
}
.form-control::placeholder { color: var(--ink-3); }
.hm-label {
  font-size: .72rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}

/* ------------------------------------------------------------------ SEGMENTED ------ */
.segmented { background: var(--surface-sunken); border-radius: var(--r-md); padding: 3px; }
.segmented__item {
  border-radius: var(--r-sm); font-size: .78rem; font-weight: 500; color: var(--ink-2);
}
.segmented__item.is-active {
  background: var(--surface); color: var(--ink); box-shadow: var(--lift-1); font-weight: 600;
}

/* ------------------------------------------------------------------ EMPTY ---------- */
.empty-state { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-xl); box-shadow: none; }
.empty-state__icon { background: var(--surface-sunken); color: var(--ink-3); border-radius: var(--r-lg); }
.empty-state__title { font-family: var(--font-display); font-weight: 600; }

/* ------------------------------------------------------------------ MISC PURGE -----
   Hardcoded colours that predate the token layer, brought back under it. ---------- */
.btn-ios-secondary:hover { background: var(--surface-2); }
.badge-scheduled { background: var(--accent-wash); color: var(--accent); }
.dash-hero { background: var(--ink); }
html[data-theme="dark"] .dash-hero { background: var(--surface); border: 1px solid var(--line-strong); }
.dash-hero::after { display: none; }        /* the glow belonged to the old blue hero */

/* ==================================================================================
 *  PHONE — a different layout, not a narrower one.
 *  On a desktop you're reviewing; on a phone you're acting: checking whether someone
 *  paid, chasing from a yard. So navigation moves to the thumb and the drawer goes.
 * ================================================================================ */
.tabbar { display: none; }

@media (max-width: 767.98px) {
  .tabbar {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1040;
    background: var(--glass-bg);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border-top: 1px solid var(--line);
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
  }
  .tabbar__item {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
    border: 0; background: transparent; color: var(--ink-3);
    font-size: .625rem; font-weight: 500; padding: 6px 0 4px;
    border-radius: var(--r-md); cursor: pointer;
    transition: color var(--dur) var(--ease);
  }
  .tabbar__item i { font-size: 1.15rem; line-height: 1; }
  .tabbar__item.is-active { color: var(--accent); }

  /* Leave room for the bar, plus the home indicator on iPhones. */
  .app-main, .view-root { padding-bottom: calc(72px + env(safe-area-inset-bottom, 0px)) !important; }

  /* The hamburger and drawer are redundant once the thumb bar exists. */
  .topbar__menu-btn { display: none; }

  .kpi-banner { grid-template-columns: repeat(2, 1fr); }
  .kpi-card:nth-child(2n) { border-right: 0; }
  .kpi-card:not(:nth-last-child(-n+2)) { border-bottom: 1px solid var(--line); }
  .view-header__title { font-size: 1.3rem; }
  .data-table thead th, .data-table tbody td { padding-left: var(--sp-4); padding-right: var(--sp-4); }
}

/* =====================================================================================
 *  LEDGER — SCREEN-BY-SCREEN CONSISTENCY PASS
 *
 *  Views inject their own <style> blocks, written before the system existed. Those rules
 *  live in the document and would otherwise sit outside it — cards floating on shadows
 *  next to cards bounded by hairlines, on the same page.
 *
 *  Rather than rewrite nine files of string-concatenated CSS (high risk, easy to break a
 *  quoted style mid-string), this brings the bespoke containers onto the system from the
 *  outside. Same result, nothing to break.
 * ===================================================================================== */

/* ---- Bespoke panels: bordered, not floating ------------------------------------- */
.notif-panel, .kbd-card, .vup, .preset-card, .thm-card, .hdr-tile, .tpl-tile,
.fnt-tile, .set-savebar, .dr-panel, .ac-panel, .pvg-card, .doc-card, .tmpl-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
}
.notif-panel, .dr-panel, .ac-panel, .set-savebar { box-shadow: var(--lift-3); }
.kbd-card, .vup, .preset-card, .thm-card, .pvg-card, .doc-card, .tmpl-card { box-shadow: none; }

/* Selectable tiles: the chosen one is marked by a navy border and wash, never a
   drop shadow. Shadow says "floating"; selection is a state, not a plane. */
.preset-card.is-active, .thm-card.is-active, .hdr-tile.is-active,
.tpl-tile.is-active, .fnt-tile.is-active, .sig-font-tile.is-active {
  border-color: var(--accent);
  background: var(--accent-wash);
  box-shadow: none;
}

/* ---- Chips and pills ------------------------------------------------------------- */
.act-chip, .pay-chip, .ph-chip, .set-tab, .dr-input {
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink-2);
  font-weight: 500;
}
.act-chip.is-on, .pay-chip.is-on, .set-tab.is-active {
  background: var(--accent-wash);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}
.dr-input { border-radius: var(--r-md); }

/* ---- Activity timeline: align with the card system ------------------------------ */
.act-ico { border: 1px solid var(--line); background: var(--surface); border-radius: var(--r-lg); }
.act-day { font-size: .66rem; letter-spacing: .09em; color: var(--ink-3); }
.act-fold { border-radius: var(--r-lg); border-color: var(--line-strong); }
.act-bubble, .wa-cf__bubble { border-radius: var(--r-lg) var(--r-lg) var(--r-lg) var(--r-xs); }

/* ---- Payments grid ---------------------------------------------------------------
   The .pvg-bar rules that lived here are gone with the bar itself (v2.12.1). They did
   not set `display`, so they were not overriding the hide — just dead weight. */

/* ---- Toasts and modals: the only things that genuinely float -------------------- */
.toast-ios, .modal-content {
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--lift-3);
  background: var(--surface);
}
.modal-header, .modal-footer { border-color: var(--line); }
.modal-title { font-family: var(--font-display); font-weight: 600; letter-spacing: -.015em; }

/* ---- Dashboard chase card, brought onto the attention token -------------------- */
.chase { border-left-width: 3px; border-radius: var(--r-xl); box-shadow: none; }
.chase__row { border-radius: var(--r-md); }

/* ---- Notification rows ----------------------------------------------------------- */
.notif-item { border-bottom: 1px solid var(--line); }
.notif-item:hover { background: var(--surface-2); }
.notif-item__icon { border-radius: var(--r-md); }

/* ---- Anything still carrying a legacy pill radius on a rectangular control ------ */
.segmented, .segmented__item { border-radius: var(--r-md); }
.segmented__item { border-radius: var(--r-sm); }

/* =====================================================================================
 *  NATIVE FEEL
 *  The details that stop an installed PWA reading as a website in a frame.
 * ===================================================================================== */

/* ---- PULL TO REFRESH ------------------------------------------------------------- */
.ptr {
  position: fixed; top: calc(env(safe-area-inset-top, 0px) + 4px); left: 50%;
  margin-left: -18px; width: 36px; height: 36px; z-index: 1200;
  display: grid; place-items: center; pointer-events: none; opacity: 0;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-pill); box-shadow: var(--lift-2);
  color: var(--ink-3);
  transform: translateY(0);
  transition: opacity .18s var(--ease);
}
.ptr.is-armed { color: var(--accent); border-color: var(--accent); }
.ptr__spin { display: grid; place-items: center; transition: transform .2s var(--ease); }
.ptr.is-armed .ptr__spin { transform: rotate(180deg); }
.ptr.is-loading .ptr__spin { animation: ptrSpin .7s linear infinite; transform: none; }
@keyframes ptrSpin { to { transform: rotate(360deg); } }

/* ---- PRESS FEEDBACK -------------------------------------------------------------
   :active is unreliable on touch — iOS won't apply it without a touch listener, and
   it lingers after scroll-cancelled taps. A class driven by touch events is honest. */
.is-pressed { transform: scale(.96); opacity: .85; transition: transform .08s ease-out; }
.tabbar__item.is-pressed { transform: scale(.92); }

/* ---- KEYBOARD ---------------------------------------------------------------------
   --kb is published by native.js from visualViewport. Without this the tab bar sits
   behind the soft keyboard and a focused field can end up underneath it. */
html.kb-open .tabbar { transform: translateY(100%); pointer-events: none; }
.tabbar { transition: transform .22s var(--ease); }
html.kb-open .app-main, html.kb-open .view-root { padding-bottom: var(--kb, 0px) !important; }

/* ---- INSTALLED APP ----------------------------------------------------------------
   Installed, the app owns the status bar area, so the top bar must extend under it or
   content collides with the clock and battery. In a browser tab this must NOT apply —
   the browser chrome is already there. */
html.is-standalone .app-topbar {
  padding-top: env(safe-area-inset-top, 0px);
  height: calc(var(--topbar-h) + env(safe-area-inset-top, 0px));
}
html.is-standalone .app-sidebar { padding-top: calc(env(safe-area-inset-top, 0px) + var(--sp-5)); }

/* ---- TOUCH CHROME -----------------------------------------------------------------
   UI chrome shouldn't be selectable — long-pressing a nav item and getting a text
   selection handle is the single clearest "this is a web page" tell. Content stays
   selectable, because copying an invoice number is a real thing people do. */
.app-topbar, .app-sidebar, .tabbar, .nav-item, .btn-ios-primary, .btn-ios-secondary,
.btn-ios-ghost, .row-act, .segmented, .badge-ios, .act-chip, .kpi-card__label {
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
.data-table td, .act-sum, .view-header, .balance__fig, .mono, .led { -webkit-user-select: text; user-select: text; }

/* Buttons and scrollers declare their gesture intent, so the browser doesn't wait
   ~300ms to find out whether a tap was the start of a double-tap zoom. */
button, .nav-item, .tabbar__item, .row-act, .segmented__item { touch-action: manipulation; }
.app-main, .view-root, .modal-body { -webkit-overflow-scrolling: touch; overscroll-behavior-y: contain; }

/* Rubber-banding the whole document reveals the page background behind the app and
   instantly breaks the illusion. Scroll belongs to the panes, not the document. */
html, body { overscroll-behavior: none; }

@media (prefers-reduced-motion: reduce) {
  .ptr__spin, .is-pressed, .tabbar { transition: none; animation: none; }
}

/* =====================================================================================
 *  NATIVE — SCREEN TRANSITIONS, SHEETS, SWIPE
 * ===================================================================================== */

/* ---- PAGE TRANSITIONS ------------------------------------------------------------
   Native apps move DIRECTIONALLY: going deeper pushes in from the right, going back
   slides out to it. That direction is the whole point — it tells you where you are in
   the hierarchy without a single word of UI. A crossfade in both directions looks
   smooth and communicates nothing.
   Transform and opacity only, so no layout runs mid-transition. */
.view-root { will-change: auto; }
.view-root.vt-push-in  { animation: vtPushIn .28s var(--ease) both; }
.view-root.vt-pop-in   { animation: vtPopIn  .28s var(--ease) both; }
@keyframes vtPushIn { from { opacity: 0; transform: translate3d(18px, 0, 0); } }
@keyframes vtPopIn  { from { opacity: 0; transform: translate3d(-18px, 0, 0); } }

/* ---- BOTTOM SHEETS ----------------------------------------------------------------
   On a phone a centred dialog is a desktop idiom: it floats in dead space and its
   buttons land where the thumb can't reach. A sheet rises from the bottom edge, which
   is both the native convention and where the hand already is. */
@media (max-width: 767.98px) {
  .modal-dialog {
    margin: 0; position: fixed; left: 0; right: 0; bottom: 0;
    max-width: none !important; width: 100%;
    transform: translateY(100%);
    transition: transform .3s var(--ease);
  }
  .modal.show .modal-dialog { transform: translateY(0); }
  .modal-content, .hm-modal {
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    border-bottom: 0;
    max-height: 92vh;
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  /* The grabber: a universally understood "drag me down". */
  .modal-content::before {
    content: ""; display: block; width: 36px; height: 4px; margin: 8px auto 0;
    background: var(--line-strong); border-radius: var(--r-pill); flex: 0 0 auto;
  }
  .modal-header { border-bottom: 1px solid var(--line); padding-top: 10px; }
  .modal-body { overflow-y: auto; -webkit-overflow-scrolling: touch; }
  /* Footer buttons go full width and stack — thumb targets, not desktop button rows. */
  .modal-footer { flex-direction: column-reverse; gap: 8px; border-top: 1px solid var(--line); }
  .modal-footer > .btn-ios-primary,
  .modal-footer > .btn-ios-secondary { width: 100%; justify-content: center; padding: 13px 16px; }
  .modal-dialog.is-dragging { transition: none; }
}

/* ---- SWIPE ACTIONS ----------------------------------------------------------------
   Revealed by dragging a row left. The row itself translates; the actions sit behind it
   and are never animated, so the whole gesture is one composited transform. */
.swipe { position: relative; overflow: hidden; }
.swipe__body { position: relative; z-index: 1; background: var(--surface); transition: transform .24s var(--ease); }
.swipe__body.is-dragging { transition: none; }
.swipe__acts {
  position: absolute; inset: 0 0 0 auto; z-index: 0;
  display: flex; align-items: stretch;
}
.swipe__act {
  width: 76px; border: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 4px; color: #fff; font-size: .7rem; font-weight: 600;
}
.swipe__act i { font-size: 1.1rem; }
.swipe__act--send { background: var(--accent); }
.swipe__act--wa   { background: #25D366; }
.swipe__act--del  { background: var(--critical); }

/* ---- OFFLINE ---------------------------------------------------------------------
   A quiet strip, not a modal. Being offline isn't an error — the write queue is
   handling it — so it should inform without interrupting. */
.net-strip {
  position: fixed; left: 0; right: 0; z-index: 1150;
  top: calc(env(safe-area-inset-top, 0px) + var(--topbar-h));
  background: var(--attention); color: #fff;
  font-size: .76rem; font-weight: 600; text-align: center; padding: 6px 12px;
  transform: translateY(-100%);
  /* translateY alone does NOT hide this. The strip sits at z-index 1150, above the
     topbar's 1030, so sliding it up parks it ON TOP of the topbar rather than behind
     it — and with no text it is still 12px of padding, reading as a coloured rule
     welded under the header. visibility is what actually removes it, and it is
     stacking-independent so no future z-index change can bring the line back.
     Delayed on hide so the slide-out finishes first; immediate on show. */
  visibility: hidden;
  transition: transform .25s var(--ease), visibility 0s .25s;
}
.net-strip.is-on {
  transform: translateY(0);
  visibility: visible;
  transition: transform .25s var(--ease), visibility 0s 0s;
}
.net-strip.is-back { background: var(--positive); }

@media (prefers-reduced-motion: reduce) {
  .view-root.vt-push-in, .view-root.vt-pop-in { animation: none; }
  .modal-dialog, .swipe__body, .net-strip { transition: none; }
}

/* =====================================================================================
 *  INVOICE LIST — TWO RENDERINGS, ONE SOURCE
 *  A seven-column table on a 390px screen is unreadable at any font size: you either
 *  scroll sideways hunting for the amount, or shrink the text past legibility. So the
 *  phone gets cards carrying the three facts that matter — who, how much, how urgent.
 *
 *  Both renderings sit in the DOM and CSS picks. The alternative — a resize listener
 *  re-rendering the list — flickers and loses scroll position on every rotation.
 * ===================================================================================== */
.inv-cards { display: none; }

@media (max-width: 767.98px) {
  .inv-table { display: none; }
  .inv-cards { display: block; }

  .inv-card { border-bottom: 1px solid var(--line); }
  .inv-card:last-child { border-bottom: 0; }
  .inv-card .swipe__body { padding: 14px var(--sp-4); cursor: pointer; }

  .inv-card__top { display: flex; align-items: center; gap: 8px; margin-bottom: 3px; }
  .inv-card__no { font-size: .82rem; font-weight: 600; color: var(--ink); }
  .inv-card__who { font-size: .875rem; color: var(--ink); font-weight: 500; margin-bottom: 8px; }

  .inv-card__foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
  .inv-card__due  { font-size: .74rem; color: var(--ink-3); }
  /* Days-late is overdue information, so it takes the overdue colour. It was amber,
     which put it in the same visual family as "payment expected" — the opposite of
     what it means. Red is reserved for late, and this is late. */
  .inv-card__late {
    font-size: .7rem; font-weight: 600; color: var(--st-late-ink);
    background: rgba(239, 68, 68, .11); padding: 3px 8px; border-radius: var(--r-pill);
  }
  /* The amount is the reason the screen exists, so it gets the most weight on the card. */
  .inv-card__amt {
    font-family: var(--font-display); font-variant-numeric: tabular-nums;
    font-size: 1.02rem; font-weight: 600; letter-spacing: -.02em;
    text-align: right; line-height: 1.2; color: var(--ink);
  }
  .inv-card__amt em {
    display: block; font-style: normal; font-size: .68rem;
    font-weight: 400; color: var(--ink-3); letter-spacing: 0;
  }
}
