/* =====================================================================
   AQUANETS — DESIGN SYSTEM
   =====================================================================
   One stylesheet drives every surface: the admin console, the three role
   portals and the standalone auth screens. Nothing here changes behaviour;
   the class and data-* contracts that includes/table.php and assets/app.js
   depend on are all preserved.

   Four rules govern the whole file:

   1. ONE TYPEFACE, ONE SCALE. Every size comes from a --fs-* token, never
      a magic number, and the tokens are redefined once at the mobile
      breakpoint. That is what makes the phone bump a single edit instead
      of forty, and what keeps every page looking like one product.

   2. EVERY TEXT/BACKGROUND PAIR MEETS WCAG AA (4.5:1) — in BOTH modes and
      on ALL FIVE accents. A gradient is only ever allowed to run between
      two stops that both pass against the text sitting on them.

   3. DEPTH IS A SCALE, NOT A DECORATION. Elevation runs --shadow-xs ->
      --shadow-lg and nothing invents its own. Each level pairs a tight
      contact shadow with a wider ambient one.

   4. NOTHING BELOW THE TOKEN BLOCK KNOWS WHAT MODE IT IS IN. Rules name
      --card / --text / --line; the theme blocks decide what those mean.
      A component that hard-codes #fff cannot be themed, so none does.

   THEMING
   -------
   <html data-theme="dark|light" data-accent="teal|blue|violet|emerald|amber">
   stamped server-side by themeAttrs() in includes/partials.php (from a
   cookie) and re-stamped live by assets/app.js when the user picks. Dark is
   the default, so a first-time visitor gets dark with no cookie at all.
   ===================================================================== */

/* ===================== accent palettes ==============================
   Each accent supplies six values. --a-brand is the filled-button ground
   and carries white text, so every one of them was picked to clear 4.5:1
   against white; --a-ink-l is the text-weight shade for LIGHT mode (read
   on a white card) and --a-ink-d the one for DARK mode (read on a dark
   card). Ratios in the comments are measured, not guessed.
   ==================================================================== */
:root,
:root[data-accent="teal"]{
  --a-bright:#12a3ad; --a-brand:#0a7681; --a-deep:#08616b;   /* white on brand 5.4:1 */
  --a-ink-l:#0b6d78;  --a-ink-d:#5fd3dd; --a-rgb:18,163,173; /* 6.1:1 light / 9.1:1 dark */
}
:root[data-accent="blue"]{
  --a-bright:#3b82f6; --a-brand:#2563eb; --a-deep:#1d4ed8;   /* white on brand 5.2:1 */
  --a-ink-l:#1d4ed8;  --a-ink-d:#93c5fd; --a-rgb:59,130,246; /* 6.7:1 light / 8.9:1 dark */
}
:root[data-accent="violet"]{
  --a-bright:#a78bfa; --a-brand:#7c3aed; --a-deep:#6d28d9;   /* white on brand 5.7:1 */
  --a-ink-l:#6d28d9;  --a-ink-d:#c4b5fd; --a-rgb:139,92,246; /* 7.1:1 light / 8.7:1 dark */
}
:root[data-accent="emerald"]{
  --a-bright:#34d399; --a-brand:#047857; --a-deep:#065f46;   /* white on brand 5.5:1 */
  --a-ink-l:#047857;  --a-ink-d:#6ee7b7; --a-rgb:16,185,129; /* 5.5:1 light / 10.5:1 dark */
}
:root[data-accent="amber"]{
  --a-bright:#f59e0b; --a-brand:#b45309; --a-deep:#92400e;   /* white on brand 5.0:1 */
  --a-ink-l:#92400e;  --a-ink-d:#fcd34d; --a-rgb:245,158,11; /* 7.1:1 light / 11.1:1 dark */
}

/* ===================== mode: DARK (the default) ===================== */
:root{
  color-scheme:dark;

  /* surfaces */
  --paper:#0d1826;
  --card:#152233;
  --card-2:#1a2839;
  --line:#27394e;
  --line-soft:#1f3044;
  --line-hover:#365071;
  --hairline:rgba(255,255,255,.055);
  --tint:#1a2c3d;
  --tint2:#21374b;
  --row-hover:#1a2a3a;
  --th-1:#1c2b3c;  --th-2:#16232f;

  /* text */
  --text:#e9f1f8;         /* 14.6:1 on --card */
  --muted:#9ab0c6;        /* 7.2:1 on --card, 8.0:1 on --paper */
  --deep:#c3d9ea;         /* secondary headings, table heads, KPI figures */

  /* accent, resolved from the palette above */
  --accent:var(--a-bright);
  --accent2:var(--a-ink-d);
  --brand:var(--a-brand);
  --brand-deep:var(--a-deep);
  --brand-glow:rgba(var(--a-rgb),.34);

  /* the sidebar and the auth hero are dark in BOTH modes; in dark mode they
     sit just under the page rather than contrasting against it */
  --sb-1:#0a1524; --sb-2:#0e2030;

  /* status — inks are light, grounds are deep tints */
  --ok:#4ade80;          --ok-bg:#122a1f;    /* 9.0:1 */
  --warn:#f87171;        --warn-bg:#2f181a;
  --gold:#fbbf24;        --gold-bg:#2d2312;
  --danger:#ef4444;
  --grace-ink:#fdba74;   --grace-bg:#2f2012;
  --info-ink:#93c5fd;    --info-bg:#172542;
  --violet-ink:#c4b5fd;  --violet-bg:#251d3f;
  --neutral-ink:#a8bccd; --neutral-bg:#1e2c3b;

  --scroll-shadow:rgba(0,0,0,.5);
  --shadow-xs:0 1px 2px rgba(0,0,0,.3);
  --shadow-sm:0 1px 2px rgba(0,0,0,.34), 0 2px 6px -1px rgba(0,0,0,.3);
  --shadow:0 1px 2px rgba(0,0,0,.3), 0 4px 10px -2px rgba(0,0,0,.34),
           0 12px 28px -10px rgba(0,0,0,.45);
  --shadow-md:0 2px 5px rgba(0,0,0,.34), 0 10px 22px -5px rgba(0,0,0,.46);
  --shadow-lg:0 6px 16px rgba(0,0,0,.4), 0 22px 52px -12px rgba(0,0,0,.62);

  --page-wash-1:rgba(var(--a-rgb),.13);
  --page-wash-2:rgba(var(--a-rgb),.07);
}

/* ========================== mode: LIGHT ============================= */
:root[data-theme="light"]{
  color-scheme:light;

  --paper:#eff5f5;
  --card:#ffffff;
  --card-2:#fbfdfd;
  --line:#d9e7e6;
  --line-soft:#e7f0ef;
  --line-hover:#b6d5d3;
  --hairline:rgba(7,32,43,.07);
  --tint:#eaf6f6;
  --tint2:#dcedec;
  --row-hover:#f6fcfc;
  --th-1:#eff8f8;  --th-2:#e7f4f3;

  --text:#07202b;         /* 17.2:1 on white */
  --muted:#47686b;        /* 6.1:1 on white, 5.8:1 on --paper */
  --deep:#0b5260;         /* 8.4:1 on white */

  --accent:var(--a-bright);
  --accent2:var(--a-ink-l);
  --brand:var(--a-brand);
  --brand-deep:var(--a-deep);
  --brand-glow:rgba(var(--a-rgb),.30);

  --sb-1:#07202b; --sb-2:#0b5260;

  --ok:#0f7a4f;          --ok-bg:#e7f6ee;    /* 4.8:1 on its ground */
  --warn:#b23f22;        --warn-bg:#fbe9e4;
  --gold:#8a6114;        --gold-bg:#faf3e4;
  --danger:#b02a1c;
  --grace-ink:#8f4e0d;   --grace-bg:#fdeee1;
  --info-ink:#3243a8;    --info-bg:#eef2fb;
  --violet-ink:#6a3a9c;  --violet-bg:#f3ecfa;
  --neutral-ink:#3d5457; --neutral-bg:#e8eded;

  --scroll-shadow:rgba(7,32,43,.12);
  --shadow-xs:0 1px 2px rgba(7,32,43,.05);
  --shadow-sm:0 1px 2px rgba(7,32,43,.05), 0 2px 5px -1px rgba(7,32,43,.045);
  --shadow:0 1px 2px rgba(7,32,43,.045), 0 3px 8px -2px rgba(7,32,43,.05),
           0 10px 24px -8px rgba(7,32,43,.07);
  --shadow-md:0 2px 4px rgba(7,32,43,.05), 0 8px 18px -4px rgba(7,32,43,.10);
  --shadow-lg:0 4px 12px rgba(7,32,43,.09), 0 18px 44px -10px rgba(7,32,43,.22);

  --page-wash-1:rgba(var(--a-rgb),.11);
  --page-wash-2:rgba(var(--a-rgb),.06);
}

/* ============== accent-derived surfaces (the tinted ramp) ===========
   Everything above gives the accent to CONTROLS only — buttons, rails,
   links — which is why switching it barely changed the page. Here the
   whole surface ramp is mixed from the same accent instead, so picking
   Violet actually produces a violet product: page, cards, sidebar,
   table heads, borders and hovers all move together.

   The mix percentages are deliberately small. Surfaces carry text, so
   every point of saturation spends contrast; these were chosen as the
   most colour the ramp can hold with all 120 mode/accent/page/width
   combinations still clearing WCAG AA. Cards stay nearest to neutral
   because they hold the densest text.

   The neutral values above remain the declared fallback: this whole
   block is behind @supports, so a browser without color-mix() renders
   the slate palette rather than losing its backgrounds entirely. */
@supports (color: color-mix(in srgb, red, blue)){
  :root{
    --paper:color-mix(in srgb, var(--a-bright) 9%, #0a1220);
    --card:color-mix(in srgb, var(--a-bright) 7%, #131d2b);
    --card-2:color-mix(in srgb, var(--a-bright) 9%, #17212f);
    --line:color-mix(in srgb, var(--a-bright) 16%, #232f3e);
    --line-soft:color-mix(in srgb, var(--a-bright) 11%, #1b2533);
    --line-hover:color-mix(in srgb, var(--a-bright) 30%, #2e4257);
    --tint:color-mix(in srgb, var(--a-bright) 14%, #162030);
    --tint2:color-mix(in srgb, var(--a-bright) 19%, #1a2434);
    --row-hover:color-mix(in srgb, var(--a-bright) 11%, #16202e);
    --th-1:color-mix(in srgb, var(--a-bright) 13%, #192331);
    --th-2:color-mix(in srgb, var(--a-bright) 10%, #131d2a);
    --sb-1:color-mix(in srgb, var(--a-bright) 9%, #070e1a);
    --sb-2:color-mix(in srgb, var(--a-bright) 20%, #0b1725);
  }
  :root[data-theme="light"]{
    --paper:color-mix(in srgb, var(--a-bright) 10%, #f2f7f7);
    --card:color-mix(in srgb, var(--a-bright) 2%, #ffffff);
    --card-2:color-mix(in srgb, var(--a-bright) 4%, #fcfdfe);
    --line:color-mix(in srgb, var(--a-bright) 18%, #dae4e6);
    --line-soft:color-mix(in srgb, var(--a-bright) 9%, #e9eff1);
    --line-hover:color-mix(in srgb, var(--a-bright) 34%, #cbd9dc);
    --tint:color-mix(in srgb, var(--a-bright) 13%, #f0f6f7);
    --tint2:color-mix(in srgb, var(--a-bright) 20%, #e6eef0);
    --row-hover:color-mix(in srgb, var(--a-bright) 8%, #f7fafb);
    --th-1:color-mix(in srgb, var(--a-bright) 11%, #f2f7f8);
    --th-2:color-mix(in srgb, var(--a-bright) 16%, #eaf1f3);
    --sb-1:color-mix(in srgb, var(--a-bright) 11%, #06151f);
    /* 24%, not the 34% this started at: the sidebar carries white nav text at
       .78 alpha, and Emerald's --a-bright is light enough that a 34% mix lifted
       the ground to 4.04:1. The lightest accent sets this ceiling for all five. */
    --sb-2:color-mix(in srgb, var(--a-bright) 24%, #072b36);
  }
}

/* ================== constants, shared by both modes ================= */
:root{
  --ink:#07202b;          /* brand near-black — for surfaces that are dark in
                             BOTH modes (auth hero, portal bar, OTP, toast) */
  --ink-2:#0b3243;
  --on-dark:#eaf7f7;      /* text on those always-dark surfaces */
  --on-dark-soft:#bfe3e4;

  /* Filled status buttons keep one ground in both modes: white text on them
     passes either way, and a "delete" control should not change weight when
     the lights go out. */
  --fill-ok:#0f7a4f;     --fill-ok-2:#118355;   /* 4.8:1 white-on; #128a5a was 4.4:1 */
  --fill-warn:#b23f22;   --fill-warn-2:#c2492a;
  --fill-gold:#8a6114;   --fill-gold-2:#9a6d17;
  --fill-danger:#b02a1c; --fill-danger-2:#c03122;

  /* ---- type ------------------------------------------------------
     Deliberately NO webfont. The app ships a Content-Security-Policy of
     `style-src 'self'; font-src 'self'`, so a Google Fonts @import would be
     blocked outright — and widening the CSP to admit a third-party origin is
     not a trade worth making for a typeface. The stack below resolves to a
     purpose-built UI face on every platform the customer actually uses:
     Segoe UI Variable on Win11, Segoe UI on Win10, SF on iOS/macOS, Roboto on
     Android. All are screen faces with tall x-heights, which is exactly what
     small text on a phone needs, and they cost zero requests and never FOUT. */
  --font:'Segoe UI Variable Text','Segoe UI',-apple-system,BlinkMacSystemFont,
         Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif;

  --fs-2xs:11.5px;  /* badges, the smallest uppercase labels */
  --fs-xs:12.5px;   /* field labels, table headers, captions */
  --fs-sm:13.5px;   /* secondary copy, notes, pager */
  --fs-md:14.5px;   /* body, table cells, buttons — the workhorse */
  --fs-lg:16px;
  --fs-xl:19px;
  --fs-2xl:23px;
  --fs-3xl:28px;
  --fs-input:var(--fs-md);

  --lh-tight:1.28;
  --lh-body:1.55;
  --lh-loose:1.7;
  --tracking-head:-.022em;

  --r-sm:7px; --r-md:10px; --r-lg:14px; --r-xl:18px; --r-pill:999px;
  --ring:inset 0 0 0 1px var(--hairline);
  --tap:44px;
  --speed:.18s;
  --ease:cubic-bezier(.4,0,.2,1);
}

/* The always-dark furniture — auth hero, portal bar, OTP panel, toast — is
   brand, not theme, so it stays dark in both modes. It still takes the accent,
   otherwise picking Amber left a teal login screen behind an amber app. */
@supports (color: color-mix(in srgb, red, blue)){
  :root{
    --ink:color-mix(in srgb, var(--a-bright) 11%, #05101a);
    --ink-2:color-mix(in srgb, var(--a-bright) 24%, #0a1f2e);
    --hero-mid:color-mix(in srgb, var(--a-bright) 30%, #0a2f3d);
    --hero-end:color-mix(in srgb, var(--a-bright) 42%, #0c4454);
  }
}
:root{
  /* fallbacks for the two hero stops, used when color-mix is unavailable */
  --hero-mid:#0a3d4d;
  --hero-end:#0b5260;
}

/* Switching mode or accent repaints the whole surface ramp at once. Without a
   transition that lands as a hard snap; with one it reads as the product
   changing clothes. Only colour properties are animated — never layout. */
body,.sidebar,.panel,.kpi,.card,.portal-top,.portal-nav,.theme-panel,
.dt-input,.field input,.field select,.field textarea,th,td,.badge,.callout,
.btn,.authcard,.authleft,.mobile-topbar,.flowbar .step{
  transition:background-color var(--speed) var(--ease),
             border-color var(--speed) var(--ease),
             color var(--speed) var(--ease)}

@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important}
}

/* ====================== reset & base typography ===================== */
*{box-sizing:border-box}
html,body{margin:0;padding:0}
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
body{
  font-family:var(--font);
  font-size:var(--fs-md);
  line-height:var(--lh-body);
  color:var(--text);
  min-height:100vh;
  background:var(--paper);
  background-image:
    radial-gradient(1100px 520px at 82% -10%, var(--page-wash-1), transparent 62%),
    radial-gradient(820px 460px at -5% 104%, var(--page-wash-2), transparent 58%);
  background-repeat:no-repeat;
  transition:background-color var(--speed) var(--ease),color var(--speed) var(--ease);
}
::selection{background:var(--brand);color:#fff}

*::-webkit-scrollbar{width:11px;height:11px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{background:var(--line);border-radius:var(--r-pill);
  border:3px solid transparent;background-clip:content-box}
*::-webkit-scrollbar-thumb:hover{background:var(--line-hover);background-clip:content-box}

/* `.sans` is deliberately a no-op now: the whole app is one family, but the
   class is still written by the templates and by assets/app.js. Dropping the
   rule would mean editing markup this change has no business touching. */
.sans{font-family:var(--font)}

h1,h2,h3,h4{margin:0;line-height:var(--lh-tight);font-weight:600;color:var(--text);
  letter-spacing:var(--tracking-head)}
p{margin:0 0 10px}
/* The underline stays — it is the affordance — but weakened to 45%, resolving
   to full strength on hover. */
a{color:var(--accent2);text-decoration-thickness:1px;text-underline-offset:3px;
  text-decoration-color:color-mix(in srgb, currentColor 45%, transparent);
  transition:color var(--speed) var(--ease),text-decoration-color var(--speed) var(--ease)}
a:hover{color:var(--accent);text-decoration-color:currentColor}
button{font-family:inherit;cursor:pointer}
input,select,textarea{font-family:inherit;font-size:var(--fs-input)}
strong,b{font-weight:600}
small{font-size:var(--fs-xs)}
td,th,.kpi .v,.otpchip,.otpshare .code,.walletmini,.statline{font-variant-numeric:tabular-nums}

a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,
textarea:focus-visible,summary:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-sm);
  box-shadow:0 0 0 5px var(--brand-glow)}

/* ========================== admin shell ============================ */
#app{display:flex;min-height:100vh}
.sidebar{width:252px;flex:0 0 252px;color:var(--on-dark);padding:20px 13px;
  position:sticky;top:0;height:100vh;overflow-y:auto;
  background-color:var(--sb-1);background-image:linear-gradient(168deg,var(--sb-1) 0%,var(--sb-2) 100%);
  box-shadow:1px 0 0 rgba(255,255,255,.06) inset, 0 0 40px rgba(0,0,0,.3)}
.sidebar::after{content:'';position:absolute;top:0;right:0;bottom:0;width:1px;
  background:linear-gradient(180deg,transparent,rgba(255,255,255,.15) 22%,
    rgba(255,255,255,.15) 78%,transparent);pointer-events:none}

.brand{display:flex;align-items:center;gap:11px;padding:5px 8px 18px;
  border-bottom:1px solid rgba(255,255,255,.12);margin-bottom:14px}
.brand .drop{width:33px;height:33px;flex:0 0 33px;
  filter:drop-shadow(0 2px 7px rgba(var(--a-rgb),.5))}
.brand .name{font-size:var(--fs-xl);letter-spacing:.1px;color:#fff}
.brand .sub{font-size:var(--fs-2xs);letter-spacing:1.9px;text-transform:uppercase;
  color:var(--on-dark-soft);margin-top:2px;font-weight:600}

nav.tabs{display:flex;flex-direction:column;gap:2px}
/* Section headings. The nav went from twelve equal links to five short,
   scannable runs — the single change that most makes a long sidebar feel
   designed rather than dumped. */
.navgroup{font-size:var(--fs-2xs);letter-spacing:1.5px;text-transform:uppercase;
  font-weight:700;color:rgba(255,255,255,.78);padding:16px 13px 6px}
.navgroup:first-child{padding-top:4px}
nav.tabs a{all:unset;font-family:var(--font);font-size:var(--fs-md);font-weight:500;
  color:rgba(255,255,255,.78);padding:10px 13px;border-radius:var(--r-md);cursor:pointer;
  display:flex;align-items:center;gap:11px;min-height:40px;box-sizing:border-box;
  position:relative;transition:background var(--speed) var(--ease),color var(--speed) var(--ease)}
nav.tabs a .icon{width:18px !important;height:18px !important;flex:0 0 18px;
  color:rgba(255,255,255,.5);transition:color var(--speed) var(--ease)}
nav.tabs a:hover{background:rgba(255,255,255,.07);color:#fff}
nav.tabs a:hover .icon{color:var(--a-bright)}
/* The selected tab is the one place the accent is a fill. Its ink is checked
   against the DARKER end of the gradient, not the lighter one. */
nav.tabs a.active{background-color:var(--sb-2);background-image:linear-gradient(135deg,
    rgba(var(--a-rgb),.22),rgba(var(--a-rgb),.10));
  color:#fff;font-weight:600;box-shadow:inset 0 0 0 1px rgba(var(--a-rgb),.32)}
nav.tabs a.active .icon{color:var(--a-bright)}
/* Leading rail on the active item — the cue that survives at a glance. */
nav.tabs a.active::before{content:'';position:absolute;left:0;top:9px;bottom:9px;width:3px;
  border-radius:0 3px 3px 0;background:var(--a-bright);
  box-shadow:0 0 12px rgba(var(--a-rgb),.8)}
nav.tabs a:focus-visible{outline:2px solid var(--a-bright);outline-offset:-2px;box-shadow:none}

/* Expandable menu group (Lead Management). The parent deliberately never takes
   .active: the filled row is always the sub-menu you are actually on, so the
   eye lands on one place and not two. The parent's own state is the chevron
   plus a quiet tint, which is enough to say "you are inside this group".

   --nav-speed is one duration for the whole gesture. The chevron used to turn
   on var(--speed) while the drawer moved on its own timing, which reads as two
   things happening; sharing a token makes it one. .18s is right for a hover
   tint but too brisk for ~500px of travel, so this sits nearer the .22s the
   panel chevron and the .28s the mobile drawer already use. */
.navparent{--nav-speed:.22s;display:flex;flex-direction:column;gap:2px}
/* A collapsed .navsub is still in the flow (that is what lets it animate), so
   without this the parent keeps the 2px gap it left for children that are no
   longer on screen and sits 2px taller than every plain link beside it. */
.navparent:not(.open){gap:0}
.navparent.open{background:rgba(255,255,255,.04);border-radius:var(--r-md);padding-bottom:4px}
.navparent.open>.navtop{color:#fff}
.navparent.open>.navtop .icon{color:var(--a-bright)}
.nav-chev{width:14px;height:14px;flex:0 0 14px;margin-left:auto;color:rgba(255,255,255,.55);
  transition:transform var(--nav-speed) var(--ease)}
.navparent.open .nav-chev{transform:rotate(90deg)}

/* The expand/collapse itself.
   height:auto cannot be animated, so the drawer is a one-row grid and the row
   is what moves: 1fr (as tall as the children) <-> 0fr (nothing). The children
   sit in .navsub-in, which clips them while the row shrinks — that inner box
   is why the markup has two divs rather than one.
   The hidden attribute stays the single source of truth for open/shut: the
   server writes it, assets/app.js toggles it, and everything below is keyed
   off it. Overriding the browser's [hidden]{display:none} is deliberate, and
   visibility carries the part display was doing that still matters — a
   collapsed sub-menu must be out of the accessibility tree and its links out
   of the tab order. It is transitioned with a delay rather than set outright
   so it holds off until the drawer has finished closing; on the way open it
   flips at 0s, before the first frame.
   prefers-reduced-motion is already handled globally: the reset near the top
   of this file forces every transition-duration to .01ms, which turns all of
   this back into the instant show/hide it replaced. */
.navsub{display:grid;grid-template-rows:1fr;visibility:visible;
  transition:grid-template-rows var(--nav-speed) var(--ease),visibility 0s}
.navsub[hidden]{display:grid;grid-template-rows:0fr;visibility:hidden;
  transition:grid-template-rows var(--nav-speed) var(--ease),visibility 0s var(--nav-speed)}
/* Carries everything that draws: the rail down the left is what makes the
   children read as belonging to the parent rather than as eleven more
   top-level links. It holds no vertical padding on purpose — with
   box-sizing:border-box any would survive the squeeze and leave a stub of rail
   behind under a closed group. */
.navsub-in{overflow:hidden;display:flex;flex-direction:column;gap:2px;
  margin-left:22px;padding-left:9px;border-left:1px solid rgba(255,255,255,.16);
  opacity:1;transition:opacity var(--nav-speed) var(--ease)}
.navsub[hidden] .navsub-in{opacity:0}
nav.tabs .navsub a{font-size:var(--fs-sm);min-height:34px;padding:7px 11px;border-radius:var(--r-sm)}
nav.tabs .navsub a.active::before{top:7px;bottom:7px}

.side-foot{margin-top:22px;padding-top:15px;border-top:1px solid rgba(255,255,255,.12);
  font-size:var(--fs-xs);color:rgba(255,255,255,.55);line-height:var(--lh-loose);display:none}

/* Signed-in identity, pinned under the nav. */
.side-user{display:flex;align-items:center;gap:10px;margin-top:18px;padding:11px 10px;
  border-top:1px solid rgba(255,255,255,.12);border-radius:var(--r-md)}
.su-av{width:34px;height:34px;flex:0 0 34px;border-radius:50%;display:flex;
  align-items:center;justify-content:center;font-weight:700;font-size:var(--fs-md);color:#fff;
  background-color:var(--a-brand);background-image:linear-gradient(150deg,var(--a-bright),var(--a-brand));
  box-shadow:0 2px 10px rgba(var(--a-rgb),.4)}
.su-meta{min-width:0;flex:1}
.su-name{font-size:var(--fs-sm);font-weight:600;color:#fff;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.su-role{font-size:var(--fs-2xs);color:rgba(255,255,255,.78);text-transform:uppercase;
  letter-spacing:1px;font-weight:600}
.su-out{all:unset;cursor:pointer;display:flex;align-items:center;justify-content:center;
  width:34px;height:34px;flex:0 0 34px;border-radius:var(--r-md);color:rgba(255,255,255,.65);
  transition:background var(--speed) var(--ease),color var(--speed) var(--ease)}
.su-out:hover{background:rgba(255,255,255,.1);color:#ffd4c9}
.su-out:focus-visible{outline:2px solid var(--a-bright);outline-offset:-2px;box-shadow:none}

/* Mobile-only chrome. These three elements are always in the markup; they stay
   hidden until the sidebar collapses into a drawer at the 900px breakpoint,
   which re-shows each one. Without this rule the hamburger bar becomes a flex
   item beside the sidebar on a desktop. */
.mobile-topbar,.sidebar-close,.sidebar-overlay{display:none}

main{flex:1;min-width:0;padding:30px 36px 68px}
.topbar{display:flex;justify-content:space-between;align-items:flex-end;
  margin-bottom:24px;gap:20px;flex-wrap:wrap}
.topbar h1{font-size:var(--fs-3xl);font-weight:600;color:var(--text)}
.topbar .desc{font-size:var(--fs-sm);color:var(--muted);margin-top:6px;max-width:70ch}

/* ============================ KPI cards ============================ */
.kpirow{display:grid;grid-template-columns:repeat(auto-fit,minmax(172px,1fr));gap:13px;
  margin-bottom:26px}
.kpi{position:relative;overflow:hidden;background-color:var(--card);background-image:linear-gradient(180deg,var(--card),var(--card-2));
  border:1px solid var(--line-soft);border-radius:var(--r-lg);padding:16px 17px;
  box-shadow:var(--shadow-sm),var(--ring)}
.kpi::after{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;
  background:var(--accent);opacity:0;transition:opacity var(--speed) var(--ease)}
.kpi .v{font-size:var(--fs-2xl);line-height:1.12;color:var(--deep);font-weight:700;
  letter-spacing:-.025em}
.kpi .l{font-size:var(--fs-xs);letter-spacing:.62px;text-transform:uppercase;
  color:var(--muted);margin-top:6px;font-weight:600;line-height:1.35}
.kpi.warn .v{color:var(--warn)} .kpi.gold .v{color:var(--gold)} .kpi.ok .v{color:var(--ok)}
.kpi.warn::after{background:var(--warn);opacity:1}
.kpi.gold::after{background:var(--gold);opacity:1}
.kpi.ok::after{background:var(--ok);opacity:1}
a.kpi{display:block;text-decoration:none;color:inherit;
  transition:transform var(--speed) var(--ease),box-shadow var(--speed) var(--ease),
  border-color var(--speed) var(--ease)}
a.kpi:hover{transform:translateY(-2px);box-shadow:var(--shadow-md),var(--ring);
  border-color:var(--line)}
a.kpi:hover::after{opacity:1}
a.kpi.active{border-color:transparent;box-shadow:0 0 0 1.5px var(--accent),var(--shadow-md)}
a.kpi.active::after{opacity:1;background:var(--accent)}
a.kpi.active .l{color:var(--accent2)}

/* ============================= panels ============================== */
.panel{background:var(--card);border:1px solid var(--line-soft);border-radius:var(--r-lg);
  box-shadow:var(--shadow),var(--ring);margin-bottom:22px;overflow:hidden}
.panel .ph{display:flex;justify-content:space-between;align-items:center;padding:16px 19px;
  border-bottom:1px solid var(--line-soft);gap:12px;flex-wrap:wrap;
  background-color:var(--card);background-image:linear-gradient(180deg,var(--card-2),var(--card))}
details.panel > summary.ph{cursor:pointer;list-style:none}
details.panel > summary.ph::-webkit-details-marker{display:none}
details.panel > summary.ph::marker{content:''}
details.panel > summary.ph:hover{background:var(--tint)}
.ph .chev{color:var(--muted);transition:transform .22s var(--ease);flex:0 0 16px}
details.panel[open] > summary.ph .chev{transform:rotate(180deg)}
details.panel:not([open]) > summary.ph{border-bottom:none}
.panel .ph h3{font-size:var(--fs-lg);font-weight:600;color:var(--text);margin:0}
.panel .ph .note{font-size:var(--fs-sm);color:var(--muted);margin-top:3px;font-weight:400;
  letter-spacing:0}
/* Two or more buttons in a panel header: without this they would be spread out
   by the header's space-between, one drifting into the middle of the bar. The
   auto margin pushes the whole group right and keeps them a pair. */
.panel .ph .phacts{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-left:auto}
.panel .pb{padding:17px 19px}

/* ============================= buttons =============================
   The gradient runs --brand -> --brand-deep, i.e. always toward the darker
   end, so white text that clears 4.5:1 on --brand clears it on every stop. */
.btn{font-family:var(--font);font-size:var(--fs-sm);font-weight:600;
  background-color:var(--brand-deep);background-image:linear-gradient(180deg,var(--brand),var(--brand-deep));
  border:1px solid var(--brand-deep);color:#fff;padding:9px 17px;border-radius:var(--r-md);
  letter-spacing:.1px;line-height:1.35;display:inline-flex;align-items:center;
  justify-content:center;gap:7px;text-decoration:none;min-height:38px;
  box-shadow:0 1px 2px rgba(0,0,0,.18), inset 0 1px 0 rgba(255,255,255,.16);
  transition:filter var(--speed) var(--ease),box-shadow var(--speed) var(--ease),
  transform var(--speed) var(--ease)}
.btn:hover{filter:brightness(1.1);color:#fff;
  box-shadow:0 3px 12px -2px rgba(var(--a-rgb),.4), inset 0 1px 0 rgba(255,255,255,.2)}
.btn:active{transform:translateY(1px);filter:brightness(.95);
  box-shadow:0 1px 2px rgba(0,0,0,.2), inset 0 1px 2px rgba(0,0,0,.25)}
.btn.ghost{background:var(--card-2);color:var(--accent2);border-color:var(--line);
  box-shadow:var(--shadow-xs)}
.btn.ghost:hover{background:var(--tint);border-color:var(--accent);color:var(--accent2);filter:none}
.btn.small{padding:6px 12px;font-size:var(--fs-xs);border-radius:var(--r-sm);min-height:32px}
.btn.warn{background:linear-gradient(180deg,var(--fill-warn-2),var(--fill-warn));border-color:#9b3519}
.btn.danger{background:linear-gradient(180deg,var(--fill-danger-2),var(--fill-danger));
  border-color:#96231a;color:#fff}
.btn.danger:hover{filter:brightness(1.08);color:#fff}
.btn.gold{background:linear-gradient(180deg,var(--fill-gold-2),var(--fill-gold));border-color:#75510f}
.btn.ok{background:linear-gradient(180deg,var(--fill-ok-2),var(--fill-ok));border-color:#0c6541}
.btn[disabled]{opacity:.45;cursor:not-allowed;filter:none;transform:none;box-shadow:var(--shadow-xs)}

/* ============================= tables ============================== */
table{width:100%;border-collapse:collapse;font-family:var(--font);font-size:var(--fs-md)}
th{text-align:left;background-color:var(--th-2);background-image:linear-gradient(180deg,var(--th-1),var(--th-2));color:var(--deep);
  padding:12px 14px;font-size:var(--fs-xs);letter-spacing:.55px;text-transform:uppercase;
  font-weight:700;border-bottom:1px solid var(--line);white-space:nowrap}
td{padding:13px 14px;border-bottom:1px solid var(--line-soft);vertical-align:middle;
  line-height:1.45}
tr:last-child td{border-bottom:none}
tr:hover td{background:var(--row-hover)}
.muted{color:var(--muted)}
.empty{padding:30px;text-align:center;color:var(--muted);font-size:var(--fs-md)}

/* ============================= badges ============================== */
.badge{display:inline-block;font-size:var(--fs-2xs);font-weight:700;letter-spacing:.45px;
  padding:4px 11px;border-radius:var(--r-pill);text-transform:uppercase;
  line-height:1.4;white-space:nowrap;box-shadow:inset 0 0 0 1px var(--hairline)}
.b-active{background:var(--ok-bg);color:var(--ok)}
.b-expiring{background:var(--gold-bg);color:var(--gold)}
.b-grace{background:var(--grace-bg);color:var(--grace-ink)}
.b-lapsed{background:var(--warn-bg);color:var(--warn)}
.b-none{background:var(--neutral-bg);color:var(--neutral-ink)}
.b-scheduled{background:var(--tint);color:var(--accent2)}
.b-assigned{background:var(--info-bg);color:var(--info-ink)}
.b-started{background:var(--gold-bg);color:var(--gold)}
.b-completed{background:var(--ok-bg);color:var(--ok)}
.b-regular{background:var(--tint);color:var(--deep)}
.b-campaign{background:var(--violet-bg);color:var(--violet-ink)}
.b-affiliate{background:var(--tint);color:var(--accent2)}
.b-store{background:var(--gold-bg);color:var(--gold)}

/* ============================== forms ============================== */
.formgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px;
  margin-bottom:6px}
.field label{display:block;font-size:var(--fs-xs);color:var(--muted);margin-bottom:6px;
  text-transform:uppercase;letter-spacing:.55px;font-weight:600}
.field input,.field select,.field textarea{width:100%;padding:10px 12px;
  border:1px solid var(--line);border-radius:var(--r-sm);font-size:var(--fs-input);
  background:var(--card-2);color:var(--text);line-height:1.4;min-height:42px;
  box-shadow:inset 0 1px 2px rgba(0,0,0,.06);
  transition:border-color var(--speed) var(--ease),box-shadow var(--speed) var(--ease)}
.field input:hover,.field select:hover,.field textarea:hover{border-color:var(--line-hover)}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;
  border-color:var(--accent);box-shadow:0 0 0 3.5px var(--brand-glow)}
.field input::placeholder,.field textarea::placeholder{color:var(--muted);opacity:.75}
.field.check{display:flex;align-items:center;gap:9px;margin-top:18px}
.field.check input{width:auto;min-height:0;accent-color:var(--brand);box-shadow:none}
.field.check label{margin-bottom:0;text-transform:none;letter-spacing:0;
  font-size:var(--fs-md);color:var(--text);font-weight:400}
/* Reminder-recipient picker (admin/settings.php): a role heading with the
   people in that role indented under it. The members are set apart by a rule
   down the left rather than by colour, so the grouping survives every theme. */
.checkgroup{margin-top:16px}
.checkgroup>.field.check label{font-weight:600}
.checkmembers{margin:6px 0 0 12px;padding-left:16px;border-left:2px solid var(--line)}
.checkmembers .field.check{margin-top:9px}
.checkmembers .submeta{margin-left:6px}
/* Running total of who is ticked. The people table searches and pages, so most
   rows are out of view at any moment — this is what stops a choice made three
   searches ago from disappearing. */
.checkpicked{font-size:var(--fs-sm);color:var(--text);background:var(--tint);
  border-radius:var(--r-sm);padding:8px 11px;margin-bottom:10px}
/* The checkbox column of the people table: no padding fight with the row. */
.checkcell{width:34px}
.checkcell input{width:auto;min-height:0;accent-color:var(--brand);box-shadow:none;margin:0}
.checkmembers td label{cursor:pointer;margin:0;text-transform:none;letter-spacing:0;
  font-size:var(--fs-md);color:var(--text);font-weight:500}
/* A member covered by its "All …" box, or with no email, reads as unavailable
   rather than merely unticked. */
.field.check input:disabled{cursor:not-allowed}
.field.check input:disabled+label{color:var(--muted)}
.formfoot{display:flex;gap:10px;justify-content: center;margin-top:17px;flex-wrap:wrap}
.addbox{background:var(--tint);border:1px dashed var(--accent);border-radius:var(--r-lg);
  padding:19px;margin-bottom:18px;box-shadow:var(--ring)}

/* =========================== small parts =========================== */
.flowbar{display:flex;align-items:center;gap:7px;flex-wrap:wrap;font-size:var(--fs-xs);
  color:var(--muted);margin:12px 0 4px}
.flowbar .step{background:var(--card-2);border:1px solid var(--line);border-radius:var(--r-pill);
  padding:5px 12px;color:var(--deep);font-weight:500;box-shadow:var(--shadow-xs)}
.flowbar .step.on{background-color:var(--brand-deep);background-image:linear-gradient(180deg,var(--brand),var(--brand-deep));color:#fff;
  border-color:var(--brand-deep);font-weight:700;
  box-shadow:0 2px 8px -1px var(--brand-glow), inset 0 1px 0 rgba(255,255,255,.18)}
.flowbar .arrow{color:var(--muted);opacity:.55}

.callout{background:var(--tint);border-left:4px solid var(--accent);
  padding:13px 16px;border-radius:0 var(--r-md) var(--r-md) 0;font-size:var(--fs-sm);
  color:var(--deep);margin:12px 0;line-height:var(--lh-body);box-shadow:var(--shadow-xs)}
.callout.gold{background:var(--gold-bg);border-color:var(--gold);color:var(--gold)}
.callout.warn{background:var(--warn-bg);border-color:var(--warn);color:var(--warn)}

.otpchip{display:inline-block;background-color:var(--ink);background-image:linear-gradient(180deg,var(--ink-2),var(--ink));
  color:#7fe3e8;padding:6px 13px;border-radius:var(--r-sm);letter-spacing:3px;font-weight:700;
  font-size:var(--fs-md);box-shadow:var(--shadow-xs), inset 0 1px 0 rgba(255,255,255,.08)}
.otpshare{background-color:var(--ink);background-image:linear-gradient(150deg,var(--ink-2),var(--ink));color:#7fe3e8;
  border-radius:var(--r-lg);padding:18px 22px;text-align:center;
  box-shadow:var(--shadow-md), inset 0 1px 0 rgba(255,255,255,.1)}
.otpshare .code{font-size:var(--fs-3xl);letter-spacing:8px;font-weight:700;margin-top:6px;
  text-shadow:0 0 22px rgba(127,227,232,.45)}

.flash{position:fixed;bottom:24px;right:24px;
  background-color:var(--ink);background-image:linear-gradient(180deg,var(--ink-2),var(--ink));color:#e7fbfb;
  padding:14px 19px;border-radius:var(--r-md);font-size:var(--fs-md);
  box-shadow:var(--shadow-lg), inset 0 1px 0 rgba(255,255,255,.1);
  border:1px solid rgba(255,255,255,.09);z-index:999;max-width:340px;line-height:1.45}
.flash.err{background:linear-gradient(180deg,var(--fill-warn-2),var(--fill-warn))}

.grid2{display:grid;grid-template-columns:1fr 1fr;gap:18px}

.walletmini{display:flex;justify-content:space-between;align-items:center;gap:12px;
  padding:11px 0;border-bottom:1px solid var(--line-soft);font-size:var(--fs-md)}
.walletmini:last-child{border-bottom:none}
.pillrow{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;align-items:center}
.statline{display:flex;justify-content:space-between;gap:12px;padding:10px 0;
  border-bottom:1px solid var(--line-soft);font-size:var(--fs-md)}
.statline:last-child{border-bottom:none}

/* =========================== text utilities ==========================
   These exist to retire the ~45 inline font-size overrides the pages used
   to carry, which hard-coded 11–13px. Frozen at a literal px value, none of
   them took part in the mobile bump at the foot of this file, so on a phone
   they stayed the smallest text on a screen where everything around them had
   grown — which is most of what "text is not clearly visible" meant.
   ==================================================================== */
.submeta{font-size:var(--fs-xs);color:var(--muted);line-height:1.45;margin-top:3px}
.cell-type{font-size:var(--fs-xs);color:var(--muted);text-transform:uppercase;letter-spacing:.45px}
.edit-head{margin:0 0 12px;font-size:var(--fs-lg);color:var(--deep);font-weight:600}
.subhead{font-size:var(--fs-xs);color:var(--muted);text-transform:uppercase;letter-spacing:.62px;
  font-weight:700;margin:0 0 8px}
.hint{font-size:var(--fs-sm);color:var(--muted);line-height:1.5;margin-top:10px}
.hint-tight{font-size:var(--fs-sm);color:var(--muted);line-height:1.5;margin:-2px 0 10px}
.note-sm{font-size:var(--fs-sm);color:var(--muted);line-height:1.5;margin-top:6px}
.text-sm{font-size:var(--fs-sm)}
.link-sm{font-size:var(--fs-sm);font-weight:500}
.role-tag{font-size:var(--fs-sm);opacity:.78}
.rowselect{padding:7px 10px;border-radius:var(--r-sm);border:1px solid var(--line);
  font-size:var(--fs-sm);background:var(--card-2);color:var(--text);max-width:100%;
  font-family:var(--font);box-shadow:inset 0 1px 2px rgba(0,0,0,.06)}
.rowselect:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--brand-glow)}

/* ==================== appearance drawer (right) =====================
   Opens from the right edge on every page. Behaviour is bound in
   assets/app.js via the data-theme-* hooks; CSP forbids inline script, so
   there is not a single handler attribute in the markup. */
.theme-fab{all:unset;position:fixed;right:0;top:50%;transform:translateY(-50%);z-index:880;
  display:flex;align-items:center;justify-content:center;width:42px;height:46px;cursor:pointer;
  border-radius:var(--r-md) 0 0 var(--r-md);color:#fff;
  background-color:var(--brand-deep);background-image:linear-gradient(180deg,var(--brand),var(--brand-deep));
  box-shadow:var(--shadow-md), inset 0 1px 0 rgba(255,255,255,.16);
  transition:width var(--speed) var(--ease),filter var(--speed) var(--ease)}
.theme-fab:hover{width:48px;filter:brightness(1.1)}
.theme-fab:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

.theme-scrim{position:fixed;inset:0;z-index:890;background:rgba(3,10,18,.55)}
.theme-scrim[hidden]{display:none}

.theme-panel{position:fixed;top:0;right:0;bottom:0;z-index:900;width:300px;max-width:88vw;
  background:var(--card);border-left:1px solid var(--line);box-shadow:var(--shadow-lg);
  padding:20px;display:flex;flex-direction:column;gap:22px;overflow-y:auto;
  transform:translateX(100%);transition:transform .22s var(--ease)}
.theme-panel.open{transform:translateX(0)}
.theme-panel[hidden]{display:none}
.tp-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.tp-head h3{font-size:var(--fs-lg)}
.tp-sub{font-size:var(--fs-xs);color:var(--muted);margin-top:3px}
.tp-close{all:unset;cursor:pointer;display:flex;align-items:center;justify-content:center;
  width:34px;height:34px;border-radius:var(--r-md);color:var(--muted);
  transition:background var(--speed) var(--ease),color var(--speed) var(--ease)}
.tp-close:hover{background:var(--tint);color:var(--text)}
.tp-close:focus-visible{outline:2px solid var(--accent);outline-offset:1px;box-shadow:none}
.tp-label{font-size:var(--fs-2xs);letter-spacing:1.3px;text-transform:uppercase;
  font-weight:700;color:var(--muted);margin-bottom:9px}

.tp-modes{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.tp-mode{all:unset;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:8px;
  padding:12px 8px;border-radius:var(--r-md);border:1.5px solid var(--line);
  font-size:var(--fs-sm);font-weight:600;color:var(--text);background:var(--card-2);
  transition:border-color var(--speed) var(--ease),background var(--speed) var(--ease)}
.tp-mode:hover{border-color:var(--line-hover)}
.tp-mode.on{border-color:var(--accent);background:var(--tint);
  box-shadow:0 0 0 3px var(--brand-glow)}
.tp-mode:focus-visible{outline:2px solid var(--accent);outline-offset:2px;box-shadow:none}
/* A two-bar miniature of the layout it selects, so the choice is shown
   rather than only named. */
.tp-prev{display:flex;gap:3px;width:100%;height:30px;border-radius:5px;padding:4px;
  border:1px solid var(--line)}
.tp-prev i{display:block;border-radius:2px}
.tp-prev i:first-child{width:26%}
.tp-prev i:last-child{flex:1}
.tp-prev-dark{background:#0d1826}
.tp-prev-dark i:first-child{background:#0a1524}
.tp-prev-dark i:last-child{background:#152233}
.tp-prev-light{background:#eff5f5}
.tp-prev-light i:first-child{background:#07202b}
.tp-prev-light i:last-child{background:#fff}

.tp-accents{display:flex;flex-direction:column;gap:5px}
.tp-accent{all:unset;cursor:pointer;display:flex;align-items:center;gap:11px;padding:9px 11px;
  border-radius:var(--r-md);border:1.5px solid transparent;font-size:var(--fs-sm);
  font-weight:500;color:var(--text);min-height:40px;box-sizing:border-box;
  transition:background var(--speed) var(--ease),border-color var(--speed) var(--ease)}
.tp-accent:hover{background:var(--tint)}
.tp-accent.on{border-color:var(--accent);background:var(--tint);font-weight:600}
.tp-accent:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.tp-dot{width:20px;height:20px;flex:0 0 20px;border-radius:50%;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.2), 0 1px 3px rgba(0,0,0,.25)}
.tp-dot-teal{background:linear-gradient(140deg,#12a3ad,#0a7681)}
.tp-dot-blue{background:linear-gradient(140deg,#3b82f6,#2563eb)}
.tp-dot-violet{background:linear-gradient(140deg,#a78bfa,#7c3aed)}
.tp-dot-emerald{background:linear-gradient(140deg,#34d399,#047857)}
.tp-dot-amber{background:linear-gradient(140deg,#f59e0b,#b45309)}

/* =========================== auth screens ==========================
   The hero is dark in both modes — it is brand furniture, not a surface the
   theme owns. */
.authwrap{min-height:100vh;display:flex}
.authleft{flex:1.1;position:relative;overflow:hidden;color:var(--on-dark);padding:62px;
  display:flex;flex-direction:column;justify-content:center;
  background-color:var(--ink);background-image:linear-gradient(152deg,var(--ink) 0%,var(--hero-mid) 55%,var(--hero-end) 100%)}
.authleft::before{content:'';position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(620px 420px at 78% 12%, rgba(var(--a-rgb),.32), transparent 66%),
    radial-gradient(520px 400px at 8% 92%, rgba(var(--a-rgb),.16), transparent 62%)}
.authleft > *{position:relative}
.authleft .brandrow{display:flex;align-items:center;gap:12px;margin-bottom:28px}
.authleft .brandrow .name{font-size:var(--fs-xl);font-weight:600;color:#fff}
.authleft h1{font-size:var(--fs-3xl);font-weight:600;max-width:440px;line-height:1.22;color:#fff}
.authleft p{font-size:var(--fs-md);color:var(--on-dark-soft);max-width:400px;margin-top:17px;
  line-height:var(--lh-loose)}
.authleft ul{margin-top:24px;font-size:var(--fs-md);color:#d5f0f0;line-height:2;padding-left:20px}
.authleft ul li{padding-left:4px}
.authright{flex:1;display:flex;align-items:center;justify-content:center;padding:40px;
  background:var(--paper)}
.authcard{width:100%;max-width:414px;background:var(--card);border:1px solid var(--line-soft);
  border-radius:var(--r-xl);box-shadow:var(--shadow-lg),var(--ring);padding:30px}
.authtabs{display:flex;gap:5px;margin-bottom:22px;background:var(--tint2);
  border-radius:var(--r-md);padding:4px;box-shadow:inset 0 1px 2px rgba(0,0,0,.08)}
.authtabs a{flex:1;all:unset;text-align:center;font-family:var(--font);font-size:var(--fs-md);
  font-weight:600;padding:10px;border-radius:var(--r-sm);color:var(--deep);cursor:pointer;
  display:block;min-height:40px;box-sizing:border-box;
  transition:background var(--speed) var(--ease),box-shadow var(--speed) var(--ease)}
.authtabs a.active{background:var(--card);box-shadow:var(--shadow-sm);color:var(--text)}
/* One pill row of links, two names for it: .roletabs picks a role on the auth
   pages, .formtabs picks which of a page's forms is showing (Create Leads).
   Both are links to a URL, not scripted switches, so the selected one is the
   page you are on — hence a.active rather than any [aria-selected] state. */
.roletabs,.formtabs{display:flex;gap:7px;margin-bottom:16px;flex-wrap:wrap}
.roletabs a,.formtabs a{all:unset;font-family:var(--font);font-size:var(--fs-sm);font-weight:500;
  padding:8px 14px;border-radius:var(--r-pill);border:1px solid var(--line);color:var(--muted);
  cursor:pointer;display:inline-block;background:var(--card-2);
  transition:background var(--speed) var(--ease),color var(--speed) var(--ease),
  border-color var(--speed) var(--ease)}
.roletabs a:hover,.formtabs a:hover{border-color:var(--accent);color:var(--accent2)}
.roletabs a.active,.formtabs a.active{background-color:var(--brand-deep);background-image:linear-gradient(180deg,var(--brand),var(--brand-deep));
  border-color:var(--brand-deep);color:#fff;font-weight:600;
  box-shadow:0 2px 8px -1px var(--brand-glow)}
.demo-creds{font-size:var(--fs-sm);color:var(--muted);background:var(--tint);
  border-radius:var(--r-md);padding:13px 15px;margin-top:16px;line-height:var(--lh-loose);
  box-shadow:var(--ring)}
.demo-creds b{color:var(--deep)}
.errmsg{background:var(--warn-bg);color:var(--warn);font-size:var(--fs-sm);font-weight:500;
  padding:12px 15px;border-radius:var(--r-md);margin-bottom:14px;line-height:1.5;
  border:1px solid var(--warn)}
.okmsg{background:var(--ok-bg);color:var(--ok);font-size:var(--fs-sm);font-weight:500;
  padding:12px 15px;border-radius:var(--r-md);margin-bottom:14px;line-height:1.55;
  border:1px solid var(--ok)}
.authleft .brandrow svg.drop{width:33px !important;height:33px !important;flex-shrink:0;
  filter:drop-shadow(0 2px 9px rgba(var(--a-rgb),.55))}

/* ===================== role portals (customer/partner/technician) === */
.portal-shell{min-height:100vh;background:var(--paper)}
.portal-top{position:relative;overflow:hidden;color:var(--on-dark);padding:17px 30px;
  display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;
  background-color:var(--sb-1);background-image:linear-gradient(120deg,var(--sb-1) 0%,var(--sb-2) 100%);
  box-shadow:0 1px 0 rgba(255,255,255,.07) inset, var(--shadow-sm)}
.portal-top::before{content:'';position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(560px 240px at 88% -40%, rgba(var(--a-rgb),.3), transparent 68%)}
.portal-top > *{position:relative}
.portal-top .brandrow{display:flex;align-items:center;gap:10px;font-size:var(--fs-lg)}
.portal-top .brandrow svg.drop{width:27px;height:27px;flex:0 0 27px;
  filter:drop-shadow(0 2px 7px rgba(var(--a-rgb),.5))}
.portal-top .who{font-size:var(--fs-sm);color:var(--on-dark-soft);text-align:right;line-height:1.5}
.portal-top .who strong{color:#fff}
.portal-top .who a{color:#ffd4c9;text-decoration:none;font-weight:500}
.portal-top .who a:hover{text-decoration:underline;color:#ffd4c9}
.portal-nav{display:flex;gap:5px;padding:12px 30px 0;flex-wrap:wrap;background:var(--card);
  border-bottom:1px solid var(--line)}
.portal-nav a{all:unset;font-family:var(--font);font-size:var(--fs-md);font-weight:500;
  padding:11px 18px;border-radius:var(--r-md) var(--r-md) 0 0;color:var(--muted);cursor:pointer;
  border:1px solid transparent;display:inline-block;min-height:40px;box-sizing:border-box;
  transition:background var(--speed) var(--ease),color var(--speed) var(--ease)}
.portal-nav a:hover{background:var(--tint);color:var(--text)}
.portal-nav a.active{color:var(--text);border:1px solid var(--line);border-bottom-color:var(--card);
  background:var(--tint);font-weight:700;margin-bottom:-1px}
.portal-body{padding:28px 30px 68px;max-width:1060px;margin:0 auto}

/* ============================ data tables ============================
   The chrome around every table in the app: the live-search toolbar, the
   horizontal scroll frame, the sortable header affordance and the pager.
   Rendered by includes/table.php, driven by the dataTable component in
   assets/app.js. One definition here is what keeps Leads, Customers, Jobs,
   the ledger and the portal tables looking and behaving identically.
   ==================================================================== */
.dt{display:flex;flex-direction:column;gap:13px;min-width:0}

.dt-tools{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.dt-search{position:relative;flex:1 1 280px;min-width:0;max-width:440px;display:flex;align-items:center}
.dt-search .dt-ico{position:absolute;left:13px;color:var(--muted);pointer-events:none}
.dt-input{width:100%;padding:10px 36px 10px 37px;border:1px solid var(--line);
  border-radius:var(--r-md);font-size:var(--fs-input);background:var(--card-2);color:var(--text);
  appearance:none;min-height:42px;box-shadow:inset 0 1px 2px rgba(0,0,0,.06);
  transition:border-color var(--speed) var(--ease),box-shadow var(--speed) var(--ease)}
.dt-input::placeholder{color:var(--muted);opacity:.75}
.dt-input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3.5px var(--brand-glow)}
/* the browser's own clear affordance duplicates .dt-x */
.dt-input::-webkit-search-cancel-button{display:none}
.dt-x{all:unset;position:absolute;right:7px;display:flex;align-items:center;justify-content:center;
  width:26px;height:26px;border-radius:50%;cursor:pointer;color:var(--muted);font-size:19px;
  line-height:1;transition:background var(--speed) var(--ease),color var(--speed) var(--ease)}
.dt-x:hover{background:var(--tint2);color:var(--text)}
.dt-x:focus-visible{outline:2px solid var(--accent);outline-offset:1px;box-shadow:none}
.dt-count{font-size:var(--fs-sm);color:var(--muted);letter-spacing:.2px;margin-left:auto;
  white-space:nowrap;font-weight:500}

/* Horizontal scroll lives on this frame, not on the table, so the table keeps
   its normal display and sticky headers stay possible. The four background
   layers are the CSS-only scroll shadow: the two `local` gradients scroll with
   the content and mask the two `scroll` shadows, so a soft edge appears only on
   the side that actually has more table to pan to — the one cue a ten-column
   table on a phone otherwise lacks.

   --scroll-shadow is deliberately weak in light mode: the frame paints BEHIND
   the table and a cell has no ground of its own, so a muted-grey cell sitting
   over that edge is read against a darkened background. In dark mode the same
   shadow only ever darkens an already-dark ground under light text, so it can
   be much stronger there. */
.dt-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;border-radius:var(--r-md);min-width:0;
  background:
    linear-gradient(to right, var(--card) 40%, rgba(255,255,255,0)) left center,
    linear-gradient(to left, var(--card) 40%, rgba(255,255,255,0)) right center,
    radial-gradient(farthest-side at 0 50%, var(--scroll-shadow), transparent) left center,
    radial-gradient(farthest-side at 100% 50%, var(--scroll-shadow), transparent) right center;
  background-repeat:no-repeat;
  background-size:26px 100%,26px 100%,13px 100%,13px 100%;
  background-attachment:local,local,scroll,scroll}
.dt-capped{max-height:300px;overflow-y:auto;border:1px solid var(--line-soft);
  border-radius:var(--r-md);background:none}
.dt-capped table{margin:0;min-width:440px}
.dt-capped th{position:sticky;top:0;z-index:1}

/* Click-to-sort. Every header that names something is sortable; a blank
   header (the actions column) is left inert by the component. */
th.dt-sortable{cursor:pointer;user-select:none;padding-right:29px;position:relative;
  transition:background var(--speed) var(--ease),color var(--speed) var(--ease)}
th.dt-sortable:hover{background:var(--tint2);color:var(--text)}
th.dt-sortable:focus-visible{outline:2px solid var(--accent);outline-offset:-3px;box-shadow:none}
/* Indicator: a faint pair of chevrons while unsorted — visible enough to say
   "this is clickable" — resolving to one solid arrow in the active direction. */
th.dt-sortable::after{content:'\21C5';position:absolute;right:10px;top:50%;transform:translateY(-50%);
  font-size:12px;opacity:.45;transition:opacity var(--speed) var(--ease)}
th.dt-sortable:hover::after{opacity:.9}
th[aria-sort="ascending"],th[aria-sort="descending"]{background:var(--tint2);color:var(--text)}
th[aria-sort="ascending"]::after{content:'\2191';opacity:1;font-size:13px}
th[aria-sort="descending"]::after{content:'\2193';opacity:1;font-size:13px}

/* The SCHEDULED → ASSIGNED → STARTED → COMPLETED chain reads as one
   horizontal run. Inside a table cell it pans with the table rather than
   wrapping into four stacked lines, which is what it did on narrow screens. */
.dt-scroll .flowbar{flex-wrap:nowrap;margin:0}

.dt-none{padding:26px}
.dt-foot:empty{display:none}
.dt-pager{align-items:center;margin:0}
.dt-pageinfo{font-size:var(--fs-sm);font-weight:500}
.dt-pager .btn[disabled]{opacity:.4;cursor:default}
/* Filter-driven totals (the ledger's "paid by …" tiles) */
.dt-sums{margin:0}

/* custom styles */
.mt36{margin-top:36px}
/* Trailing note under a form that is followed by more of the same panel —
   without it the note sits flush against the next section's heading. */
.mb18{margin-bottom:18px}
.statline .submeta {
    margin-left: 10px;
}
/* ============================== tablet ============================== */
@media(max-width:1200px){
  main{padding:26px 24px 60px}
  .sidebar{width:224px;flex:0 0 224px}
  .portal-body{padding:24px 24px 60px}
  .portal-top,.portal-nav{padding-left:24px;padding-right:24px}
}
@media(max-width:1024px){
  .grid2{grid-template-columns:1fr}
}

/* ============================== mobile ==============================
   The size bump is the whole point of this block: one redefinition of the
   scale lifts every label, cell, badge and button at once, because nothing
   above hard-codes a px size. --fs-input goes to 16px specifically — below
   that, iOS Safari zooms the page on focus and the user has to pinch back. */
@media(max-width:900px){
  :root{
    --fs-2xs:12.5px;
    --fs-xs:13px;
    --fs-sm:14.5px;
    --fs-md:15.5px;
    --fs-lg:17px;
    --fs-xl:20px;
    --fs-2xl:24px;
    --fs-3xl:26px;
    --fs-input:16px;
  }
  body{line-height:1.6}

  #app{flex-direction:column}
  main{padding:18px 16px 56px}
  .topbar{margin-bottom:18px}

  .mobile-topbar{display:flex;align-items:center;gap:12px;padding:11px 14px;
    color:var(--on-dark);position:sticky;top:0;z-index:500;
    background-color:var(--sb-1);background-image:linear-gradient(120deg,var(--sb-1),var(--sb-2));
    box-shadow:0 2px 14px rgba(0,0,0,.3), inset 0 -1px 0 rgba(255,255,255,.08)}
  .mobile-topbar .hamburger{all:unset;cursor:pointer;display:flex;align-items:center;
    justify-content:center;width:var(--tap);height:var(--tap);border-radius:var(--r-md);
    flex:0 0 var(--tap);color:var(--on-dark)}
  .mobile-topbar .hamburger:hover{background:rgba(255,255,255,.12)}
  .mobile-topbar .hamburger:focus-visible{outline:2px solid var(--a-bright);outline-offset:-2px;box-shadow:none}
  .mobile-topbar .mbrand{display:flex;align-items:center;gap:9px;font-weight:600;font-size:var(--fs-lg)}
  .mobile-topbar .mbrand svg{width:25px;height:25px;flex:0 0 25px;
    filter:drop-shadow(0 2px 6px rgba(var(--a-rgb),.5))}

  .sidebar{position:fixed;top:0;left:-100%;width:84%;max-width:312px;height:100vh;z-index:1000;
    transition:left .28s var(--ease);box-shadow:8px 0 40px rgba(0,0,0,.5)}
  .sidebar.open{left:0}
  .brand{position:relative;padding-right:44px}
  /* background/border reset is deliberate: this is a <button>, and without it
     the UA's grey chrome shows through on the dark drawer. The sibling
     .hamburger has always used all:unset; this one never did. */
  .sidebar-close{background:none;border:0;padding:0;cursor:pointer;
    display:flex;align-items:center;justify-content:center;position:absolute;
    right:-6px;top:-6px;width:var(--tap);height:var(--tap);border-radius:var(--r-md);
    color:var(--on-dark)}
  .sidebar-close:hover{background:rgba(255,255,255,.12)}
  .sidebar-close:focus-visible{outline:2px solid var(--a-bright);outline-offset:-2px;box-shadow:none}
  nav.tabs a{min-height:var(--tap);padding:11px 14px}

  .sidebar-overlay{display:none;position:fixed;inset:0;z-index:900;
    background:rgba(3,10,18,.62);backdrop-filter:blur(2px)}
  .sidebar-overlay.show{display:block}

  /* Touch targets: a 32px button is a miss on a phone. */
  .btn{min-height:var(--tap);padding:11px 19px}
  .btn.small{min-height:38px;padding:8px 14px}
  .portal-nav a,.authtabs a{min-height:var(--tap);display:flex;align-items:center;justify-content:center}
  .roletabs a,.formtabs a{padding:10px 16px}

  .kpirow{grid-template-columns:repeat(auto-fit,minmax(152px,1fr));gap:11px;margin-bottom:20px}
  .kpi{padding:15px}
  .panel .ph,.panel .pb{padding:15px 16px}
  .formgrid{grid-template-columns:1fr;gap:14px}
  .field input,.field select,.field textarea{min-height:46px}
  .formfoot .btn{flex:1 1 auto}

  .portal-top{padding:15px 16px}
  .portal-top .who{text-align:left}
  .portal-nav{padding:10px 16px 0;gap:4px}
  .portal-nav a{padding:10px 15px}
  .portal-body{padding:18px 16px 56px}

  /* The drawer handle drops to the lower-right corner, clear of the thumb's
     natural scroll path down the right edge. */
  .theme-fab{top:auto;bottom:84px;transform:none;width:46px;height:46px;
    border-radius:var(--r-lg) 0 0 var(--r-lg)}
  .theme-fab:hover{width:50px}
  .theme-panel{width:88vw}

  /* The flash toast spans the width on a phone instead of hugging a corner,
     where a 340px card would overhang a 360px screen. */
  .flash{left:14px;right:14px;bottom:14px;max-width:none}

  /* Table text rides the same bump as everything else. Cells stop wrapping so
     a ten-column table pans sideways inside .dt-scroll rather than collapsing
     into unreadable slivers — the scroll shadow above is what advertises it. */
  .dt-tools{gap:9px}
  .dt-search{flex:1 1 100%;max-width:none}
  .dt-count{margin-left:0}
  .dt-scroll td,.dt-scroll th{white-space:nowrap}
  .dt-scroll td{padding:14px}
  .dt-pager{justify-content:center}
}

/* Small phones: trim the horizontal padding further so the content column
   keeps its measure, without touching the type scale. */
@media(max-width:400px){
  main{padding:16px 12px 48px}
  .portal-body{padding:16px 12px 48px}
  .panel .ph,.panel .pb{padding:14px 13px}
}

/* ======================= auth screens on mobile ===================== */
@media(max-width:800px){
  .authwrap{flex-direction:column}
  .authleft{padding:32px 22px;flex:none}
  .authleft h1{font-size:var(--fs-2xl)}
  .authleft p{margin-top:13px}
  .authleft ul{margin-top:17px;line-height:1.9}
  .authright{padding:22px 16px 44px;flex:none}
  .authcard{max-width:100%;padding:24px 19px}
}
