@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap');

/* ============================================================
   CULMEN MANAGEMENT PANEL
   Tokens taken verbatim from assets/culmen-tune.css (theme-2)
   and matched to the subscriber portal at my.culmensoft.com.

   This file is the ONLY thing that changed — no PHP was edited.
   Revert with:  cp /root/panel-backups/style.css.pre-culmen \
                    /var/www/html/manage/style.css
   ============================================================ */

:root{
  color-scheme:dark;
  --bg:#15151D; --bg-2:#1B1B25; --surface:#20202B; --surface-2:#282835;
  --line:rgba(255,255,255,.09); --line-2:rgba(255,255,255,.18);
  --text:#F5F5F9; --muted:#A9A9BA; --faint:#82828F; --label:#C9C9D8;

  --violet:#8B5CF6; --violet-hi:#A78BFA; --violet-fill:#7C3AED; --violet-deep:#6A28D4;
  --violet-dim:rgba(139,92,246,.16); --glow:rgba(139,92,246,.38);
  --cyan:#67E8F9; --cyan-ink:#4FD3E8; --cyan-dim:rgba(103,232,249,.09);
  --good:#5CD6A0; --good-dim:rgba(92,214,160,.12);
  --warn:#E0A458; --warn-dim:rgba(224,164,88,.13);
  --alert:#F26D78; --alert-dim:rgba(242,109,120,.12);

  --display:'Space Grotesk',system-ui,sans-serif;
  --sans:'Inter',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,monospace;

  --pad:24px; --maxw:1560px; --r:12px;
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--text);
  font:15px/1.6 var(--sans);-webkit-font-smoothing:antialiased;
  background-image:
    radial-gradient(70% 40% at 82% -5%, rgba(103,232,249,.13), transparent 62%),
    radial-gradient(50% 30% at 8% 4%, rgba(103,232,249,.05), transparent 62%);
  background-attachment:fixed;background-repeat:no-repeat;
}
a{color:var(--violet-hi);text-decoration:none}
a:hover{color:var(--cyan)}
::selection{background:var(--violet);color:#fff}
::-moz-selection{background:var(--violet);color:#fff}
:focus-visible{outline:2px solid var(--violet-hi);outline-offset:2px}

/* ============================================================
   1 · SIGN IN
   ============================================================ */
body.loginpage{
  display:flex;align-items:center;justify-content:center;min-height:100vh;
  padding:24px;position:relative;overflow:hidden
}
body.loginpage::before{
  content:'';position:fixed;inset:-10%;z-index:0;pointer-events:none;
  background:
    radial-gradient(38% 30% at 22% 24%, rgba(103,232,249,.22), transparent 62%),
    radial-gradient(34% 26% at 78% 72%, rgba(103,232,249,.10), transparent 62%);
  animation:drift 20s ease-in-out infinite; will-change:transform;
}
/* quiet star field — brand gradients, not anyone else's logo */
body.loginpage::after{
  content:'';position:fixed;inset:-60%;z-index:0;pointer-events:none;opacity:.55;
  background-image:
    radial-gradient(1px 1px at 24px 38px,  rgba(255,255,255,.50), transparent),
    radial-gradient(1px 1px at 128px 96px, rgba(255,255,255,.32), transparent),
    radial-gradient(1px 1px at 76px 176px, rgba(127,180,245,.42), transparent),
    radial-gradient(1px 1px at 190px 24px, rgba(255,255,255,.26), transparent),
    radial-gradient(1px 1px at 158px 208px,rgba(103,232,249,.34), transparent);
  background-size:240px 240px;
  animation:stars 55s linear infinite;
}
/* 2026-08-24 - scale() removed. This animates body.loginpage::before, which
   carries two large radial-gradient layers. A translate is a compositor move:
   the layer rasterises once and shifts. scale() changes the raster size, so
   the browser re-paints both gradients on every frame of a 20s loop for as
   long as the sign-in page is open. Translate alone looks the same. */
@keyframes drift{
  0%  {transform:translate3d(0,0,0)}
  25% {transform:translate3d(3vw,-5vh,0)}
  50% {transform:translate3d(-2vw,-8vh,0)}
  75% {transform:translate3d(-4vw,-3vh,0)}
  100%{transform:translate3d(0,0,0)}
}
@keyframes stars{to{transform:translate3d(-240px,-240px,0)}}

.loginbox{
  position:relative;z-index:1;width:360px;padding:36px 32px 18px;
  background:rgba(32,32,43,.86);backdrop-filter:blur(16px) saturate(150%);
  -webkit-backdrop-filter:blur(16px) saturate(150%);
  border:1px solid var(--line-2);border-radius:18px;
  box-shadow:0 30px 70px -34px rgba(0,0,0,.8), 0 0 44px -16px var(--glow);
}
.loginbox h1{
  margin:0 0 6px;font-family:var(--display);font-size:25px;font-weight:600;
  letter-spacing:-.02em;text-align:center;color:var(--text)
}
.loginbox h1::after{
  content:'MANAGEMENT CONSOLE';display:block;margin-top:9px;
  font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.24em;
  color:var(--violet-hi)
}
.loginbox form,.loginbox{--_x:0}
.loginbox input{
  width:100%;margin:0 0 14px;padding:12px 14px;font:15px var(--mono);
  background:var(--bg-2);border:1px solid var(--line-2);border-radius:10px;color:var(--text);
  caret-color:var(--violet-hi)
}
.loginbox input:first-of-type{margin-top:22px}
.loginbox input:focus{outline:none;border-color:rgba(103,232,249,.6);box-shadow:0 0 0 3px var(--violet-dim)}
.loginbox button{
  width:100%;margin-top:4px;padding:13px;font:600 15px var(--sans);
  background:var(--violet-fill);border:0;border-radius:10px;color:#fff;cursor:pointer;
  box-shadow:0 0 24px -8px var(--glow);transition:.2s ease
}
.loginbox button:hover{background:var(--violet)}
.err{
  margin:0 0 14px;padding:11px 13px;font-size:13.5px;color:var(--alert);
  background:var(--alert-dim);border:1px solid rgba(242,109,120,.32);
  border-inline-start:2px solid var(--alert);border-radius:9px
}

/* ============================================================
   2 · HEADER
   ============================================================ */
header{
  position:sticky;top:0;z-index:40;
  display:flex;align-items:center;gap:16px;padding:14px var(--pad);
  background:rgba(21,21,29,.86);backdrop-filter:blur(14px) saturate(160%);
  -webkit-backdrop-filter:blur(14px) saturate(160%);
  border-bottom:1px solid var(--line)
}
header h1{
  margin:0;font-family:var(--display);font-size:18px;font-weight:600;
  letter-spacing:-.01em;color:var(--text);white-space:nowrap
}
.meter{
  font-family:var(--mono);font-size:11.5px;font-weight:500;letter-spacing:.04em;
  padding:5px 12px;border-radius:20px;white-space:nowrap
}
.meter.on{background:var(--violet-dim);color:var(--violet-hi);border:1px solid rgba(103,232,249,.42)}
.meter.off{background:var(--cyan-dim);color:var(--cyan-ink);border:1px solid rgba(103,232,249,.34)}
.who{
  margin-left:auto;font-family:var(--mono);font-size:12.5px;font-weight:500;color:var(--label)
}
.out{
  margin-left:14px;padding:6px 13px;font-size:13px;font-weight:500;
  border:1px solid var(--line-2);border-radius:8px;color:var(--muted);transition:.2s ease
}
.out:hover{border-color:rgba(242,109,120,.5);color:var(--alert);background:var(--alert-dim)}

/* ============================================================
   3 · FLASH + FALLBACK COMMAND
   ============================================================ */
.flash{
  max-width:var(--maxw);margin:16px auto;padding:13px 16px;
  border-radius:10px;font-size:14px;font-weight:500
}
.flash.ok{background:var(--good-dim);color:var(--good);border:1px solid rgba(92,214,160,.3);border-inline-start:2px solid var(--good)}
.flash.err{background:var(--alert-dim);color:var(--alert);border:1px solid rgba(242,109,120,.3);border-inline-start:2px solid var(--alert)}

.cmd{
  max-width:var(--maxw);margin:0 auto 14px;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r);font-size:14px
}
.cmd summary{color:var(--warn);font-family:var(--sans)}
.cmd summary::marker{color:var(--warn)}
.cmd code{
  display:block;margin-top:8px;padding:12px 14px;background:var(--bg);
  border:1px solid var(--line);border-radius:9px;
  font:13px var(--mono);color:var(--cyan-ink);word-break:break-all
}
.cmd button{
  font:500 13px var(--sans);background:var(--surface-2);color:var(--label);
  border:1px solid var(--line-2);border-radius:8px;transition:.2s ease
}
.cmd button:hover{border-color:rgba(103,232,249,.5);color:var(--text)}

/* ============================================================
   4 · STAT CARDS
   ============================================================ */
.stats{
  display:grid;grid-template-columns:repeat(5,1fr);gap:12px;
  max-width:var(--maxw);margin:16px auto;padding:0 var(--pad)
}
.stats > div{
  background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:16px 18px;
  transition:border-color .25s ease,transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s ease
}
.stats > div:hover{
  border-color:rgba(103,232,249,.5);transform:translateY(-2px);
  box-shadow:0 18px 40px -24px rgba(0,0,0,.7), 0 0 34px -10px var(--glow)
}
.stats b{
  display:block;font-family:var(--display);font-size:28px;font-weight:600;
  letter-spacing:-.03em;line-height:1;color:var(--text)
}
.stats span{
  display:block;margin-top:6px;font-family:var(--mono);font-size:10px;font-weight:700;
  text-transform:uppercase;letter-spacing:.08em;color:var(--violet-hi);line-height:1.35
}
.s-c b{color:var(--warn)}
.s-d b,.s-e b{color:var(--alert)}
.s-c:hover,.s-d:hover,.s-e:hover,.s-f:hover{
  border-color:var(--line-2)!important;box-shadow:0 18px 40px -24px rgba(0,0,0,.7)!important
}

/* ============================================================
   5 · PANELS — add / edit / search
   ============================================================ */
.addbox,.editbox{
  max-width:var(--maxw);margin:0 auto 14px;padding:16px 20px;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r)
}
.editbox{border-color:rgba(103,232,249,.35);box-shadow:0 0 34px -18px var(--glow)}
.addbox summary{
  cursor:pointer;font-family:var(--mono);font-size:12.5px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--violet-hi)
}
.addbox summary::marker{color:var(--violet)}
.editbox h2{
  margin:0 0 12px;font-family:var(--mono);font-size:12.5px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--violet-hi)
}

.row{display:flex;gap:10px;margin-top:14px;flex-wrap:wrap;align-items:flex-start}
.row input,.row select{
  padding:11px 13px;font:14px var(--sans);background-color:var(--bg-2);
  border:1px solid var(--line-2);border-radius:9px;color:var(--text)
}
.row input[name=name_ar]{font-family:var(--sans)}
.row input[dir=ltr],.row input[name=username],.row input[name=password],.row input[name=phone]{font-family:var(--mono);font-size:13.5px}
.row input:focus,.row select:focus{outline:none;border-color:rgba(103,232,249,.6);box-shadow:0 0 0 3px var(--violet-dim)}
.row button{
  padding:11px 22px;font:600 14px var(--sans);background:var(--violet-fill);
  border:0;border-radius:9px;color:#fff;cursor:pointer;
  box-shadow:0 0 22px -9px var(--glow);transition:.2s ease
}
.row button:hover{background:var(--violet)}
.cancel{align-self:center;font-size:13.5px;color:var(--muted)}

.search{
  display:flex;gap:10px;align-items:center;flex-wrap:wrap;
  max-width:var(--maxw);margin:0 auto 14px;padding:0 var(--pad)
}
.search input{
  width:300px;padding:10px 14px;font:14px var(--sans);background:var(--surface);
  border:1px solid var(--line-2);border-radius:9px;color:var(--text)
}
.search input:focus{outline:none;border-color:rgba(103,232,249,.6);box-shadow:0 0 0 3px var(--violet-dim)}
.search button{
  padding:10px 18px;font:500 14px var(--sans);background:var(--surface-2);
  border:1px solid var(--line-2);border-radius:9px;color:var(--label);cursor:pointer;transition:.2s ease
}
.search button:hover{border-color:rgba(103,232,249,.5);color:var(--text)}
.exp{
  padding:10px 16px;font-size:13.5px;font-weight:500;background:var(--cyan-dim);
  border:1px solid rgba(103,232,249,.32);border-radius:9px;color:var(--cyan-ink);transition:.2s ease
}
.exp:hover{background:rgba(103,232,249,.16);color:var(--cyan)}
.exp:first-of-type{margin-left:auto}

/* ============================================================
   6 · TABLE
   ============================================================ */
table{
  width:100%;max-width:var(--maxw);margin:0 auto 22px;border-collapse:separate;border-spacing:0;
  background:var(--surface);border:1px solid var(--line);border-radius:14px;overflow:hidden
}
th{
  text-align:left;padding:13px 15px;background:var(--bg-2);
  font-family:var(--mono);font-size:10.5px;font-weight:700;
  text-transform:uppercase;letter-spacing:.11em;color:var(--violet-hi);
  border-bottom:1px solid var(--line-2);white-space:nowrap
}
td{padding:13px 15px;border-top:1px solid var(--line);vertical-align:middle}
tbody tr{transition:background .18s ease}
tbody tr:hover{background:rgba(103,232,249,.055)}
.empty{text-align:center;color:var(--muted);padding:34px;font-size:14px}

td b{font-family:var(--mono);font-size:14px;font-weight:500;color:var(--text)}
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:9px;vertical-align:middle}
.dot.up{background:var(--good);box-shadow:0 0 0 3px rgba(92,214,160,.18)}
.dot.down{background:#4A4A58}
.sub{margin-top:4px;margin-left:17px;font-size:13px;color:var(--label)}

/* ⚠️ Plaintext passwords are printed here by index.php. Blurred until you
   deliberately hover, so a shoulder-surfer cannot read all 46 at a glance.
   The real fix is removing them from the markup — this is a mitigation. */
.pw{
  margin-left:10px;padding:1px 7px;border-radius:6px;
  font:12.5px var(--mono);color:var(--faint);background:var(--bg);
  filter:blur(4.5px);transition:filter .15s ease;cursor:default;user-select:none
}
.pw:hover{filter:none;color:var(--cyan-ink);user-select:text}

.inline{display:inline}
select{
  padding:8px 11px;font:13px var(--mono);background:var(--bg-2);
  border:1px solid var(--line-2);border-radius:8px;color:var(--text);cursor:pointer
}
select:focus{outline:none;border-color:rgba(103,232,249,.6)}

/* usage meter — matches the subscriber portal */
.usage{min-width:180px}
.bar{height:6px;background:var(--surface-2);border-radius:20px;overflow:hidden;margin-bottom:7px}
.bar i{display:block;height:100%;border-radius:20px;background:linear-gradient(90deg,var(--violet-deep),var(--violet-fill))}
.bar i.warn{background:linear-gradient(90deg,#C98A3A,var(--warn))}
.bar i.full{background:linear-gradient(90deg,#D65A64,var(--alert))}
.usage small{font:12.5px var(--mono);font-weight:500;color:var(--label)}

.exp-col div{font:13.5px var(--mono);font-weight:500;color:var(--text)}
.exp-col small{font:12.5px var(--mono);font-weight:500;color:var(--muted)}
.exp-col small.soon{color:var(--warn)}
.exp-col small.bad{color:var(--alert)}

/* status pills */
.tag{
  display:inline-block;padding:5px 12px;border-radius:20px;
  font:700 11px var(--mono);letter-spacing:.06em;text-transform:uppercase;
  border:1px solid var(--line-2);white-space:nowrap
}
.tag.on{background:var(--cyan-dim);color:var(--cyan-ink);border-color:rgba(103,232,249,.42)}
.tag.off{background:var(--surface-2);color:var(--muted)}
.tag.thr{background:var(--warn-dim);color:var(--warn);border-color:rgba(224,164,88,.45)}
.tag.susp{background:var(--alert-dim);color:var(--alert);border-color:rgba(242,109,120,.45)}

/* ============================================================
   7 · ROW ACTIONS
   ============================================================ */
.act{white-space:nowrap;text-align:right}
.mini{
  display:inline-block;padding:6px 12px;margin:2px 0 2px 5px;
  font:500 12.5px var(--sans);background:var(--surface-2);color:var(--label);
  border:1px solid transparent;border-radius:8px;cursor:pointer;transition:.18s ease;text-decoration:none
}
.mini:hover{background:var(--violet-dim);border-color:rgba(103,232,249,.45);color:var(--text)}
a.mini{color:var(--label)}
a.mini:hover{color:var(--text)}
.mini.good{background:var(--cyan-dim);color:var(--cyan-ink);border-color:rgba(103,232,249,.3)}
.mini.good:hover{background:rgba(103,232,249,.17);border-color:rgba(103,232,249,.55);color:var(--cyan)}
.mini.del{background:var(--alert-dim);color:var(--alert);border-color:rgba(242,109,120,.26)}
.mini.del:hover{background:rgba(242,109,120,.2);border-color:rgba(242,109,120,.55)}

/* ============================================================
   8 · FOOT
   ============================================================ */
.foot{
  max-width:var(--maxw);margin:0 auto 36px;padding:18px var(--pad) 0;
  border-top:1px solid var(--line);font-size:13px;color:var(--muted)
}

/* ============================================================
   9 · RESPONSIVE
   ============================================================ */
@media (max-width:1200px){
  .stats{grid-template-columns:repeat(3,1fr)}
}
@media (max-width:820px){
  :root{--pad:14px}
  header{gap:10px;flex-wrap:wrap;padding:12px 14px}
  header h1{font-size:16px}
  .who{margin-left:auto;font-size:12px}
  .stats{grid-template-columns:repeat(2,1fr);gap:10px}
  .stats b{font-size:25px}
  .search input{width:100%}
  .exp:first-of-type{margin-left:0}
  /* the table is wide by nature — scroll it rather than crushing it */
  table{display:block;overflow-x:auto;white-space:nowrap;border-radius:12px}
  .act{white-space:nowrap}
  .pw{filter:blur(4.5px)}   /* touch: stays blurred, tap toggles .show */
}

/* ============================================================
   10 · TOUCH + REDUCED MOTION
   ============================================================ */
@media (hover:none){
  .stats > div:hover{transform:none;box-shadow:none;border-color:var(--line)}
  tbody tr:hover{background:transparent}
}
@media (prefers-reduced-motion:reduce){
  *{transition:none!important;animation:none!important}
  body.loginpage::before,body.loginpage::after{animation:none}
}

/* ============================================================
   11 · APP SHELL — sidebar layout (v2 markup)
   ============================================================ */
body.app{background-attachment:fixed}
.shell{display:block;min-height:100vh}

.side{
  position:sticky;top:0;z-index:40;display:flex;align-items:center;gap:12px;
  padding:10px 14px;background:rgba(21,21,29,.9);
  backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%);
  border-bottom:1px solid var(--line);overflow-x:auto;scrollbar-width:none
}
.side::-webkit-scrollbar{display:none}
.side .navgrp,.side .nav,.side .sidefoot{display:none}
/* 2026-08-18 — the rule above hides every nav group when the sidebar collapses
   to a horizontal bar below 1080px, which made the Accounts link desktop-only.
   Exempt the admin nav and render it as an inline chip in that bar. Its
   "Admin" heading stays hidden — a section title makes no sense in a row.
   Desktop is unaffected: the min-width:1080px block re-styles .side and this
   rule is scoped below that. */
@media (max-width:1079px){
  .side .nav.adminnav{
    display:flex;align-items:center;flex:none;margin-inline-start:6px
  }
  .side .nav.adminnav a{
    display:inline-flex;align-items:center;gap:7px;white-space:nowrap;
    padding:7px 12px;border-radius:9px;
    font:600 12.5px var(--sans);color:var(--label);text-decoration:none;
    background:var(--surface-2);border:1px solid var(--line)
  }
  .side .nav.adminnav a:hover{
    color:var(--text);border-color:rgba(103,232,249,.32)
  }
  .side .nav.adminnav .ic{width:15px;height:15px}
}

.brandbox{display:flex;align-items:center;gap:10px;flex:none}
.bmark{width:30px;height:30px;color:var(--violet-hi);flex:none}
.bmark svg{width:100%;height:100%;display:block}
.btext{display:flex;flex-direction:column;line-height:1.1}
.btext b{font-family:var(--display);font-size:16px;font-weight:600;letter-spacing:-.01em;color:var(--text)}
.btext span{font-family:var(--mono);font-size:8.5px;font-weight:700;letter-spacing:.22em;color:var(--violet-hi)}

.main{padding:18px var(--pad) 40px;max-width:var(--maxw);margin:0 auto}

/* ---------- top bar ---------- */
.topbar{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:18px}
.topbar h1{margin:0;font-family:var(--display);font-size:24px;font-weight:600;letter-spacing:-.02em;color:var(--text)}
.crumb{margin:7px 0 0;display:flex;align-items:center;gap:7px;flex-wrap:wrap;font-size:13px;color:var(--muted)}
.crumb .mono{font-family:var(--mono);font-weight:500;color:var(--label)}
.live{width:7px;height:7px;border-radius:50%;background:var(--good);box-shadow:0 0 0 3px rgba(92,214,160,.18);animation:pulse 2.4s ease-in-out infinite}
@keyframes pulse{50%{opacity:.35}}
.topact{display:flex;gap:9px;flex:none}
.btn{
  display:inline-flex;align-items:center;gap:7px;padding:9px 16px;border-radius:9px;
  font:500 13.5px var(--sans);cursor:pointer;border:1px solid transparent;transition:.2s ease
}
.btn .ic{width:16px;height:16px}
.btn.ghost{background:var(--surface);border-color:var(--line-2);color:var(--label)}
.btn.ghost:hover{border-color:rgba(103,232,249,.5);color:var(--text)}
.btn.fill{background:var(--violet-fill);color:#fff;box-shadow:0 0 22px -9px var(--glow)}
.btn.fill:hover{background:var(--violet)}

/* ---------- filter chips (mobile + narrow) ---------- */
.chips{display:flex;gap:7px;overflow-x:auto;padding-bottom:4px;margin-bottom:14px;scrollbar-width:none}
.chips::-webkit-scrollbar{display:none}
.chip{
  display:inline-flex;align-items:center;gap:7px;padding:7px 13px;border-radius:20px;white-space:nowrap;
  font:500 13px var(--sans);background:var(--surface);border:1px solid var(--line-2);color:var(--muted);transition:.2s ease
}
.chip i{font:700 11px var(--mono);font-style:normal;color:var(--faint)}
.chip:hover{border-color:rgba(103,232,249,.45);color:var(--text)}
.chip.on{background:var(--violet-dim);border-color:rgba(103,232,249,.55);color:var(--violet-hi);box-shadow:0 0 22px -10px var(--glow)}
.chip.on i{color:var(--violet-hi)}

/* ---------- stat cards with icon tiles ---------- */
.stats{grid-template-columns:repeat(3,1fr);padding:0}
.stats > div{display:flex;align-items:center;gap:13px}
.stats > div > div{display:flex;flex-direction:column;min-width:0}
.si{
  display:grid;place-items:center;width:40px;height:40px;border-radius:11px;flex:none;
  background:var(--violet-dim);color:var(--violet-hi);border:1px solid rgba(103,232,249,.3)
}
.si .ic{width:19px;height:19px}
.s-b .si{background:var(--cyan-dim);color:var(--cyan-ink);border-color:rgba(103,232,249,.3)}
.s-c .si,.s-f .si{background:var(--warn-dim);color:var(--warn);border-color:rgba(224,164,88,.32)}
.s-d .si,.s-e .si{background:var(--alert-dim);color:var(--alert);border-color:rgba(242,109,120,.32)}
.stats b{font-size:26px}
.stats span{margin-top:5px}
.s-f b{color:var(--warn)}

/* ---------- search row ---------- */
.sw{display:flex;align-items:center;gap:9px;padding:0 13px;background:var(--surface);border:1px solid var(--line-2);border-radius:9px;color:var(--faint)}
.sw .ic{width:16px;height:16px;flex:none}
.sw input{width:280px;padding:10px 0;background:transparent;border:0;color:var(--text);font:14px var(--sans)}
.sw input:focus{outline:none}
.sw:focus-within{border-color:rgba(103,232,249,.6);box-shadow:0 0 0 3px var(--violet-dim)}
.clr{font-size:13px;color:var(--muted)}
.legend{display:flex;align-items:center;gap:7px;margin-left:auto;font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.05em;text-transform:uppercase;color:var(--faint);flex-wrap:wrap}
.legend b{width:7px;height:7px;border-radius:50%;display:inline-block;margin-left:8px}
.legend b:first-child{margin-left:0}
.l-on{background:var(--good)}.l-off{background:#4A4A58}
.l-thr{background:var(--warn)}.l-bad{background:var(--alert)}

/* ---------- table ---------- */
.tablewrap{overflow-x:auto;border-radius:14px;border:1px solid var(--line);background:var(--surface)}
.tablewrap table{border:0;margin:0;border-radius:0;min-width:1080px}
th.ar,td.act{text-align:right}
.cust{display:flex;align-items:flex-start;gap:2px}
.cust .dot{margin-top:7px}
.ubar{display:flex;justify-content:flex-end;font:700 11.5px var(--mono);color:var(--label);margin-bottom:4px}
.usage{min-width:190px}

/* ---------- icon action buttons ---------- */
.act .mini{padding:7px;margin:2px 0 2px 4px;line-height:0}
.act .mini .ic{width:16px;height:16px}
/* 2026-08-18 — accounts.php actions cell. It mixes a bare <button> with two
   block-level <form class="inline"> siblings, which text-align:right does not
   reliably line up. A flex row does. Scoped to .actrow so the subscriber
   table's own .act cells are untouched. */
td.actrow{
  white-space:nowrap
}
td.actrow > *{
  display:inline-flex;vertical-align:middle;margin:0
}
td.actrow{
  display:flex;align-items:center;justify-content:flex-end;gap:6px
}
td.actrow .mini{margin:0}

/* ---------- forms ---------- */
.stack{display:flex;flex-direction:column;gap:7px}
.stack input{min-width:240px}
.addbox summary,.editbox h2{display:flex;align-items:center;gap:8px}
.addbox summary .ic,.editbox h2 .ic{width:15px;height:15px}
.addbox,.editbox,.flash,.cmd,.stats,.chips,.search,.tablewrap,.foot{max-width:none}
.stats,.search{padding-inline:0}
.foot{padding-inline:0}
.foot b{font-family:var(--mono);color:var(--label)}

/* ============================================================
   12 · DESKTOP — real sidebar
   ============================================================ */
@media (min-width:1080px){
  .shell{display:grid;grid-template-columns:246px 1fr}
  .side{
    position:sticky;top:0;height:100vh;flex-direction:column;align-items:stretch;
    gap:0;padding:22px 14px;overflow-x:visible;overflow-y:auto;
    border-bottom:0;border-inline-end:1px solid var(--line);background:rgba(27,27,37,.55)
  }
  .brandbox{padding:0 10px;margin-bottom:26px}
  .bmark{width:34px;height:34px}
  .btext b{font-size:18px}
  .side .navgrp{
    display:block;padding:0 10px;margin:16px 0 8px;
    font:700 9.5px var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--faint)
  }
  .side .nav{display:flex;flex-direction:column;gap:2px}
  .side .nav a{
    display:flex;align-items:center;gap:11px;padding:9px 11px;border-radius:9px;
    font:500 14px var(--sans);color:var(--label);transition:.18s ease
  }
  .side .nav a .ic{width:17px;height:17px;flex:none;opacity:.85}
  .side .nav a span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .side .nav a i{
    font:700 11px var(--mono);font-style:normal;color:var(--faint);
    background:var(--surface-2);padding:2px 8px;border-radius:20px;flex:none
  }
  .side .nav a i.hot{color:var(--violet-hi);background:var(--violet-dim)}
  .side .nav a:hover{background:rgba(255,255,255,.05);color:var(--text)}
  .side .nav a.on{background:var(--violet-dim);color:var(--violet-hi);box-shadow:0 0 26px -12px var(--glow)}
  .side .nav a.on .ic{opacity:1}
  .side .nav a.on i{background:rgba(103,232,249,.3);color:#fff}

  .side .sidefoot{display:block;margin-top:auto;padding-top:16px;border-top:1px solid var(--line)}
  .whobox{display:flex;align-items:center;gap:10px;padding:8px 10px}
  .av{
    display:grid;place-items:center;width:32px;height:32px;border-radius:9px;flex:none;
    background:var(--violet-fill);color:#fff;font:600 14px var(--display)
  }
  .whobox div{display:flex;flex-direction:column;line-height:1.25;min-width:0}
  .whobox b{font-size:13.5px;font-weight:600;color:var(--text)}
  .whobox span{font-family:var(--mono);font-size:10px;color:var(--faint)}
  .signout{
    display:flex;align-items:center;gap:11px;margin-top:4px;padding:9px 11px;border-radius:9px;
    font:500 13.5px var(--sans);color:var(--muted);transition:.18s ease
  }
  .signout .ic{width:17px;height:17px}
  .signout:hover{background:var(--alert-dim);color:var(--alert)}

  .chips{display:none}
  .main{padding:26px 30px 46px;max-width:1560px;margin:0}
  .stats{grid-template-columns:repeat(3,1fr)}
  .topbar h1{font-size:27px}
}
@media (min-width:1660px){
  .stats{grid-template-columns:repeat(6,1fr)}
}
@media (max-width:640px){
  .stats{grid-template-columns:repeat(2,1fr)}
  .sw input{width:100%}
  .search{flex-wrap:wrap}
  .legend{margin-left:0;width:100%}
  .topact{width:100%}
  .btn{flex:1;justify-content:center}
}


/* ============================================================
   13 · CORRECTIONS
   ============================================================ */
td select{max-width:180px;width:100%}
.stats > div > div{overflow:hidden}
.stats > div > div b{white-space:nowrap}
.bar{height:7px}
.act .mini{margin-left:5px}
.act form.inline,.act a.mini{vertical-align:middle}
.addbox{padding:14px 18px}
.addbox summary{font-size:12px}


/* ============================================================
   14 · STAT CARDS — final
   auto-fit instead of fixed column counts. Cards can never be
   squeezed below 200px regardless of sidebar width or zoom,
   so no breakpoint can get this wrong again.
   ============================================================ */
.stats{
  display:grid!important;
  grid-template-columns:repeat(auto-fit,minmax(200px,1fr))!important;
  gap:12px
}
.stats > div{padding:15px 16px;gap:14px}

/* solid icon tile, white glyph — matches the reference panel */
.si{
  width:42px;height:42px;border-radius:12px;border:0;
  background:var(--violet-fill);color:#fff;
  box-shadow:0 0 20px -8px var(--glow)
}
.si .ic{width:20px;height:20px;stroke-width:1.9}
.s-b .si{background:#1E9CB0;color:#fff;box-shadow:none}
.s-c .si,.s-f .si{background:#C4863A;color:#fff;box-shadow:none}
.s-d .si,.s-e .si{background:#C9525C;color:#fff;box-shadow:none}

/* the tile carries the colour, so the number stays white */
.stats b,.s-c b,.s-d b,.s-e b,.s-f b{color:var(--text)!important;font-size:27px}
.stats span{color:var(--muted)}

/* ---------- add-subscriber reads as a control, not a banner ---------- */
.addbox{
  display:inline-block;width:auto;min-width:260px;padding:0;
  background:transparent;border:0;margin-bottom:16px
}
.addbox[open]{display:block;width:100%;padding:14px 18px;background:var(--surface);border:1px solid var(--line)}
.addbox summary{
  display:inline-flex;padding:10px 18px;border-radius:9px;
  background:var(--surface);border:1px solid var(--line-2);color:var(--violet-hi);
  transition:.2s ease;list-style:none
}
.addbox summary::-webkit-details-marker{display:none}
.addbox summary::marker{content:''}
.addbox summary:hover{border-color:rgba(103,232,249,.55);background:var(--violet-dim)}
.addbox[open] summary{margin-bottom:2px}

/* ============================================================
   15 · LAYOUT CORRECTIONS  (verified by local render)
   ============================================================ */
/* explicit counts — auto-fit left a single orphan card on row 2 */
.stats{grid-template-columns:repeat(2,1fr)!important}
@media (min-width:760px){ .stats{grid-template-columns:repeat(3,1fr)!important} }
@media (min-width:1500px){ .stats{grid-template-columns:repeat(6,1fr)!important} }

/* column proportions — Bundle was collapsing, Customer was hogging */
table{table-layout:fixed}
th:nth-child(1),td:nth-child(1){width:26%}
th:nth-child(2),td:nth-child(2){width:150px}
th:nth-child(3),td:nth-child(3){width:200px}
th:nth-child(4),td:nth-child(4){width:130px}
th:nth-child(5),td:nth-child(5){width:150px}
th:nth-child(6),td:nth-child(6){width:auto}

/* the dropdown must show its text, not just the arrow */
td select{max-width:none;width:100%;min-width:0;font-size:12.5px;padding:8px 9px}

/* dates on one line */
.exp-col div{white-space:nowrap;font-size:13px}
.exp-col small{white-space:nowrap}

/* tighter rows */
td{padding:11px 14px}
.sub{font-size:12.5px}
.cust b{font-size:13.5px}

/* ============================================================
   16 · TABLE COLUMN MATHS  (verified by local render)
   Percentages + table-layout:fixed squeezed Actions until the
   icons were clipped. Fixed px widths that sum to min-width, so
   the table scrolls instead of crushing a column.
   ============================================================ */
.tablewrap table{min-width:1210px}
th:nth-child(1),td:nth-child(1){width:300px}
th:nth-child(2),td:nth-child(2){width:140px}
th:nth-child(3),td:nth-child(3){width:190px}
th:nth-child(4),td:nth-child(4){width:130px}
th:nth-child(5),td:nth-child(5){width:150px}
th:nth-child(6),td:nth-child(6){width:300px}

.act{padding-right:16px}
.act .mini{padding:6px;margin-left:3px}
.act .mini .ic{width:15px;height:15px}

/* dropdown was clipping "PREM-12 (12GB)" */
th:nth-child(2),td:nth-child(2){width:168px}
td select{font-size:12px;padding:8px 7px}
.tablewrap table{min-width:1238px}

/* ============================================================
   17 · STAT CARDS — final form
   Six in one row, label above the number, subtle colour tint.
   Matches the reference panel. Verified by local render.
   ============================================================ */
.stats{
  grid-template-columns:repeat(2,1fr)!important;
  gap:10px!important
}
@media (min-width:640px){  .stats{grid-template-columns:repeat(3,1fr)!important} }
@media (min-width:1080px){ .stats{grid-template-columns:repeat(6,1fr)!important} }

.stats > div{
  padding:13px 14px!important;gap:11px!important;align-items:center;
  border-radius:13px;overflow:hidden;position:relative
}
/* label sits above the number — DOM order is number-first, so reverse it */
.stats > div > div{
  display:flex!important;flex-direction:column-reverse!important;
  gap:5px;min-width:0;flex:1
}
.stats b{
  font-size:24px!important;line-height:1;letter-spacing:-.03em;
  color:var(--text)!important;margin:0
}
.stats span{
  margin:0!important;font-size:9px!important;font-weight:700;letter-spacing:.07em;
  line-height:1.25;white-space:normal;overflow-wrap:anywhere
}
.si{width:36px!important;height:36px!important;border-radius:10px}
.si .ic{width:18px!important;height:18px!important}

/* each card carries a hint of its own colour */
.s-a{background:linear-gradient(135deg,rgba(103,232,249,.10),transparent 62%),var(--surface)!important}
/* 2026-08-30 — was .18, exactly DOUBLE the .09 every sibling uses
   (.s-c/.s-f amber, .s-d/.s-e red), which is why the online card read as a
   strong blue block next to them rather than a tinted one. The earlier fix
   for this only scoped .vcard.s-b (line ~2822), so the subscription card was
   neutralised while the stat/tier cards kept the heavy wash. */
.s-b{background:linear-gradient(135deg,rgba(103,232,249,.09),transparent 62%),var(--surface)!important}
.s-c,.s-f{background:linear-gradient(135deg,rgba(224,164,88,.09),transparent 62%),var(--surface)!important}
.s-d,.s-e{background:linear-gradient(135deg,rgba(242,109,120,.09),transparent 62%),var(--surface)!important}
.s-a span{color:var(--violet-hi)!important}
.s-b span{color:var(--cyan-ink)!important}
.s-c span,.s-f span{color:var(--warn)!important}
.s-d span,.s-e span{color:var(--alert)!important}

/* ============================================================
   18 · THE ACTUAL BUG
   .shell is `grid-template-columns:246px 1fr`. A 1fr track has an
   implicit min-width:auto, so the table's min-width:1238px forced
   .main wider than the viewport and dragged every child with it —
   which is why the 6th stat card sat off-screen.
   minmax(0,1fr) lets the track shrink; the table scrolls inside
   .tablewrap instead of widening the page.
   ============================================================ */
.shell{grid-template-columns:246px minmax(0,1fr)}
.main{min-width:0;max-width:100%;overflow-x:hidden}
.tablewrap{max-width:100%;min-width:0}
.stats{grid-template-columns:repeat(2,minmax(0,1fr))!important}
@media (min-width:640px){  .stats{grid-template-columns:repeat(3,minmax(0,1fr))!important} }
@media (min-width:1080px){ .stats{grid-template-columns:repeat(6,minmax(0,1fr))!important} }
.stats > div{min-width:0}
.chips,.search,.topbar{max-width:100%}

/* the horizontal scroll inside the table should be visible, not guessed at */
.tablewrap{scrollbar-width:thin;scrollbar-color:var(--surface-2) transparent}
.tablewrap::-webkit-scrollbar{height:9px}
.tablewrap::-webkit-scrollbar-track{background:var(--bg-2);border-radius:20px}
.tablewrap::-webkit-scrollbar-thumb{background:var(--surface-2);border-radius:20px}
.tablewrap::-webkit-scrollbar-thumb:hover{background:rgba(103,232,249,.55)}

/* filled glyphs need more of the tile than stroked ones did */
.si .ic{width:21px!important;height:21px!important}

/* icons removed from the stat cards */
.si{display:none!important}
.stats > div{padding:14px 16px!important}

/* ============================================================
   19 · NO ICON TILES ON STAT CARDS
   ============================================================ */
.si{display:none!important}
.stats > div{padding:14px 16px!important;gap:0!important}
.stats > div > div{width:100%}
.stats b{font-size:27px!important}
.stats span{font-size:9.5px!important;letter-spacing:.09em}
/* --- Culmen brand lock: sidebar logo is monochrome only --- */
.sidebar .logo, .sidebar .logo *,
.sidebar .brand, .sidebar .brand *,
aside .logo, aside .logo *, aside .brand, aside .brand * {
  color: currentColor !important;
  fill: currentColor !important;
  stroke: currentColor !important;
  background: none !important;
  background-image: none !important;
  background-clip: border-box !important;
  -webkit-background-clip: border-box !important;
  -webkit-text-fill-color: currentColor !important;
  filter: grayscale(1) !important;
}
/* --- Culmen palette: cyan accent, violet retired --- */
:root{
  --violet:#67E8F9;
  --violet-hi:#A5F3FC;
  --violet-fill:#0E7490;
  --violet-dim:rgba(103,232,249,.15);
  --violet-deep:#155E75;
  --glow:rgba(103,232,249,.32);
}
.row input:focus,.row select:focus{border-color:rgba(103,232,249,.55)}
.btn.ghost:hover{border-color:rgba(103,232,249,.5)}
/* brand mark stays monochrome regardless of accent */
.sidebar .logo,.sidebar .logo *,.sidebar .brand,.sidebar .brand *,
.loginbox .logo,.loginbox .logo *{
  color:#fff!important;fill:#fff!important;stroke:#fff!important;
  background:none!important;filter:grayscale(1)!important
}
/* --- login brand lockup --- */
.loginbox .brand{display:flex;align-items:center;gap:13px;margin:0 0 20px}
.loginbox .brand h1{margin:0;line-height:1.1}
.loginbox .brand .logo{width:40px;height:40px;flex:none;display:block}
.loginbox .brand .logo{margin-left:-2px}
/* --- FINAL: brand lockup, centred + forced white --- */
.loginbox .brand{
  display:flex!important;flex-direction:row!important;
  align-items:center!important;justify-content:center!important;
  gap:13px!important;margin:0 0 20px!important;width:100%!important;text-align:center!important
}
.loginbox .brand h1{margin:0!important;text-align:center!important;flex:0 0 auto!important}
.loginbox .brand .logo{
  width:40px!important;height:40px!important;flex:0 0 auto!important;margin:0!important;
  filter:brightness(0) invert(1)!important
}
/* sidebar mark: white, never the accent */
.side .brand .logo,.side .brand .logo *,.sidebar .brand .logo,.sidebar .brand .logo *{
  color:#fff!important;fill:#fff!important;stroke:#fff!important;filter:none!important
}
/* --- FIX: sidebar lockup is .brandbox — mark + wordmark white --- */
.brandbox{color:#fff!important}
.brandbox *{
  color:#fff!important;-webkit-text-fill-color:#fff!important;
  background:none!important;background-image:none!important;
  background-clip:border-box!important;-webkit-background-clip:border-box!important
}
.brandbox svg{filter:none!important}
.brandbox svg [fill]:not([fill="none"]){fill:#fff!important}
.brandbox svg [stroke]:not([stroke="none"]){stroke:#fff!important}
/* --- brandbox: wordmark sub-line white too --- */
.brandbox,.brandbox *,.brandbox::after,.brandbox *::after,.brandbox *::before{
  color:#fff!important;-webkit-text-fill-color:#fff!important
}
/* --- login lockup: optical nudge --- */
.loginbox .brand{transform:translateX(-10px)}
/* --- stats: 3-up so nine cards fill evenly --- */
@media (min-width:900px){
  .stats{grid-template-columns:repeat(3,1fr)!important}
}
/* --- tier cards: colour the label, matching cards 1-6 --- */
.s-g span:last-child{color:#7FA0E8!important}
.s-h span:last-child{color:#A98BE0!important}
.s-i span:last-child{color:#5FC4B0!important}
/* --- gaming tier card --- */
.s-j span:last-child{color:#D98BC4!important}
/* --- tier cards: online count on the right --- */
.s-g,.s-h,.s-i,.s-j{position:relative}
.tol{
  position:absolute;top:14px;right:16px;
  font:600 11.5px var(--mono);font-style:normal;letter-spacing:.04em;
  color:var(--cyan-ink);opacity:.85
}
/* --- VPN / VPS renewal cards -------------------------------------------
   2026-08-18: .tol is absolutely positioned top-right and nothing reserved
   space for it. Fine for "PREMIUM" + "27 on"; "OUIBERG VPS" + "09 Sep 00:00"
   collided. .vcard is only on the renewal cards, so the shared stat cards
   (throttled / expired / blocked / expiring soon) are untouched.
   Scoped to >=520px because the cards stack single-column below that and
   the label needs the full width on a phone. */
/* 2026-08-19 — round four, structural this time. An absolutely-positioned
   top-right .tol inside cards governed by eleven .stats layers found a new
   collision at every width. The renewal cards now STACK: number + label,
   then the date as a normal block underneath. No absolute positioning, no
   reserved percentages — overlap is geometrically impossible. */
.vcard{flex-direction:column!important;align-items:flex-start!important;gap:4px!important}
.vcard>div{min-width:0;width:100%;padding-right:0}
.vcard>div>span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vcard .tol{
  position:static!important;display:block;max-width:100%;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap
}
/* 2026-08-19 — accounts table rescue. The global table-layout:fixed and
   nth-child percentage widths were written for the seven-column subscriber
   table; on the five-column accounts table they collapsed everything, and
   the .actrow flex td removed itself from table layout entirely (a td that
   is display:flex is no longer a table-cell). Scope it all back out. */
table.acct{table-layout:auto!important;white-space:normal}
table.acct th,table.acct td{width:auto!important}
table.acct td.actrow{display:table-cell!important;text-align:right;white-space:nowrap}
table.acct td.actrow form.inline{display:inline-block;margin-left:6px}
table.acct td.actrow .mini{vertical-align:middle}
/* 2026-08-19 — dashboard fragments: Subscriptions / Tiers / Customers */
.sgroup{max-width:var(--maxw);margin:22px auto 14px;padding:0 var(--pad)}
.sgroup:first-of-type{margin-top:16px}
.sgroup .glabel{
  display:flex;align-items:center;gap:10px;margin:0 0 10px;
  font:700 10.5px var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--cyan-ink);opacity:.9
}
.sgroup .glabel::after{
  content:"";flex:1;height:1px;
  background:linear-gradient(90deg,rgba(103,232,249,.45),rgba(103,232,249,.04))
}
/* 2026-08-19 — mobile header on accounts.php: when the sidebar collapses to a
   row, the brand and signed-in labels rendered inline with no gap
   ("CulmenRADIUS", "adminsigned in"). Harmless at desktop widths where the
   spans stack as blocks. */
@media (max-width:820px){
  .brandbox b+span{margin-left:5px}
  .whobox b+span{margin-left:5px}
}
/* 2026-08-19 — fragment alignment: .stats kept its own margin+padding inside
   .sgroup, which carries the same padding, so every card grid sat indented
   relative to its label and the search row. The wrapper owns the box now. */
.sgroup .stats{margin:0!important;padding:0!important;max-width:none}
/* 2026-08-19 — accounts table on phones: the global display:block + nowrap
   scroll pattern kept producing phantom scroll width for this 5-column
   table. Below 820px each account row becomes a stacked card instead —
   nothing overflows because nothing is wider than the screen. */
@media (max-width:820px){
  table.acct{display:block;white-space:normal!important;overflow:visible!important}
  table.acct thead{display:none}
  table.acct tbody{display:block}
  table.acct tr{
    display:block;border:1px solid var(--line-2);border-radius:12px;
    margin:0 0 10px;padding:10px 12px;background:var(--surface)
  }
  table.acct td{
    display:block;width:100%!important;padding:4px 0;
    text-align:left!important;white-space:normal!important;border:0
  }
  table.acct td.actrow{display:block!important;text-align:left!important;padding-top:8px}
}
/* 2026-08-19 — kill the phantom width to the right of the account cards.
   Content provably fits the screen (left screenful renders complete), so
   whatever legacy rule inflates intrinsic width gets caged: every element
   capped at 100%, and the wrapper's sideways scroll disabled outright. */
@media (max-width:820px){
  .tablewrap:has(> table.acct){overflow-x:hidden!important}
  table.acct, table.acct *{max-width:100%!important;min-width:0!important;box-sizing:border-box}
  table.acct{width:100%!important}
  /* 2026-08-19 — the cards read as boxes inside one huge outer box: the
     desktop table shell (border/background/radius on wrapper and table)
     was still painting around the stacked cards. Shell hidden on phones;
     the cards float on the page background. */
  .tablewrap:has(> table.acct){border:0!important;background:transparent!important;box-shadow:none!important;padding:0!important;border-radius:0!important}
  table.acct{border:0!important;background:transparent!important;box-shadow:none!important;border-radius:0!important;padding:0!important}
  table.acct tbody{background:transparent!important;border:0!important}
}
/* 2026-08-19 — confirm dialog for row actions. Rides the existing .modal
   styling; only the text and the two-button row are new. */
.confirmbox{max-width:380px}
.cdlg-text{margin:10px 0 16px;font-size:14px;line-height:1.5;color:var(--ink,#e7edf5)}
.cdlg-btns{display:flex;gap:10px;justify-content:flex-end}
.cdlg-btns button{
  padding:8px 18px;border-radius:10px;border:1px solid transparent;
  font:inherit;font-size:13px;cursor:pointer
}
.cdlg-confirm{background:#0e7490;color:#fff}
.cdlg-confirm:hover{background:#0891b2}
.cdlg-cancel{background:transparent;color:inherit;border-color:rgba(148,163,184,.35)}
.cdlg-cancel:hover{border-color:rgba(148,163,184,.7)}
/* 2026-08-19 — finance page. Rides .stats cards and the rescued .acct
   table; only the add-row form, amount editor and heading nav are new. */
.finh{margin:26px 0 10px;font-size:16px;display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
.finnav{font-size:12px;font-weight:400;opacity:.75}
.finnav a{color:inherit;text-decoration:none}
.finnav a:hover{text-decoration:underline}
.finadd{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 12px}
.finadd input,.finadd select{
  padding:8px 10px;border-radius:10px;border:1px solid rgba(148,163,184,.25);
  background:rgba(15,18,28,.6);color:inherit;font:inherit;font-size:13px;min-width:0
}
.finadd input[name=label]{flex:1 1 160px}
.finadd input[name=note]{flex:1 1 140px}
.finadd input[name=amount]{width:110px}
.finamt{display:inline-flex;align-items:stretch}
.finamt .cur{
  display:flex;align-items:center;padding:0 9px;font-size:12px;opacity:.65;
  border:1px solid rgba(148,163,184,.25);border-right:0;border-radius:8px 0 0 8px;
  background:rgba(15,18,28,.6)
}
.finamt input{
  width:96px;padding:6px 8px;text-align:right;border-radius:0;
  border:1px solid rgba(148,163,184,.25);
  background:rgba(15,18,28,.6);color:inherit;font:inherit;font-size:13px
}
.finamt input:focus{outline:none;border-color:rgba(103,232,249,.5)}
.finamt button{
  display:flex;align-items:center;padding:0 10px;cursor:pointer;
  border:1px solid rgba(148,163,184,.25);border-left:0;border-radius:0 8px 8px 0;
  background:rgba(14,116,144,.25);color:#67e8f9
}
.finamt button:hover{background:rgba(14,116,144,.5)}
/* 2026-08-19 — aura on all finance form fields, mirroring the modal-row
   pattern. Hover only; the no-:focus rule (~line 1323) applies here too.
   The fused amount group tints as one unit when any part is hovered. */
.finadd input,.finadd select,.finamt input{
  transition:background-image .18s ease,border-color .18s ease
}
.finadd input:hover,.finadd select:hover,.finamt input:hover{
  background-image:radial-gradient(180px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.18),transparent 66%);
  border-color:rgba(103,232,249,.4)
}
.finadd input:not(:hover),.finadd select:not(:hover),.finamt input:not(:hover){background-image:none}
.finamt:hover .cur,.finamt:hover button{border-color:rgba(103,232,249,.4)}
.finstats{margin-top:8px}
/* 2026-08-19 — finance tables never scroll sideways, any viewport. The
   operator confirms content fits; whatever inflates intrinsic width is
   caged and long text wraps instead. Scoped to .fintable only. */
.tablewrap:has(> table.fintable){overflow-x:hidden!important}
table.fintable{width:100%!important;table-layout:auto!important}
table.fintable, table.fintable *{max-width:100%!important;min-width:0!important;box-sizing:border-box}
table.fintable th, table.fintable td{white-space:normal!important}
/* 2026-08-19 — screenshot round: (1) the save check rendered zero-width
   because .ic sizing is .mini-scoped and this button isn't .mini;
   (2) on stacked cards the amount control and delete now share a line;
   (3) title aura zone trimmed ~28% so the glow stays on the text, not
   the empty right half of the row. */
.finamt button .ic{width:15px;height:15px;display:block}
.fintable .cust{width:fit-content;max-width:100%}
@media (max-width:820px){
  table.fintable td:nth-last-child(2){
    display:inline-flex!important;width:auto!important;
    vertical-align:middle;padding-right:12px;border:0
  }
  table.fintable td.actrow{
    display:inline-flex!important;width:auto!important;
    vertical-align:middle;padding-top:6px
  }
}
.sgroup .stats{margin:0!important;padding:0!important;max-width:none}
/* Special tier card joins the tier family */
.s-k{position:relative}
.s-k .si{background:var(--cyan-dim);color:var(--cyan-ink);border-color:rgba(103,232,249,.3)}
.s-k span:last-child{color:#67E8F9!important}
/* --- "Was <tier>" mismatch badge --- */
.wastier{
  display:inline-block;margin-left:8px;padding:3px 8px;border-radius:7px;
  font:600 11px var(--mono);letter-spacing:.03em;white-space:nowrap;
  color:var(--warn);background:var(--warn-dim);
  border:1px solid rgba(224,164,88,.32)
}
/* 2026-08-18 alignment fix — the badge lives inside the bundle <td>, after the
   <select>, in <form class="inline">. It was inline-block + nowrap with nothing
   constraining the cell, so below full width it could not wrap and spilled
   sideways over the usage column. Make that form a wrapping flex row so the
   badge drops under the select instead. Scoped with :has() to the form that
   actually contains a .wastier, so other inline forms are untouched. */
form.inline:has(.wastier){
  display:flex;flex-wrap:wrap;align-items:center;gap:6px;min-width:0
}
form.inline:has(.wastier) select{flex:1 1 auto;min-width:0}
.wastier{margin-left:0;max-width:100%;overflow:hidden;text-overflow:ellipsis}
@media (max-width:1400px){
  form.inline:has(.wastier) .wastier{flex-basis:100%}
}
/* --- "WAS xGB" 24h change badge --- */
.waswas{
  display:inline-block;margin-bottom:6px;padding:3px 9px;border-radius:7px;
  font:700 11px var(--mono);letter-spacing:.05em;
  color:var(--warn);background:var(--warn-dim);
  border:1px solid rgba(224,164,88,.32)
}
/* --- cursor-tracked glow, ported from culmen-polish --- */
.cust{position:relative;border-radius:10px;padding:6px 9px;margin:-6px -9px;isolation:isolate}
.cust:before{
  content:"";position:absolute;inset:0;border-radius:10px;
  opacity:0;pointer-events:none;z-index:0;transition:opacity .4s ease;
  background:radial-gradient(260px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.09),transparent 62%)
}
.cust:hover:before{opacity:1}

.inline{position:relative;isolation:isolate}
.inline:before{
  content:"";position:absolute;inset:0;border-radius:10px;
  opacity:0;pointer-events:none;z-index:0;transition:opacity .4s ease;
  background:radial-gradient(220px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.18),transparent 62%)
}
.inline:hover:before{opacity:1}
.inline select{transition:border-color .25s ease,box-shadow .25s ease}
.inline select:hover{border-color:rgba(103,232,249,.32)}
.inline select:focus{outline:none;border-color:var(--violet);box-shadow:0 0 0 3px var(--violet-dim)}
.cust > *,.inline > *{position:relative;z-index:1}
/* --- bundle select: cursor-tracked glow --- */
.inline select{
  transition:border-color .25s ease, box-shadow .25s ease, background .25s ease
}
.inline select:hover{
  border-color:rgba(103,232,249,.34);
  background-image:radial-gradient(170px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.18),transparent 62%)
}
.inline select:focus{
  outline:none;border-color:var(--violet);box-shadow:0 0 0 3px var(--violet-dim)
}
/* --- sidebar nav: cursor-tracked glow --- */
.side .nav a{transition:background .2s ease,color .2s ease,background-image .25s ease}
.side .nav a:hover{
  background-image:radial-gradient(150px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.15),transparent 64%)
}
.side .nav a.on:hover{
  background-image:radial-gradient(150px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.2),transparent 64%)
}
/* --- cursor-tracked glow: interactive surfaces --- */
.stats > div,.chip,.btn,.loginbox{
  transition:background-image .25s ease,border-color .25s ease
}
.stats > div:hover{
  background-image:radial-gradient(320px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.18),transparent 62%)
}
.chip:hover,.btn.ghost:hover{
  background-image:radial-gradient(140px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.15),transparent 64%)
}
.btn.fill:hover{
  background:#1BA8C4!important;background-image:none!important;
  box-shadow:0 0 26px -9px rgba(103,232,249,.45)!important
}
/* --- cursor glow: search + add subscriber --- */
.search input,.search button,.addbox summary{
  transition:background-image .25s ease,border-color .25s ease
}
.search .sw:hover{
  background-image:radial-gradient(220px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.13),transparent 64%)
}
.search button:hover{
  background-image:radial-gradient(140px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.16),transparent 64%)
}
.addbox summary:hover{
  border-color:rgba(103,232,249,.4)!important;
  background:radial-gradient(320px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.13),transparent 62%),rgba(103,232,249,.06)!important
}
/* --- glow layered over the six status-card gradients --- */
.s-a:hover{background:radial-gradient(320px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.13),transparent 62%),linear-gradient(135deg,rgba(103,232,249,.10),transparent 62%),var(--surface)!important}
.s-b:hover{background:radial-gradient(320px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.13),transparent 62%),linear-gradient(135deg,rgba(103,232,249,.09),transparent 62%),var(--surface)!important}
.s-c:hover,.s-f:hover{background:radial-gradient(320px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.13),transparent 62%),linear-gradient(135deg,rgba(224,164,88,.09),transparent 62%),var(--surface)!important}
.s-d:hover,.s-e:hover{background:radial-gradient(320px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.13),transparent 62%),linear-gradient(135deg,rgba(242,109,120,.09),transparent 62%),var(--surface)!important}
/* --- search: let the wrapper glow show through the input --- */
.search .sw{border-radius:10px;overflow:hidden}
.search .sw input{
  background:transparent!important;background-image:none!important;
  border:0!important;box-shadow:none!important
}
.search .sw:focus-within{border-color:rgba(103,232,249,.6)}

/* password reveal - tap toggles on touch, hover still works on desktop */
.pw{cursor:pointer}
.pw.show{filter:none!important;color:var(--cyan-ink);user-select:text}

/* parity-v3b — panel-wide glow removed; buttons only. */

/* ============================================================
   21 · parity-v2b — SIDEBAR NAV BUTTONS
   The cyan gradient at line ~873 was correct and live; the flat
   white background-color on :hover (line ~512, inside the 1080px
   block) sat under it and greyed it out. Cyan wash + cyan border
   + the same glow the stat cards use.
   ============================================================ */
.side .nav a{
  border:1px solid transparent;
  transition:background-color .2s ease,background-image .25s ease,
             border-color .2s ease,box-shadow .25s ease,color .2s ease
}
.side .nav a:hover{
  background-color:rgba(103,232,249,.05);
  background-image:radial-gradient(180px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.26),transparent 66%);
  border-color:rgba(103,232,249,.30);
  box-shadow:0 0 26px -12px var(--glow);
  color:var(--text)
}
.side .nav a.on:hover{
  background-color:var(--violet-dim);
  background-image:radial-gradient(180px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.32),transparent 66%);
  border-color:rgba(103,232,249,.42)
}

/* --- 22 · parity-v2c — sign-out link (lives in .sidefoot, not .nav) --- */
.side .signout{
  border:1px solid transparent;
  transition:background-color .2s ease,background-image .25s ease,
             border-color .2s ease,box-shadow .25s ease,color .2s ease
}
.side .signout:hover{
  background-color:var(--alert-dim);
  background-image:radial-gradient(180px circle at var(--mx,50%) var(--my,50%),rgba(242,109,120,.22),transparent 66%);
  border-color:rgba(242,109,120,.32);
  box-shadow:0 0 26px -12px rgba(242,109,120,.5);
  color:var(--alert)
}
/* the identity block also gets one, cyan */
.side .whobox{
  border:1px solid transparent;border-radius:10px;
  transition:background-image .25s ease,border-color .2s ease
}
.side .whobox:hover{
  background-image:radial-gradient(200px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.14),transparent 66%);
  border-color:rgba(103,232,249,.22)
}

/* ===== MODALS — 2026-08-16 =====
   These are <dialog> and not positioned divs on purpose: a dialog
   opened with showModal() renders in the browser's TOP LAYER, so
   nothing in the ~900 lines above can reach it with z-index,
   overflow:hidden, or !important. Sidesteps the fight instead of
   trying to win it. Do not convert these back to divs. */
dialog.modal{
  border:1px solid var(--line-2,rgba(255,255,255,.18));
  border-radius:14px;
  background:var(--surface,#20202B);
  color:var(--text,#F5F5F9);
  padding:26px 28px;
  width:min(580px,calc(100vw - 32px));
  max-height:calc(100vh - 64px);
  overflow:auto;
  box-shadow:0 30px 80px -30px rgba(0,0,0,.85);
}
dialog.modal::backdrop{background:rgba(7,7,11,.64)}
dialog.modal h2{margin:0 0 18px;font-size:18px;display:flex;align-items:center;gap:9px}
dialog.modal .cancel{margin-left:10px;color:var(--faint,#82828F);cursor:pointer}
dialog.modal .cancel:hover{color:var(--text,#F5F5F9)}

/* ===== COLUMN SORT — 2026-08-16 =====
   Keys come from PHP as data-k on each <td>, never from parsing the
   rendered text. "12.4 GB" and "22 Aug" string-sort wrong, and a
   "no expiry" row has no date to parse at all — those rows carry
   999999 so they land at the bottom instead of the top. */
.tablewrap th.s{cursor:pointer;user-select:none;white-space:nowrap}
.tablewrap th.s:hover{color:var(--text,#F5F5F9)}
.tablewrap th.s::after{content:'\2195';margin-left:6px;font-size:9px;opacity:.28}
.tablewrap th.s.sa::after{content:'\25B2';opacity:.85}
.tablewrap th.s.sd::after{content:'\25BC';opacity:.85}

/* ⚠️ REQUIRED. The UA rule dialog:not([open]){display:none} is
   UA-ORIGIN, so ANY author rule setting display on .addbox or
   .editbox beats it and leaves the dialog permanently on screen —
   unstyled, unbackdropped, in normal flow. Author origin + higher
   specificity is what actually hides it. Do not remove. */
dialog.modal:not([open]){display:none!important}
dialog.modal[open]{display:block;position:fixed;inset:0;margin:auto}

/* ===== DIALOG FORM FIT =====
   .row was built for a full-width inline block: flex-wrap with
   min-width:240px inputs, which in a 580px dialog collapses to a
   ragged two-up. Grid instead, and let .stack span the full width.
   ⚠️ Lines 593-606 and the .addbox summary rules are DEAD — they
   styled the <details>/<summary> that no longer exists. Left in
   place rather than deleted mid-session; prune them deliberately. */
dialog.modal .row{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:0}
dialog.modal .row .stack{grid-column:1/-1}
dialog.modal .row .stack input{min-width:0;width:100%}
dialog.modal .row input,
dialog.modal .row select{width:100%;min-width:0;box-sizing:border-box}
dialog.modal .row button{grid-column:1/-1;justify-self:start;margin-top:4px}
dialog.modal .row .cancel{align-self:center;justify-self:start;width:auto}
/* ===== #4 — Cancel must match Create / Save box size — 2026-08-18 =====
   .cancel is an <a>, so it rendered as bare inline text beside a padded
   <button>: no padding, no border, smaller font. Mirror the button's box
   metrics exactly and give both the same min-width, so "Cancel" lines up
   with "Save" (4 chars) as well as "Create" (6). Kept visually secondary —
   transparent fill, no violet glow — so the primary action still reads
   as the primary one. Applies to the add and edit dialogs. */
.row button,
dialog.modal .row button,
.row .cancel,
dialog.modal .row .cancel{
  min-width:132px;
  box-sizing:border-box
}
.row .cancel,
dialog.modal .row .cancel{
  display:inline-flex;align-items:center;justify-content:center;
  margin:4px 0 0;
  padding:11px 22px;
  font:600 14px var(--sans);
  background:transparent;
  border:1px solid var(--line-2);
  border-radius:9px;
  color:var(--muted);
  text-decoration:none;
  cursor:pointer;
  transition:.2s ease
}
.row .cancel:hover,
dialog.modal .row .cancel:hover{
  background:var(--surface-2);
  border-color:rgba(103,232,249,.32);
  color:var(--text)
}
@media(max-width:600px){dialog.modal .row{grid-template-columns:1fr}}

/* ⚠️ ic() emits a bare inline <svg> with NO width/height attribute —
   it fills its container unless CSS sizes it. Line 477 sized it via
   `.addbox summary .ic`, which stopped matching when <summary>
   became <h2>, so the plus rendered ~800px tall. Any new ic() call
   site MUST be added here or it will do the same thing. */
dialog.modal h2 .ic,
dialog.modal h2 svg{width:15px;height:15px;flex:none}
dialog.modal h2{font-size:16px;line-height:1.3}

/* ⚠️ inset:0 on a sized dialog defeats width — left AND right pin to
   0, so the box stretches full-bleed and width:min(580px,…) never
   applies. Centre with 50% + translate instead. Do not reintroduce
   inset:0 here. */
dialog.modal[open]{
  position:fixed;
  inset:auto;
  left:50%;
  top:50%;
  transform:translate(-50%,-50%);
  margin:0;
  width:min(580px,calc(100vw - 32px));
  max-height:calc(100vh - 64px);
  border-radius:16px;
}


/* ===== DIALOG FIELD AURA =====
   Same pattern as .search input at line ~940. Cyan only (§27).
   ⚠️ background-image, NOT background — .row input at line 218 sets
   background-color, and the shorthand would wipe it and leave the
   fields transparent over the dialog surface. */
dialog.modal .row input,
dialog.modal .row select,
dialog.modal .row button{
  transition:background-image .18s ease, border-color .18s ease;
}
dialog.modal .row input:hover,
dialog.modal .row select:hover{
  background-image:radial-gradient(180px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.20),transparent 66%);
  border-color:rgba(103,232,249,.45);
}
/* ⚠️ NO :focus aura. Focus outlives the cursor, so the gradient
   freezes at the last --mx/--my and reads as stuck. Hover only.
   The focus ring at line ~224 is the focus affordance. */
dialog.modal .row input:not(:hover),
dialog.modal .row select:not(:hover){background-image:none}
dialog.modal .row button:hover{
  background-image:radial-gradient(140px circle at var(--mx,50%) var(--my,50%),rgba(255,255,255,.18),transparent 64%);
}


/* Cancel is an <a>, not a <button>, so it misses .row button. */
dialog.modal .cancel{
  padding:8px 14px;border-radius:9px;
  transition:background-image .18s ease, color .18s ease;
}
dialog.modal .cancel:hover{
  background-image:radial-gradient(120px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.16),transparent 64%);
}


#netbg{position:fixed;inset:0;z-index:0;width:100%;height:100%;pointer-events:none;display:block}
body.loginpage::before,
body.loginpage::after{display:none}


.loginbox .by{
  display:block;text-align:center;margin:18px 0 0;
  font-family:var(--sans);font-size:12.5px;color:var(--muted);
}
.loginbox input:first-of-type{margin-top:14px}


.addfab{position:fixed;right:32px;bottom:32px;z-index:40}


/* 2026-08-29 — the sticky first column (Customer frozen while the rest of the
   row scrolled under it) was removed at the operator's request: on a narrow
   screen it pinned one column and made every other column slide beneath it,
   which read as a broken table rather than a convenience. The whole table now
   scrolls as one. Do not reinstate without asking. */


/* 2026-08-22 — Actions column was unreachable. Three stacked min-width values
   (1080 / 1210 / 1238px) capped the table box; the Remote Web column added
   2026-08-22 pushed .act past 1238px. .act is white-space:nowrap so it cannot
   shrink - it overflowed the table's own box, and overflow beyond that box is
   NOT scrollable inside .tablewrap, so the scrollbar stopped short of it.
   max-content sizes the box to its contents, so adding a column can never
   clip the last one again. Numeric value kept as a fallback. */
.tablewrap table{min-width:1238px;min-width:max-content}

/* ===== 2026-08-22 — sidebar section headings adopt the .glabel treatment =====
   Cyan label + trailing hairline rule, matching the Tiers group. Scoped inside
   the same min-width:1080px query that turns .navgrp on: it is display:none by
   default (line 396) and only enabled for the desktop sidebar, so an unscoped
   rule here would make headings appear on mobile where the sidebar is hidden. */
@media (min-width:1080px){
  .side .navgrp{
    display:flex;align-items:center;gap:10px;
    padding:0 10px;margin:16px 0 8px;
    font:700 10px var(--mono);letter-spacing:.15em;text-transform:uppercase;
    color:var(--cyan-ink);opacity:.9
  }
  .side .navgrp::after{
    content:"";flex:1;height:1px;
    background:linear-gradient(90deg,rgba(103,232,249,.45),rgba(103,232,249,.04))
  }
}

/* ===== 2026-08-22 — animated network background, ported from culmensoft.com =====
   #netbg sits at z-index:-1. A negative-z fixed element paints BEHIND body's
   background, so the page background must move to <html> or the canvas is
   invisible. Palette unchanged - same --bg and same gradients, relocated only.
   Scoped to body.app so the login page keeps its own background. */
html{
  background:#15151D; background:var(--bg);
  background-image:
    radial-gradient(70% 40% at 82% -5%, rgba(103,232,249,.13), transparent 62%),
    radial-gradient(50% 30% at 8% 4%, rgba(103,232,249,.05), transparent 62%);
  background-repeat:no-repeat;background-attachment:fixed;
}
body.app{background:transparent!important;background-image:none!important}
body.app #netbg{position:fixed;inset:0;width:100%;height:100%;z-index:-1;pointer-events:none;opacity:.55}

/* 2026-08-22 — Actions column, final. Buttons stay on ONE row and the table
   scrolls horizontally to reach them. Two things were wrong: the table's
   min-width (1238px) was set when there were SIX columns, and table{max-width}
   capped it - so with seven columns there was nothing left to scroll to and the
   last column was simply squeezed. Explicit width for Actions so it cannot be
   starved, explicit table min-width so .tablewrap always has scroll range. */
.tablewrap table{min-width:1520px!important;max-width:none!important}
.tablewrap th.ar,
.tablewrap td.act{
  white-space:nowrap!important;
  width:400px!important;min-width:400px;
  text-align:right;
}
.tablewrap td.act .mini{margin:2px 0 2px 4px}

/* 2026-08-22 — align the upper and lower sections.
   .stats and .search sit INSIDE .main, which already pads 30px, but each adds
   another padding:0 var(--pad) (24px) - so they began at 54px while .tablewrap
   began at 30px. Meeting in the middle rather than snapping either one fully:
   upper moves 12px left (24 -> 12), lower moves 12px right. Both land at 42px. */
.stats,.search{padding-left:12px!important;padding-right:12px!important}
.tablewrap{margin-left:12px;margin-right:12px}

/* Customer column was 300px against ~140px of content - reduce ~15% to 255px
   and take the same 45px off the table min-width so the space is not simply
   redistributed back into the column. */
.tablewrap th:nth-child(1),
.tablewrap td:nth-child(1){width:255px!important}
.tablewrap table{min-width:1475px!important}

/* 2026-08-22 — correction to the alignment block above. The stats grids live
   inside .sgroup, which is what carries padding:0 var(--pad); line 919 forces
   .sgroup .stats to padding:0 precisely so the grid does not double-indent.
   Padding .stats directly re-broke that: cards went to 24+12=36px while the
   .glabel heading stayed at 24px. The offset belongs on .sgroup itself. */
.sgroup{padding-left:12px!important;padding-right:12px!important}
.sgroup .stats{padding:0!important;margin:0!important}

/* 2026-08-22 — Username column inserted at position 2. FULL checklist done, the
   one this table keeps failing: data-i renumbered, colspan widened, nth-child
   widths rewritten for EIGHT columns, table min-width raised to match. */
.tablewrap th:nth-child(1),.tablewrap td:nth-child(1){width:255px!important} /* Customer   */
.tablewrap th:nth-child(2),.tablewrap td:nth-child(2){width:150px!important} /* Username   */
.tablewrap th:nth-child(3),.tablewrap td:nth-child(3){width:120px!important} /* Remote Web */
.tablewrap th:nth-child(4),.tablewrap td:nth-child(4){width:200px!important} /* Bundle     */
.tablewrap th:nth-child(5),.tablewrap td:nth-child(5){width:130px!important} /* Used today */
.tablewrap th:nth-child(6),.tablewrap td:nth-child(6){width:150px!important} /* Expires    */
.tablewrap th:nth-child(7),.tablewrap td:nth-child(7){width:150px!important} /* Status     */
.tablewrap th:nth-child(8),.tablewrap td:nth-child(8){width:400px!important} /* Actions    */
.tablewrap table{min-width:1560px!important}
.tablewrap td.uname{font-family:var(--mono);font-size:13px;color:var(--label)}

/* 2026-08-22 — column width tuning: Customer had dead space before Username;
   Used today and Expires were cramped (the "872.8 MB / 12 GB" line wrapped).
   Bundle widened for the new "Gaming 6G 10M -> 2M" labels. min-width follows. */
.tablewrap th:nth-child(1),.tablewrap td:nth-child(1){width:210px!important} /* Customer   */
.tablewrap th:nth-child(4),.tablewrap td:nth-child(4){width:250px!important} /* Bundle     */
.tablewrap th:nth-child(5),.tablewrap td:nth-child(5){width:175px!important} /* Used today */
.tablewrap th:nth-child(6),.tablewrap td:nth-child(6){width:165px!important} /* Expires    */
.tablewrap table{min-width:1620px!important}

/* 2026-08-22 — equal left/right gutters. Line 575 overrides the base
   .main{margin:0 auto} with margin:0 at desktop, so whenever the grid track is
   wider than max-width:1560px the entire surplus lands on the right and the
   page looks off-centre. Restore centring; the 30px padding + 12px section
   offset then apply equally on both sides. */
.main{margin-left:auto!important;margin-right:auto!important}

/* 2026-08-22 — equal breathing room above and below the sidebar logo.
   Above = .side padding-top (22px). Below was .brandbox margin-bottom 26px
   PLUS .navgrp margin-top 16px = 42px, so the logo sat high in its space.
   Zero the first navgrp's top margin and set the brandbox gap to 22px; later
   navgrp headings keep their 16px separation. Adjacent-sibling selector is
   used because :first-of-type would match .brandbox, not .navgrp. */
@media (min-width:1080px){
  .side .brandbox{margin-bottom:22px}
  .side .brandbox + .navgrp{margin-top:0}
}

/* 2026-08-22 — more air around the sidebar logo: 22px was still tight against
   the top corner. Both sides raised to 34px, kept equal. Side padding stays
   14px; only the top changes. */
@media (min-width:1080px){
  .side{padding-top:34px}
  .side .brandbox{margin-bottom:34px}
}

/* 2026-08-22 — sidebar logo scaled up. Mark 34->42, wordmark 18->21,
   "RADIUS" 8.5->9.5 and the mark/text gap 10->12, so all four grow together
   rather than the glyph outrunning the text. */
@media (min-width:1080px){
  .side .bmark{width:42px;height:42px}
  .side .btext b{font-size:21px}
  .side .btext span{font-size:9.5px}
  .side .brandbox{gap:12px}
}

/* 2026-08-22 — trim back: logo 42->38, wordmark 21->19.5, RADIUS 9.5->9,
   gap 12->11; spacing 34->28 above and below, still equal. */
@media (min-width:1080px){
  .side .bmark{width:38px;height:38px}
  .side .btext b{font-size:19.5px}
  .side .btext span{font-size:9px}
  .side .brandbox{gap:11px}
  .side{padding-top:28px}
  .side .brandbox{margin-bottom:28px}
}

/* 2026-08-22 — optical alignment of the wordmark. "Culmen" is var(--display),
   "RADIUS" is var(--mono); the two faces have different left side-bearings, so
   the flex column aligns the boxes while the letterforms look offset. Nudge the
   mono line right to match the ink, not the box. */
@media (min-width:1080px){
  .side .btext span{margin-left:2px}
}

/* 2026-08-22 — Status column was 150px while the throttled tag reads
   "THROTTLED 3M/3M" at white-space:nowrap, so it overflowed into Actions and
   shoved that row's buttons out of line. Widen Status to fit the longest tag
   and raise the table min-width by the same amount. */
.tablewrap th:nth-child(7),.tablewrap td:nth-child(7){width:195px!important}
.tablewrap table{min-width:1665px!important}

/* 2026-08-22 — Finance sat on an inconsistent gap because .sidefoot uses
   margin-top:auto, pinning sign-out to the sidebar floor; the gap was therefore
   leftover space, varying with window height, while every other group uses a
   fixed 16px. Match the rhythm. TRADE-OFF: sign-out now flows directly after
   Finance instead of being pinned to the bottom. */
@media (min-width:1080px){
  .side .sidefoot{margin-top:16px}
}

/* 2026-08-22 — the sign-out block now carries the same cyan label + hairline
   rule as every other sidebar section, so the plain 1px border-top it used as a
   divider is redundant and would read as a double line. The .navgrp rule
   already styles the heading; only the old divider needs removing. */
@media (min-width:1080px){
  .side .sidefoot{border-top:0;padding-top:0}
}

/* 2026-08-22 — the ACCOUNT heading inside .sidefoot already carries the same
   16px top margin every other group heading has, so the .sidefoot margin-top
   added earlier was stacking on it and producing 32px under Finance. The
   heading owns the spacing now. */
@media (min-width:1080px){
  .side .sidefoot{margin-top:0}
}

/* 2026-08-22 — ⚠️ REGRESSION FIX. Every column-width rule added today was
   scoped to .tablewrap, but accounts.php and finance.php put their tables in
   .tablewrap too. A 5-column accounts table was inheriting the subscribers'
   8-column widths and a 1665px min-width, which is why its spacing exploded.
   The subscribers table now carries .subs; everything else is reset to auto.
   Any future width rule for that table MUST be scoped to table.subs. */
.tablewrap table:not(.subs){min-width:0!important}
.tablewrap table:not(.subs) th,
.tablewrap table:not(.subs) td{width:auto!important}

/* 2026-08-22 — accounts.php column widths. With every cell reset to width:auto
   the browser spread the surplus across all five columns, leaving a wide gap
   between Last login and Actions. Pin the middle columns and let Account (the
   leftmost, holding names) absorb the slack instead. Scoped to table.acct so it
   cannot leak to the subscribers table - the mistake that caused this. */
.tablewrap table.acct th:nth-child(2),.tablewrap table.acct td:nth-child(2){width:130px!important}
.tablewrap table.acct th:nth-child(3),.tablewrap table.acct td:nth-child(3){width:120px!important}
.tablewrap table.acct th:nth-child(4),.tablewrap table.acct td:nth-child(4){width:190px!important}
.tablewrap table.acct th.ar,.tablewrap table.acct td.actrow{width:150px!important}

/* 2026-08-22 — stop horizontal overscroll from firing the browser's
   back/forward gesture, WITHOUT disabling scrolling. First attempt also put
   overscroll-behavior-x:none on html/body and on the table itself, which broke
   scrolling the table sideways. 'contain' on the scroll container alone is the
   correct scope: scrolling inside .tablewrap works exactly as before, but once
   it reaches either end the overscroll stops there instead of chaining up to
   the page and triggering navigation. */
.tablewrap{overscroll-behavior-x:contain}

/* 2026-08-22 — Bundle column trimmed 250 -> 208px. The select stretches to the
   column (.wastier is max-width:100%), so the column width was showing as dead
   space between the label and the chevron. 208px fits the longest label,
   "Premium 800G/mo 80M -> 4M". Table min-width follows it down. */
.tablewrap table.subs th:nth-child(4),
.tablewrap table.subs td:nth-child(4){width:208px!important}
.tablewrap table.subs{min-width:1623px!important}

/* 2026-08-22 — Bundle column 208 -> 232px. 208 clipped the trailing "M" on
   21-char labels, and a clipped bundle label is a correctness risk: this select
   changes a customer's plan, and the truncated part is the throttle rate that
   distinguishes two otherwise identical entries. Paired with removing the
   spaces around the arrow, so 232 leaves headroom rather than fitting exactly. */
.tablewrap table.subs th:nth-child(4),
.tablewrap table.subs td:nth-child(4){width:232px!important}
.tablewrap table.subs{min-width:1647px!important}

/* 2026-08-24 — Fee column, inserted between Expires and Status.
   ⚠️ Checklist §52.4 followed in full: data-i renumbered (Status 6->7, Fee=6),
   empty-state colspan 8->9, this width rule added, min-width raised below.
   Fee sits at nth-child(7); Bundle stays nth-child(4) because the insertion is
   AFTER it, so the existing 232px Bundle rule is deliberately left untouched. */
.tablewrap table.subs th:nth-child(7),
.tablewrap table.subs td:nth-child(7){width:104px!important}
.tablewrap table.subs{min-width:1751px!important}   /* 1647 + 104 */

.tablewrap table.subs td.fee-col{padding-right:10px}
.tablewrap table.subs .feein{
  width:100%;max-width:88px;padding:5px 7px;border-radius:7px;
  border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.04);
  color:inherit;font:inherit;font-size:13px;text-align:right;
}
.tablewrap table.subs .feein:focus{
  outline:none;border-color:rgba(103,232,249,.55);background:rgba(103,232,249,.07);
}
/* Hide the spinner arrows - they steal width in a 104px column. */
.tablewrap table.subs .feein::-webkit-outer-spin-button,
.tablewrap table.subs .feein::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.tablewrap table.subs .feein{-moz-appearance:textfield;appearance:textfield}

/* 2026-08-27 — Address column, inserted between Status and Actions.
   ⚠️ Checklist §52.4 followed in full: placed AFTER every sortable column so no
   existing data-i or nth-child rule moves (data-i=8, nth-child(9)); Actions is
   targeted by class (.ar/.act), not position; empty-state colspan 9->10; width
   rule here; min-width raised by the same 220 below. One column shared with the
   Maintenance page (fup_details.address). */
.tablewrap table.subs th:nth-child(9),
.tablewrap table.subs td:nth-child(9){width:220px!important}
.tablewrap table.subs{min-width:1971px!important}   /* 1751 + 220 */
.tablewrap table.subs td.addr-col{padding-right:10px}
.tablewrap table.subs .js-addr{display:inline-flex;align-items:stretch;max-width:100%}
.tablewrap table.subs .addrin{
  width:158px;max-width:158px;padding:5px 7px;border-radius:7px 0 0 7px;
  border:1px solid rgba(255,255,255,.14);border-right:0;background:rgba(255,255,255,.04);
  color:inherit;font:inherit;font-size:13px;
}
.tablewrap table.subs .addrsave{
  padding:0 8px;line-height:0;cursor:pointer;border-radius:0 7px 7px 0;
  border:1px solid rgba(255,255,255,.14);background:var(--surface-2);color:var(--label);
}
.tablewrap table.subs .addrsave .ic{width:14px;height:14px}
.tablewrap table.subs .addrsave:hover{background:var(--violet-dim);color:var(--text);border-color:rgba(103,232,249,.45)}
.tablewrap table.subs .addrin:focus{
  outline:none;border-color:rgba(103,232,249,.55);background:rgba(103,232,249,.07);
}

/* 2026-08-27 — Status column dead space (operator screenshot). Root cause: the
   unscoped ".tablewrap th:nth-child(8){width:400px}" rule from the 8-column era
   was written for Actions; since the Fee insertion (2026-08-24) nth-child(8) IS
   Status, so Status has silently been 400px wide. Scoped rule, higher
   specificity, wins. 215px fits the nowrap "since 26 Aug 22:30 · 17h 43m" line
   and "THROTTLED 3M/3M". min-width recomputed as the real column sum:
   210+150+120+232+175+165+104+215+220+400 = 1991. */
.tablewrap table.subs th:nth-child(8),
.tablewrap table.subs td:nth-child(8){width:215px!important}
.tablewrap table.subs{min-width:1991px!important}

/* 2026-08-27 — flash notification: (1) dismiss button on every page;
   (2) aligned to the 42px line everything else sits on (.stats/.search pad 12px,
   .tablewrap margins 12px - the 2026-08-22 alignment block above). The flash
   was the one element still starting at 30px, so it looked 12px wider than the
   cards under it on both sides. */
.flash{position:relative;margin:16px 12px;padding-right:44px}
.flash-x{
  position:absolute;right:8px;top:50%;transform:translateY(-50%);
  width:28px;height:28px;padding:0;border:0;border-radius:7px;background:transparent;
  color:inherit;font:400 20px/1 var(--sans);cursor:pointer;opacity:.6;transition:.15s ease
}
.flash-x:hover{opacity:1;background:rgba(255,255,255,.08)}

/* EMG tier card joins the tier family (2026-08-28) */
.s-l{position:relative}
.s-l span:last-child{color:#E8B07F!important}

/* ============================================================
   2026-08-28 · FINANCE ON PHONES — compact cards + one action row
   The generic table.acct stacked-card pattern (2026-08-19) made every
   finance td a full-width block: title, badge, amount and four buttons
   each on their own line — one line item ate half a screen. Scoped to
   .fintable only; the accounts table keeps the old pattern.
   ============================================================ */
@media (max-width:820px){
  table.fintable tr{
    display:flex!important;flex-wrap:wrap;align-items:center;gap:4px 8px;
    padding:9px 11px!important;margin:0 0 8px!important
  }
  table.fintable td{
    display:inline-flex!important;align-items:center;width:auto!important;
    padding:0!important;border:0!important
  }
  /* title + note own the first line */
  table.fintable td:has(> .cust){flex:1 1 100%;display:block!important}
  .fintable .cust b{font-size:14px}
  .fintable .cust .sub{font-size:11.5px;margin-top:1px}
  /* second line: badge · amount · actions pushed right */
  .fintable .tag{font-size:10.5px;padding:2px 8px}
  .fintable .finamt input{width:84px;padding:6px 8px;font-size:13px}
  table.fintable td.actrow{margin-left:auto;gap:4px}
  table.fintable td.actrow form.inline{display:inline-flex}
  /* one-off ledger: date renders as a small chip above the title */
  table.fintable td:has(> small){flex:0 0 auto;font-size:11px;opacity:.75}

  /* add-line form: tight 2-col grid instead of a tall stack */
  .finadd{display:grid!important;grid-template-columns:1fr 1fr;gap:8px}
  .finadd input,.finadd select{padding:9px 10px;font-size:13.5px;min-width:0;width:100%;box-sizing:border-box}
  .finadd input[name=label],.finadd input[name=note]{grid-column:1/-1}
  .finadd .btn{grid-column:1/-1;padding:10px;justify-content:center}
}

/* ============================================================
   2026-08-28b · FINANCE PHONE ROUND 2 (operator screenshots)
   Card = line1: title+note left, badge right · line2: money box
   left, edit/trash right. Arrows are gone; ≡ grip drags instead.
   tr::before is a flex line-break (tr is the flex container, so
   its pseudo-element is a flex item).
   ============================================================ */
.grip{
  display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;border-radius:9px;border:1px solid var(--line);
  background:var(--surface-2);color:var(--muted);
  font:700 15px var(--mono);cursor:grab;
  touch-action:none;user-select:none;-webkit-user-select:none
}
.grip:active{cursor:grabbing;color:var(--cyan-ink)}
#fintbl tr.dragging{opacity:.55;outline:1px dashed rgba(103,232,249,.6)}
#fintbl tr.dragging td{background:var(--surface-2)}

td.act .mini, td.act form.inline, td.act a.mini, td.act .grip{vertical-align:middle}

@media (max-width:820px){
  table.fintable td:has(> .cust){flex:1 1 auto!important;min-width:0;order:0;display:block!important}
  table.fintable td:has(> .tag){order:1;margin-left:auto}
  table.fintable td:has(> small){order:0}
  table.fintable tr::before{content:"";flex-basis:100%;height:0;order:2}
  table.fintable td:has(> .finamt){order:3}
  table.fintable td.actrow{order:4;margin-left:auto}

  /* WAS-badge rows: badge takes its own line so the bundle select
     stops being shoved off the right edge of the screen. */
  .waswas{display:block;width:max-content;max-width:100%}
  td:has(> .waswas) form.inline{display:flex;width:100%;min-width:0}
  td:has(> .waswas) form.inline select{flex:1 1 auto;width:100%;min-width:0;box-sizing:border-box}

  /* dashboard action row: ping / live-speed sat off-baseline next to
     the form-wrapped buttons; center the whole strip instead. */
  td.act{display:flex!important;flex-wrap:wrap;align-items:center;gap:4px}
  td.act form.inline{display:inline-flex;align-items:center}
}

/* 2026-08-28b · phone chip bar — requested order: Maintenance, Accounts,
   Finance, Export Excel. DOM already yields the first three; this parks
   Export last. Desktop sidebar (≥1080 column layout) is untouched. */
@media (max-width:1079px){
  .side .nav.sb-export{order:9}
  .side .nav.adminnav{gap:6px}
}

/* 2026-08-28c(v2) · finance card as a strict 2-col grid — the absolute-pin
   attempt died against the .tablewrap fixed column widths (line ~1594:
   nth-child 130/120/190px !important, written for desktop but not media-
   guarded), so: grid places every cell deterministically instead, and the
   width locks are re-declared auto at ≥equal specificity for phones.
   Recurring row → title+note | badge  /  money | grip·edit·trash.
   One-off row  → date | +$badge  /  title+note | edit·trash. */
@media (max-width:820px){
  table.fintable tr{
    display:grid!important;position:static;
    grid-template-columns:minmax(0,1fr) auto;
    align-items:start;gap:6px 10px;
    padding:10px 12px!important;margin:0 0 8px!important
  }
  table.fintable tr::before{content:none}
  table.fintable td{
    display:block!important;min-width:0;width:auto!important;
    padding:0!important;border:0!important
  }
  .tablewrap table.fintable th:nth-child(2),.tablewrap table.fintable td:nth-child(2),
  .tablewrap table.fintable th:nth-child(3),.tablewrap table.fintable td:nth-child(3),
  .tablewrap table.fintable th:nth-child(4),.tablewrap table.fintable td:nth-child(4),
  .tablewrap table.fintable td.actrow{width:auto!important}
  table.fintable td:has(> .cust){grid-column:1}
  table.fintable td:has(> .tag){grid-column:2;justify-self:end;position:static!important;margin:0}
  table.fintable td:has(> small){grid-column:1}
  table.fintable td:has(> .finamt){grid-column:1;align-self:center}
  table.fintable td.actrow{
    grid-column:2;justify-self:end;align-self:center;
    display:inline-flex!important;align-items:center;gap:4px;margin:0
  }
  .fintable .cust .sub{margin-bottom:0}
}

/* 2026-08-28d · finance actions: the delete button sits inside a form that
   inherits the generic actrow form margin (+6px) while grip and edit are
   bare elements — one uneven gap. Zero it for fintable; whitespace (desktop)
   and the flex gap (phone grid) now space all three identically. */
table.fintable td.actrow form.inline{margin-left:0}

/* 2026-08-28e · add-line forms on exactly two lines at every width.
   DOM order is kind,label,amount,note,btn — auto-placement plus the old
   full-width label rule stacked them 4–5 high. Explicit areas instead:
   [kind | line name | $] / [description | Add]; the one-off ledger form
   (detected via its date field) gets a date slot on line 1. */
.finadd{
  display:grid!important;
  grid-template-columns:auto minmax(0,1fr) 120px;
  grid-template-areas:"kind label amt" "note note add";
  gap:8px;align-items:stretch
}
.finadd:has(input[name=entry_date]){
  grid-template-columns:auto auto minmax(0,1fr) 120px;
  grid-template-areas:"date kind label amt" "note note note add";
}
.finadd input[name=entry_date]{grid-area:date;min-width:0}
.finadd select{grid-area:kind}
.finadd input[name=label]{grid-area:label;min-width:0}
.finadd input[name=amount]{grid-area:amt;min-width:0}
.finadd input[name=note]{grid-area:note}
.finadd .btn{grid-area:add;margin:0;justify-content:center}
@media (max-width:820px){
  .finadd{grid-template-columns:auto minmax(0,1fr) 92px}
  .finadd:has(input[name=entry_date]){grid-template-columns:auto auto minmax(0,1fr) 92px}
}

/* 2026-08-28f · month prev/next read as plain text — dress finnav links
   as chip buttons. finnav spans that hold only text are unaffected. */
.finnav a{
  display:inline-flex;align-items:center;gap:4px;
  padding:5px 11px;border:1px solid var(--line);border-radius:9px;
  background:var(--surface-2);color:inherit;text-decoration:none;
  font:600 12px var(--mono);letter-spacing:.03em
}
.finnav a:hover{border-color:rgba(103,232,249,.65);color:#67E8F9}

/* ============================================================
   2026-08-29 · OPERATOR SCREENSHOTS — three corrections
   ============================================================ */

/* 1. FIRST-FLING DEAD ZONE.
   At <=820px the generic rule at line 371 gives every <table> its own
   overflow-x:auto. The customer table already lives inside .tablewrap,
   which is also overflow-x:auto — so on a phone there were TWO nested
   horizontal scrollers. A fling is delivered to the inner one first and
   only chains outward once the inner hits its end, which is why the first
   throw stopped dead and the second carried. The table inside .tablewrap
   must not scroll itself; .tablewrap is the only scroll container. */
@media (max-width:820px){
  .tablewrap table{display:table;overflow-x:visible;white-space:nowrap}
}

/* 2. SEARCH FIELD WIDTH.
   .sw is a content-sized flex item, so "width:100%" on the input resolved
   against a box that was itself sized by the input — it never filled the
   row. Making .sw a full-width flex line lands its right edge exactly on
   the right edge of Add subscriber, matching the left edge it already had. */
@media (max-width:820px){
  .search .sw{flex:1 1 100%;width:100%;min-width:0}
  .search .sw input{width:100%}
}

/* 3. Table min-width — superseded 2026-08-29c by the authoritative column
   block at the end of this file, which sizes table.subs from the real sum of
   its columns. The rule that sat here was unscoped and so also widened the
   accounts and finance tables, which have nothing to do with this column. */

/* ============================================================
   2026-08-29b · PHONE TOP BAR — Home chip + Sign out chip
   ============================================================ */

/* Desktop keeps its own Dashboard link and its own .sidefoot sign-out, so
   both new chips are phone-only — otherwise each would appear twice. */
@media (min-width:1080px){
  .side .nav.sb-home{display:none!important}
  .sbout{display:none!important}
}

@media (max-width:1079px){
  /* Home sits after the brand, before Maintenance. DOM order already does
     that — the earlier order:-1 is what threw it to the left of the logo.
     Spacing comes from .side's gap like every other chip. */
  .side .nav.sb-home{margin-inline-start:0}

  /* Sign out lives OUTSIDE .side (see _sidebar.php) because .side carries
     backdrop-filter, which makes it the containing block for fixed
     descendants — a fixed chip in there anchors to the scrolling bar, not
     the viewport, and rides along on top of Maintenance.

     The gap beside it is a transparent BORDER, not a margin or padding.
     Padding fails because the padding area is still scrollport, so chips
     scroll straight through it. Margin fails because it ends the bar early
     and exposes the page background, which reads as an odd lighter patch
     next to the button. A border is inside the element — the bar's own
     background paints across it, so the strip is the same black as the rest
     of the bar — while the scrollport stops at the padding box, so no chip
     can enter it. */
  .side{border-inline-end:56px solid transparent}

  /* Labels only. The icons were most of each chip's width, and dropping them
     lets the whole bar breathe without shrinking the text. */
  .side .nav.adminnav a .ic{display:none}
  .side .nav.adminnav a{gap:0;padding:6px 13px}

  /* One spacing source. Gaps were coming from .side's gap, adminnav's own
     gap and a margin-inline-start on top of it, so no two chips sat the same
     distance apart. Everything now spaces from .side's gap alone. */
  .side{gap:8px}
  .side .nav.adminnav{margin-inline-start:0;gap:8px}

  .sbout{
    position:fixed;top:9px;inset-inline-end:11px;z-index:200;
    display:inline-flex;align-items:center;justify-content:center;
    width:34px;height:34px;border-radius:9px;
    color:var(--alert);border:1px solid rgba(239,83,101,.34);
    background:var(--alert-dim);
    text-decoration:none
  }
  .sbout .ic{width:17px;height:17px}
  .sbout:active{border-color:var(--alert)}
}

/* 2026-08-29 — upstream plan, shown read-only in the Bundle column for
   bewise/myisp mirrors in place of the bundle dropdown (which cannot change
   anything on the provider's side). */
.upplan{display:flex;flex-direction:column;gap:2px;line-height:1.3}
.upplan b{font:600 12.5px var(--sans);color:var(--text);white-space:normal}
.upplan small{font:11.5px var(--mono);color:var(--muted)}

/* ============================================================
   2026-08-29c · SUBSCRIBER TABLE COLUMN WIDTHS — AUTHORITATIVE
   ------------------------------------------------------------
   Column widths in this file are pinned by POSITION (nth-child).
   Inserting the "This month" column at position 6 therefore shifted
   every rule after it onto the wrong column: Fee inherited the 400px
   meant for Actions, Status took Address's 220px, and Actions was left
   with no width at all — which is why its ten buttons spilled sideways
   and drew on top of the address field.

   Several of the older rules were already stale, written for an
   8-column table from before Fee and Address existed. Rather than
   patch them one by one, this block states every column once, scoped
   to table.subs so the accounts and finance tables are untouched, and
   placed last so it wins.

   ⚠️ ADDING OR REMOVING A COLUMN? Update this block and the table
   min-width below, or the same overlap comes straight back.

   1 Customer · 2 Username · 3 Remote Web · 4 Bundle · 5 Provider
   6 Used today · 7 This month · 8 Expires · 9 Fee · 10 Status
   11 Address · 12 Actions
   ============================================================ */
.tablewrap table.subs th:nth-child(1), .tablewrap table.subs td:nth-child(1){min-width:255px!important;width:auto!important}
.tablewrap table.subs th:nth-child(2), .tablewrap table.subs td:nth-child(2){min-width:150px!important;width:auto!important}
.tablewrap table.subs th:nth-child(3), .tablewrap table.subs td:nth-child(3){min-width:120px!important;width:auto!important}
.tablewrap table.subs th:nth-child(4), .tablewrap table.subs td:nth-child(4){min-width:208px!important;width:auto!important}
.tablewrap table.subs th:nth-child(5), .tablewrap table.subs td:nth-child(5){min-width:130px!important;width:auto!important}
.tablewrap table.subs th:nth-child(6), .tablewrap table.subs td:nth-child(6){min-width:190px!important;width:auto!important}
.tablewrap table.subs th:nth-child(7), .tablewrap table.subs td:nth-child(7){min-width:190px!important;width:auto!important}
.tablewrap table.subs th:nth-child(8), .tablewrap table.subs td:nth-child(8){min-width:200px!important;width:auto!important}
.tablewrap table.subs th:nth-child(9), .tablewrap table.subs td:nth-child(9){min-width:155px!important;width:auto!important}  /* Fee — fused $ + input + save (~126px) plus 28px cell padding */
.tablewrap table.subs th:nth-child(10), .tablewrap table.subs td:nth-child(10){min-width:150px!important;width:auto!important}
.tablewrap table.subs th:nth-child(11), .tablewrap table.subs td:nth-child(11){min-width:220px!important;width:auto!important}
.tablewrap table.subs th:nth-child(12), .tablewrap table.subs td:nth-child(12){min-width:445px!important;width:auto!important}

/* Sum of the widths above. Too small and the browser squeezes columns
   until their contents overlap; this is what actually holds the layout. */
/* superseded — see the 2378px rule at the end of this file */

/* The action buttons must stay on one line inside their own column and
   never spill into Address. */
.tablewrap table.subs td.act{white-space:nowrap;overflow:hidden}
.tablewrap table.subs td.addr-col{overflow:hidden}
.tablewrap table.subs td.addr-col .addrin{max-width:100%;box-sizing:border-box}

/* ============================================================
   2026-08-29d · OVERFLOW CONTAINMENT + ALIGNMENT
   ============================================================ */

/* 1. NO CELL MAY EVER PAINT OVER ITS NEIGHBOUR.
   The table sets white-space:nowrap and the columns have fixed widths, so
   any content wider than its column simply drew on top of the next one —
   the Expires timestamp spilling into Fee was the case in the screenshot.

   SUPERSEDED 2026-08-29j: clipping was the wrong answer — it hid values
   instead of showing them. The table now uses auto layout with per-column
   MINIMUMS (see the block at the end of this file), so a column grows to fit
   its content and cells still cannot collide. Kept visible here on purpose. */
.tablewrap table.subs th,
.tablewrap table.subs td{
  overflow:visible;
  vertical-align:middle;
}
/* 2. BREATHING ROOM between columns, so values never touch the edge of the
   next one even when a column is nearly full. */
.tablewrap table.subs th,
.tablewrap table.subs td{padding-left:14px;padding-right:14px}

/* Expires holds a full "2026-09-11 12:30:00" for mirror rows — 19 mono
   characters. Sized to fit it whole rather than clip something this
   important. Table min-width follows (+50px). */
/* superseded by the authoritative 12-column block above (Expires is now 8) */

/* 3. Action buttons centred in their column, horizontally and vertically —
   they were pinned right and sat at the top of a tall row. */
.tablewrap table.subs td.act{text-align:center;vertical-align:middle}
.tablewrap table.subs th:nth-child(12){text-align:center}

/* Mirror usage header: badge pushed to the left edge, percentage held at the
   right where every other row shows it. .ubar is justify-content:flex-end,
   which stacked both against the right. */
.ubar.ubar-split{justify-content:space-between;gap:8px}
.ubar.ubar-split .tag{flex:none}

/* 2026-08-29e — actions centred for real.
   Third attempt, and the previous two failed for the same reason: they relied
   on the cell centring its own content (vertical-align:middle, then a flex
   child sized by min-height:100%). Neither resolves reliably inside a
   table-cell whose height is set by a sibling column.

   This does not depend on the cell at all. The wrapper is absolutely
   positioned to fill the cell exactly and centres the buttons inside itself.
   Whatever drives the row height, the buttons sit dead centre.

   ⚠️ Offsets are written LONGHAND on purpose. The `inset:0` shorthand is
   recent; where it is not understood the offsets fall back to auto, which
   parks the box at its static position — top of the cell — and shrink-wraps
   it so it spills out of the column. That is precisely what went wrong.
   Likewise the spacing uses margins, not flex `gap`, which is newer than the
   rest of the flexbox support this panel relies on. */
.tablewrap table.subs td.act{
  /* ⚠️ THE ACTUAL CAUSE of three failed centring attempts. A mobile-only rule
     (~line 1766) sets `td.act{display:flex!important}` as an old workaround
     for ping/live-speed sitting off-baseline. That takes the cell out of
     table-cell layout, so it no longer stretches to the row height — it
     shrink-wraps to the buttons and sits at the row's top edge. Every attempt
     to centre "within the cell" was therefore centring inside a box already
     the height of its content. Restoring table-cell makes the cell fill the
     row again, and the absolute wrapper below then has real height to centre
     against. The off-baseline problem that rule solved is handled properly by
     .actwrap's align-items:center. */
  display:table-cell!important;
  position:relative;
}
.tablewrap table.subs td.act .actwrap{
  position:absolute;
  top:0; right:0; bottom:0; left:0;
  display:flex;
  align-items:center;
  justify-content:center;
  flex-wrap:nowrap;
  padding:0 12px;
}
/* Spacing lives on the direct children. The two rules that used to sit here
   set margin:0 on form.inline and .mini — both more specific than the
   "> *" selector above, so they silently cancelled the spacing. The form now
   carries the margin and only the .mini INSIDE a form is zeroed, so buttons
   are evenly spaced whether or not they are wrapped in a form. */
.tablewrap table.subs td.act .actwrap > *{margin:0 4px}
.tablewrap table.subs td.act .actwrap form.inline{display:inline-flex;align-items:center;margin:0 4px}
.tablewrap table.subs td.act .actwrap form.inline .mini{margin:0}

/* MYISP badge one pixel further left, flush with the column's inner edge. */
.ubar.ubar-split .tag{margin-left:-1px}

/* 2026-08-29f — spacing between action buttons, and a column wide enough to
   hold the worst case. The widest row carries 11 buttons (base 8 + disconnect
   + release + clear-MAC): 11 x 29px icons + 8px spacing each + 20px wrapper
   padding = ~435px. At the old 400px the last button was being clipped, which
   is visible on the fuller rows. Column and table min-width both follow.
   ⚠️ Add another action button and this needs another ~37px. */
/* superseded by the authoritative 12-column block above (Actions is now 12) */
.tablewrap table.subs td.act .actwrap{padding:0 10px}
.tablewrap table.subs{min-width:2413px!important}

/* 2026-08-29g — Provider column + fee currency marker.
   Provider tag: upstream resale providers (myisp / bewise) read differently
   from our own tiers, so they are tinted apart at a glance. */
.provtag{
  display:inline-block;padding:4px 10px;border-radius:999px;
  font:700 11px var(--mono);letter-spacing:.04em;white-space:nowrap;
  border:1px solid var(--line-2);color:var(--label);background:var(--surface-2)
}
/* One colour per tier, taken from the dashboard tier cards (.s-g … .s-l) so
   a tier looks the same wherever it appears. Each tag is its own hue at low
   opacity for the fill, the same hue stronger for the border and text. */
.prov-prem{color:#7FA0E8;border-color:rgba(127,160,232,.38);background:rgba(127,160,232,.14)}
.prov-dmd {color:#A98BE0;border-color:rgba(169,139,224,.38);background:rgba(169,139,224,.14)}
.prov-dmd2{color:#5FC4B0;border-color:rgba( 95,196,176,.38);background:rgba( 95,196,176,.14)}
.prov-g   {color:#D98BC4;border-color:rgba(217,139,196,.38);background:rgba(217,139,196,.14)}
.prov-spc {color:#67E8F9;border-color:rgba(103,232,249,.38);background:rgba(103,232,249,.14)}
.prov-emg {color:#E8B07F;border-color:rgba(232,176,127,.38);background:rgba(232,176,127,.14)}
/* The two upstream resale providers — deliberately outside the tier palette
   so resold customers read as a different kind of thing, not another tier. */
.prov-my  {color:#6EE7B7;border-color:rgba(110,231,183,.38);background:rgba(110,231,183,.14)}
.prov-bw  {color:#E8D57F;border-color:rgba(232,213,127,.38);background:rgba(232,213,127,.14)}

/* 2026-08-29h — the bespoke fee box that used to live here is GONE. The fee
   control is now the finance page's .finamt, used verbatim in index.php, so
   it inherits that styling directly and the two pages cannot drift apart.
   Only two things are still needed: the older .feein overrides below had to
   be neutralised (they predate this and would fight .finamt input), and the
   green $ tint further down covers .finamt .cur on both pages at once. */
.tablewrap table.subs .finamt .feein{
  width:66px;max-width:none;padding:6px 8px;border-radius:0;
  border:1px solid rgba(148,163,184,.25);
  background:rgba(15,18,28,.6);text-align:right;
}
.tablewrap table.subs .finamt .feein:focus{
  border-color:rgba(103,232,249,.5);background:rgba(15,18,28,.6);
}

/* 2026-08-29h — the $ segment, green on BOTH pages.
   One rule covering the finance rows (.finamt) and the customer table's fee
   table (both use .finamt now), so the two can never drift apart. Light tint
   with a matching border; the number field keeps its neutral background so
   the figure stays the thing your eye lands on. */
.finamt .cur{
  display:flex;align-items:center;padding:0 9px;
  font:600 12px var(--mono);
  border:1px solid rgba(52,211,153,.38);border-right:0;
  border-radius:8px 0 0 8px;
  background:rgba(52,211,153,.16);
  color:#6ee7b7;
  opacity:1;
  flex:none;
}
.finamt:hover .cur{
  background:rgba(52,211,153,.24);
  border-color:rgba(52,211,153,.55);
}

/* 2026-08-29i — the bespoke save button is gone; .finamt button styles it,
   exactly as on the finance page. */
.tablewrap table.subs .finamt button .ic{width:15px;height:15px;display:block}

/* ============================================================
   2026-08-29j · COLUMNS GROW, THEY DO NOT HIDE
   ------------------------------------------------------------
   The global rule at line 663 puts every table in table-layout:fixed.
   Under fixed layout a column is exactly its declared width and anything
   longer has nowhere to go — which is why "since 26 Aug 22:30 · 2d" was
   being cut off mid-value. Clipping it was the wrong answer: a truncated
   timestamp is worse than a wider column, and the table scrolls anyway.

   Auto layout instead, with the per-column figures above turned into
   min-widths. A column can never be squeezed below its minimum (so cells
   still cannot collide, which was the original complaint) but it is free to
   grow to fit whatever it holds. Nothing gets hidden.
   ============================================================ */
.tablewrap table.subs{table-layout:auto!important}

/* Content stays on one line so the COLUMN widens rather than the row growing
   taller — except the address, which can be long enough to be worth wrapping
   instead of pushing every other column off-screen. */
.tablewrap table.subs td.addr-col{white-space:normal}

/* The actions cell is the one exception to "grow": its buttons live in an
   absolutely positioned wrapper that contributes no intrinsic width, so the
   column sits at exactly its minimum. Clipping stays here to guarantee the
   wrapper can never paint outside its own cell. */
.tablewrap table.subs td.act{overflow:hidden}

/* 2026-08-29k — brand block nudged left in the phone bar: the wordmark sits
   closer to the logo, and the gap before Home is opened up. Previously both
   gaps were the same (10px inside .brandbox, 8px from .side's gap), so
   "Culmen RADIUS" read as floating midway between the mark and the first
   chip rather than belonging to the mark. Phone bar only — the desktop
   sidebar stacks vertically and is unaffected. */
@media (max-width:1079px){
  .side .brandbox{gap:6px;margin-inline-end:12px}
}

/* Dashboard tier cards for the two upstream providers, matching the Provider
   column tags above so the counts and the rows agree on colour. */
.s-m span:last-child{color:#6EE7B7!important}
.s-n span:last-child{color:#E8D57F!important}

/* ============================================================
   2026-08-29l · TIER CARDS ARE FILTERS
   ------------------------------------------------------------
   The tier cards became <a> elements so they can filter the table. Every
   existing stat-card rule targets ".stats > div", so without this block they
   would render as bare links with none of the card styling. Rather than
   rewrite those rules (they are shared with the dashboard's other stat rows)
   the anchors are given the same treatment here.
   ============================================================ */
.stats > a.tiercard{
  display:flex;align-items:center;gap:13px;min-width:0;
  text-decoration:none;color:inherit;cursor:pointer;
  padding:14px 16px;border-radius:14px;
  border:1px solid var(--line);background:var(--surface);
  transition:border-color .18s ease, background .18s ease, transform .18s ease;
}
.stats > a.tiercard > div{display:flex;flex-direction:column;min-width:0;overflow:hidden}
.stats > a.tiercard > div b{white-space:nowrap}

/* The selected tier is unmistakable — everything else reads as "not chosen"
   rather than the active one merely being slightly brighter. */
.stats > a.tiercard.on{
  border-color:rgba(103,232,249,.55);
  background:var(--cyan-dim);
  box-shadow:0 0 0 1px rgba(103,232,249,.25) inset;
}
.stats > a.tiercard:hover{border-color:rgba(103,232,249,.35)}
.stats > a.tiercard:active{transform:translateY(1px)}

/* ALL leads the row and is intentionally plain: it is the "no filter" state,
   not a tier of its own. */
.s-all .si{background:var(--surface-2);color:var(--label);border-color:var(--line-2)}
.s-all span:last-child{color:var(--label)!important}

/* 2026-08-29m — filter chips aligned with the rows above and below.
   .stats and .search are inset 12px (line ~1444) and .tablewrap by a 12px
   margin, but .chips had no horizontal inset at all, so the row started 12px
   further left than everything else and ran to the very edge on the right.

   MARGIN, not padding: .chips is overflow-x:auto, and the padding area of a
   scroller is still scrollport — chips would slide straight through it and
   touch the screen edge again. A margin narrows the scroll box itself, so
   the row genuinely begins and ends where the cards and the search field do.
   Same reason .tablewrap uses margins for this. */
.chips{margin-left:12px;margin-right:12px}

/* ============================================================
   2026-08-29n · SCROLL PROGRESS BAR
   Ported from culmensoft.com (assets/culmen-polish.css) via eeestock.
   Two things are load-bearing:

   1. transform:scaleX, NOT width. Animating width forces a LAYOUT on every
      scroll frame; scaleX is compositor-only, so the GPU moves it without
      touching layout or paint — exactly when the browser is busiest.
      ⚠️ The JS must set transform, never width. Change one without the
      other and the bar stays invisible at scale 0.

   2. z-index above everything. It has to clear the sticky top bar (40) and
      the modals; at any lower value it slides underneath and looks broken.

   3px rather than culmensoft's 2px — on a phone at reduced zoom a 2px bar is
   most of the way to vanishing.
   ============================================================ */
.progress{
  position:fixed;top:0;left:0;width:100%;height:3px;z-index:9999;
  transform:scaleX(0);transform-origin:0 50%;will-change:transform;
  background:linear-gradient(90deg,var(--cyan-dim),var(--cyan));
  box-shadow:0 0 14px rgba(103,232,249,.55);
  transition:transform .1s linear;pointer-events:none
}

/* ============================================================
   2026-08-29o · CARDS IN THE EEESTOCK SHAPE
   ------------------------------------------------------------
   Rebuilt to the pattern eeestock settled on after three rejected palettes.
   The lesson recorded there was that it is NOT about hex values:

     "the tile carries the colour, so the number stays white"

   A tinted number over a washed background reads faded whatever palette you
   pick. A SOLID 42px tile in a saturated hue with a white glyph carries it,
   and everything else can stay quiet.

   Shape:
     [solid tile]  Name        (18px display, tinted — LARGEST, FIRST)
                   N UNIT      (15px white + mono unit label)     corner count

   ⚠️ The name is largest AND first. Inverting the sizes without also swapping
   the order leaves a big title under a small figure, which reads as a caption
   in the wrong place — eeestock hit exactly that.

   ⚠️ The corner count is IN FLOW, not absolutely positioned. §15 of the master
   notes records this panel's own .tol colliding with longer labels at every
   width until it was rewritten. Not repeating it.

   ⚠️ SCOPED TO .sgroup. finance.php and maintenance.php also use .stats (as
   .stats.finstats) with a different inner structure; an unscoped rule would
   have restyled two pages nobody asked about. .sgroup exists only here.
   ============================================================ */
.sgroup .stats > a.tiercard,
.sgroup .stats > div{
  display:flex;align-items:center;gap:14px;
  padding:15px 16px;border-radius:14px;
  background-color:var(--surface);border:1px solid var(--line);
  transition:border-color .25s ease,transform .25s cubic-bezier(.16,1,.3,1),
             box-shadow .25s ease,background-image .18s ease;
}

/* Tile + lifted label pairs, exactly eeestock's five, extended to the tiers
   this panel has. Tile is dark enough for a white glyph; the label is the
   same hue raised for a dark surface. None collides with --good/--warn/
   --alert, so those still mean verdict and nothing else. */
.s-all  {--tile:#4A5568;--tint:#A0AEC0;--tint-glow:rgba(160,174,192,.26)}
.s-g,.s-a{--tile:#4C74C9;--tint:#7FA0E8;--tint-glow:rgba(127,160,232,.26)}
.s-h,.s-b{--tile:#7A5FC4;--tint:#A98BE0;--tint-glow:rgba(169,139,224,.26)}
.s-i,.s-c{--tile:#2C9E8B;--tint:#5FC4B0;--tint-glow:rgba(95,196,176,.26)}
.s-j,.s-d{--tile:#AE5B94;--tint:#D98BC4;--tint-glow:rgba(217,139,196,.26)}
.s-k,.s-e{--tile:#1E9CB0;--tint:#67E8F9;--tint-glow:rgba(103,232,249,.26)}
.s-l,.s-f{--tile:#B5793F;--tint:#E8B07F;--tint-glow:rgba(232,176,127,.26)}
.s-m    {--tile:#2E9E76;--tint:#6EE7B7;--tint-glow:rgba(110,231,183,.26)}
.s-n    {--tile:#B09A3C;--tint:#E8D57F;--tint-glow:rgba(232,213,127,.26)}

/* The tile. Solid fill, white glyph — this is the piece that carries colour.
   A logo can replace the glyph: .si.haslogo drops the padding and the image
   fills the square, with the tint still underneath so a transparent PNG sits
   on the card's own colour. Same mechanism as an eeestock box logo. */
.sgroup .stats .si{
  display:grid;place-items:center;width:42px;height:42px;border-radius:12px;
  flex:none;border:0;background:var(--tile,#1E9CB0)!important;color:#fff!important;
  font:700 14px var(--mono);letter-spacing:.02em;
}
.sgroup .stats .si .ic{width:20px;height:20px;stroke-width:1.9}
.sgroup .stats .si.haslogo{overflow:hidden;padding:0}
.sgroup .stats .si img{width:100%;height:100%;object-fit:cover;display:block;border-radius:inherit}

/* Body: name first and largest, figure beneath it. */
.sgroup .stats > a.tiercard > div,
.sgroup .stats > div > div{display:flex;flex-direction:column;min-width:0;flex:1;gap:5px}

.sgroup .stats > a.tiercard > div > span,
.sgroup .stats > div > div > span{
  order:-1;                                  /* name above the number */
  font:600 18px/1.15 var(--display);letter-spacing:-.01em;
  color:var(--tint,#67E8F9)!important;
  text-transform:none;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.sgroup .stats > a.tiercard > div > b,
.sgroup .stats > div > div > b{
  display:flex;align-items:baseline;gap:5px;
  font:600 15px/1 var(--display);letter-spacing:-.02em;color:var(--text);
}
/* The unit keeps the smaller figure unambiguous against the corner count. */
.sgroup .stats > a.tiercard > div > b::after,
.sgroup .stats > div > div > b::after{
  content:attr(data-unit);
  font:700 9.5px var(--mono);text-transform:uppercase;
  letter-spacing:.1em;color:var(--faint);
}

/* Corner count — in flow, never absolute. */
.sgroup .stats .tol{
  flex:none;align-self:flex-start;font-style:normal;
  font:400 10.5px var(--mono);color:var(--faint);white-space:nowrap;
}

.sgroup .stats > a.tiercard:hover,
.sgroup .stats > div:hover{
  transform:translateY(-2px);border-color:var(--line-2);
  box-shadow:0 18px 40px -24px rgba(0,0,0,.7);
  background-image:radial-gradient(300px circle at var(--mx,50%) var(--my,50%),
    var(--tint-glow,rgba(103,232,249,.26)),transparent 66%);
}
.sgroup .stats > a.tiercard.on{
  border-color:var(--tint,#67E8F9);
  box-shadow:0 0 0 1px var(--tint,#67E8F9) inset;
  background-image:radial-gradient(320px circle at 50% 0%,
    var(--tint-glow,rgba(103,232,249,.26)),transparent 70%);
}

/* ============================================================
   2026-08-29p(v2) · CARD LOGO + RENAME DIALOG
   ------------------------------------------------------------
   The first version put the preview, the name and a native file input on one
   line. Two things broke: a browser file input renders "Choose file / No file
   chosen" at a width the page cannot control, which pushed the row over; and
   the name then had no room, so "Diamond2" truncated to "Diamond" — two rows
   reading identically, which is worse than ugly.

   Now the PREVIEW IS the upload button (the real input is inside the label,
   visually hidden) and the name is an editable field with the default shown
   underneath it as a hint. Nothing depends on unstyleable browser chrome.
   ============================================================ */
.sgroup .stats .si.haslogo{overflow:hidden;padding:0}
.sgroup .stats .si.haslogo img{
  width:100%;height:100%;object-fit:cover;display:block;border-radius:inherit
}

.glabel{display:flex;align-items:center;gap:10px}
.logobtn{
  font:600 11px var(--mono);letter-spacing:.08em;text-transform:uppercase;
  padding:4px 10px;border-radius:999px;cursor:pointer;
  border:1px solid var(--line-2);background:var(--surface-2);color:var(--label);
  transition:border-color .2s ease,color .2s ease
}
.logobtn:hover{border-color:rgba(103,232,249,.5);color:var(--cyan-ink)}

.logodlg{
  width:min(560px,calc(100vw - 28px));padding:20px;
  /* ⚠️ ONE scroller, not two. The dialog previously scrolled AND .logogrid
     scrolled (max-height:56vh), so a fling was delivered to the inner box and
     only chained outward once it hit its end — the same nested-scroller bug
     as the customer table. The dialog is now overflow:hidden and sized to its
     content up to the viewport; the grid is the single scroll area, with the
     footer pinned outside it so Save is always reachable. */
  display:flex;flex-direction:column;overflow:hidden;
  /* vh FIRST as the fallback, dvh second so it wins where supported. Written
     the other way round the fallback overrides the modern unit, and on a
     phone vh ignores the collapsing URL bar — the footer ends up under it. */
  max-height:calc(100vh - 40px);max-height:calc(100dvh - 40px);
}
#logoform{display:flex;flex-direction:column;min-height:0;flex:1}
.logodlg h2{margin:0 0 8px}
.logodlg .hint{margin:0 0 14px;font-size:12.5px;color:var(--muted);line-height:1.5}
.logogrid{
  display:flex;flex-direction:column;gap:8px;
  flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding-right:4px;-webkit-overflow-scrolling:touch
}

.logorow{
  display:flex;align-items:center;gap:8px;
  padding:9px 10px;border-radius:12px;
  border:1px solid var(--line);background:var(--surface-2)
}
/* .logoform (the old per-row form) is gone — the dialog is one form now. */

/* Preview doubles as the file picker. */
.logoprev{
  width:42px;height:42px;flex:none;border-radius:11px;overflow:hidden;cursor:pointer;
  border:1px dashed var(--line-2);background:var(--surface);color:var(--faint);
  display:grid;place-items:center;transition:border-color .18s ease,color .18s ease
}
.logoprev:hover{border-color:rgba(103,232,249,.55);color:var(--cyan-ink)}
.logoprev.has{border-style:solid;border-color:var(--line-2)}
.logoprev img{width:100%;height:100%;object-fit:cover;display:block}
.logoprev .ic{width:18px;height:18px}
/* The native input is the thing that cannot be styled, so it is removed from
   view entirely and driven by the label around it. */
.logoprev{position:relative}
/* 2026-08-29x — the input must occupy the SAME 42px square as its label.
   As a 1x1 abspos box its containing block was the transformed dialog, so
   its resolved position was nowhere near the square the user tapped —
   Android Chrome's focus autoscroll then panned the view to that phantom
   spot and shoved the dialog header off-screen ("popup breaks" on tap).
   Longhand offsets on purpose (house rule 2026-08-29e: no inset shorthand). */
.logoprev input[type=file]{position:absolute;left:0;top:0;width:100%;height:100%;opacity:0;pointer-events:none}

.logofield{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}
.logoname{
  width:100%;padding:7px 9px;border-radius:8px;
  border:1px solid var(--line-2);background:var(--surface);
  color:var(--text);font:600 13.5px var(--sans);
}
.logoname:focus{outline:none;border-color:rgba(103,232,249,.6)}
.logoname::placeholder{color:var(--faint);font-weight:400}
.logofield small{font:400 10.5px var(--mono);color:var(--faint);padding-left:2px}

.logodel{display:flex;align-items:center}
.logorow .mini{margin:0;flex:none}


/* ============================================================
   2026-08-29q · ⚠️ SUPERSEDES §19 "NO ICON TILES ON STAT CARDS"
   ------------------------------------------------------------
   §19 (line ~781) hides the tile with `.si{display:none!important}` and
   forces `.stats b{font-size:27px!important}` +
   `.stats span{font-size:9.5px!important}`. Those three !important
   declarations were quietly beating the eeestock block above, which is why
   the cards kept rendering as a big number over a small caption with no
   tile — the layout order changed but nothing else did.

   The operator has now asked for the tile back, because the tile is the
   logo slot. §19 is therefore superseded FOR THE DASHBOARD GROUPS ONLY
   (.sgroup); any other .stats on the site keeps §19's treatment.

   ⚠️ !important is required on every line here, not for tidiness but because
   each one is answering an existing !important. Removing it silently
   reverts that property to §19.
   ============================================================ */
.sgroup .stats .si{
  display:grid!important;place-items:center;
  width:42px!important;height:42px!important;border-radius:12px!important;
  flex:none!important;border:0!important;
  background:var(--tile,#1E9CB0)!important;color:#fff!important;
  box-shadow:none!important;
}
.sgroup .stats .si .ic{width:21px!important;height:21px!important;stroke-width:1.9}
.sgroup .stats .si.haslogo{overflow:hidden;padding:0!important}
.sgroup .stats .si.haslogo img{
  width:100%!important;height:100%!important;object-fit:cover;display:block;border-radius:inherit
}

/* §19 set padding + gap:0 on the card; the tile needs the gap back. */
.sgroup .stats > a.tiercard,
.sgroup .stats > div{padding:15px 16px!important;gap:14px!important}

/* Name large and tinted, number smaller — §19 forced the opposite. */
.sgroup .stats > a.tiercard > div > span,
.sgroup .stats > div > div > span{
  font-size:18px!important;font-weight:600!important;
  letter-spacing:-.01em!important;text-transform:none!important;
  color:var(--tint,#67E8F9)!important;
}
.sgroup .stats > a.tiercard > div > b,
.sgroup .stats > div > div > b{font-size:15px!important;color:var(--text)!important}

/* ============================================================
   2026-08-29r · RENEWAL CARDS BACK TO ONE ROW, SAME SIZE AS THE REST
   ------------------------------------------------------------
   .vcard carries `flex-direction:column!important` (line ~884). That was the
   right fix in August for the right reason: .tol is `position:absolute` (line
   867), nothing reserved space for it, and "OUIBERG VPS" + "09 Sep 00:00"
   collided at every width. Stacking made overlap geometrically impossible —
   at the cost of these six cards being much taller than every other card.

   ⚠️ The ORDER of the two rules below matters. Restoring the row is only safe
   BECAUSE .tol is taken out of absolute positioning first. Undo the first and
   the August collision returns immediately. This is the same conclusion
   eeestock reached: "NOT absolutely positioned … kept in flow".
   ============================================================ */
.sgroup .stats .tol{
  position:static!important;top:auto!important;right:auto!important;
  flex:none;align-self:flex-start;
  font:400 10.5px var(--mono)!important;font-style:normal;
  letter-spacing:.04em;color:var(--faint)!important;opacity:1!important;
  white-space:nowrap;
}
.sgroup .stats .vcard{
  flex-direction:row!important;align-items:center!important;gap:14px!important;
  padding:15px 16px!important;
}
.sgroup .stats .vcard > div{width:auto!important;flex:1;min-width:0}
.sgroup .stats .vcard .tol{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis}

/* ============================================================
   2026-08-29s · ONE BOX PER ROW
   ------------------------------------------------------------
   Eleven separate rules set grid-template-columns on .stats across six
   breakpoints (2, 3 and 6 columns depending on width), several with
   !important. Rather than unpick them, this states the intent once for the
   dashboard groups and wins on specificity + position.

   Full-width rows are what makes the eeestock card work: the tile, the name,
   the figure and the corner count all sit on one line with room to breathe.
   At half width the name truncates and the corner count crowds it.
   ============================================================ */
.sgroup .stats{
  grid-template-columns:minmax(0,1fr)!important;
  gap:10px!important;
}

/* ============================================================
   2026-08-29t · PAGER
   ============================================================ */
.pager{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:6px;
  margin:14px 12px 4px;
}
.pg{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:38px;height:38px;padding:0 12px;border-radius:10px;
  font:600 13px var(--mono);text-decoration:none;
  color:var(--label);background:var(--surface);border:1px solid var(--line);
  transition:border-color .18s ease,color .18s ease,background .18s ease;
}
.pg:hover{border-color:rgba(103,232,249,.45);color:var(--text)}
.pg.on{
  color:var(--cyan-ink);background:var(--cyan-dim);
  border-color:rgba(103,232,249,.55);
}
/* Prev/Next at the ends are shown but inert rather than removed, so the row
   does not change width as you page and the buttons stay under your thumb. */
.pg.off{opacity:.35;pointer-events:none}
.pgap{color:var(--faint);font:600 13px var(--mono);padding:0 2px}

/* 2026-08-29p(v3) — one save for the whole dialog.
   The remove control is a CHECKBOX, not a delete button: the dialog is a
   single <form> so every card can save together, and a form cannot be nested
   inside another form. Ticking it clears that logo on save; picking a new
   file unticks it, because choosing a replacement is not a removal. */
.logorm{
  display:grid;place-items:center;width:34px;height:34px;flex:none;cursor:pointer;
  border-radius:9px;border:1px solid var(--line-2);background:var(--surface);
  color:var(--faint);transition:.18s ease
}
.logorm input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.logorm .ic{width:15px;height:15px}
.logorm:hover{border-color:rgba(242,109,120,.5);color:var(--alert)}
.logorm:has(input:checked){
  background:var(--alert-dim);border-color:rgba(242,109,120,.6);color:var(--alert)
}
/* A row whose logo is queued for removal should look queued, not unchanged. */
.logorow:has(.logorm input:checked) .logoprev{opacity:.4}

/* A freshly picked file has not been saved yet — say so rather than letting
   it look identical to a stored logo. */
.logoprev.pending{border-color:rgba(103,232,249,.65);box-shadow:0 0 0 2px var(--cyan-dim)}

.logodlg .formfoot{
  flex:none;                      /* pinned: never scrolls away from the rows */
  display:flex;justify-content:flex-end;gap:8px;
  padding:14px 0 0;margin-top:12px;border-top:1px solid var(--line);background:none
}
.logodlg .formfoot .btn{display:inline-flex;align-items:center;gap:7px}

/* ============================================================
   2026-08-29u · ⚠️ LOGO DIALOG SCROLL — SPECIFICITY FIX
   ------------------------------------------------------------
   `dialog.modal[open]{display:block}` (line 1247) is element + class +
   attribute, which outranks the plain `.logodlg` class. So the flex column
   never applied: #logoform got no height, .logogrid's `flex:1;min-height:0`
   resolved against nothing, and the dialog's own `overflow:hidden` then
   clipped the rows with no scroller anywhere. The dialog looked bricked.

   Matching that specificity is the whole fix. Everything below has to keep
   `dialog.modal.logodlg[open]` or it silently reverts to display:block.
   ============================================================ */
dialog.modal.logodlg[open]{
  display:flex;flex-direction:column;
  /* ⚠️ Centring copied from the base rule at line 1312, NOT inset:0/margin:auto.
     That rule centres with left/top:50% plus translate(-50%,-50%); overriding
     only inset would leave the transform in place and shove the dialog half
     off-screen. */
  position:fixed;inset:auto;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;
  overflow:hidden;                 /* the grid scrolls, not the dialog */
  padding:20px;
  width:min(560px,calc(100vw - 28px));
  max-height:calc(100vh - 40px);
  max-height:calc(100dvh - 40px);
}
dialog.modal.logodlg[open] h2{flex:none;margin:0 0 8px}
dialog.modal.logodlg[open] .hint{flex:none}
dialog.modal.logodlg[open] #logoform{
  display:flex;flex-direction:column;min-height:0;flex:1 1 auto;width:100%
}
dialog.modal.logodlg[open] .logogrid{
  flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch
}
dialog.modal.logodlg[open] .formfoot{flex:none}

/* ============================================================
   2026-08-29v · ACCOUNTS PAGE — TWO ROWS, NOT FIVE
   ------------------------------------------------------------
   The August fix (line ~929) made every cell `display:block;width:100%` on
   phones. That solved a real phantom-scroll problem, but it also meant each
   account occupied five stacked lines — name, role, status, last login,
   actions — so three logins filled the screen.

   Same principle, less of it: the row is a 3-column grid placed by area, so
   nothing is ever wider than the screen (the phantom-width fix still holds)
   while an account reads as two lines:

       name .................. status
       role   last login ..... actions

   ⚠️ Placement is positional (nth-child), and BOTH row shapes — real
   accounts and the config.php legacy rows — have exactly five cells. Add a
   column to the table and this block needs updating with it.
   ============================================================ */
@media (max-width:820px){
  table.acct tbody tr{
    display:grid!important;
    grid-template-columns:minmax(0,1fr) auto auto;
    grid-template-areas:
      "name name  status"
      "role seen  acts";
    gap:8px 10px;align-items:center;
    padding:12px 14px;
  }
  table.acct tbody td{
    width:auto!important;padding:0!important;min-width:0;
    display:block;border:0;
  }
  table.acct tbody td:nth-child(1){grid-area:name}
  table.acct tbody td:nth-child(2){grid-area:role}
  table.acct tbody td:nth-child(3){grid-area:status;justify-self:end}
  table.acct tbody td:nth-child(4){grid-area:seen}
  table.acct tbody td:nth-child(5){
    grid-area:acts;justify-self:end;text-align:right!important;padding-top:0!important
  }

  /* The role select was full-width purely because every cell was; it only
     needs to fit "admin". */
  table.acct tbody td:nth-child(2) select{width:auto!important;min-width:104px;padding:6px 9px}
  /* Last login is reference, not headline — mono and quiet, on one line. */
  table.acct tbody td:nth-child(4){
    font:400 11px var(--mono);color:var(--faint);white-space:nowrap
  }
  table.acct tbody td:nth-child(1) b{font-size:15px}
  table.acct tbody td.actrow,
  table.acct tbody td.act{display:flex!important;justify-content:flex-end;gap:6px;flex-wrap:nowrap}
  table.acct tbody .mini{margin:0}
}

/* `.tablewrap table.acct td.actrow{width:150px!important}` (line ~1590) is
   three classes deep and outranks the `table.acct tbody td` reset above, so
   the actions cell would hold a fixed 150px track inside the new grid and
   squeeze the last-login column. Matched here at equal depth. */
@media (max-width:820px){
  .tablewrap table.acct tbody td.actrow,
  .tablewrap table.acct tbody td.act{width:auto!important}
}

/* ============================================================
   2026-08-29v(fix) · ACCOUNTS ROW WAS STILL OVERFLOWING
   ------------------------------------------------------------
   The 3-column grid I added was `minmax(0,1fr) auto auto` with the role
   select forced to min-width:104px and the date set to nowrap. On a 360px
   screen that is 104 + ~150 + ~150 = well over the width, so the row spilled
   and — because .tablewrap clips overflow for this table — the delete button
   and the start of the date were simply cut off.

   Corrected: the ROLE is auto (it only ever says admin or staff), the DATE
   takes the flexible track and is allowed to ellipsis rather than nowrap
   past the edge, and the actions stay auto. Nothing has a hard minimum, so
   the row can always fit.
   ============================================================ */
@media (max-width:820px){
  table.acct tbody tr{
    grid-template-columns:auto minmax(0,1fr) auto!important;
    gap:8px 8px!important;padding:12px!important
  }
  table.acct tbody td:nth-child(2) select{
    width:auto!important;min-width:0!important;padding:5px 7px;font-size:12.5px
  }
  table.acct tbody td:nth-child(4){
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0
  }
  /* Buttons trimmed so three of them plus the date always clear the edge. */
  table.acct tbody td.actrow .mini{padding:6px}
  table.acct tbody td.actrow .mini .ic{width:15px;height:15px}
}

/* ============================================================
   2026-08-29w · FINANCE ROWS — DETERMINISTIC PLACEMENT
   ------------------------------------------------------------
   The existing mobile block places cells with :has() selectors —
   `td:has(> .tag){grid-column:2}`, `td.actrow{grid-column:2}` — and gives
   neither an explicit ROW. Auto-placement then packed the type tag and the
   action buttons into the same band, so INCOME/EXPENSE was squeezed to
   "INCOM"/"EXPEN" with the grip drawn over it.

   The row has exactly four cells in a fixed order (item · type · amount ·
   actions), so they are placed positionally and by area instead. Nothing is
   left to auto-placement, and the tag gets its own line-end with room to
   print in full.

       item ................ TYPE
       $ amount ........ ≡ ✎ 🗑
   ============================================================ */
@media (max-width:820px){
  table.fintable tbody tr{
    grid-template-columns:minmax(0,1fr) auto!important;
    grid-template-areas:"item type" "amt acts"!important;
    align-items:center!important;gap:8px 10px!important;
  }
  table.fintable tbody td:nth-child(1){grid-area:item!important;min-width:0}
  table.fintable tbody td:nth-child(2){grid-area:type!important;justify-self:end!important}
  table.fintable tbody td:nth-child(3){grid-area:amt!important;align-self:center!important}
  table.fintable tbody td:nth-child(4){
    grid-area:acts!important;justify-self:end!important;align-self:center!important;
    display:inline-flex!important;align-items:center;gap:6px;flex-wrap:nowrap
  }
  /* The tag must never be clipped — it is the only thing saying whether the
     figure beside it is money in or money out. */
  table.fintable tbody td:nth-child(2) .tag{
    white-space:nowrap;overflow:visible;display:inline-block
  }
}

/* ============================================================
   2026-08-29x · LOGO DIALOG ON PHONES — TOP-ANCHORED, NOT CENTRED
   ------------------------------------------------------------
   translate(-50%,-50%) centring means any browser-initiated pan or
   viewport change (file-picker round trip, focus autoscroll) can leave
   the header above the screen with no way to scroll it back — the
   dialog is overflow:hidden by design. Anchoring to the top on phones
   makes that state unreachable: the header is always at y=16.
   Desktop keeps the centred placement. Specificity must stay
   dialog.modal.logodlg[open] (see 2026-08-29u) and this block must
   stay AFTER that one so it wins on order.
   ============================================================ */
@media (max-width:820px){
  dialog.modal.logodlg[open]{
    top:16px;
    transform:translate(-50%,0);
    max-height:calc(100vh - 32px);
    max-height:calc(100dvh - 32px);
  }
}

/* ============================================================
   2026-08-29y · LOGO TILES — NO TINT UNDER A TRANSPARENT LOGO
   ------------------------------------------------------------
   The tile keeps its solid tint under a logo ("so a transparent PNG sits
   on the card's own colour", 2026-08-29o). With glyph-like logos that was
   right; with a full app-icon logo that has its OWN rounded corners the
   tint peeks through the transparent corners as coloured crescents —
   BeWise showed gold arcs. A tile with a logo now paints nothing itself.
   Four classes, so it outranks 2026-08-29q's three-class !important tint.
   ============================================================ */
.sgroup .stats .si.haslogo{background:transparent!important;box-shadow:none!important;border-radius:12px}

/* ============================================================
   2026-08-30 · SUBSCRIPTION CARDS
   ------------------------------------------------------------
   1) The s-b renewal card (4th in the cycle — VPN 7555 today) carried the
      cyan .18 wash from lines ~740/1143, double every other card tint
      (.09/.10), so one box read as "selected". Neutralized to the shared
      surface; hover keeps a faint aura so it still feels alive.
   2) Cards that carry data-suburl (owner login only) are clickable →
      confirm dialog → provider page. Pointer + press feedback.
   3) .subdlg is that confirm dialog; base look inherits dialog.modal.
   ============================================================ */
.sgroup .stats .vcard.s-b{background:var(--surface)!important}
.sgroup .stats .vcard.s-b:hover{background:radial-gradient(320px circle at var(--mx,50%) var(--my,50%),rgba(103,232,249,.10),transparent 62%),var(--surface)!important}
.vcard[data-suburl]{cursor:pointer}
.vcard[data-suburl]:active{transform:translateY(1px)}
/* 2026-08-30b — kill the native Android/Chrome tap flash. Tapping a card
   painted a solid blue-teal rectangle over the whole tile (logo included,
   which is how you can tell it was the browser's -webkit-tap-highlight-color
   and not any rule of ours) for the instant before the dialog opened.
   :active above already gives press feedback, so nothing is lost.
   Focus ring is dropped for pointer taps but KEPT for :focus-visible, so
   keyboard tabbing still shows where you are. user-select stops the blue
   text-selection box on a long press. */
.vcard[data-suburl]{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
.vcard[data-suburl]:focus{outline:none}
.vcard[data-suburl]:focus-visible{outline:2px solid var(--cyan-ink,#67e8f9);outline-offset:2px}
dialog.subdlg{max-width:360px;width:calc(100vw - 48px)}
/* 2026-08-30c — dialog title icon is a plain <img> built from data-sublogo,
   deliberately NOT the card's .si tile: that tile is governed by two competing
   40px/42px rules plus per-class fills, which rendered blank in the heading. */
dialog.subdlg h2{display:flex;align-items:center;gap:10px}
dialog.subdlg h2 #subgoicon{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center}
dialog.subdlg h2 #subgoicon img{width:30px;height:30px;border-radius:8px;object-fit:cover;display:block}
dialog.subdlg h2 #subgoicon svg{width:22px;height:22px}
dialog.subdlg p{margin:2px 0 18px;color:var(--faint,#9aa4b2);font-size:14px;line-height:1.5}
dialog.subdlg .subgorow{display:flex;gap:12px;justify-content:flex-end}

/* ============================================================
   2026-08-30 · AURA ON BOXES + TIERS + CUSTOMER TABLE
   ------------------------------------------------------------
   EXACT copy of the eeestock login box aura
   (eeestock/public/assets/app.css → .loginbox), values unchanged:
     0 0 0 4px      rgba(103,232,249,.07)   halo hugging the border
     0 0 78px -14px rgba(103,232,249,.42)   wide aura
     0 34px 90px    rgba(0,0,0,.60)         shadow that seats the card
   ⚠️ Tier cards are <a class="tiercard">, NOT <div> — `.stats > div`
   silently skips them. Any future rule meant for "every box" must list
   .tiercard explicitly or it will miss the whole Tiers row.
   ============================================================ */
.stats > div,
.stats > a,
.tiercard,
.tablewrap{
  /* Same solid cyan border as .loginbox. !important because
     `.sgroup .stats > a.tiercard, .sgroup .stats > div` (line ~2295) is more
     specific than anything sane to write here and would keep var(--line).
     SOLID, not a gradient — see the .loginbox note: a linear gradient lights
     two corners and fades through the middle of every edge. */
  border:1.5px solid rgba(103,232,249,.55)!important;
  box-shadow:
    /* 12px everywhere. The elements sit in grids and rows with 10–12px gaps,
       and a wider blur reaches out from BOTH neighbours, so the gap between
       two boxes lit up twice over — worst where the search field and the
       Search button stack, which is the tightest pair on the page.
       12px fades out around the midpoint of the gap. */
    0 0 12px rgba(103,232,249,.24),
    0 34px 90px rgba(0,0,0,.60)
}
/* Hover keeps its lift, with the same aura underneath so the halo does not
   vanish the moment the card rises. */
.stats > div:hover,
.stats > a:hover,
.tiercard:hover{
  border-color:rgba(103,232,249,.55)!important;
  box-shadow:
    0 0 12px rgba(103,232,249,.24),
    0 18px 40px -24px rgba(0,0,0,.7),
    0 0 34px -10px var(--glow)
}
/* --- 2026-08-30 · align the Edit / Add subscriber row with the cards.
   The cards are inset TWICE: .main has padding var(--pad) and .sgroup adds
   another var(--pad) inside it. .topact sits in .topbar, which is inset once,
   so the row ran wider than the Claude card at both ends. Matching that
   second inset here rather than removing .sgroup's padding, because .sgroup
   is used on other pages where .main is not its parent.
   box-sizing:border-box is global (line 33), so on mobile the padding comes
   out of the 100% width instead of overflowing it. */
.topact{padding-right:var(--pad)}
@media (max-width:640px){
  .topact{width:100%;padding:0 var(--pad)}
}
/* --- same aura on the Edit / Search buttons and the search box.
   Scoped deliberately:
   · .btn:not(.fill) — you circled Edit and Search but NOT "Add subscriber",
     which sits between them. .fill carries its own solid blue and would be
     muddied by a cyan outline, so it is left alone.
   · .search .sw — the input's wrapper, so the halo sits around the whole
     field including the magnifier, not just the <input> box.
   ⚠️ 2026-08-30 — `.nav.adminnav a` REMOVED from this list. It had no media
   query, so the chip aura leaked onto the DESKTOP sidebar and boxed in
   Maintenance / Export Excel / Accounts, which should look like the plain
   Subscribers links. The chip styling those links are designed for lives in
   @media (max-width:1079px), and the aura now lives there too — see below. */
.search .sw,
.search > button,
.btn:not(.fill){
  border:1.5px solid rgba(103,232,249,.55)!important;
  box-shadow:
    0 0 12px rgba(103,232,249,.24),
    0 34px 90px rgba(0,0,0,.60)
}
.search .sw:focus-within,
.search > button:hover,
.btn:not(.fill):hover{
  border-color:rgba(103,232,249,.55)!important;
  box-shadow:
    0 0 12px rgba(103,232,249,.24),
    0 0 34px -10px var(--glow)
}

/* --- 2026-08-30 · nav chips only, 20% less aura (operator request).
   .24 × 0.8 = .19. Border left at .55; this reduces the glow, not the outline.
   ⚠️ PHONE BAR ONLY. Below 1080px the sidebar collapses to a horizontal bar
   and these render as chips, where the outline is wanted. On desktop they are
   ordinary sidebar links and must match their neighbours — giving them an
   outline there also broke the hover border, because the shared
   `.side .nav a` base is `border:1px solid transparent` and `:hover` merely
   colours it in. Overriding with `border:none` killed the border-style, so
   hover had nothing left to paint. */
@media (max-width:1079px){
  .nav.adminnav a{
    border:1.5px solid rgba(103,232,249,.55)!important;
    box-shadow:
      0 0 12px rgba(103,232,249,.19),
      0 34px 90px rgba(0,0,0,.60)
  }
  .nav.adminnav a:hover{
    border-color:rgba(103,232,249,.55)!important;
    box-shadow:
      0 0 12px rgba(103,232,249,.19),
      0 0 34px -10px var(--glow)
  }
}

/* --- 2026-08-30 · wider aura for the customers table (operator request).
   12px is tuned for the card grid, where neighbours sit 10-12px apart and a
   wide blur double-lights the gap. The table has no such neighbour — it is
   one large element like the login box it was copied from — so it takes a
   wide spread without bleeding into the legend above or the pager below.
   40px → 34px, 15% back off, on operator review.
   Appended at the END of the file on purpose: the shared rule earlier also
   matches .tablewrap, and at equal specificity the LAST rule wins. */
.tablewrap{
  box-shadow:
    0 0 34px rgba(103,232,249,.24),
    0 34px 90px rgba(0,0,0,.60)
}

/* ============================================================
   2026-08-30 · DESKTOP ONLY — dashboard card sections go
   multi-column. MOBILE IS UNCHANGED, and that is the whole
   point of the media query.
   ============================================================

   `.sgroup .stats` earlier in this file forces a single column
   with !important. That was deliberate — full-width rows stop
   the longer names truncating — but the operator wants several
   per row on a laptop. auto-fit + minmax means the column count
   follows the available width instead of a fixed guess, so no
   breakpoint can get it wrong.

   ⚠️ 260px is the tuning knob. Lower it for more columns, raise
   it if a name starts truncating. The `.tol` corner label is
   what crowds first.

   ⚠️ The sidebar half of this block was REMOVED on 2026-08-30.
   It killed `border` and `box-shadow` on `.side .nav.adminnav a`
   to strip the leaked chip aura — but the plain sidebar base is
   `border:1px solid transparent` and `:hover` only colours that
   border in, so `border:none` left hover nothing to paint and
   the Tools/Admin links lost their hover outline entirely.
   Fixed at the source instead: the chip aura is now scoped to
   @media (max-width:1079px). Do not reintroduce an override here.

   ⚠️ Appended at the END on purpose: the rule this overrides sits
      earlier at equal specificity, and the LAST one wins.
   ============================================================ */
@media (min-width:1080px){
  .sgroup .stats{
    grid-template-columns:repeat(auto-fit,minmax(260px,1fr))!important;
  }
}

/* ============================================================
   2026-08-30 · TIER CARD HOVER GLOW — MATCH THE SUBSCRIPTION
   CARDS. Operator: "more shiny than the aura in subscribers".
   ============================================================
   It was exactly DOUBLE, and here is why:

   · Subscription cards use s-f / s-e / s-c / s-b. Those match the
     `.s-a:hover` … `.s-f:hover` family (~line 1147), whose
     `background:` SHORTHAND carries !important — so it beats the
     `background-image` at line 2367 and pins them to cyan .13.

   · Tier cards use s-all and s-g … s-n. None of those is in the
     s-a…s-f family, so nothing overrides line 2367 and they render
     `var(--tint-glow)`, which every tier class defines at .26.

   .26 → .13 brings them into line. Matching the subscription cards
   means matching their COLOUR too, so the hover wash is cyan rather
   than the per-tier hue. The resting tint and the `.on` state keep
   their own colour — only the cursor glow is neutralised.

   ⚠️ If you want the per-tier hue back at the lower strength, this
   needs a second softer variable per class (nine declarations at
   ~.13) rather than a flat cyan here.

   Not media-scoped on purpose: the mismatch exists at every width,
   and :hover is a no-op on touch so mobile is unaffected in practice.
   ⚠️ Must stay at the END — line 2367 is more specific than this
   selector alone, hence !important.
   ============================================================ */
.sgroup .stats > a.tiercard:hover{
  background-image:radial-gradient(300px circle at var(--mx,50%) var(--my,50%),
    rgba(103,232,249,.13),transparent 66%)!important;
}

/* 2026-09-01 — split search: name/phone on the left, username on the right.
   The username box is deliberately narrower - usernames are short and the two
   boxes have to share one row on a laptop without wrapping. The mobile rule at
   the bottom lets both go full width and stack, matching the existing .sw rule
   for the single box. */
.sw--user input{width:200px}
.sw--user{margin-inline-start:2px}

/* 2026-09-01 — "All" pager button. Tinted so it reads as a mode switch rather
   than another page number, and it carries the row count so the operator knows
   what they are about to render before they click it. */
.pg--all{
  border-color:rgba(103,232,249,.38);
  color:#67E8F9;
  background:rgba(103,232,249,.10);
  font-weight:600;
}
.pg--all:hover{border-color:rgba(103,232,249,.7);background:rgba(103,232,249,.18);color:#A5F3FC}

@media (max-width:900px){
  .search .sw--user{flex:1 1 100%;width:100%;min-width:0}
  .sw--user input{width:100%}
}
/* 2026-09-09 — renewal date field inside the cards dialog.
   Sits next to the name field on VPN / VPS / subscription rows only. Narrow,
   because the row already carries a logo square, a name box and a remove
   checkbox, and the dialog must not start wrapping on a phone. */
.logorow .logodue { display:flex; align-items:center; }
.logorow .logodue input[type="date"]{
  width: 148px; padding: 7px 9px; border-radius: 10px;
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.14);
  color: var(--ink, #f4f4f7);
  font: inherit; font-size: 13px; line-height: 1.1;
}
.logorow .logodue input[type="date"]:focus{
  outline: none; border-color: rgba(103,232,249,.55);
  box-shadow: 0 0 0 3px rgba(103,232,249,.12);
}
.logorow .logodue input[type="date"]::-webkit-calendar-picker-indicator{
  filter: invert(1) opacity(.55); cursor: pointer;
}
@media (max-width: 560px){
  .logorow .logodue input[type="date"]{ width: 128px; }
}

/* 2026-10-04 (operator) - the customer table's sideways scrollbar sits on the card's
   bottom edge; its thumb and dark track ran into the 14px rounded corners and showed
   as a square block at the bottom-left on phones. Track transparent and kept clear
   of both corners; the thumb stays visible so the sideways scroll is not guessed at. */
.tablewrap::-webkit-scrollbar-track{background:transparent;margin:0 14px}
.tablewrap::-webkit-scrollbar-corner{background:transparent}

/* ============================================================
   2026-10-11 OPERATOR · MAC COLUMN — SUBSCRIBER TABLE WIDTHS, AUTHORITATIVE (13 columns)
   ------------------------------------------------------------
   "add column for mac address". MAC sits right after Remote Web, so every
   later position moved by one. This block states all 13 columns once and is
   placed LAST so it wins over every older positional rule (the 2026-08-29c
   12-column block above is superseded). Actions is centred by class (.ar),
   not by position. min-width = the real sum: 2413 + 165 = 2578.
   1 Customer · 2 Username · 3 Remote Web · 4 MAC · 5 Bundle · 6 Provider
   7 Used today · 8 This month · 9 Expires · 10 Fee · 11 Status · 12 Address
   13 Actions
   ⚠️ ADDING OR REMOVING A COLUMN? Update this block, the empty-state colspan
   in index.php and the min-width below.
   Revert: restore style.css from /var/www/office-bot/stage/mac-search-20261011/style.css.live-20261011
   ============================================================ */
.tablewrap table.subs th:nth-child(1),  .tablewrap table.subs td:nth-child(1){min-width:255px!important;width:auto!important}
.tablewrap table.subs th:nth-child(2),  .tablewrap table.subs td:nth-child(2){min-width:150px!important;width:auto!important}
.tablewrap table.subs th:nth-child(3),  .tablewrap table.subs td:nth-child(3){min-width:120px!important;width:auto!important}
.tablewrap table.subs th:nth-child(4),  .tablewrap table.subs td:nth-child(4){min-width:165px!important;width:auto!important}  /* MAC */
.tablewrap table.subs th:nth-child(5),  .tablewrap table.subs td:nth-child(5){min-width:208px!important;width:auto!important}
.tablewrap table.subs th:nth-child(6),  .tablewrap table.subs td:nth-child(6){min-width:130px!important;width:auto!important}
.tablewrap table.subs th:nth-child(7),  .tablewrap table.subs td:nth-child(7){min-width:190px!important;width:auto!important}
.tablewrap table.subs th:nth-child(8),  .tablewrap table.subs td:nth-child(8){min-width:190px!important;width:auto!important}
.tablewrap table.subs th:nth-child(9),  .tablewrap table.subs td:nth-child(9){min-width:200px!important;width:auto!important}
.tablewrap table.subs th:nth-child(10), .tablewrap table.subs td:nth-child(10){min-width:155px!important;width:auto!important} /* Fee */
.tablewrap table.subs th:nth-child(11), .tablewrap table.subs td:nth-child(11){min-width:150px!important;width:auto!important}
.tablewrap table.subs th:nth-child(12), .tablewrap table.subs td:nth-child(12){min-width:220px!important;width:auto!important} /* Address */
.tablewrap table.subs th:nth-child(13), .tablewrap table.subs td:nth-child(13){min-width:445px!important;width:auto!important} /* Actions */
.tablewrap table.subs th:nth-child(12){text-align:left}
.tablewrap table.subs th.ar{text-align:center}
.tablewrap table.subs{min-width:2578px!important}
.tablewrap table.subs td.mac-col{font-family:var(--mono);font-size:13px;white-space:nowrap;color:var(--label)}
.tablewrap table.subs td.mac-col .sub{font-size:11px;opacity:.7}
