:root{
  color-scheme: light;
  --navy:#0a2a5e; --blue:#1a56c4; --blue-light:#eaf1fc; --blue-line:#c7d9f2;
  --gold:#d4af37; --gold-dark:#8a611f;
  --text:#12233f; --mgray:#5c6b85; --lgray:#c7d3e6;
  --fill:#f4f7fc; --fill2:#e9eff9; --line:#dbe4f3; --good:#006300; --bad:#a4342f;
  --page:#dde3ee; --card:#ffffff; --bezel-1:#16233b; --bezel-2:#33415c;
  --pill-active-bg:#e9f2ea; --pill-active-fg:#2f6b45;
  --pill-blocked-bg:#fbeaea; --pill-blocked-fg:#a4342f;
  --pill-info-bg:#eaf1fc; --pill-info-fg:#1a56c4;
  --shadow-card: 0 1px 3px rgba(10,42,94,0.05);
  --shadow-pop: 0 10px 30px rgba(10,42,94,0.12);

  /* ---- Chart tokens (validated — see educoin-app palette validation) ---- */
  --viz-surface:#fcfcfb; --viz-page:#f9f9f7;
  --viz-ink:#0b0b0b; --viz-ink-2:#52514e; --viz-ink-muted:#898781;
  --viz-grid:#e1e0d9; --viz-baseline:#c3c2b7;
  --viz-series-1:#2a78d6; --viz-series-2:#eb6834; --viz-series-3:#1baf7a;
  --viz-series-4:#eda100; --viz-series-5:#e87ba4; --viz-series-6:#008300;
  --viz-series-7:#4a3aa7; --viz-series-8:#e34948;
  --viz-good:#006300; --viz-critical:#d03b3b;
  --heading: var(--navy);
  --auth-bg:#fbfbfa;
}
/* Dark mode: OS preference (unless the toggle stamped a light override), or
   an explicit toggle stamp — the toggle always wins over the OS setting. */
@media (prefers-color-scheme: dark){
  :root:where(:not([data-theme="light"])){
    color-scheme: dark;
    --blue:#3d7ce0; --blue-light:#152540; --blue-line:#28406e;
    --text:#eaf0ff; --mgray:#9fadcf; --lgray:#3a4870;
    --fill:#0f1626; --fill2:#141d33; --line:#26314e; --good:#3ddc6b; --bad:#f16a6a;
    --page:#070b14; --card:#131b2e; --bezel-1:#05070c; --bezel-2:#1c2436;
    --pill-active-bg:#0f2e1c; --pill-active-fg:#4bd884;
    --pill-blocked-bg:#331414; --pill-blocked-fg:#f28b8b;
    --pill-info-bg:#152540; --pill-info-fg:#7fb0ff;
    --shadow-card: 0 1px 3px rgba(0,0,0,0.35);
    --shadow-pop: 0 10px 30px rgba(0,0,0,0.5);

    --viz-surface:#1a1a19; --viz-page:#0d0d0d;
    --viz-ink:#ffffff; --viz-ink-2:#c3c2b7; --viz-ink-muted:#898781;
    --viz-grid:#2c2c2a; --viz-baseline:#383835;
    --viz-series-1:#3987e5; --viz-series-2:#d95926; --viz-series-3:#199e70;
    --viz-series-4:#c98500; --viz-series-5:#d55181; --viz-series-6:#008300;
    --viz-series-7:#9085e9; --viz-series-8:#e66767;
    --viz-good:#0ca30c; --viz-critical:#e66767;
    --heading:#9dc4ff;
    --auth-bg:#05070c;
  }
}
:root[data-theme="dark"]{
  color-scheme: dark;
  --blue:#3d7ce0; --blue-light:#152540; --blue-line:#28406e;
  --text:#eaf0ff; --mgray:#9fadcf; --lgray:#3a4870;
  --fill:#0f1626; --fill2:#141d33; --line:#26314e; --good:#3ddc6b; --bad:#f16a6a;
  --page:#070b14; --card:#131b2e; --bezel-1:#05070c; --bezel-2:#1c2436;
  --pill-active-bg:#0f2e1c; --pill-active-fg:#4bd884;
  --pill-blocked-bg:#331414; --pill-blocked-fg:#f28b8b;
  --pill-info-bg:#152540; --pill-info-fg:#7fb0ff;
  --shadow-card: 0 1px 3px rgba(0,0,0,0.35);
  --shadow-pop: 0 10px 30px rgba(0,0,0,0.5);

  --viz-surface:#1a1a19; --viz-page:#0d0d0d;
  --viz-ink:#ffffff; --viz-ink-2:#c3c2b7; --viz-ink-muted:#898781;
  --viz-grid:#2c2c2a; --viz-baseline:#383835;
  --viz-series-1:#3987e5; --viz-series-2:#d95926; --viz-series-3:#199e70;
  --viz-series-4:#c98500; --viz-series-5:#d55181; --viz-series-6:#008300;
  --viz-series-7:#9085e9; --viz-series-8:#e66767;
  --viz-good:#0ca30c; --viz-critical:#e66767;
  --heading:#9dc4ff;
  --auth-bg:#05070c;
}
*{box-sizing:border-box;}
html, body{ height:100%; }
body{
  margin:0; font-family:'Segoe UI', Calibri, Arial, sans-serif; background:var(--page); color:var(--text);
  transition:background-color 0.25s ease, color 0.25s ease;
}

/* ---------- Native app behaviour ----------
   Applied by app-config.js, which puts .is-native / .platform-ios /
   .platform-android on <html>. These stop the page from feeling like a
   web page inside an app: no rubber-band scroll on the whole document,
   no long-press text selection on chrome, no tap highlight flash. */
.is-native, .is-native body{
  overscroll-behavior:none;
  -webkit-tap-highlight-color:transparent;
}
.is-native{ -webkit-user-select:none; user-select:none; }
/* …but the user must still be able to select/copy real content, and inputs
   break entirely without it. */
.is-native input, .is-native select, .is-native textarea,
.is-native table, .is-native .split-row, .is-native .success-amount{
  -webkit-user-select:text; user-select:text;
}

/* ---------- Phone app shell ----------
   On real phones this fills the viewport like any normal web app.
   On wider screens (laptop, for demos) it renders as a centered
   phone-shaped frame with a dark bezel, so it visually reads as
   an app rather than a stretched website. */
.app-shell{
  width:100%; max-width:430px; margin:0 auto; min-height:100vh;
  background:var(--fill); display:flex; flex-direction:column; position:relative;
}
@media (min-width:640px){
  body{ display:flex; justify-content:center; align-items:center; padding:30px 0; }
  .app-shell{
    height:860px; min-height:860px; border-radius:36px; overflow:hidden;
    box-shadow: 0 24px 60px rgba(10,42,94,0.28), 0 0 0 9px var(--bezel-1), 0 0 0 11px var(--bezel-2);
  }
}

/* Real phone, real app: fill the actual screen, never the fake bezel frame,
   and use the dynamic viewport unit so the iOS URL-bar-less WebView and the
   Android keyboard don't leave a dead strip at the bottom. */
.is-native .app-shell{
  max-width:none; min-height:100dvh; height:100dvh;
  border-radius:0; box-shadow:none;
}
.is-native body{ display:block; padding:0; background:var(--fill); }

/* Notch / Dynamic Island / home indicator.
   On Android the status bar is handled natively (setOverlaysWebView(false)),
   so only iOS needs the top inset — but the bottom inset applies to both. */
.platform-ios .appbar{ padding-top:calc(16px + env(safe-area-inset-top)); }
/* Android is meant to reserve the status-bar strip natively (see
   StatusBar.setOverlaysWebView in app-config.js) so this normally resolves
   to the same 16px as before — but that native call is wrapped in a silent
   .catch(), so if it ever fails on a given device this is the backstop that
   keeps the header readable instead of failing invisibly. */
.platform-android .appbar{ padding-top:calc(16px + env(safe-area-inset-top)); }
.platform-ios .login-hero{ padding-top:calc(38px + env(safe-area-inset-top)); }
.is-native .bottom-nav{ padding-bottom:calc(8px + env(safe-area-inset-bottom)); }
.is-native .app-main{
  padding-left:calc(14px + env(safe-area-inset-left));
  padding-right:calc(14px + env(safe-area-inset-right));
}
/* Pages with no bottom nav (admin, receiver, login) need their own clearance
   above the home indicator. On tabbed pages this just adds harmless scroll
   slack below the last card, since the nav sits outside .app-main. */
.is-native .app-main{
  padding-bottom:calc(24px + env(safe-area-inset-bottom));
}

/* Server-address footer on the login card (packaged app only). */
.server-footer{
  margin-top:14px; padding-top:12px; border-top:1px solid var(--line);
  text-align:center; font-size:11px; color:var(--mgray); word-break:break-all;
}
.server-footer a{ color:var(--blue); text-decoration:none; font-weight:700; white-space:nowrap; }

/* ---------- App bar ---------- */
.appbar{
  background:linear-gradient(135deg, var(--navy), var(--blue)); color:#fff;
  padding:16px 18px; display:flex; align-items:center; justify-content:space-between;
  flex-shrink:0; gap:10px;
}
.brand{ display:flex; align-items:center; gap:10px; min-width:0; }
.brand svg{ display:block; flex-shrink:0; }
.brand-text h1{ font-size:16px; margin:0; font-weight:800; color:#fff; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.appbar .sub{ font-size:10.5px; color:#cfe0fb; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.appbar .who{ font-size:11px; color:#dbe8fd; display:flex; align-items:center; gap:10px; flex-shrink:0; }
.appbar button.logout{
  background:rgba(255,255,255,0.14); border:1px solid rgba(255,255,255,0.35); border-radius:999px;
  padding:6px 12px; cursor:pointer; font-size:11px; color:#fff; font-weight:600; white-space:nowrap;
}
.appbar button.logout:hover{ background:rgba(255,255,255,0.24); }
.appbar .settings-link{ font-size:11px; color:#dbe8fd; text-decoration:none; white-space:nowrap; }
.appbar .settings-link:hover{ text-decoration:underline; }

/* ---------- Scrollable content ---------- */
.app-main{ flex:1; overflow-y:auto; padding:16px 14px 24px; -webkit-overflow-scrolling:touch; }

/* ---------- Bottom navigation (app-style tabs) ---------- */
.bottom-nav{
  display:flex; background:var(--card); border-top:1px solid var(--line); flex-shrink:0; padding:6px 0 8px;
}
.bottom-nav button{
  flex:1; background:none; border:none; cursor:pointer; display:flex; flex-direction:column;
  align-items:center; gap:3px; font-size:10.5px; font-weight:600; color:var(--mgray); padding:6px 2px;
}
.bottom-nav button svg{ width:21px; height:21px; }
.bottom-nav button.active{ color:var(--blue); }

/* ---------- Cards & forms ---------- */
.card{ background:var(--card); border:1px solid var(--line); border-radius:14px; padding:18px; margin-bottom:14px; box-shadow:var(--shadow-card); transition:background-color 0.25s ease, border-color 0.25s ease;}
.card h2{ font-size:15px; margin:0 0 12px; color:var(--heading);}
label{ display:block; font-size:11px; color:var(--mgray); margin-bottom:5px; font-weight:600; text-transform:uppercase; letter-spacing:0.4px;}
select, input, textarea{
  width:100%; padding:12px 13px; border:1px solid var(--lgray); border-radius:10px; font-size:15px; margin-bottom:13px; background:var(--card); color:var(--text);
}
textarea{ font-family:inherit; resize:vertical; }
select:focus, input:focus, textarea:focus{ outline:none; border-color:var(--blue); box-shadow:0 0 0 3px rgba(26,86,196,0.12);}
button.primary{
  padding:13px 18px; background:var(--blue); color:#fff; border:none; border-radius:999px;
  font-size:14.5px; font-weight:700; cursor:pointer; transition:background-color 0.15s ease, transform 0.1s ease;
}
button.primary:hover{ background:var(--navy);}
button.primary:active{ transform:scale(0.97); }
button.primary:disabled{ background:var(--lgray); cursor:not-allowed; transform:none;}
button.secondary{
  padding:13px 18px; background:var(--card); color:var(--blue); border:1.5px solid var(--blue); border-radius:999px;
  font-size:14.5px; font-weight:700; cursor:pointer; transition:background-color 0.15s ease, transform 0.1s ease;
}
button.secondary:hover{ background:var(--blue-light); }
button.secondary:active{ transform:scale(0.97); }
table{ width:100%; border-collapse:collapse; font-size:12.5px;}
th, td{ text-align:left; padding:9px 6px; border-bottom:1px solid var(--line);}
th{ color:var(--mgray); text-transform:uppercase; font-size:10.5px; letter-spacing:0.4px;}
.msg{ padding:11px 14px; border-radius:10px; font-size:13px; margin-bottom:13px;}
.msg.err{ background:var(--pill-blocked-bg); color:var(--bad); border:1px solid rgba(164,52,47,0.28);}
.msg.ok{ background:var(--pill-active-bg); color:var(--good); border:1px solid rgba(47,107,69,0.28);}
.msg.info{ background:var(--blue-light); color:var(--heading); border:1px solid var(--blue-line);}
.balance-box{
  display:flex; justify-content:space-between; align-items:center;
  background:linear-gradient(135deg, var(--navy), var(--blue));
  color:#fff; border-radius:16px; padding:20px 20px; margin-bottom:16px;
  box-shadow:0 8px 20px rgba(10,42,94,0.25);
}
.balance-box .amt{ font-size:25px; font-weight:800;}
.balance-box .lbl{ font-size:11px; color:#cfe0fb; text-transform:uppercase; letter-spacing:0.5px;}
.muted{ color:var(--mgray); font-size:12px;}
.pill{ display:inline-block; padding:3px 9px; border-radius:20px; font-size:10.5px; font-weight:700; }
.pill.credit{ background:var(--pill-active-bg); color:var(--good);}
.pill.payment{ background:var(--blue-light); color:var(--blue);}
.pill.active{ background:var(--pill-active-bg); color:var(--pill-active-fg); }
.pill.blocked{ background:var(--pill-blocked-bg); color:var(--pill-blocked-fg); }
.pill.info{ background:var(--pill-info-bg); color:var(--pill-info-fg); }

.ticket-item{
  background:var(--card); border:1px solid var(--line); border-radius:14px;
  padding:14px; margin-bottom:10px; cursor:pointer; box-shadow:var(--shadow-card);
}
.ticket-item-top{ display:flex; align-items:flex-start; justify-content:space-between; gap:10px; margin-bottom:4px; }
.ticket-item-subject{ font-size:14px; font-weight:700; color:var(--heading); }
.ticket-item-meta{ font-size:11.5px; color:var(--mgray); margin-top:2px; }
.ticket-item-message{ font-size:13px; color:var(--text); margin-top:8px; line-height:1.5; }
.ticket-item-reply{
  margin-top:10px; padding:10px 12px; border-radius:10px; background:var(--blue-light);
  font-size:12.5px; color:var(--heading); line-height:1.5;
}
.ticket-item-reply b{ display:block; font-size:10.5px; text-transform:uppercase; letter-spacing:0.4px; margin-bottom:3px; color:var(--mgray); }
.ticket-item-attachment{
  display:inline-block; margin-top:8px; font-size:12px; font-weight:600; color:var(--blue); text-decoration:none;
}
.ticket-item-attachment:hover{ text-decoration:underline; }

.row-flex{ display:flex; gap:10px;}
.row-flex > *{ flex:1;}
.step-tag{ font-size:10.5px; color:var(--mgray); text-transform:uppercase; letter-spacing:0.5px; margin-bottom:4px;}
.dept-info{ background:var(--blue-light); border-radius:10px; padding:14px 15px; margin-bottom:14px; font-size:13.5px; color:var(--text);}
.dept-info b{ display:block; font-size:15px; margin-bottom:2px; color:var(--heading);}
.split-row{ display:flex; justify-content:space-between; padding:8px 0; border-bottom:1px solid var(--line); font-size:13.5px;}
.split-row.total{ font-weight:700; border-bottom:none; padding-top:10px;}
#video-wrap{ position:relative; width:100%; border-radius:12px; overflow:hidden; background:#000; margin-bottom:13px;}
video{ width:100%; display:block;}
/* A true square regardless of the camera preview's own aspect ratio — two
   independent 60%/60% percentages of a non-square box render as a rectangle,
   so this sizes from one axis (width) and lets aspect-ratio compute the other. */
.scan-frame{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:min(65%, 65vh); aspect-ratio:1; height:auto;
  border:3px solid #fff; border-radius:12px; box-shadow:0 0 0 999px rgba(0,0,0,0.35);
}
.pin-dots{ display:flex; gap:10px; justify-content:center; margin:10px 0 16px;}
.pin-dots span{ width:13px; height:13px; border-radius:50%; border:2px solid var(--blue); display:inline-block; transition:transform 0.15s ease;}
.pin-dots span.filled{ background:var(--blue); transform:scale(1.12);}
.keypad{ display:grid; grid-template-columns:repeat(3,1fr); gap:9px; max-width:280px; margin:0 auto;}
.keypad button{ padding:15px; font-size:17px; border-radius:12px; border:1px solid var(--lgray); background:var(--card); cursor:pointer; color:var(--text); transition:background-color 0.12s ease, transform 0.1s ease;}
.keypad button:hover{ background:var(--blue-light);}
.keypad button:active{ transform:scale(0.94); background:var(--blue-light);}
#qr-output{ display:flex; justify-content:center; padding:14px 0;}

/* ---------- Login hero ---------- */
.login-hero{
  position:relative; overflow:hidden; padding:38px 20px 34px; text-align:center;
  background:linear-gradient(160deg, #0a2a5e 0%, #12408f 55%, #1a56c4 100%);
  color:#fff; flex-shrink:0;
}
.hero-glow{
  position:absolute; top:-60px; left:50%; transform:translateX(-50%); width:280px; height:280px;
  background:radial-gradient(circle, rgba(240,201,86,0.35) 0%, rgba(240,201,86,0) 70%);
  pointer-events:none;
}
.coin-3d{ width:88px; height:88px; margin:0 auto 14px; perspective:600px; }
.coin-3d svg{
  width:100%; height:100%; display:block; filter:drop-shadow(0 8px 18px rgba(0,0,0,0.35));
  animation:heroCoinSpin 3.4s linear infinite;
}
@keyframes heroCoinSpin{
  0%{ transform:rotateY(0deg); }
  100%{ transform:rotateY(360deg); }
}
.login-hero h1{ font-size:23px; margin:0 0 4px; font-weight:800; position:relative; letter-spacing:0.2px; }
.hero-sub{ font-size:12.5px; color:#cfe0fb; position:relative; }
.login-main{ padding-top:22px; }
.login-wrap .card{ margin-top:-6px; box-shadow:0 10px 30px rgba(10,42,94,0.12); }
.role-tabs{ display:flex; gap:8px; margin-bottom:16px;}
.role-tabs button{ flex:1; padding:10px; border:1px solid var(--lgray); background:var(--card); border-radius:10px; cursor:pointer; font-size:12.5px; color:var(--mgray); font-weight:700;}
.role-tabs button.active{ background:var(--navy); color:#fff; border-color:var(--navy);}
.demo-toggle{ font-size:11.5px; color:var(--blue); text-decoration:none; display:block; text-align:center; margin-top:14px; }
.demo-toggle:hover{ text-decoration:underline; }
.creds-hint{ font-size:11px; color:var(--mgray); margin-top:10px; line-height:1.6; background:var(--blue-light); padding:10px 12px; border-radius:10px; display:none;}
.settings-link{ font-size:12px; color:var(--blue); text-decoration:none; cursor:pointer;}
.settings-link:hover{ text-decoration:underline;}

/* ---------- Payment processing / success animation ---------- */
.processing-overlay{
  position:fixed; inset:0; background:rgba(10,20,40,0.55); display:flex; align-items:center; justify-content:center;
  z-index:50;
}
.processing-panel{
  background:var(--card); border-radius:20px; padding:40px 36px; display:flex; flex-direction:column; align-items:center; gap:18px;
  box-shadow:0 20px 50px rgba(10,42,94,0.3);
}
.coin-flip{ width:76px; height:76px; animation:coinFlip 0.65s linear infinite; }
@keyframes coinFlip{
  0%{ transform:rotateY(0deg); }
  100%{ transform:rotateY(360deg); }
}
.processing-text{ font-size:14px; font-weight:700; color:var(--heading); letter-spacing:0.3px; }

.success-anim-wrap{ display:flex; flex-direction:column; align-items:center; gap:6px; padding:8px 0 18px; position:relative; }
.check-anim{ width:64px; height:64px; }
.check-circle{ stroke:var(--good); stroke-width:2.5; fill:none; stroke-dasharray:151; stroke-dashoffset:151; animation:circleDraw 0.5s ease-out forwards; }
.check-mark{ stroke:var(--good); stroke-width:3.5; fill:none; stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:36; stroke-dashoffset:36; animation:checkDraw 0.35s 0.45s ease-out forwards; }
@keyframes circleDraw{ to{ stroke-dashoffset:0; } }
@keyframes checkDraw{ to{ stroke-dashoffset:0; } }
.success-amount{ font-size:24px; font-weight:800; color:var(--heading); opacity:0; animation:fadeUp 0.4s 0.55s ease-out forwards; }
@keyframes fadeUp{ from{ opacity:0; transform:translateY(6px);} to{ opacity:1; transform:translateY(0);} }

/* Confetti burst — vector, not a GIF: a dozen tiny rects flung out from the
   checkmark on payment success. Cheap (pure CSS transforms), crisp at any
   density, and themeable via currentColor-free hex per-piece below. */
.confetti-piece{
  position:absolute; top:50%; left:50%; width:6px; height:10px; border-radius:1px;
  opacity:0; animation:confettiBurst 0.9s 0.35s cubic-bezier(.2,.8,.3,1) forwards;
}
@keyframes confettiBurst{
  0%{ opacity:1; transform:translate(-50%,-50%) rotate(0deg) translateY(0); }
  100%{ opacity:0; transform:translate(-50%,-50%) rotate(var(--rot,180deg)) translate(var(--dx,0),var(--dy,-70px)); }
}

/* ==========================================================================
   Theme toggle switch (Light / System / Dark)
   ========================================================================== */
.theme-switch{
  display:flex; background:var(--fill2); border-radius:999px; padding:3px; gap:2px;
}
.theme-switch button{
  flex:1; border:none; background:none; padding:7px 4px; border-radius:999px; cursor:pointer;
  font-size:11px; font-weight:700; color:var(--mgray); display:flex; align-items:center; justify-content:center; gap:5px;
  transition:background-color 0.15s ease, color 0.15s ease;
}
.theme-switch button svg{ width:14px; height:14px; }
.theme-switch button.active{ background:var(--card); color:var(--blue); box-shadow:0 1px 3px rgba(10,42,94,0.12); }

/* ==========================================================================
   Analytics: KPI row, stat tiles, date-range filter, tab nav for admin
   ========================================================================== */
.kpi-row{ display:grid; grid-template-columns:repeat(2,1fr); gap:10px; margin-bottom:14px; }
/* Secondary row — sits below a .balance-hero, so it should read as supporting
   detail, not compete with it: three-up, smaller value, no border/shadow. */
.kpi-row-secondary{ grid-template-columns:repeat(3,1fr); gap:8px; margin-top:4px; }
.kpi-row-secondary .viz-stat-tile{ background:none; border:none; box-shadow:none; padding:0; }
.kpi-row-secondary .viz-stat-value{ font-size:17px; }
.kpi-row-secondary .viz-stat-label{ font-size:9.5px; }
.viz-stat-tile{
  background:var(--card); border:1px solid var(--line); border-radius:14px; padding:14px 15px;
  box-shadow:var(--shadow-card);
}
.viz-stat-label{ font-size:10.5px; color:var(--mgray); text-transform:uppercase; letter-spacing:0.4px; font-weight:700; margin-bottom:6px; }
.viz-stat-value{ font-size:22px; font-weight:800; color:var(--heading); line-height:1.15; }
.viz-stat-delta{ font-size:11.5px; font-weight:700; margin-top:4px; }
.viz-stat-delta.viz-delta-good{ color:var(--viz-good); }
.viz-stat-delta.viz-delta-bad{ color:var(--viz-critical); }
.viz-stat-caption{ font-size:11px; color:var(--mgray); margin-top:4px; }

.range-filter{ display:flex; gap:6px; margin-bottom:14px; overflow-x:auto; padding-bottom:2px; }
.range-filter button{
  flex:0 0 auto; padding:8px 14px; border-radius:999px; border:1px solid var(--lgray); background:var(--card);
  color:var(--mgray); font-size:12px; font-weight:700; cursor:pointer; white-space:nowrap;
  transition:background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.range-filter button.active{ background:var(--navy); color:#fff; border-color:var(--navy); }

.viz-card{
  background:var(--viz-surface); border:1px solid var(--line); border-radius:14px; padding:16px;
  margin-bottom:14px; box-shadow:var(--shadow-card);
}
.viz-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:10px; margin-bottom:12px; }
.viz-title{ font-size:14px; font-weight:700; color:var(--viz-ink); margin:0; }
.viz-subtitle{ font-size:11px; color:var(--viz-ink-2); margin-top:2px; }
.viz-table-toggle{
  flex-shrink:0; border:1px solid var(--line); background:none; color:var(--blue); font-size:11px; font-weight:700;
  padding:5px 10px; border-radius:999px; cursor:pointer;
}
.viz-svg{ width:100%; height:auto; display:block; overflow:visible; }
.viz-gridline{ stroke:var(--viz-grid); stroke-width:1; }
.viz-baseline{ stroke:var(--viz-baseline); stroke-width:1; }
.viz-axis-label{ font-size:9.5px; fill:var(--viz-ink-muted); font-family:inherit; }
.viz-area{ opacity:0.10; }
.viz-line{ stroke-width:2; }
.viz-end-dot{ stroke:var(--viz-surface); stroke-width:2; }
.viz-end-label{ font-size:11px; font-weight:700; fill:var(--viz-ink); font-family:inherit; }
.viz-crosshair{ stroke:var(--viz-baseline); stroke-width:1; stroke-dasharray:3,3; pointer-events:none; }
.viz-hover-dot{ stroke:var(--viz-surface); stroke-width:2; pointer-events:none; }
.viz-bar{ transition:opacity 0.1s ease; }
.viz-bar.viz-bar-hover{ opacity:0.82; }
.viz-bar-label{ font-size:11px; fill:var(--viz-ink-2); font-family:inherit; }
.viz-bar-value{ font-size:10.5px; font-weight:700; fill:var(--viz-ink-2); font-family:inherit; }
.viz-bar-value-inside{ fill:#ffffff; }
.viz-empty{ padding:36px 12px; text-align:center; color:var(--viz-ink-muted); font-size:12.5px; }

.viz-legend{ display:flex; flex-wrap:wrap; gap:10px 14px; margin-bottom:12px; }
.viz-legend-item{ display:flex; align-items:center; gap:6px; font-size:11px; color:var(--viz-ink-2); }
.viz-legend-swatch{ width:9px; height:9px; border-radius:2px; flex-shrink:0; }

.viz-tooltip{
  position:fixed; z-index:200; background:var(--viz-ink); color:var(--viz-surface); border-radius:8px;
  padding:8px 10px; font-size:11.5px; pointer-events:none; box-shadow:0 8px 20px rgba(0,0,0,0.3); max-width:220px;
}
:root[data-theme="dark"] .viz-tooltip{ background:var(--viz-surface); color:var(--viz-ink); border:1px solid var(--viz-grid); }
@media (prefers-color-scheme:dark){
  :root:where(:not([data-theme="light"])) .viz-tooltip{ background:var(--viz-surface); color:var(--viz-ink); border:1px solid var(--viz-grid); }
}
.viz-tooltip-row{ display:flex; align-items:center; gap:6px; white-space:nowrap; }
.viz-tooltip-row + .viz-tooltip-row{ margin-top:4px; }
.viz-tooltip-key{ width:8px; height:2px; border-radius:1px; flex-shrink:0; }
.viz-tooltip-value{ font-weight:700; }
.viz-tooltip-label{ opacity:0.75; }

.viz-table-wrap{ overflow-x:auto; }
.viz-table{ width:100%; border-collapse:collapse; font-size:12px; }
.viz-table th, .viz-table td{ text-align:left; padding:7px 8px; border-bottom:1px solid var(--line); color:var(--text); }
.viz-table th{ color:var(--mgray); text-transform:uppercase; font-size:10px; letter-spacing:0.3px; }

/* ---------- Skeleton loaders — shown while first data fetch is in flight ---------- */
.skeleton{ background:linear-gradient(90deg, var(--fill2) 25%, var(--line) 37%, var(--fill2) 63%); background-size:400% 100%; animation:skeletonWave 1.4s ease infinite; border-radius:8px; }
@keyframes skeletonWave{ 0%{ background-position:100% 50%; } 100%{ background-position:0 50%; } }
.skeleton-line{ height:12px; margin-bottom:8px; }
.skeleton-tile{ height:74px; border-radius:14px; }
.skeleton-chart{ height:180px; border-radius:14px; }

/* ---------- Tab content transitions (fade, not an instant display swap) ---------- */
.tab-content{ animation:tabFadeIn 0.22s ease; }
@keyframes tabFadeIn{ from{ opacity:0; transform:translateY(4px);} to{ opacity:1; transform:translateY(0);} }
@media (prefers-reduced-motion: reduce){
  .tab-content{ animation:none; }
  .confetti-piece{ animation:none; opacity:0; }
}

/* ==========================================================================
   Auth screen (login.html) — bold & spacious, one accent, no boxed cards.
   Deliberately its own component set rather than reusing .card/input/.primary:
   the rest of the app still uses the denser form language; this is the first
   surface redesigned toward something closer to Apple Wallet / Cash App —
   huge type, generous air, a plain canvas instead of a bordered panel.
   ========================================================================== */
.auth-shell{ background:var(--auth-bg); }
.auth-main{
  flex:1; display:flex; flex-direction:column; justify-content:center;
  padding:32px 28px calc(40px + env(safe-area-inset-bottom));
  max-width:380px; margin:0 auto; width:100%;
}
.auth-brand{ text-align:center; margin-bottom:40px; }
.auth-brand .coin-3d{ width:76px; height:76px; margin:0 auto 20px; }
.auth-title{
  font-size:40px; font-weight:800; letter-spacing:-0.5px; margin:0 0 6px;
  color:var(--heading);
}
.auth-subtitle{ font-size:14px; color:var(--mgray); letter-spacing:0.2px; }

.auth-panel{ display:flex; flex-direction:column; gap:0; }
.auth-kicker{
  font-size:11px; font-weight:800; letter-spacing:0.8px; text-transform:uppercase;
  color:var(--mgray); text-align:center; margin-bottom:16px;
}
.auth-hint{ font-size:13.5px; color:var(--mgray); text-align:center; line-height:1.5; margin:0 0 20px; }

.auth-input{
  width:100%; padding:17px 18px; margin-bottom:12px; border:none; border-radius:16px;
  background:var(--fill2); color:var(--text); font-size:16.5px;
  transition:box-shadow 0.15s ease;
}
.auth-input::placeholder{ color:var(--mgray); }
.auth-input:focus{ outline:none; box-shadow:0 0 0 2.5px var(--blue); }

.auth-primary-btn{
  width:100%; padding:17px 18px; margin-top:6px; border:none; border-radius:16px;
  background:var(--blue); color:#fff; font-size:16.5px; font-weight:700; cursor:pointer;
  transition:background-color 0.15s ease, transform 0.1s ease;
}
.auth-primary-btn:hover{ background:var(--navy); }
.auth-primary-btn:active{ transform:scale(0.98); }
.auth-primary-btn:disabled{ background:var(--lgray); cursor:not-allowed; }

.auth-secondary-btn{
  width:100%; padding:16px 18px; border:1.5px solid var(--line); border-radius:16px;
  background:none; color:var(--text); font-size:15.5px; font-weight:700; cursor:pointer;
  display:flex; align-items:center; justify-content:center; gap:10px;
  transition:background-color 0.15s ease, transform 0.1s ease;
}
.auth-secondary-btn:hover{ background:var(--fill2); }
.auth-secondary-btn:active{ transform:scale(0.98); }

.auth-divider{
  display:flex; align-items:center; gap:12px; margin:20px 0 16px;
  color:var(--mgray); font-size:12px; text-transform:uppercase; letter-spacing:0.5px;
}
.auth-divider::before, .auth-divider::after{ content:""; flex:1; height:1px; background:var(--line); }

.auth-link{
  display:block; text-align:center; margin-top:20px; font-size:13.5px;
  color:var(--mgray); text-decoration:none;
}
.auth-link:hover{ color:var(--blue); text-decoration:underline; }

.auth-field-label{
  display:block; font-size:11px; color:var(--mgray); margin:4px 0 8px; font-weight:700;
  text-transform:uppercase; letter-spacing:0.4px;
}
.auth-server-footer{
  margin-top:24px; padding-top:16px; border-top:1px solid var(--line);
  text-align:center; font-size:11px; color:var(--mgray); word-break:break-all;
}
.auth-server-footer a{ color:var(--blue); text-decoration:none; font-weight:700; white-space:nowrap; }

/* ==========================================================================
   Balance hero (student Pay/Wallet) — the number IS the design; no colored
   gradient panel around it, just huge type on the page. Replaces .balance-box.
   ========================================================================== */
.balance-hero{ padding:22px 2px 26px; }
.balance-hero-lbl{
  font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:0.6px;
  color:var(--mgray); margin-bottom:6px;
}
.balance-hero-amt{ line-height:1.05; }
.balance-hero-num{ font-size:46px; font-weight:800; color:var(--heading); letter-spacing:-0.8px; }
.balance-hero-unit{ font-size:18px; font-weight:700; color:var(--mgray); }

/* ==========================================================================
   Airy card — a lighter-weight step panel for the student Pay wizard: no
   border/shadow, more generous padding, bigger headings. The denser bordered
   .card stays as-is for Admin/Receiver, where many distinct sections on one
   screen benefit from a visible boundary; the Pay flow shows one step at a
   time, so that boundary is just extra visual weight with nothing to separate.
   ========================================================================== */
.card-airy{ background:none; border:none; box-shadow:none; padding:8px 2px 22px; margin-bottom:0; }
.card-airy h2{ font-size:20px; font-weight:800; color:var(--heading); margin:0 0 16px; letter-spacing:-0.2px; }
.card-airy input, .card-airy select{
  padding:16px 17px; border-radius:16px; border:none; background:var(--fill2); font-size:16px; margin-bottom:12px;
}
.card-airy input:focus, .card-airy select:focus{ box-shadow:0 0 0 2.5px var(--blue); }
.card-airy button.primary, .card-airy button.secondary{ padding:16px 18px; font-size:16px; border-radius:16px; }
.card-airy button.secondary{ border-width:1.5px; }
#amount-input.airy-amount{
  font-size:32px; font-weight:800; text-align:center; padding:20px 16px; color:var(--heading);
}

/* ==========================================================================
   Transaction list (student Wallet) — a row list instead of a dense table.
   The admin/receiver tables stay tables (many columns, scan-for-a-record use
   case); a student's own history is a short, personal list, closer to a bank
   statement than a spreadsheet.
   ========================================================================== */
.tx-list{ display:flex; flex-direction:column; }
.tx-row{
  display:flex; align-items:center; gap:12px; padding:14px 2px;
  border-bottom:1px solid var(--line);
}
.tx-row:last-child{ border-bottom:none; }
.tx-row-icon{
  width:40px; height:40px; border-radius:50%; flex-shrink:0;
  display:flex; align-items:center; justify-content:center; font-size:17px; font-weight:800;
  background:var(--fill2); color:var(--heading);
}
.tx-row-icon.credit{ background:var(--pill-active-bg); color:var(--pill-active-fg); }
.tx-row-body{ flex:1; min-width:0; }
.tx-row-title{ font-size:14.5px; font-weight:700; color:var(--text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tx-row-time{ font-size:12px; color:var(--mgray); margin-top:2px; }
.tx-row-amt{ font-size:15px; font-weight:800; text-align:right; flex-shrink:0; white-space:nowrap; }
.tx-row-amt.credit{ color:var(--viz-good); }
.tx-row-cash{ font-size:11px; color:var(--mgray); font-weight:500; text-align:right; margin-top:2px; }

/* ==========================================================================
   Accessibility / mobile-width utilities (UX audit pass)
   ========================================================================== */
/* Visually hidden but still announced by screen readers — used for live
   status text that has a visual equivalent (e.g. PIN dot fill). */
.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
}
/* Lets a many-column table scroll horizontally inside the fixed 430px
   phone shell instead of overflowing or squashing text unreadably —
   same pattern already used for .viz-table-wrap. */
.table-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch; }

/* Pay-wizard step progress (visual companion to the "Step N of 4" text tag). */
.pay-stepper{ display:flex; gap:6px; margin:0 2px 18px; }
.pay-stepper span{ flex:1; height:4px; border-radius:2px; background:var(--lgray); transition:background-color 0.2s ease; }
.pay-stepper span.on{ background:var(--blue); }

/* Shows the projected post-payment balance under the live balance hero
   during the Pay flow, without touching the trusted server-truth number
   above it. */
.balance-hero-projection{ font-size:13px; color:var(--mgray); margin-top:4px; font-weight:600; }
.balance-hero-projection b{ color:var(--heading); }

/* ==========================================================================
   Logout confirmation (native back-button interception)
   ========================================================================== */
.confirm-overlay{
  position:fixed; inset:0; background:rgba(10,20,40,0.55); display:flex;
  align-items:center; justify-content:center; z-index:100; padding:24px;
  animation:confirmFadeIn 0.15s ease;
}
@keyframes confirmFadeIn{ from{ opacity:0; } to{ opacity:1; } }
.confirm-dialog{
  background:var(--card); border-radius:18px; padding:24px 22px; max-width:320px; width:100%;
  box-shadow:var(--shadow-pop); animation:confirmPopIn 0.18s ease-out;
}
@keyframes confirmPopIn{ from{ opacity:0; transform:translateY(8px) scale(0.97); } to{ opacity:1; transform:translateY(0) scale(1); } }
.confirm-dialog h3{ margin:0 0 8px; font-size:17px; color:var(--heading); }
.confirm-dialog p{ margin:0 0 20px; font-size:13.5px; color:var(--mgray); }
.confirm-actions{ display:flex; gap:10px; }
.confirm-actions button{
  flex:1; padding:12px 14px; border-radius:999px; font-size:14px; font-weight:700; cursor:pointer;
  transition:background-color 0.15s ease, transform 0.1s ease;
}
.confirm-actions button:active{ transform:scale(0.97); }
.confirm-cancel{ background:var(--fill2); color:var(--text); border:none; }
.confirm-cancel:hover{ background:var(--line); }
.confirm-logout{ background:var(--bad); color:#fff; border:none; }
.confirm-logout:hover{ filter:brightness(1.08); }

/* ==========================================================================
   Accordion (Settings screens)
   ========================================================================== */
.accordion{
  background:var(--card); border:1px solid var(--line); border-radius:14px;
  margin-bottom:14px; box-shadow:var(--shadow-card); overflow:hidden;
}
.accordion-header{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:16px 18px; cursor:pointer;
}
.accordion-header:focus-visible{ outline:2px solid var(--blue); outline-offset:-2px; }
.accordion-heading{ min-width:0; }
.accordion-title{ font-size:15px; font-weight:700; color:var(--heading); }
.accordion-summary{ font-size:12px; color:var(--mgray); margin-top:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.accordion-chevron{ width:18px; height:18px; flex-shrink:0; color:var(--mgray); transition:transform 0.2s ease; }
.accordion.open .accordion-chevron{ transform:rotate(180deg); }
.accordion-body{ display:none; padding:0 18px 18px; border-top:1px solid var(--line); padding-top:16px; }
.accordion.open .accordion-body{ display:block; animation:tabFadeIn 0.22s ease; }
/* Cards used inside an accordion body lose their own border/shadow — the
   accordion shell is already the card. */
.accordion-body .card{ background:none; border:none; box-shadow:none; padding:0; margin:0; }

/* ==========================================================================
   Scroll-triggered reveal
   ========================================================================== */
.reveal-on-scroll{ opacity:0; transform:translateY(12px); transition:opacity 0.3s ease-out, transform 0.3s ease-out; }
.reveal-on-scroll.revealed{ opacity:1; transform:translateY(0); }
@media (prefers-reduced-motion: reduce){
  .reveal-on-scroll{ opacity:1; transform:none; transition:none; }
}

/* ==========================================================================
   Button loading state
   ========================================================================== */
button.is-loading{ cursor:default; opacity:0.85; }
.btn-spinner{
  display:inline-block; width:14px; height:14px; margin-right:8px; vertical-align:-2px;
  border:2px solid rgba(255,255,255,0.4); border-top-color:#fff; border-radius:50%;
  animation:btnSpin 0.7s linear infinite;
}
/* A secondary/outlined button's spinner needs to match its own (non-white)
   text color rather than the primary button's white-on-blue assumption. */
button.secondary .btn-spinner, .auth-secondary-btn .btn-spinner{ border-color:rgba(26,86,196,0.35); border-top-color:var(--blue); }
@keyframes btnSpin{ to{ transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce){
  .btn-spinner{ animation:none; }
}

/* ==========================================================================
   "Advanced" disclosure (login screen server address)
   ========================================================================== */
.advanced-toggle{
  display:block; text-align:center; margin-top:20px; font-size:12px; color:var(--mgray);
  background:none; border:none; cursor:pointer; width:100%; padding:4px;
}
.advanced-toggle:hover{ color:var(--blue); }

/* ==========================================================================
   Print isolation — "Print QR" (receiver) must print only the QR code and
   its department label, not the app chrome/bezel/nav/analytics around it.
   Standard visibility-based technique: hide everything, then re-reveal just
   the target subtree and pin it to the page origin.
   ========================================================================== */
@media print{
  body *{ visibility:hidden; }
  #qr-print-area, #qr-print-area *{ visibility:visible; }
  #qr-print-area{ position:absolute; top:0; left:0; width:100%; padding:24px; }
}
