/* ============================================================
   DonutAFK — dashboard theme
   Every selector here mirrors the markup/JS 1:1; this file is
   purely presentational, so restyling never risks behaviour.
   ============================================================ */

/* Universal fallback for classList.add('hidden'). Existing components each
   had their own .X.hidden rule instead of relying on this (found the hard
   way: #billing-pending had no such rule and was visibly showing despite
   carrying the class) -- this doesn't remove any of those, a compound
   selector is more specific and wins regardless, it just guarantees any
   .hidden element is actually hidden even if a component-specific rule is
   missing or a new one is added later without remembering to add one. */
.hidden { display: none; }

/* ---------- Glass surfaces ----------
   One rule for every raised surface in the panel, so a card, a popover and a
   modal are unmistakably the same material. The components below set only
   their layout; anything that painted its own background has had that
   declaration removed rather than left to fight this one.

   The inset highlight is the whole trick. A translucent panel with a flat
   edge reads as a hole cut in the page; a 1px lighter line along the top
   reads as a pane sitting above it, catching the light. */
.card,
.stat-card,
.side-stat,
.status-pill,
.notif-item,
.modal-body,
.login-card,
.dropdown-menu,
.notif-popover {
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  box-shadow: var(--shadow), inset 0 1px 0 var(--glass-hi);
}

/* Controls are the same material a step lighter, so they read as sitting on
   the glass rather than punched through it. */
input:where(:not([type=checkbox]):not([type=radio]):not([type=range])),
textarea,
.btn.dark,
.mono-key,
.login-tabs {
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 var(--glass-hi);
}

:root {
  /* Surfaces: near-black with a cool blue cast, layered by elevation */
  --bg: #05070c;
  --sidebar: #080a11;
  --panel: #10141d;
  --panel-2: #161b26;
  --elevated: #1b2130;

  --hover: rgba(255, 255, 255, 0.045);
  --line: rgba(255, 255, 255, 0.06);
  --line-2: rgba(255, 255, 255, 0.11);

  /* Text ramp */
  --text: #eef2f8;
  --text-2: #b8c2d0;
  --muted: #7d8798;
  --faint: #58616f;

  /* Accents */
  --primary: #6366f1;
  --primary-2: #8b8ff8;
  --primary-soft: rgba(255, 45, 120, 0.15);
  --primary-glow: rgba(255, 45, 120, 0.35);
  --accent: #f5b423;
  --amber: #f0a935;
  --amber-soft: rgba(240, 169, 53, 0.13);
  --danger: #f4635e;
  --danger-soft: rgba(244, 99, 94, 0.12);
  --ok: #34c47c;
  --ok-soft: rgba(52, 196, 124, 0.13);

  /* Glass. Every raised surface is one of these two rather than a flat
     fill: a translucent tint over the ambient wash, blurred, with a 1px
     light inset along the top edge where a real pane would catch light.
     The same material macOS and Windows 11 use for sidebars and popovers. */
  --glass:       rgba(18, 22, 38, 0.58);
  --glass-2:     rgba(26, 31, 50, 0.50);
  --glass-hi:    rgba(255, 255, 255, 0.075);
  --glass-blur:  saturate(160%) blur(22px);

  /* Depth */
  --shadow: 0 24px 56px rgba(0, 0, 0, 0.6), 0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.3);

  --radius: 16px;
  --radius-sm: 12px;
  --radius-xs: 9px;

  --ease: cubic-bezier(0.4, 0, 0.2, 1);
}

* { box-sizing: border-box; }
/* Reserve the scrollbar's 9px whether or not it is showing. Opening a panel
   makes the page taller, the scrollbar appears, and the entire layout is
   suddenly 9px narrower -- every card, the grid, the header all reflow
   sideways mid-animation. Closing it snaps them back. The gutter costs 9px of
   width once; the alternative is the whole page twitching on every toggle. */
html { scrollbar-gutter: stable; }
html, body {
  margin: 0; padding: 0;
  /* Enough wash for the glass to have something to pick up, and no more.
     Turned up far enough to see clearly, the page stops reading as a dark
     app and starts reading as a purple one. */
  background:
    radial-gradient(1100px 680px at 68% -6%, rgba(255, 45, 120, 0.16), transparent 62%),
    radial-gradient(880px 600px at 4% 22%, rgba(168, 85, 247, 0.09), transparent 64%),
    radial-gradient(900px 580px at 46% 102%, rgba(129, 140, 248, 0.07), transparent 62%),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 15.5px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Slim, unobtrusive scrollbars everywhere they still appear */
* { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.14) transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; border: 2px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); background-clip: content-box; }

.announcement-banner {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  padding: 10px 44px 10px 18px; background: rgba(245, 180, 35, 0.12); color: var(--accent);
  border-bottom: 1px solid rgba(245, 180, 35, 0.25); font-size: 15.5px; font-weight: 600;
  text-align: center; position: relative;
}
.announcement-banner.hidden { display: none; }
.announcement-banner .announcement-actions { position: absolute; right: 14px; top: 50%; transform: translateY(-50%); }
.announcement-banner .icon-btn { color: var(--accent); }

/* Shell is defined by the command-bar layout further down. The old three-rail
   grid and its breakpoints are removed -- the 1280px rule used to display:none
   the chat rail, which is the bug the drawer exists to fix. */

/* ---------- Sidebar ---------- */
.sidebar {
  /* The rails are the deepest panes in the stack: more blur, less tint, so
     the wash behind them stays visible down the whole column. */
  background: rgba(7, 9, 16, 0.62);
  backdrop-filter: saturate(180%) blur(30px);
  -webkit-backdrop-filter: saturate(180%) blur(30px);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column; padding: 20px 12px;
  position: sticky; top: 0; height: 100vh;
}
.brand { display: flex; align-items: center; gap: 10px; padding: 4px 8px 24px; }
.brand-mark {
  width: 32px; height: 32px; flex: 0 0 auto;
  display: grid; place-items: center;
  border-radius: 11px; font-size: 17.5px; font-weight: 700; color: #fff;
  box-shadow: 0 4px 14px rgba(255, 45, 120, 0.4);
}
.brand-name { font-weight: 680; font-size: 17px; line-height: 1.2; letter-spacing: -0.01em; }
.brand-sub { color: var(--muted); font-size: 13px; letter-spacing: 0.02em; }

.nav-list { display: flex; flex-direction: column; gap: 2px; flex: 1; }
.nav {
  position: relative;
  display: flex; align-items: center; gap: 11px;
  background: transparent; border: 0; color: var(--muted);
  padding: 9px 11px; border-radius: var(--radius-xs);
  font: inherit; font-size: 15.5px; font-weight: 550; text-align: left; cursor: pointer;
  transition: background 0.16s var(--ease), color 0.16s var(--ease);
}
.nav svg { flex: 0 0 auto; opacity: 0.8; width: 16px; height: 16px; }
.nav:hover { background: var(--hover); color: var(--text); }
.nav.active {
  background: linear-gradient(135deg, rgba(255, 45, 120, 0.20), rgba(168, 85, 247, 0.14));
  color: var(--text); font-weight: 650;
  box-shadow: inset 0 0 0 1px rgba(255, 45, 120, 0.30);
}
.nav.active svg { opacity: 1; color: var(--primary-2); }
.nav.hidden { display: none; }

.notif-bell-wrap { position: relative; margin-left: auto; }
.notif-bell-btn { position: relative; }
.notif-badge {
  position: absolute; top: 0; right: 0; pointer-events: none;
  background: var(--danger); color: #fff; font-size: 11.5px; font-weight: 700;
  line-height: 1; padding: 2px 5px; border-radius: 999px; min-width: 8px; text-align: center;
  box-shadow: 0 0 0 2px var(--sidebar), 0 2px 6px rgba(244, 99, 94, 0.5);
}
.notif-badge.hidden { display: none; }

.notif-popover {
  /* Anchored to the RIGHT edge of the bell. It used to open rightward from a
     left-hand sidebar; from a top-right command bar that ran straight off the
     screen. */
  position: absolute; top: calc(100% + 10px); right: 0; left: auto;
  width: 340px; max-width: calc(100vw - 24px);
  max-height: 420px; overflow-y: auto;
  background: rgba(20, 14, 26, 0.97);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm); padding: 11px; box-shadow: var(--shadow); z-index: 70;
}
.notif-popover.hidden { display: none; }
.notif-popover-head {
  display: flex; align-items: center; justify-content: space-between;
  font-weight: 650; font-size: 15.5px; margin-bottom: 9px; padding: 0 2px;
}

.notif-list { display: flex; flex-direction: column; gap: 8px; }
.notif-item {
  display: flex; flex-direction: column; gap: 4px;
  border-left: 2px solid var(--danger);
  border-radius: var(--radius-xs);
  padding: 11px 13px;
}
.notif-item-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.notif-item-account { font-weight: 650; font-size: 15px; }
.notif-item-time { color: var(--faint); font-size: 13px; flex: 0 0 auto; }
.notif-item-message { color: var(--danger); font-size: 15px; line-height: 1.45; }

/* Live figures parked in the sidebar's dead space (it used to be a tall empty
   gap between the nav and the footer) so they're readable from any tab. */
.side-stats {
  margin-top: auto; display: flex; flex-direction: column; gap: 8px;
  padding: 0 2px 14px;
}
.side-stat {
  display: flex; flex-direction: column; gap: 3px;
  padding: 11px 13px;
  border-radius: var(--radius-sm);
}
.side-stat em {
  font-style: normal; font-size: 11.5px; color: var(--faint);
  text-transform: uppercase; letter-spacing: 0.09em; font-weight: 620;
}
.side-stat span {
  font-size: 18.5px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.side-stat-money { color: var(--ok) !important; }

.sidebar-footer { padding-top: 12px; }
.instance-picker { margin-bottom: 9px; }
.instance-picker label {
  display: block; font-size: 12px; color: var(--faint); text-transform: uppercase;
  letter-spacing: 0.08em; margin-bottom: 6px; padding-left: 2px; font-weight: 600;
}
.select-ghost.full { width: 100%; }
.status-pill {
  display: flex; align-items: center; gap: 10px; padding: 10px 11px;
  border-radius: var(--radius-sm);
}
.dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--danger);
  box-shadow: 0 0 0 3px rgba(244, 99, 94, 0.14), 0 0 8px rgba(244, 99, 94, 0.5);
  flex: 0 0 auto;
}
.dot.connected { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-soft), 0 0 9px rgba(52, 196, 124, 0.55); }
.dot.connecting { background: var(--accent); box-shadow: 0 0 0 3px rgba(245, 180, 35, 0.14); animation: pulse 1.2s infinite; }
@keyframes pulse { 50% { opacity: 0.35; } }
.status-label { font-size: 14.5px; font-weight: 620; text-transform: capitalize; letter-spacing: -0.005em; }
.status-sub { font-size: 13px; color: var(--muted); }
.status-sub.warn { color: var(--danger); font-weight: 600; }

/* ---------- Content / views ---------- */
.content { padding: 34px 40px 72px; overflow-x: hidden; }
.view { display: none; }
.view.active { display: block; animation: viewIn 0.24s var(--ease); }
/* Opacity only -- deliberately NO transform. A transformed ancestor becomes the
   containing block for position:fixed descendants, which would re-break the
   row dropdown menus (see .dropdown-menu) by offsetting them by the sidebar +
   padding width. */
@keyframes viewIn { from { opacity: 0; } to { opacity: 1; } }

.page-header { margin-bottom: 30px; display: flex; align-items: flex-start; justify-content: space-between; gap: 8px 20px; flex-wrap: wrap; }
.page-header h1 { margin: 0; font-size: 26.5px; font-weight: 680; letter-spacing: -0.025em; }
/* The subtitle belongs under the title, not opposite it. The header is
   space-between so a page can put actions on the right, and with the
   subtitle as a plain sibling that rule flung it to the far edge of the
   screen -- on Overview it ended up a full column away from the heading it
   describes. Giving it the whole next line puts it back under the title and
   still leaves the right-hand side free for actions. */
.page-header > .subtitle { flex: 1 1 100%; order: 3; }
.page-header .subtitle { margin: 0; color: var(--muted); font-size: 15.5px; line-height: 1.5; }
.controls-header-right { display: flex; align-items: center; gap: 10px; padding-top: 2px; flex-wrap: wrap; }

/* align-items:start, not the default stretch. Stretched, a short card is
   padded out to match the tallest one beside it -- which is where the big
   dead space under Look Direction came from. Each card is now its own
   height. */
.page-grid { display: grid; gap: 20px; align-items: start; }
.page-grid.two-col { grid-template-columns: 1fr 1fr; }
.page-grid.three-col { grid-template-columns: 1fr 1fr 1fr; }
@media (max-width: 1100px) { .page-grid.three-col { grid-template-columns: 1fr; } }
@media (max-width: 800px) { .page-grid.two-col { grid-template-columns: 1fr; } }

.section { margin-top: 38px; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; margin-bottom: 15px; flex-wrap: wrap; }
.section-head h2 { margin: 0; font-size: 14px; font-weight: 680; color: var(--muted); text-transform: uppercase; letter-spacing: 0.09em; }
.section-head .hint { margin: 0; }

/* ---------- Card ---------- */
.card {
  border-radius: var(--radius);
  padding: 22px;
}
.card h2 {
  margin: 0 0 14px; font-size: 14px;
  letter-spacing: 0.09em; color: var(--muted); font-weight: 680;
  text-transform: uppercase;
}
.hint { color: var(--muted); font-size: 14.5px; margin: 5px 0 9px; line-height: 1.55; }
.hint code { background: var(--panel-2); padding: 2px 6px; border-radius: 5px; color: var(--text-2); font-size: 14px; }

/* Card header with a mode-switcher dropdown on the right (e.g. the Selling
   card's Manual Sell / Sell Axe picker) instead of a plain <h2>. */
.card-head-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.card-head-row h2 { margin: 0 !important; }
.sell-mode-panel.hidden { display: none; }

/* ---------- Forms ---------- */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 13px; }
.field > span { font-size: 14.5px; color: var(--muted); font-weight: 550; }

input, select, textarea {
  font: inherit; color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius-xs); padding: 10px 12px; width: 100%;
  transition: border-color 0.16s var(--ease), box-shadow 0.16s var(--ease), background 0.16s var(--ease);
}
input::placeholder, textarea::placeholder { color: var(--faint); }
/* The dropdown *list* a <select> opens is drawn by the OS/browser, not this
   stylesheet's box model -- background-color/color on <option> is the only
   part of it modern Chromium/Firefox actually respect. */
select { color-scheme: dark; }
select option { background: #161b26; color: #eef2f8; }
input:hover, select:hover, textarea:hover { border-color: var(--line-2); }
input:focus, select:focus, textarea:focus { box-shadow: 0 0 0 3px var(--primary-soft); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft), inset 0 1px 0 var(--glass-hi); }
textarea { resize: vertical; font-family: ui-monospace, Consolas, monospace; font-size: 15.5px; margin-bottom: 8px; }

.select-ghost {
  width: auto; background-color: transparent; border: 1px solid var(--line-2);
  color: var(--text-2); font-size: 15px; padding: 7px 28px 7px 12px; border-radius: 999px;
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%237d8798' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.select-ghost:hover { border-color: var(--primary); color: var(--text); background-color: var(--hover); }

.row { display: flex; gap: 9px; margin-bottom: 10px; }
.row > * { flex: 1; }
.row > .btn { flex: 0 0 auto; }

.add-account-row { display: flex; gap: 9px; margin: -8px 0 22px; }
.add-account-row.hidden { display: none; }
.add-account-row input { flex: 1; }

.toolbar-row { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; flex-wrap: wrap; }
.toolbar-row input { flex: 1; min-width: 140px; }
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-scroll .accounts-table { min-width: 880px; }

/* ---------- Buttons ---------- */
/* Neutral buttons are made of the same glass as the panels they sit on: the
   lighter control tint, a hairline border and the 1px top highlight. Coloured
   buttons (primary, danger, ok) are the exception -- they are solid and
   un-blurred on purpose, because an action you want someone to take should
   not be made of the same translucent material as the surface behind it. */
.btn {
  cursor: pointer; font: inherit; font-weight: 620; font-size: 15.5px;
  border-radius: 999px; padding: 9px 17px;
  border: 1px solid var(--line-2);
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 var(--glass-hi);
  color: var(--text-2);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  transition: background 0.16s var(--ease), color 0.16s var(--ease),
              border-color 0.16s var(--ease), box-shadow 0.16s var(--ease), transform 0.1s var(--ease);
  white-space: nowrap;
}
.btn:hover { color: var(--text); border-color: var(--line-2); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16); }
.btn:active { transform: translateY(1px); }
.btn.full { width: 100%; margin-top: 9px; }
.btn.small { padding: 7px 13px; font-size: 14.5px; }

/* Every solid button turns the blur off. Left on, the tint underneath shows
   through the gradient and the colour goes muddy against a dark card. */
.btn.primary, .btn.danger, .btn.ok {
  backdrop-filter: none; -webkit-backdrop-filter: none;
  border-color: transparent;
}
.btn.primary {
  background: linear-gradient(135deg, rgba(255, 45, 120, 0.14), rgba(255, 45, 120, 0.07)), var(--panel);
  color: #ffd5e6;
  font-weight: 600;
  border-color: rgba(255, 83, 145, 0.4);
  box-shadow: inset 0 1px 0 rgba(255, 160, 195, 0.07);
}
.btn.primary:hover {
  background: linear-gradient(135deg, rgba(255, 45, 120, 0.21), rgba(255, 45, 120, 0.12)), var(--panel);
  color: #ffe7f0;
  border-color: rgba(255, 104, 160, 0.6);
  box-shadow: inset 0 1px 0 rgba(255, 160, 195, 0.1);
}
.btn.ghost { background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none; color: var(--text-2); border-color: var(--line-2); }
.btn.ghost:hover { background: var(--glass-2); color: var(--text); box-shadow: inset 0 1px 0 var(--glass-hi); }
.btn.dark { color: var(--text-2); border-color: var(--line); }
.btn.dark:hover { color: var(--text); border-color: var(--line-2); }
.btn.danger {
  background: linear-gradient(135deg, #ff7a75, var(--danger)); color: #fff;
  box-shadow: 0 4px 16px rgba(244, 99, 94, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.22);
}
.btn.danger:hover { filter: brightness(1.09); box-shadow: 0 8px 22px rgba(244, 99, 94, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.28); }
.btn.danger-outline { background: var(--danger-soft); color: var(--danger); border-color: rgba(244, 99, 94, 0.35); }
.btn.danger-outline:hover { background: rgba(244, 99, 94, 0.2); border-color: var(--danger); }
/* Connect / Disconnect -- promoted out of the row overflow menu, so they need
   to read as distinct actions at a glance rather than as generic buttons. */
.btn.ok {
  background: linear-gradient(135deg, #45e096, var(--ok));
  color: #05240f; font-weight: 680;
  box-shadow: 0 4px 14px rgba(52, 196, 124, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.btn.ok:hover { filter: brightness(1.08); box-shadow: 0 8px 20px rgba(52, 196, 124, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.36); }
.btn.warn-outline { background: var(--amber-soft); color: var(--amber); border-color: rgba(240, 169, 53, 0.4); }
.btn.warn-outline:hover { background: rgba(240, 169, 53, 0.2); border-color: var(--amber); color: #ffc76b; }
.btn.hidden { display: none; }

/* ---------- Segmented control ----------
   One piece of glass divided by hairlines, the way a platform toolbar groups
   related actions. Reads as "three ways to drive the same thing" instead of
   three unrelated buttons that happen to sit near each other. */
.segmented {
  display: inline-flex; align-items: stretch;
  margin-bottom: 20px;
  border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 var(--glass-hi);
  overflow: hidden;
}
.seg {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 10px 18px;
  border: 0; border-left: 1px solid var(--line);
  background: transparent; color: var(--text-2);
  font: inherit; font-size: 15.5px; font-weight: 620;
  cursor: pointer; white-space: nowrap;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.seg:first-child { border-left: 0; }
.seg svg { opacity: 0.8; }
.seg:hover { background: rgba(255, 255, 255, 0.06); color: var(--text); }
.seg:active { background: rgba(255, 255, 255, 0.1); }
/* The two that carry consequence get their colour on hover only, so the
   control reads as one object at rest rather than a traffic light. */
.seg-go:hover { background: rgba(52, 196, 124, 0.16); color: #6ee7a8; }
.seg-go:hover svg { opacity: 1; }
.seg-stop:hover { background: var(--danger-soft); color: var(--danger); }
.seg-stop:hover svg { opacity: 1; }

/* Start/Stop + Connect/Disconnect share the Action column */
.action-cell { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }

/* License list: account-limit inline edit */
.license-max-cell { display: flex; align-items: center; gap: 5px; }
.license-max-input {
  width: 56px; padding: 5px 7px; font-size: 15px;
  background: var(--panel-2); border: 1px solid var(--line-2); border-radius: var(--radius-xs); color: var(--text);
}

.icon-btn {
  width: 32px; height: 32px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-xs);
  color: var(--muted); cursor: pointer; font-size: 18.5px; line-height: 1;
  transition: background 0.14s var(--ease), color 0.14s var(--ease), border-color 0.14s var(--ease);
}
/* Transparent until hovered, then it becomes the same glass as the other
   controls -- an icon button that is always outlined turns a toolbar into a
   row of competing boxes. */
.icon-btn:hover {
  background: var(--glass-2); color: var(--text);
  border-color: var(--line); box-shadow: inset 0 1px 0 var(--glass-hi);
}
.icon-btn-danger:hover { background: var(--danger-soft); color: var(--danger); }
/* Proxy-assign icon: tinted whenever a proxy is actually assigned, so the
   fact is visible at a glance without spelling out the label in the row. */
.proxy-trigger-active { color: var(--primary-2); }
.proxy-trigger-active:hover { background: var(--primary-soft); color: var(--primary-2); }
.proxy-choice-active { color: var(--primary-2) !important; font-weight: 650 !important; }
.proxy-choice-active::after { content: ' ✓'; }

/* ---------- Dropdown menu ---------- */
.menu-wrap { position: relative; display: inline-flex; }
/* Fixed, not absolute: the row menus live inside .table-scroll, whose
   overflow-x:auto forces overflow-y to compute to auto as well -- an absolutely
   positioned menu taller than the remaining table height turned into a
   scrollbar instead of just overflowing (very noticeable with a single
   account, where the user had to scroll to reach "Connect"). Fixed positioning
   escapes that clipping context; JS sets top/left from the trigger's rect. */
.dropdown-menu {
  position: fixed; min-width: 158px;
  border-color: var(--line-2); border-radius: var(--radius-sm);
  padding: 6px; z-index: 60;
}
.dropdown-menu.hidden { display: none; }
.dropdown-menu button {
  display: block; width: 100%; text-align: left; padding: 8px 10px;
  background: none; border: none; color: var(--text-2); font-size: 15.5px;
  border-radius: var(--radius-xs); cursor: pointer; font-weight: 520;
  transition: background 0.13s var(--ease), color 0.13s var(--ease);
}
.dropdown-menu button:hover { background: var(--hover); color: var(--text); }
.dropdown-menu .menu-danger { color: var(--danger); }
.dropdown-menu .menu-danger:hover { background: var(--danger-soft); }
.menu-divider { height: 1px; background: var(--line); margin: 5px 4px; }

/* ---------- Account nickname ---------- */
/* The real profile name stays visible under the nickname -- you still need to
   know which Minecraft account a row actually is when assigning proxies or
   reading a kick message. */
.account-realname {
  display: block;
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Hidden until the row is hovered or the button is focused, so eight rows
   don't each carry a permanently visible pencil competing with the names. */
.nickname-edit { opacity: 0; transition: opacity 0.13s var(--ease); }

@media (hover: none) { .nickname-edit { opacity: 1; } }
.nickname-input {
  width: 100%; min-width: 0; max-width: 190px;
  padding: 5px 8px; font-size: 15px; font-weight: 600;
  background: var(--glass-2); border: 1px solid var(--primary-2);
  border-radius: var(--radius-xs); color: var(--text);
}
.nickname-input:focus { outline: none; }

/* ---------- Per-account colour picker ---------- */
/* The trigger is the swatch itself -- an icon would need a colour of its own
   competing with the one it's setting. */
.color-dot {
  width: 13px; height: 13px; border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.35), 0 0 0 1px var(--glass-hi);
}
.color-menu { min-width: 186px; padding: 9px; }
.color-menu-title {
  font-size: 12.5px; font-weight: 650; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--muted); padding: 2px 4px 8px;
}
.color-swatches { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; padding: 0 2px; }
.color-swatch {
  width: 100%; aspect-ratio: 1; padding: 0; border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.35); cursor: pointer;
  transition: transform 0.12s var(--ease), box-shadow 0.12s var(--ease);
}
.color-swatch:hover { transform: scale(1.16); }
/* Ring rather than a tick: a checkmark over a saturated dot this small is
   unreadable on the lighter presets. */
.color-swatch-active { box-shadow: 0 0 0 2px var(--panel), 0 0 0 3.5px currentColor; }
.color-custom { display: flex; align-items: center; gap: 7px; padding: 2px; }
/* Native picker, restyled to a swatch -- the browser's own wheel is the one
   part of this worth not reimplementing, but its default chrome is a grey
   box that looks pasted in from another decade. */
.color-wheel {
  width: 30px; height: 30px; flex: 0 0 auto; padding: 0;
  background: none; border: 1px solid var(--line-2); border-radius: var(--radius-xs);
  cursor: pointer;
}
.color-wheel::-webkit-color-swatch-wrapper { padding: 3px; }
.color-wheel::-webkit-color-swatch { border: none; border-radius: 3px; }
.color-wheel::-moz-color-swatch { border: none; border-radius: 3px; }
.color-hex {
  flex: 1 1 auto; min-width: 0; width: 100%;
  padding: 6px 8px; font-size: 14.5px;
  font-family: ui-monospace, Consolas, monospace;
  background: var(--glass-2); border: 1px solid var(--line-2);
  border-radius: var(--radius-xs); color: var(--text);
}
.color-hex:focus { outline: none; border-color: var(--primary-2); }

/* ---------- Custom on-theme select ----------
   A native <select>'s open option list is OS/browser chrome no CSS can
   restyle. The real <select> stays in the DOM (visually hidden, still the
   source of truth) with a themed button + .dropdown-menu layered on top --
   see enhanceSelect() in app.js. */
.custom-select { position: relative; display: inline-flex; }
.custom-select.full { display: flex; width: 100%; }
.custom-select.full .custom-select-btn { flex: 1; width: 100%; }
.native-select-hidden { position: absolute; width: 0; height: 0; padding: 0; margin: 0; border: 0; opacity: 0; pointer-events: none; }
/* The trigger styles itself rather than inheriting from whatever classes the
   original <select> happened to carry. It used to copy the select's class
   list, so a select without `.select-ghost` -- the licence tier picker, built
   by JS -- rendered as an unstyled white button on a dark page. `.select-ghost`
   still layers its pill shape on top where it's present. */
.custom-select-btn {
  font: inherit; font-size: 15px; text-align: left; cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 30px 8px 12px;
  border: 1px solid var(--line-2); border-radius: var(--radius-xs);
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 var(--glass-hi);
  color: var(--text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%237d8798' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  transition: border-color 0.16s var(--ease), color 0.16s var(--ease);
}
.custom-select-btn:hover { border-color: var(--primary); color: var(--text); }
.custom-select-btn:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
.custom-select-menu { max-height: 260px; overflow-y: auto; }

/* ---------- Switch rows ---------- */
.switch-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 12px 0; border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.switch-row:last-of-type { border-bottom: 0; }
.switch-row > span { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
.switch-row > span b { font-size: 15.5px; font-weight: 620; color: var(--text); }
.switch-row > span em { font-style: normal; font-size: 14px; color: var(--muted); line-height: 1.45; }

/* Custom pill switch */
input[type=checkbox].switch {
  appearance: none; -webkit-appearance: none;
  width: 42px; height: 24px; border-radius: 999px; padding: 0;
  background: var(--glass-2); border: 1px solid var(--line-2);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4);
  position: relative; cursor: pointer; flex: 0 0 auto;
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
input[type=checkbox].switch::before {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--muted); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  transition: transform 0.2s var(--ease), background 0.2s var(--ease);
}
input[type=checkbox].switch:checked {
  background: var(--grad);
  border-color: transparent; box-shadow: 0 0 10px var(--primary-glow);
}
input[type=checkbox].switch:checked::before { transform: translateX(18px); background: #fff; }

/* ---------- Slider + number combo ---------- */
.speed-input-row { display: flex; align-items: center; gap: 14px; }
.speed-input-row input[type=number] { width: 88px; flex: 0 0 auto; text-align: center; font-variant-numeric: tabular-nums; }
input[type=range].speed-slider {
  appearance: none; -webkit-appearance: none; flex: 1;
  height: 6px; padding: 0; margin: 0;
  /* A visible track. At the old tint it was nearly invisible against the
     card, so the thumb looked like it was floating on nothing. */
  background: rgba(255, 255, 255, 0.10);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
  border: none; border-radius: 999px;
}
input[type=range].speed-slider::-webkit-slider-thumb {
  appearance: none; -webkit-appearance: none;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--grad);
  border: 2px solid #fff; cursor: pointer;
  box-shadow: 0 2px 6px rgba(0,0,0,0.45);
}
input[type=range].speed-slider::-moz-range-thumb {
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--primary); border: 2px solid #fff; cursor: pointer;
  box-shadow: 0 2px 6px rgba(0,0,0,0.45);
}
input[type=range].speed-slider::-moz-range-track { background: rgba(255, 255, 255, 0.10); height: 6px; border-radius: 999px; }

/* ---------- Stats ---------- */
.stats { display: grid; grid-template-columns: 90px 1fr; gap: 9px 12px; margin: 0 0 14px; }
.stats dt { color: var(--muted); font-size: 15.5px; }
.stats dd { margin: 0; font-variant-numeric: tabular-nums; font-size: 16.5px; }
#stat-balance { color: var(--ok); font-weight: 700; }

/* ---------- Chat rail (persistent, right column) ---------- */
.chat-rail {
  border-left: 1px solid var(--line);
  background: rgba(7, 9, 16, 0.62);
  backdrop-filter: saturate(180%) blur(30px);
  -webkit-backdrop-filter: saturate(180%) blur(30px);
  display: flex; flex-direction: column;
  padding: 20px 16px;
  position: sticky; top: 0; height: 100vh;
}
.chat-rail-head {
  /* Title and its live badge read as one label on the left; the close button is
     the only thing on the right, where a drawer's dismiss belongs. */
  display: flex; align-items: center; gap: 10px;
  font-size: 16px; font-weight: 660; color: var(--text); margin-bottom: 13px; flex: 0 0 auto;
  letter-spacing: -0.01em;
}
.chat-rail-head .chat-rail-title { margin-right: 2px; }
.chat-rail-head #chat-close { margin-left: auto; }
.live-dot { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 680; color: var(--ok); text-transform: uppercase; letter-spacing: 0.07em; }
.live-dot-glow { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 8px var(--ok); animation: pulse 2s infinite; }
.chat-log {
  flex: 1; min-height: 0; overflow-y: auto;
  background: rgba(4, 6, 11, 0.72); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 13px 14px;
  font-family: ui-monospace, Consolas, monospace; font-size: 14.5px;
  line-height: 1.6;
}
.chat-log .line { white-space: pre-wrap; word-break: break-word; padding: 1px 0; }
.chat-log .system { color: var(--primary-2); }
.chat-log .ts { color: var(--faint); margin-right: 8px; }
.chat-log .who { font-weight: 700; margin-right: 6px; }
.chat-log .who::after { content: ':'; opacity: 0.55; }
.chat-form { display: flex; gap: 8px; margin-top: 12px; flex: 0 0 auto; }
.chat-form input { flex: 1; }

/* ---------- Search ---------- */
.search-input { flex: 1.4 1 auto; border-radius: 999px; padding: 9px 15px; }

/* ---------- Stat cards ---------- */
.stat-card {
  display: flex; align-items: center; gap: 12px;
  border-radius: var(--radius-sm); padding: 13px 17px;
  /* Equal width, not width-by-content. Sized to their text, "3 / Tracked
     Accounts" came out visibly wider than "3/3 / Online Now" beside it, so a
     row of sibling figures read as three unrelated boxes instead of one set. */
  flex: 1 1 0; min-width: 150px;
  transition: border-color 0.16s var(--ease), box-shadow 0.16s var(--ease);
}
/* No lift on hover. Glass panes don't hop -- the edge brightens instead,
   which is what the platform toolkits do and what stops a row of tiles
   bouncing as the pointer crosses it. */
.stat-card:hover { border-color: var(--line-2); box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,0.16); }
.stat-card svg { color: var(--primary-2); flex: 0 0 auto; }
.stat-card div { display: flex; flex-direction: column; line-height: 1.25; }
.stat-card span { font-size: 18.5px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.stat-card em { font-style: normal; font-size: 12px; color: var(--faint); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; }

/* ---------- Overview: online accounts list ---------- */
.ov-online-list { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }
.ov-online-row {
  display: flex; align-items: center; gap: 11px;
  padding: 11px 8px; border-bottom: 1px solid var(--line);
  border-radius: var(--radius-xs);
  transition: background 0.14s var(--ease);
}
.ov-online-row:hover { background: var(--hover); }
.ov-online-row:last-child { border-bottom: none; }
.ov-online-row .sb-avatar { width: 32px; height: 32px; border-radius: 9px; }
.ov-online-row .ov-name-block { flex: 1; min-width: 0; }
.ov-online-row .ov-name-block b { display: block; font-size: 15.5px; font-weight: 620; }
.ov-online-row .ov-name-block span { font-size: 14px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.ov-empty { color: var(--muted); font-size: 15.5px; padding: 12px 8px; }

.batch-bar {
  display: flex; align-items: center; gap: 10px; margin-bottom: 12px;
  padding: 10px 14px;
  background: linear-gradient(135deg, var(--primary-soft), rgba(168, 85, 247, 0.08));
  border: 1px solid rgba(255, 45, 120, 0.25);
  border-radius: var(--radius-sm);
}
.batch-bar.hidden { display: none; }
.batch-count { font-size: 15px; color: var(--primary-2); font-weight: 620; margin-right: auto; }

/* ---------- Accounts table ---------- */
.accounts-table { width: 100%; border-collapse: collapse; margin-top: 4px; }
.accounts-table th, .accounts-table td { text-align: left; padding: 14px 11px; }
.accounts-table th {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--faint); font-weight: 680; padding-bottom: 9px; border-bottom: 1px solid var(--line);
}
.accounts-table tbody tr { border-bottom: 1px solid var(--line); transition: background 0.14s var(--ease); }
.accounts-table tbody tr:last-child { border-bottom: none; }
.accounts-table tbody tr:hover { background: var(--hover); }
.accounts-table td { font-size: 15.5px; }
.accounts-table .sb-avatar { width: 40px; height: 40px; border-radius: 11px; }
.accounts-table .account-name-block b { font-size: 16px; font-weight: 630; }
.server-link { color: var(--primary-2); font-size: 15px; }
.mono-key {
  font-family: ui-monospace, Consolas, monospace; font-size: 14px; letter-spacing: 0.03em;
  color: var(--text-2); padding: 4px 9px; border-radius: 6px;
  border: 1px solid var(--line);
}
.badge {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px;
  font-size: 12.5px; font-weight: 650; text-transform: uppercase; letter-spacing: 0.05em;
}
.badge::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.badge.connected { background: var(--ok-soft); color: var(--ok); box-shadow: inset 0 0 0 1px rgba(52,196,124,0.2); }
.badge.disconnected { background: rgba(125, 135, 152, 0.13); color: var(--muted); box-shadow: inset 0 0 0 1px rgba(125,135,152,0.16); }
.badge.connecting { background: rgba(245, 180, 35, 0.13); color: var(--accent); box-shadow: inset 0 0 0 1px rgba(245,180,35,0.2); }
.type-pill {
  display: inline-flex; padding: 4px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  background: var(--amber-soft); color: var(--amber);
  box-shadow: inset 0 0 0 1px rgba(240, 169, 53, 0.2);
}
.error-cell { color: var(--faint); font-size: 15px; }
.error-text { color: var(--danger); }
.row-actions { display: flex; gap: 7px; justify-content: flex-end; align-items: center; }
.row-actions .select-ghost { font-size: 14px; padding: 6px 22px 6px 10px; max-width: 118px; background-position: right 8px center; }

.group-row td { padding: 22px 11px 7px; background: transparent; border-bottom: none; }
.group-row:first-child td { padding-top: 6px; }
.group-label { font-size: 12.5px; font-weight: 680; color: var(--faint); text-transform: uppercase; letter-spacing: 0.08em; }
.group-count {
  margin-left: 8px; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums;
  background: var(--panel-2); border-radius: 999px; padding: 2px 8px; border: 1px solid var(--line);
}

/* ---------- Look pad ----------
   A d-pad and its numeric entry are two ways to do one thing, so they sit
   side by side on one row. Stacked, the pad left a tall empty card and the
   two fields stretched to fill a width they had no use for. */
.look-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
}
/* Fixed footprint: nothing here may resize as the numbers change. */
.look-controls .joystick { flex: 0 0 auto; margin: 0; }

.look-pad {
  display: grid;
  grid-template-columns: repeat(3, 42px);
  grid-template-rows: repeat(3, 42px);
  gap: 6px; width: fit-content; flex: 0 0 auto;
  /* No glow behind it. A radial wash under a plus-shaped cluster of buttons
     is decoration pretending to be structure -- the grid already reads as a
     pad without a halo around it. */
}
.look-btn {
  display: flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; padding: 0; margin: 0;
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line-2); border-radius: 12px;
  color: var(--text-2); cursor: pointer;
  box-shadow: inset 0 1px 0 var(--glass-hi);
  transition: background 0.15s var(--ease), color 0.15s var(--ease),
              border-color 0.15s var(--ease), transform 0.1s var(--ease);
}
.look-btn:hover { background: var(--primary-soft); color: var(--primary-2); border-color: var(--primary); }
.look-btn:active { transform: scale(0.94); }
.look-btn-up    { grid-column: 2; grid-row: 1; }
.look-btn-left  { grid-column: 1; grid-row: 2; }
.look-btn-center{ grid-column: 2; grid-row: 2; }
.look-btn-right { grid-column: 3; grid-row: 2; }
.look-btn-down  { grid-column: 2; grid-row: 3; }
/* The centre is "look level" -- a reset. It used to be the only solid indigo
   button on the page, which pulled the eye to the least important control in
   the card. It's quieter than its neighbours now, not louder. */
.look-btn-center { background: transparent; box-shadow: none; border-style: dashed; color: var(--faint); }
.look-btn-center:hover { background: var(--glass-2); color: var(--text-2); border-style: solid; }

.look-manual { display: flex; gap: 10px; align-items: flex-end; }
.look-manual .field { flex: 0 0 auto; margin-bottom: 0; }
.look-manual .btn { flex: 0 0 auto; }

/* Numeric entry sized to its content. A field that holds "53" or "-180" does
   not need to be as wide as the card it sits in. */
.field-num { max-width: 104px; }
.field-num input { width: 100%; text-align: center; font-variant-numeric: tabular-nums; }

.total-bal-card {
  background: linear-gradient(150deg, rgba(52, 196, 124, 0.09), rgba(52, 196, 124, 0.02));
  border-color: rgba(52, 196, 124, 0.24);
}
.total-bal-label { color: var(--muted); font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 7px; font-weight: 620; }
.total-bal-value { font-size: 33px; font-weight: 720; color: var(--ok); font-variant-numeric: tabular-nums; letter-spacing: -0.03em; }
.per-inst-list { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.per-inst-list .inst { background: var(--panel-2); padding: 7px 11px; border-radius: var(--radius-xs); font-size: 15.5px; border: 1px solid var(--line); }
.per-inst-list .inst-label { color: var(--muted); margin-right: 6px; }

.account-list { margin-top: 12px; display: flex; flex-direction: column; gap: 9px; }
.account-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas: "check info badge" "check info actions";
  gap: 6px 14px;
  align-items: center;
  padding: 13px 15px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  transition: border-color 0.14s var(--ease);
}
.account-row:hover { border-color: var(--line-2); }
.account-row .account-check { grid-area: check; }
.account-row .account-info { grid-area: info; min-width: 0; }
.account-info b { display: block; font-size: 15.5px; font-weight: 620; }
.account-email { color: var(--muted); font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.account-row .badge { grid-area: badge; justify-self: end; }
.account-row .account-actions { grid-area: actions; display: flex; gap: 7px; flex-wrap: wrap; justify-content: flex-end; }
.account-actions button[disabled] { opacity: 0.4; cursor: not-allowed; }

.toast {
  position: fixed;
  bottom: 26px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(27, 33, 48, 0.97);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  padding: 12px 18px;
  color: var(--text);
  font-size: 15.5px; font-weight: 520;
  box-shadow: var(--shadow);
  z-index: 200;
  max-width: 90vw;
  animation: toastIn 0.26s var(--ease);
}
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 12px); } to { opacity: 1; transform: translate(-50%, 0); } }
.toast.error { border-color: rgba(244, 99, 94, 0.5); color: var(--danger); }
.toast.success { border-color: rgba(52, 196, 124, 0.5); color: var(--ok); }
.toast.hidden { display: none; }
.check-inline { display: flex; align-items: center; gap: 7px; font-size: 15.5px; color: var(--muted); cursor: pointer; }
.check-inline input { width: 17px; height: 17px; }

/* ---------- Checkbox ----------
   `accent-color` only recolours Chrome's own control -- the shape, the tick,
   the corner radius and the focus ring all stay the browser's, which is why
   these read as a form widget dropped onto the page rather than part of it.
   Drawn from scratch instead: same glass as every other control, our radius,
   and a tick stroked in CSS. */
input[type=checkbox]:not(.switch) {
  appearance: none; -webkit-appearance: none;
  width: 17px; height: 17px; flex: 0 0 auto;
  margin: 0; padding: 0;
  border: 1px solid var(--line-2); border-radius: 5px;
  background: var(--glass-2);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
  cursor: pointer; position: relative;
  transition: background 0.14s var(--ease), border-color 0.14s var(--ease);
}
input[type=checkbox]:not(.switch):hover { border-color: var(--primary); }
input[type=checkbox]:not(.switch):checked {
  background: var(--grad);
  border-color: transparent;
  box-shadow: 0 2px 8px rgba(255, 45, 120, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
/* The tick is a rotated L, not a glyph -- a font character would sit at
   whatever baseline the font decides and never centre reliably. */
input[type=checkbox]:not(.switch):checked::after {
  content: '';
  position: absolute; left: 5px; top: 1.5px;
  width: 4px; height: 8px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(43deg);
}
input[type=checkbox]:not(.switch):focus-visible {
  outline: none; box-shadow: 0 0 0 3px var(--primary-soft);
}
.badge.running { background: var(--ok-soft); color: var(--ok); box-shadow: inset 0 0 0 1px rgba(52,196,124,0.2); }
.badge.stopped { background: rgba(125, 135, 152, 0.13); color: var(--muted); box-shadow: inset 0 0 0 1px rgba(125,135,152,0.16); }

.inv-grid { display: grid; grid-template-columns: repeat(9, minmax(0, 56px)); gap: 5px; justify-content: start; }
.inv-slot {
  width: 56px; height: 56px; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 7px; display: flex; align-items: flex-end; justify-content: flex-end;
  padding: 4px; position: relative; font-family: ui-monospace, Consolas, monospace;
  overflow: hidden;
}
.inv-slot.held { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 0 12px rgba(245,180,35,0.25); }
.inv-slot .name {
  position: absolute; top: 3px; left: 4px; right: 4px;
  font-size: 12px; color: var(--muted); line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.inv-slot .count { font-weight: 700; font-size: 15.5px; text-shadow: 1px 1px 0 #000; color: var(--text); }
.inv-divider { grid-column: 1 / -1; height: 1px; background: var(--line); margin: 5px 0; }

.signin-block { padding: 14px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); margin: 14px 0; }

/* ---------- Hero ----------
   One number, far bigger than anything else on the page, with a tiny tracked
   label above it and a single grey line of context underneath. No card around
   it: a figure this size doesn't need a box to be found. */
.hero { margin: 2px 0 40px; }
.hero-label {
  font-size: 12px; font-weight: 680; text-transform: uppercase;
  letter-spacing: 0.11em; color: var(--muted);
}
.hero-value {
  margin-top: 8px;
  font-size: clamp(44px, 6.4vw, 76px); font-weight: 720; line-height: 1;
  letter-spacing: -0.035em; font-variant-numeric: tabular-nums;
  background: linear-gradient(105deg, #ffffff 0%, #ffd7e8 48%, var(--primary-2) 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.hero-sub { margin-top: 12px; color: var(--muted); font-size: 15.5px; }

/* The sidebar mirrors total balance and online count so they're readable from
   the other tabs. On Overview the hero says both, three inches away -- so
   they're hidden here rather than printed twice on one screen. Browsers
   without :has() simply keep showing them, which is the old behaviour. */
body:has(#view-overview.active) .side-stats { display: none; }

/* ---------- Figure row ----------
   Related figures that belong to one story live on one surface divided by
   hairlines. Four separate tiles make four separate claims; alignment
   already groups them, so the boxes were doing nothing the layout wasn't. */
.figure-row {
  display: grid; grid-template-columns: repeat(4, 1fr);
  padding: 20px 0; margin-bottom: 18px;
  border-radius: var(--radius);
  background: var(--glass);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  box-shadow: var(--shadow), inset 0 1px 0 var(--glass-hi);
}
.figure { padding: 0 22px; min-width: 0; border-left: 1px solid var(--line); }
.figure:first-child { border-left: 0; }
.figure-label {
  font-size: 12px; font-weight: 680; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--muted);
}
.figure-value {
  margin-top: 8px; font-size: 27.5px; font-weight: 700;
  letter-spacing: -0.03em; font-variant-numeric: tabular-nums;
}
@media (max-width: 1100px) {
  .figure-row { grid-template-columns: repeat(2, 1fr); row-gap: 22px; }
  .figure:nth-child(3) { border-left: 0; }
}
@media (max-width: 620px) {
  .figure-row { grid-template-columns: 1fr; row-gap: 20px; }
  .figure { border-left: 0; }
}

/* Secondary actions, folded away until wanted. */
.more-actions { margin-top: 34px; display: flex; flex-direction: column; gap: 10px; }
.more-actions summary { cursor: pointer; margin: 0; }
.more-actions details[open] { padding-bottom: 6px; }

/* ---------- Scoreboard ---------- */
.sb-board { margin-top: 4px; }
.sb-row {
  position: relative;
  display: flex; align-items: center; gap: 13px; padding: 14px 10px;
  border-bottom: 1px solid var(--line);
  transition: background 0.14s var(--ease);
}
/* The rule under each row IS the share bar: accent for this account's share
   of the total, hairline grey for the rest. One element, two jobs. */
.sb-row::after {
  content: ''; position: absolute; left: 0; bottom: -1px; height: 2px;
  width: var(--share, 0);
  background: linear-gradient(90deg, var(--primary), var(--primary-2));
  border-radius: 2px;
  transition: width 0.5s var(--ease);
}
.sb-row:hover { background: var(--hover); }
.sb-row.head::after { display: none; }
.sb-row.head {
  background: transparent; color: var(--faint); font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.09em; font-weight: 680; padding: 2px 10px 10px;
  border-bottom-color: var(--line-2);
}
.sb-row.head:hover { background: transparent; }
.sb-rank { width: 22px; text-align: center; font-weight: 700; color: var(--faint); font-size: 15.5px; flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.sb-rank.g1 { color: var(--accent); } .sb-rank.g2 { color: #cfd6e0; } .sb-rank.g3 { color: #d18a4f; }
.sb-avatar { width: 34px; height: 34px; border-radius: 10px; flex: 0 0 auto; image-rendering: pixelated; background: var(--panel-2); }
.sb-pdot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 7px; vertical-align: middle; flex: none; background: var(--muted); opacity: 0.4; }
.sb-pdot.on { background: var(--ok); box-shadow: 0 0 7px var(--ok); opacity: 1; }
.sb-pdot.off { opacity: 0.4; }
.sb-name { flex: 1; min-width: 0; font-weight: 620; font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-name .sub { display: block; font-size: 14px; color: var(--muted); font-weight: 500; }
.sb-name .sub.err { color: var(--danger); }
.sb-share { width: 54px; text-align: right; flex: 0 0 auto; color: var(--faint); font-size: 14.5px; font-variant-numeric: tabular-nums; }
.sb-bal { min-width: 116px; text-align: right; flex: 0 0 auto; font-weight: 700; font-size: 17.5px; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.sb-bal .cur { color: var(--accent); }
.sb-bal.pending { color: var(--muted); font-weight: 500; }
.sb-delta { font-size: 14px; font-weight: 700; margin-top: 2px; }
.sb-delta.up { color: var(--ok); } .sb-delta.down { color: var(--danger); }

/* A last-known balance during a DonutSMP API outage: still readable, visibly
   not live. Dimmed rather than greyed to nothing -- the number is the whole
   reason the row is still worth showing. */
.sb-name .sub.warn { color: var(--warn, #f0a84b); }
.sb-bal.stale { opacity: 0.55; font-weight: 640; }
.sb-bal.stale .cur { color: var(--muted); }
.sb-share.stale { opacity: 0.5; }
.sb-outage {
  display: flex;
  gap: 8px;
  margin: 0 0 10px;
  padding: 10px 12px;
  border: 1px solid color-mix(in srgb, var(--warn, #f0a84b) 40%, transparent);
  border-left: 3px solid var(--warn, #f0a84b);
  border-radius: 10px;
  background: color-mix(in srgb, var(--warn, #f0a84b) 9%, transparent);
  color: var(--text);
  font-size: 15px;
  line-height: 1.45;
}
.sb-outage b { color: var(--warn, #f0a84b); }

/* ---------- Modal ---------- */
.modal {
  position: fixed; inset: 0;
  background: rgba(4, 6, 10, 0.72);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center; z-index: 100; padding: 20px;
  animation: fadeIn 0.2s var(--ease);
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.modal.hidden { display: none; }
.modal-body {
  border-color: var(--line-2); padding: 26px; border-radius: var(--radius);
  max-width: 460px; width: 100%;
  animation: modalIn 0.24s var(--ease);
}
@keyframes modalIn { from { opacity: 0; transform: translateY(10px) scale(0.99); } to { opacity: 1; transform: none; } }
.modal-body h2 { margin: 0; color: var(--text); font-size: 19.5px; font-weight: 660; text-transform: none; letter-spacing: -0.015em; }
.modal-body ol { margin: 0 0 16px; padding-left: 20px; line-height: 1.8; }
.modal-body a { color: var(--primary-2); }
.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
.modal-close {
  background: none; border: none; color: var(--faint); font-size: 14px; font-weight: 700;
  letter-spacing: 0.07em; cursor: pointer; padding: 4px 6px; flex: 0 0 auto;
  transition: color 0.14s var(--ease);
}
.modal-close:hover { color: var(--text); }
.code-row { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
.modal-body code {
  font-size: 26.5px; background: var(--panel-2); padding: 10px 16px; border-radius: var(--radius-sm);
  color: var(--accent); letter-spacing: 3px; font-family: ui-monospace, Consolas, monospace;
  border: 1px solid var(--line);
}

/* ---------- Confirm modal ---------- */
.confirm-body { max-width: 390px; }
.confirm-body p { margin: 0 0 22px; color: var(--text-2); font-size: 16px; line-height: 1.6; }
.confirm-actions { display: flex; justify-content: flex-end; gap: 9px; }

/* ---------- Login page ---------- */
.login-body {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background:
    radial-gradient(900px 580px at 84% -4%, rgba(255, 45, 120, 0.18), transparent 62%),
    radial-gradient(780px 500px at 8% 34%, rgba(168, 85, 247, 0.10), transparent 62%),
    var(--bg);
  padding: 20px;
}
.login-card {
  width: 100%; max-width: 372px;
  border-color: var(--line-2);
  border-radius: var(--radius); padding: 34px 30px 30px;
}
.login-brand { display: flex; align-items: center; gap: 11px; margin-bottom: 28px; }
.login-title { margin: 0 0 5px; font-size: 23.5px; font-weight: 680; letter-spacing: -0.025em; }
.login-sub { margin: 0 0 20px; color: var(--muted); font-size: 15.5px; line-height: 1.5; }
.login-tabs { display: flex; gap: 4px; border-radius: var(--radius-xs); padding: 4px; margin-bottom: 20px; }
.login-tabs.hidden { display: none; }
.login-tab {
  flex: 1; background: none; border: none; color: var(--muted); font: inherit; font-size: 14px; font-weight: 620;
  padding: 9px 10px; border-radius: 7px; cursor: pointer;
  transition: background 0.16s var(--ease), color 0.16s var(--ease);
}
.login-tab.active { background: var(--elevated); color: var(--text); box-shadow: var(--shadow-sm); }
#login-form-signin.hidden, #login-form-redeem.hidden, #login-form-forgot.hidden { display: none; }
.login-error {
  background: var(--danger-soft); border: 1px solid rgba(244, 99, 94, 0.35);
  color: var(--danger); font-size: 15px; padding: 10px 12px; border-radius: var(--radius-xs);
  margin-bottom: 14px; line-height: 1.5;
}
.login-error.hidden { display: none; }
.login-success {
  background: var(--ok-soft); border: 1px solid rgba(52, 196, 124, 0.35);
  color: var(--ok); font-size: 15px; padding: 10px 12px; border-radius: var(--radius-xs);
  margin-bottom: 14px; line-height: 1.5;
}
.login-success.hidden { display: none; }
.consent-hint { color: var(--text-2); font-size: 14.5px; line-height: 1.6; margin: 0 0 14px; }
.consent-hint a { color: var(--primary-2); text-decoration: underline; }
.consent-hint a:hover { color: var(--text); }
.login-footer-links { text-align: center; margin-top: 18px; display: flex; justify-content: center; gap: 14px; }
.login-footer-links a { color: var(--faint); font-size: 13px; text-decoration: none; }
.login-footer-links a:hover { color: var(--muted); text-decoration: underline; }

/* ---------- Legal/document pages (Terms, Privacy) ---------- */
.doc-body { min-height: 100vh; background: var(--bg); padding: 50px 20px 80px; }
.doc-page { max-width: 720px; margin: 0 auto; }
.doc-back { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 15px; text-decoration: none; margin-bottom: 22px; }
.doc-back:hover { color: var(--text); }
.doc-page h1 { font-size: 29.5px; margin: 0 0 6px; letter-spacing: -0.02em; }
.doc-updated { color: var(--muted); font-size: 14.5px; margin: 0 0 34px; }
.doc-page h2 { font-size: 16.5px; margin: 30px 0 10px; color: var(--text); }
.doc-page p, .doc-page li { color: var(--text-2); font-size: 15.5px; line-height: 1.7; }
.doc-page ul { margin: 8px 0; padding-left: 20px; }
.doc-page a { color: var(--primary-2); }
.doc-page strong { color: var(--text); }
.doc-footer { margin-top: 46px; padding-top: 20px; border-top: 1px solid var(--line); color: var(--faint); font-size: 14.5px; }
.link-btn {
  display: block; width: 100%; text-align: center; margin-top: 14px;
  background: none; border: none; color: var(--muted); font: inherit; font-size: 14.5px;
  cursor: pointer; padding: 4px;
  transition: color 0.14s var(--ease);
}
.link-btn:hover { color: var(--text); text-decoration: underline; }

/* ---------- Billing (customers: pick a tier, pay in-game) ---------- */
.billing-tier-card { display: flex; flex-direction: column; }.billing-feature-list { list-style: none; padding: 0; margin: 0 0 18px; flex: 1; display: flex; flex-direction: column; gap: 9px; }
.billing-feature-list li { font-size: 15.5px; color: var(--text-2); padding-left: 22px; position: relative; }
.billing-feature-list li::before {
  content: '✓'; position: absolute; left: 0; color: var(--ok); font-weight: 700;
}
.billing-feature-list li.billing-feature-off { color: var(--faint); text-decoration: line-through; }
.billing-feature-list li.billing-feature-off::before { content: '✕'; color: var(--faint); }

/* Compact tier picker for the (narrow, 372px) login card -- stacked rows
   instead of the dashboard's side-by-side cards. */
/* ---------- Invoice box (crypto or DonutSMP-money order summary) ---------- */
/* ---------- Mobile layout ---------- */
@media (max-width: 900px) {
  .sidebar {
    position: static; height: auto; width: 100%;
    flex-direction: row; flex-wrap: wrap; align-items: center;
    gap: 10px 14px; padding: 12px 14px;
    border-right: none; border-bottom: 1px solid var(--line);
    /* No frosted glass on the rail once it becomes a full-width top bar.
       A backdrop-filter that spans the viewport pulls the page's fixed
       background wash into its backdrop, and Chrome then renders everything
       below the bar washed out to the point of being unreadable -- verified
       by turning each of the two off in isolation, either one alone fixes it.
       Desktop keeps the blur, where the rail is a narrow column and the bug
       doesn't appear. Near-opaque here looks identical anyway, since on this
       layout nothing ever scrolls underneath it. */
    background: rgba(7, 9, 16, 0.95);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .brand { padding: 0; flex: 0 0 auto; }
  .nav-list {
    flex-direction: row; flex: 1 1 100%; order: 3;
    overflow-x: auto; gap: 6px; -webkit-overflow-scrolling: touch;
  }
  .nav { flex: 0 0 auto; white-space: nowrap; padding: 9px 13px; }
  .nav.active::before { display: none; }
  /* Sidebar is a horizontal bar here -- lay the stats out inline and compact
     so they don't eat vertical space above the content. */
  .side-stats {
    flex: 1 1 100%; order: 4; margin-top: 0; padding: 0;
    flex-direction: row; gap: 8px;
  }
  .side-stat { flex: 1; padding: 8px 11px; }
  .side-stat span { font-size: 16.5px; }
  .sidebar-footer {
    flex: 1 1 100%; order: 2; margin-top: 0; padding-top: 0;
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  }
  .instance-picker { margin-bottom: 0; flex: 1 1 160px; }
  .status-pill { flex: 1 1 160px; }
  #sign-out-btn { width: auto; flex: 0 0 auto; margin-top: 0 !important; }

  .content { padding: 16px 14px 40px; }
  .chat-card { height: 60vh; }
  .sb-card { height: auto; max-height: 50vh; }

  /* Header rows stack instead of squeezing side by side */
  .page-header { flex-direction: column; align-items: stretch; gap: 12px; margin-bottom: 20px; }
  .page-header h1 { font-size: 22.5px; }
  .controls-header-right { width: 100%; }
  .stat-card { flex: 1 1 140px; }

  /* Toolbars: let every control take the full row width when wrapped */
  .toolbar-row > * { flex: 1 1 100%; min-width: 0; }
  .toolbar-row .btn { flex: 1 1 auto; }
  .add-account-row { flex-wrap: wrap; }
  .add-account-row input { flex: 1 1 100%; }
  .batch-bar { flex-wrap: wrap; }
  .row { flex-wrap: wrap; }
  .row > * { flex: 1 1 140px; }
  .look-manual { flex-wrap: wrap; }
  .look-manual .field { flex: 1 1 130px; }
}

/* ── Locked (expired / revoked) subscription ────────────────────────────────
   The customer stays signed in so they can see their setup is intact and get
   to Billing, but every other pane is greyed out behind a padlock. Purely
   visual -- the server independently rejects any mutating request from a
   locked session, so this can't be bypassed by editing the DOM. */
.locked-pane {
  position: relative;
}
.locked-pane > *:not(.lock-overlay) {
  filter: grayscale(1) blur(1.5px);
  opacity: .45;
  pointer-events: none;
  user-select: none;
}
.lock-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 20;
  padding: 24px;
}
.lock-card {
  max-width: 420px;
  text-align: center;
  padding: 28px 26px;
  border-radius: 16px;
  background: var(--panel, #14161c);
  border: 1px solid var(--border, #262a33);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .45);
}
.lock-icon { font-size: 35px; line-height: 1; margin-bottom: 10px; }
.lock-card h2 { margin: 0 0 8px; font-size: 21px; text-transform: capitalize; }
.lock-card p { margin: 0 0 18px; font-size: 15.5px; line-height: 1.5; opacity: .8; }

/* The sidebar stays usable so Billing and Sign out remain reachable, but the
   locks make it obvious why the other tabs lead nowhere. */
body.is-locked button.nav:not(#nav-billing)::after {
  content: '🔒';
  margin-left: auto;
  font-size: 14px;
  opacity: .7;
}

/* ---------- Keyboard focus ---------- */
/* The stylesheet had exactly one :focus-visible rule, so tabbing through the
   panel was effectively invisible -- you could not tell which control you were
   on. One ring, defined once, on everything focusable. :focus-visible (not
   :focus) so it only appears for keyboard use and never rings a mouse click. */
:where(button, a, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--primary-2);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}
/* The row menus and swatches sit on dark glass, where the outline alone can
   sit too close to the border it lands on. */
.color-swatch:focus-visible,
.dropdown-menu button:focus-visible {
  outline-offset: 3px;
}

/* ---------- Reduced motion ---------- */
/* Every transition in here is decorative -- hover fades, the swatch scale, the
   toast. Nothing conveys state by movement alone, so honouring the OS setting
   costs nothing and stops the panel animating at people who asked it not to. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Chat feed filters ---------- */
.chat-filter-row {
  display: flex; gap: 6px; align-items: center;
  padding: 8px 10px; border-bottom: 1px solid var(--line);
}
.chat-filter-row .search-input { flex: 1 1 auto; min-width: 0; }
.chat-filter-row .custom-select { flex: 0 0 auto; max-width: 42%; }
/* Filtering hides lines rather than removing them -- the feed is a rolling
   buffer, and dropping non-matching lines would mean clearing a filter left
   you with a hole instead of the history. */
.chat-hidden { display: none !important; }

/* ---------- Batch bar additions ---------- */
.batch-bar .menu-wrap { display: inline-flex; }
/* Non-interactive while a batch runs, so a second click can't start an
   overlapping run over the same selection. */
.batch-busy { pointer-events: none; opacity: 0.65; }
.batch-busy .batch-count { font-variant-numeric: tabular-nums; }

/* ---------- Section head actions ---------- */
.section-head-actions { display: flex; align-items: center; gap: 10px; }

/* ---------- Notice callout ---------- */
/* Used on the Upgrade page to state the proxy requirement before money
   changes hands. Deliberately above the tier cards and not inside a <details>:
   a customer who buys expecting proxies to be included is a refund request and
   a support conversation, so this has to be impossible to scroll past. */
.notice {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 14px 16px;
  border: 1px solid var(--line-2);
  border-left: 3px solid var(--primary-2);
  border-radius: var(--radius-sm);
  background: var(--glass-2);
}
.notice p { margin: 5px 0 0; font-size: 15.5px; color: var(--text-2); line-height: 1.62; }
.notice b { color: var(--text); }
.notice-icon { font-size: 16.5px; line-height: 1.5; flex: 0 0 auto; }
.notice-warn { border-left-color: var(--warn, #f0a84b); }
.notice-warn .notice-icon { color: var(--warn, #f0a84b); }

/* Reads as a caveat, not a feature the plan grants -- it sits in the same list
   as the included features, so it must not look like one of them. */
.billing-feature-byo {
  color: var(--muted);
  font-style: italic;
  border-top: 1px solid var(--line);
  margin-top: 4px;
  padding-top: 8px;
}

/* Unassigned proxy on an account that requires one -- tinted so the row shows
   what is missing before the customer presses Start and gets refused. */
.proxy-trigger-missing { color: var(--danger); }
.proxy-trigger-missing:hover { background: var(--danger-soft); color: var(--danger); }

/* ---------- Proxy capacity ---------- */
/* DonutSMP allows 5 accounts per IP, so headroom is something the customer has
   to be able to see before they assign, not after they get refused. */
.proxy-usage {
  display: inline-block; margin-left: 8px;
  font-size: 13px; font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.proxy-usage.full { color: var(--danger); font-weight: 600; }
.proxy-choice-count { float: right; color: var(--muted); font-variant-numeric: tabular-nums; }
.proxy-choice-full { opacity: 0.5; cursor: not-allowed; }
.proxy-choice-full:hover { background: none !important; }

/* ---------- Purchase flow (login.html "Buy Access" + in-app Upgrade) ---------- */
/* The buy screens were the thinnest surface in the product: three one-line rows,
   then a near-empty page reading "Premium selected." This is the only place a
   stranger decides whether to trust the thing with money, so it states what
   they get, what it costs, and what they still need to supply. *//* One tier carries the eye. Border only -- a filled card would fight the
   primary button that follows it. */.tier-badge {
  position: absolute; top: -8px; right: 14px;
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px;
  background: var(--primary); color: #fff;
}
/* Splits the two payment routes into labelled sections. They used to sit as a
   button and an "or crypto:" fragment, which read as an afterthought. */
/* The "Get access" tab: no checkout, just how to reach a human. One accent
   colour and one boxed caveat -- the old version stacked two differently
   coloured notice boxes and a raw mailto link, which read as three competing
   alerts in a 372px-wide card. */
.access-lede { margin: 0 0 16px; font-size: 15px; line-height: 1.6; color: var(--text-2); }
.access-lede b { color: var(--text); font-weight: 650; }

.access-label {
  margin: 0 0 8px;
  font-size: 12.5px; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
}
.access-label + .access-contacts, .access-label + .access-plans { margin-top: 0; }

/* Same hairline-separated list as the contact rows -- the catalogue is
   information here, not a set of things to click, so it should not look like
   three buttons competing with the one real action. */
.access-plans {
  margin: 0 0 18px;
  display: grid; gap: 1px;
  background: var(--line-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-xs);
  overflow: hidden;
}
.plan-row { padding: 11px 13px; background: var(--glass-2); }
.plan-row-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.plan-row-top b { font-size: 15.5px; font-weight: 650; color: var(--text); }
.plan-row-price { text-align: right; white-space: nowrap; }
.plan-row-usd { font-size: 15.5px; font-weight: 680; color: var(--text); font-variant-numeric: tabular-nums; }
.plan-row-usd span { font-size: 12.5px; font-weight: 550; color: var(--muted); }
.plan-row-dsmp { margin-top: 1px; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.plan-row-feats { margin-top: 3px; font-size: 13.5px; line-height: 1.5; color: var(--muted); }
.access-plans-msg { padding: 11px 13px; background: var(--glass-2); font-size: 14.5px; color: var(--muted); }

/* Price on the wider in-panel plan cards. */
.plan-price {
  margin: 2px 0 12px;
  font-size: 25.5px; font-weight: 700; letter-spacing: -0.02em;
  color: var(--text); font-variant-numeric: tabular-nums;
}
.plan-price span { font-size: 15px; font-weight: 550; color: var(--muted); letter-spacing: 0; }
.plan-price-dsmp { margin: -8px 0 12px; font-size: 14.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

.access-contacts {
  margin: 0 0 16px;
  display: grid; gap: 1px;
  background: var(--line-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-xs);
  overflow: hidden;
}
.access-contacts > div {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 10px 13px;
  background: var(--glass-2);
}
.access-contacts dt {
  font-size: 12.5px; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
}
/* user-select: all so a whole handle is one click to copy on a phone. */
.access-contacts dd { margin: 0; font-size: 15px; color: var(--text); user-select: all; }
/* Green rather than the brand pink: this marks the better choice, it should
   not compete with the one actual button on the card. */
.access-rec {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 6px;
  border-radius: 999px;
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  line-height: 1.35;
  color: var(--ok);
  background: color-mix(in srgb, var(--ok) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--ok) 34%, transparent);
  vertical-align: 1px;
  white-space: nowrap;
}
.access-contacts a { color: var(--primary-2); text-decoration: none; }
.access-contacts a:hover { text-decoration: underline; }

a.btn { text-decoration: none; }

.access-note {
  margin: 16px 0 0; padding: 11px 13px;
  font-size: 14px; line-height: 1.6; color: var(--text-2);
  background: var(--glass-2);
  border: 1px solid var(--line-2);
  border-left: 3px solid var(--warn, #f0a84b);
  border-radius: var(--radius-xs);
}
.access-note b { display: block; color: var(--text); margin-bottom: 2px; }
.btn.full { width: 100%; }

/* In-app Upgrade cards, matched to the login purchase flow so the two screens
   read as one product rather than two different eras of the app. */
.billing-tier-card { position: relative; }.billing-tier-card.is-current { border-color: var(--ok); }
.tier-badge-current { background: var(--ok); }

/* ============================================================
   MAGENTA THEME
   Repalettes through the existing tokens rather than overriding
   components, so every surface, badge and button inherits it and
   nothing has to be restyled twice. Surfaces keep a magenta cast
   instead of the old blue one; the text ramp stays close to
   neutral because this is a data tool and tinted body text on a
   dark ground is where readability goes first.
   ============================================================ */
:root {
  --bg: #06040a;
  --sidebar: #08060d;
  --panel: #0e0b13;
  --panel-2: #131019;
  --elevated: #181420;

  --hover: rgba(255, 45, 120, 0.06);
  --line: rgba(255, 255, 255, 0.065);
  --line-2: rgba(255, 120, 175, 0.16);

  --text: #f7eef4;
  --text-2: #c9c2ce;
  --muted: #948c9c;
  --faint: #6d6070;

  /* Hot magenta lead, violet second. The gradient is the pairing
     of the two -- used on the primary button and the wordmark only,
     so it reads as a brand signature, not as decoration everywhere. */
  --primary: #ff2d78;
  --primary-2: #ff7fb0;
  --primary-soft: rgba(255, 45, 120, 0.15);
  --primary-glow: rgba(255, 45, 120, 0.42);
  --violet: #a855f7;
  --grad: linear-gradient(135deg, #ff2d78 0%, #c026d3 55%, #a855f7 100%);

  --glass:   rgba(20, 14, 26, 0.62);
  --glass-2: rgba(28, 20, 36, 0.55);
  --glass-hi: rgba(255, 255, 255, 0.07);

  --shadow: 0 24px 56px rgba(0, 0, 0, 0.65), 0 2px 8px rgba(0, 0, 0, 0.4);
}

/* ---------- Bubble display face ---------- */
/* Fredoka on identity and headings only. The accounts table stays on the
   UI font on purpose: nine rows of usernames, servers and error strings at
   12-13px is exactly where a rounded face stops being readable. */
.brand-name,
.login-title,
.hero-value,
h1, h2,
.billing-tier-card h2,
.tier-badge {
  font-family: 'Fredoka', system-ui, -apple-system, 'Segoe UI', sans-serif;
  letter-spacing: -0.01em;
}
.brand-name { font-weight: 600; font-size: 17px; }
.login-title, h1 { font-weight: 600; }
.hero-value { font-weight: 600; letter-spacing: -0.02em; }

/* Wordmark carries the gradient. One element, so it stays a signature. */
.brand-name {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ---------- Ambient bloom ---------- */
/* Fixed, behind everything, pointer-events:none so it can never intercept a
   click on a row menu or a button. Three soft radials rather than one, so the
   page has depth instead of a single flat vignette. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  mix-blend-mode: screen;
  background:
    radial-gradient(58vw 44vw at 78% 8%,  rgba(255, 45, 120, 0.13), transparent 62%),
    radial-gradient(48vw 40vw at 12% 88%, rgba(168, 85, 247, 0.13), transparent 64%),
    radial-gradient(38vw 30vw at 50% 48%, rgba(192, 38, 211, 0.07), transparent 70%);
}
/* Faint grid, straight from the reference. Kept very low contrast so it reads
   as texture and never competes with table rules. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.017) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.017) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(circle at 50% 30%, #000 0%, transparent 78%);
  -webkit-mask-image: radial-gradient(circle at 50% 30%, #000 0%, transparent 78%);
}

/* ---------- Cursor glow ---------- */
/* Position is driven by a transform on an rAF loop (see theme.js) -- never by
   top/left, which would force layout on every mouse move and fight the 2.5s
   poll that re-renders the tables. */
#cursor-glow {
  position: fixed;
  top: 0; left: 0;
  width: 230px; height: 230px;
  margin: -115px 0 0 -115px;
  z-index: 4;
  pointer-events: none;
  mix-blend-mode: screen;
  border-radius: 50%;
  opacity: 0;
  transition: opacity 0.45s var(--ease);
  background: radial-gradient(circle closest-side,
    rgba(255, 45, 120, 0.15) 0%,
    rgba(226, 60, 170, 0.10) 30%,
    rgba(168, 85, 247, 0.055) 58%,
    transparent 78%);
  will-change: transform;
}
#cursor-glow.on { opacity: 1; }

/* No media-query hide. (hover: none) and reduced-motion both matched on
   Windows machines that do have a mouse, and hid the glow for the owner.
   theme.js only lights it on a non-touch pointermove, so a touch device
   never shows it and the rAF loop stops itself once the glow is still. */

/* ---------- Bubble font everywhere ---------- */
/* Fredoka across the whole UI, not just headings. Form controls and buttons
   don't inherit font-family by default, so they're named explicitly -- miss
   them and you get rounded headings sitting above system-font inputs. */
body,
input, select, textarea, button,
table, th, td,
.login-card, .app {
  font-family: 'Fredoka', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}

/* Monospace survives in the few places it is doing real work rather than
   decoration: licence keys, crypto addresses, hex colours and raw log output.
   A rounded proportional face makes 0/O and 1/l/I ambiguous, and these are
   strings people copy character-by-character or compare by eye -- a wrong
   character in a wallet address is money gone. */
code,
.mono-key,
#log-content,
.color-hex,
#buy-pay-receiver,
#billing-pay-receiver,
#twofa-secret {
  font-family: ui-monospace, 'Cascadia Mono', Consolas, 'SF Mono', monospace;
}

/* Fredoka's default figures are proportional, so columns of money would no
   longer line up. Force tabular figures anywhere digits stack vertically. */
.sb-bal, .sb-share, .hero-value, .side-stat-money,
.tally .n, td {
  font-variant-numeric: tabular-nums;
}

/* ---------- Controls: compass presets ---------- */
.compass-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin: 0 0 14px; }
.compass-row .btn { width: 100%; }

/* Status and its error now share one cell, so they need to stack rather than
   collide when an error string is long. */
.status-cell { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; }
.status-cell .error-text {
  font-size: 13px; line-height: 1.35;
  max-width: 26ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The server address field sat unlabelled in a toolbar row and read as a stray
   text box; it is a normal labelled field now. */
.server-addr-field { display: block; max-width: 420px; margin: 10px 0 4px; }

/* ---------- Controls page ---------- */
/* Was two cards floating in a mostly empty page. Now: the account being
   controlled stated at the top, then a three-track grid that actually occupies
   the width, with the third track showing live state the client was already
   reporting and the UI was throwing away. */
.control-target {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  margin-bottom: 16px;
  background: var(--glass-2);
  border: 1px solid var(--line-2);
  border-left: 3px solid var(--primary);
  border-radius: var(--radius-sm);
}
.ctl-avatar { border-radius: 8px; flex: 0 0 auto; image-rendering: pixelated; }
.ctl-target-id { display: flex; flex-direction: column; min-width: 0; }
.ctl-eyebrow {
  font-size: 12px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted);
}
.ctl-target-id b { font-size: 18.5px; font-weight: 650; }
.ctl-target-state { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.ctl-server { font-size: 14px; color: var(--muted); }

.controls-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}
@media (max-width: 1100px) { .controls-grid { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 820px)  { .controls-grid { grid-template-columns: minmax(0, 1fr); } }

/* Live stat readouts */
.stat-list { display: flex; flex-direction: column; gap: 12px; margin-top: 4px; }
.stat-line {
  display: grid;
  grid-template-columns: 62px 1fr auto;
  align-items: center;
  gap: 10px;
  font-size: 15px;
}
.stat-line > span { color: var(--muted); }
.stat-line > b { font-variant-numeric: tabular-nums; font-size: 15px; }
.stat-line-plain { grid-template-columns: 62px 1fr; }
.stat-line-plain > b { text-align: right; }
.ctl-pos { font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace; font-size: 14px; }

/* Health and hunger are both out of 20, so one meter style serves both --
   colour is what separates them, not shape. */
.meter {
  height: 7px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}
.meter > i {
  display: block; height: 100%; width: 0%;
  border-radius: 99px;
  background: linear-gradient(90deg, #ff2d78, #ff6b9d);
  transition: width 0.4s var(--ease);
}
.meter-food > i { background: linear-gradient(90deg, #f0a935, #ffce6b); }
.ctl-empty { margin: 14px 0 0; font-size: 14px; }
.ctl-live .hint { margin-bottom: 10px; }
.controls-grid .hint { max-width: 62ch; }
/* Aim and Bot status share the right track. */
.controls-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

/* ============================================================
   ACCOUNTS — card grid
   A six-column table for a handful of accounts was cramped, scrolled
   sideways on anything narrow, and spent most of its width on columns
   that repeated themselves. Each account is a card now: identity at the
   top, state under it, the action you actually want, then tools.
   ============================================================ */
.account-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
  gap: 12px;
  align-items: start;
}

.acct-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 15px 12px;
  background: var(--glass-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease), transform 0.15s var(--ease);
}
/* The account's own colour as an edge, so a card is identifiable before you
   read anything on it. */
.acct-card::before {
  content: '';
  position: absolute;
  left: 0; top: 12px; bottom: 12px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--acct, var(--primary));
  opacity: 0.85;
}
.acct-card:hover { border-color: var(--line-2); background: var(--hover); }
.acct-card.is-online { border-color: rgba(52, 196, 124, 0.28); }
.acct-card.is-selected {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary-soft);
}

.acct-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
.acct-avatar { width: 34px; height: 34px; border-radius: 8px; flex: 0 0 auto; image-rendering: pixelated; }
.acct-top .account-name-block { flex: 1 1 auto; min-width: 0; }
.acct-top .account-name-block b {
  display: block; font-size: 16px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Rename stays hidden until the card is hovered, so a wall of cards isn't a
   wall of pencils. */
.acct-card .nickname-edit { opacity: 0; }
.acct-card:hover .nickname-edit,
.acct-card .nickname-edit:focus-visible { opacity: 1; }
@media (hover: none) { .acct-card .nickname-edit { opacity: 1; } }

.acct-status { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.acct-server { font-size: 13px; color: var(--muted); }
.acct-err { align-self: flex-start; }
.acct-err-text {
  margin: 0; font-size: 13px; line-height: 1.4; color: var(--danger);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

.acct-actions { display: flex; gap: 7px; flex-wrap: wrap; }
.acct-actions .btn { flex: 1 1 auto; min-width: 84px; }

/* Tools sit below a divider so they read as secondary to the main action. */
.acct-tools {
  display: flex; justify-content: flex-end;
  padding-top: 9px;
  border-top: 1px solid var(--line);
}

/* Proxy grouping spans the whole grid rather than sitting in one column. */
.group-row {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 8px;
  margin: 8px 0 -2px;
  background: none;
}
.acct-empty { grid-column: 1 / -1; }

.select-all-row {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 4px 0 12px;
  font-size: 14.5px; color: var(--muted); cursor: pointer;
}

/* Number field and its explanation sit side by side. Nesting the prose inside
   .field-num (max-width 104px) is what turned it into a vertical ribbon. */
.slot-field-row { display: flex; align-items: flex-start; gap: 14px; margin-top: 12px; }
.slot-hint { margin: 0; flex: 1 1 auto; min-width: 0; align-self: center; }
@media (max-width: 640px) { .slot-field-row { flex-direction: column; gap: 8px; } }

/* ---------- Aim joystick ---------- */
/* Replaces the five-button d-pad. A pad you drag reads as "point the bot"
   far more directly than five arrows that each nudge by 15 degrees. */
.joystick {
  position: relative;
  width: 168px; height: 168px;
  margin: 4px auto 10px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 38%, rgba(255, 45, 120, 0.10), transparent 62%),
    var(--glass-2);
  border: 1px solid var(--line-2);
  cursor: grab;
  touch-action: none;              /* let the pad own the gesture, not the page scroll */
  user-select: none;
  transition: border-color 0.15s var(--ease), box-shadow 0.2s var(--ease);
}
.joystick:hover { border-color: var(--primary-2); }
.joystick.is-active {
  cursor: grabbing;
  border-color: var(--primary);
  /* Blur only, no spread -- a spread ring changes the circle's size the moment
     you press, which looks exactly like the outer circle moving. */
  box-shadow: 0 0 30px var(--primary-glow);
}
/* Inner ring marks the usable travel; the cross marks dead centre (level). */
.joy-ring {
  position: absolute; inset: 22px;
  border-radius: 50%;
  border: 1px dashed rgba(255, 255, 255, 0.10);
  pointer-events: none;
}
.joy-cross {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to right, transparent calc(50% - 0.5px), rgba(255,255,255,0.07) calc(50% - 0.5px), rgba(255,255,255,0.07) calc(50% + 0.5px), transparent calc(50% + 0.5px)),
    linear-gradient(to bottom, transparent calc(50% - 0.5px), rgba(255,255,255,0.07) calc(50% - 0.5px), rgba(255,255,255,0.07) calc(50% + 0.5px), transparent calc(50% + 0.5px));
}
.joy-knob {
  position: absolute; top: 50%; left: 50%;
  width: 46px; height: 46px;
  margin: -23px 0 0 -23px;
  border-radius: 50%;
  background: var(--grad);
  box-shadow: 0 4px 16px rgba(0,0,0,0.45), 0 0 18px var(--primary-glow);
  pointer-events: none;
  transition: transform 0.28s cubic-bezier(0.34, 1.4, 0.64, 1);
}
/* No easing while dragging -- the knob must sit exactly under the pointer. */
.joystick.is-active .joy-knob { transition: none; }   /* follow the finger exactly while dragging */
.joy-readout {
  margin: 0;
  min-width: 210px;
  text-align: center;
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ---------- Account cards: denser ---------- */
/* The first pass left a lot of empty card. These rows carry values the poll
   already provides, so the card says something even while stopped. */
.acct-meta {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  padding: 9px 0;
  border-top: 1px solid var(--line);
  font-size: 14px;
}
.acct-meta dt { color: var(--muted); }
.acct-meta dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-meta .no-proxy { color: var(--danger); }


/* The cursor glow sweeping across the joystick made its rings appear to move.
   Suppressed while the stick is held. */
body.joy-dragging #cursor-glow { opacity: 0; }

/* ============================================================
   COMMAND BAR LAYOUT
   Replaces the fixed side rails. Global state and global actions live
   in one persistent bar; the body below is only ever the current view;
   chat is a drawer rather than a third column. The old rail was
   display:none below 1280px, which meant the live feed — and the
   per-account colours in it — simply vanished on a laptop.
   ============================================================ */
.app {
  display: grid;
  grid-template-columns: 1fr;      /* overrides the three-column rail layout */
  grid-template-rows: auto 1fr;
  min-height: 100vh;
}

.commandbar {
  position: sticky; top: 0; z-index: 60;
  display: flex; align-items: center; gap: 18px;
  padding: 10px 20px;
  background: rgba(9, 6, 13, 0.82);
  backdrop-filter: saturate(180%) blur(26px);
  -webkit-backdrop-filter: saturate(180%) blur(26px);
  border-bottom: 1px solid var(--line);
}
.cb-brand { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; }
.cb-brand .brand-name { font-size: 16.5px; }

.cb-nav { display: flex; align-items: center; gap: 2px; flex: 1 1 auto; min-width: 0; overflow-x: auto; }
/* The nav buttons were vertical rail items; horizontal now, and the old
   full-width/left-aligned rules have to be undone explicitly. */
.commandbar .nav {
  width: auto; justify-content: center;
  padding: 7px 13px;
  border-radius: 999px;
  font-size: 15.5px; white-space: nowrap;
  background: none; border: 1px solid transparent;
}
.commandbar .nav:hover { background: var(--hover); }
.commandbar .nav.active {
  background: var(--primary-soft);
  border-color: var(--line-2);
  color: var(--text);
}
.commandbar .nav svg { display: none; }   /* icons belonged to the rail */

.cb-stats { display: flex; align-items: center; gap: 16px; flex: 0 0 auto; }
.cb-stat { display: flex; flex-direction: column; line-height: 1.2; }
.cb-stat em {
  font-style: normal; font-size: 11.5px; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--muted);
}
.cb-stat span { font-size: 15.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.cb-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

@media (max-width: 1080px) {
  .commandbar { flex-wrap: wrap; gap: 10px; }
  .cb-nav { order: 3; width: 100%; }
  .cb-stats { margin-left: auto; }
}
@media (max-width: 560px) {
  .cb-stats .cb-stat-expiry { display: none !important; }
}

/* ---------- Chat drawer ---------- */
/* Off-canvas at every width, so it can never be the thing that disappears. */
.chat-rail {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: 360px; max-width: 92vw;
  height: 100vh;
  z-index: 80;
  transform: translateX(100%);
  transition: transform 0.26s var(--ease);
  border-left: 1px solid var(--line-2);
  box-shadow: -20px 0 50px rgba(0, 0, 0, 0.55);
}
body.chat-open .chat-rail { transform: none; }
.chat-backdrop {
  position: fixed; inset: 0; z-index: 79;
  background: rgba(4, 2, 7, 0.5);
  opacity: 0; pointer-events: none;
  transition: opacity 0.26s var(--ease);
}
body.chat-open .chat-backdrop { opacity: 1; pointer-events: auto; }
#chat-toggle.is-open { color: var(--primary-2); background: var(--primary-soft); }

/* The support-chat launcher is fixed to the bottom-right corner at a z-index far
   above everything else, so an open chat drawer gets its Send button sat on.
   Fold the launcher away while the drawer owns that corner; leave it alone when
   the support panel itself is open, since that panel covers the drawer anyway. */
body.chat-open .sc-root:not(.sc-open) {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.18s var(--ease), visibility 0.18s var(--ease);
}

/* The picker that drives the Controls page now sits on it. */
.ctl-picker {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 12px;
}
.ctl-picker label {
  font-size: 12px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted);
}
.ctl-picker .status-pill { margin-left: auto; }

.content { padding: 26px 28px 72px; }
@media (max-width: 700px) { .content { padding: 20px 16px 60px; } }

/* ============================================================
   DENSITY
   The command bar freed the full width, which made sparse pages look
   sparser. Content gets a real measure, sections get weight, and the
   headline numbers get a row of their own instead of one lonely figure.
   ============================================================ */
.content {
  max-width: none;
  padding: 26px clamp(18px, 3vw, 40px) 90px;
}

/* Four headline figures across the top of Overview. */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}
.stat-card {
  display: flex; flex-direction: column; gap: 3px;
  padding: 16px 18px;
  background: var(--glass-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
}
.stat-card em {
  font-style: normal; font-size: 12px; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--muted);
}
.stat-card b {
  font-size: 27.5px; font-weight: 600; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1.15;
}
.stat-card span { font-size: 13px; color: var(--faint); }
/* The first card is the one people come to the page for. */
.stat-card:first-child { border-color: rgba(255, 45, 120, 0.24); }
.stat-card:first-child b {
  background: var(--grad);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* Sections read as blocks with weight rather than floating content. */
.section {
  padding: 18px 20px;
  background: var(--glass);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: 18px;
}
.section-head { margin-bottom: 14px; }

/* Roomier cards now that there is width to spend. */
.account-grid { grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 14px; }
.acct-card { padding: 16px 17px 13px; gap: 11px; }
.acct-avatar { width: 38px; height: 38px; }
.acct-top .account-name-block b { font-size: 17px; }

.page-header { margin-bottom: 18px; }
.page-header h1 { font-size: 27.5px; }

/* ============================================================
   COMPACT PASS
   The density work added real content but also real height. This trims
   padding and type scale rather than removing anything, so the pages stay
   full but stop scrolling as far. Information density goes up; whitespace
   goes down.
   ============================================================ */
.content { padding: 18px clamp(16px, 2.4vw, 32px) 40px; }

.stat-row { gap: 10px; margin-bottom: 14px; }
.stat-card { padding: 11px 14px; gap: 1px; }
.stat-card b { font-size: 22.5px; }
.stat-card em { font-size: 11.5px; }
.stat-card span { font-size: 12px; }

.section { padding: 14px 16px; margin-bottom: 14px; }
.section-head { margin-bottom: 10px; }
.page-header { margin-bottom: 14px; }

/* Cards: same content, less air. The meta rows are the main saving -- they
   were laid out like a definition list with generous gaps. */
.account-grid { gap: 11px; }
.acct-card { padding: 12px 13px 10px; gap: 8px; }
.acct-meta { padding: 7px 0; gap: 2px 12px; font-size: 13px; }
.acct-tools { padding-top: 7px; }
.acct-actions { gap: 6px; }
.acct-avatar { width: 34px; height: 34px; }

/* Controls: the joystick was the single tallest element on the page. */
.joystick { width: 132px; height: 132px; margin: 2px auto 8px; }
.joy-knob { width: 38px; height: 38px; margin: -19px 0 0 -19px; }
.joy-ring { inset: 18px; }
.look-controls { gap: 9px; margin-top: 12px; }
.stat-list { gap: 9px; }
.ctl-picker { margin-bottom: 10px; }
.control-target { padding: 11px 15px; margin-bottom: 12px; }

/* The chat feed is a drawer now, so it owns the full viewport height and does
   not need to reserve space in the page. */
.chat-log { flex: 1 1 auto; }

/* ============================================================
   MOBILE
   ============================================================ */
/* A bare minmax(340px, …) track cannot shrink below its minimum, so on a
   ~343px-wide phone the grid overflows and the whole page scrolls sideways.
   min(340px, 100%) lets the track collapse to the viewport instead. */
.account-grid { grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); }
.stat-row { grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr)); }

@media (max-width: 700px) {
  /* Command bar: brand shrinks to the mark, stats stay because they are the
     reason to look at the bar at all. */
  .commandbar { gap: 8px; padding: 8px 12px; }
  .cb-brand .brand-name { display: none; }
  .cb-stat b, .cb-stat span { font-size: 14.5px; }
  .cb-stats { gap: 12px; }
  .cb-actions { gap: 4px; }
  #sign-out-btn { display: none; }          /* lives in the drawer footer instead */

  .cb-nav {
    order: 3; width: 100%;
    overflow-x: auto; scrollbar-width: none;
    padding-bottom: 2px;
  }
  .cb-nav::-webkit-scrollbar { display: none; }
  .commandbar .nav { padding: 6px 11px; font-size: 14.5px; }

  /* Two stat cards per row reads better than four cramped ones. */
  .stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .stat-card { padding: 10px 12px; }
  .stat-card b { font-size: 20px; }

  .section { padding: 12px 12px; border-radius: var(--radius-sm); }
  .content { padding: 14px 12px 40px; }

  /* Actions go full width so they are thumb-sized rather than tiny targets. */
  .acct-actions .btn { flex: 1 1 100%; min-height: 38px; }
  .acct-tools { justify-content: space-between; }
  .acct-tools .icon-btn { width: 38px; height: 38px; }

  .batch-bar { flex-wrap: wrap; gap: 6px; }
  .toolbar-row { flex-wrap: wrap; gap: 8px; }
  .server-addr-field { max-width: none; }

  /* The drawer takes the full screen on a phone; a 360px panel beside a sliver
     of dimmed page is worse than just owning the view. */
  .chat-rail { width: 100%; max-width: 100%; }
}

@media (max-width: 420px) {
  .stat-row { grid-template-columns: minmax(0, 1fr); }
  .compass-row { grid-template-columns: repeat(2, 1fr); }
}

/* Wide data tables (Licences, Customer Accounts) keep their own scroller so the
   page body never scrolls sideways because of them. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ---------- Use the full width ---------- */
/* A dashboard is scanned, not read, so it takes the width it is given rather
   than sitting in a centred column with empty gutters. */
.content { max-width: none; margin: 0; }

/* The scoreboard was built for a narrow column: name hard left, share and
   balance hard right, a wide dead gap between them on a big monitor. Giving the
   name track a sane cap and the numbers fixed widths keeps the row readable
   instead of stretched across the screen. */
.sb-row {
  grid-template-columns: 34px 30px minmax(0, 1fr) 90px 180px;
  gap: 12px;
}
.sb-name { max-width: 46ch; }

/* More cards per row now that the gutters are gone. */
@media (min-width: 1500px) {
  .account-grid { grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }
  .stat-row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* Controls has three panes of content; above this width it can show them all
   side by side instead of stacking Aim under Selling. */
@media (min-width: 1600px) {
  .controls-grid { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 1fr); }
  .controls-side { display: contents; }
}

/* ---------- Width, settled ---------- */
/* 1560px left visible gutters; no cap at all ran edge to edge. This sits
   between: on a ~2000px screen it uses most of the width with a deliberate
   margin rather than sprawling. Single knob -- change this one value to taste. */
.content {
  max-width: 1720px;
  margin: 0 auto;
  padding: 22px clamp(18px, 2.6vw, 44px) 56px;
}

/* ---------- Width, relative to the monitor ---------- */
/* A fixed pixel cap behaves completely differently depending on the display:
   1720px is ~90% of a 1920 screen but only ~50% of an ultrawide, which is why
   the same number read as "too full" on one and "too empty" on another.
   Cap by percentage instead, with a pixel ceiling so a very wide monitor does
   not stretch rows to absurd lengths. */
.content {
  max-width: min(2400px, 82vw);
  margin: 0 auto;
}
/* Below desktop widths the percentage would waste space, so use it all. */
@media (max-width: 1400px) {
  .content { max-width: none; }
}


/* The coin figure is what must be sent exactly, so it is the headline and it is
   monospaced -- people compare it character by character against a wallet. The
   USD line under it is orientation, not the number to type. */
/* On a phone the popover is nearly as wide as the screen, so anchoring it to
   the bell pushes its left edge off-viewport. Pin it to the viewport instead
   and let it span the width it has. */
@media (max-width: 560px) {
  .notif-popover {
    position: fixed;
    top: 58px; right: 10px; left: 10px;
    width: auto; max-width: none;
    max-height: 70vh;
  }
}


/* ==========================================================================
   Readability, reach and motion pass
   --------------------------------------------------------------------------
   Everything above this line is the original design. This block only widens,
   enlarges and animates it, so it can be deleted wholesale to get the old
   look back. Three complaints drove it: the type was too small, the panels
   did not use the screen, and tapping things on a phone or iPad was fiddly.
   ========================================================================== */

/* ---------- Use the screen ---------- */
/* 82vw left a wide margin on a 1080p laptop, which is where most people are.
   92vw keeps a deliberate gutter without the content feeling stranded in the
   middle of the display. */
.content {
  max-width: min(2400px, 92vw);
  padding: 26px clamp(18px, 2.4vw, 46px) 64px;
}

/* Wider rows deserve denser grids -- otherwise the extra width just stretches
   cards instead of fitting more of them. */
@media (min-width: 1500px) {
  .account-grid { grid-template-columns: repeat(auto-fill, minmax(min(360px, 100%), 1fr)); }
}

/* ---------- Bigger hit targets ---------- */
/* 44px is the size a fingertip actually reliably hits -- the iOS and Android
   guidelines both land there, and it is why the old 32px rows were frustrating
   on a phone without being obviously broken on a desktop. */
.btn {
  min-height: 44px;
  padding: 12px 18px;
}
.btn.small {
  min-height: 38px;
  padding: 9px 15px;
}
input:where(:not([type=checkbox]):not([type=radio]):not([type=range])),
select,
textarea {
  min-height: 46px;
  padding: 12px 15px;
}
textarea { min-height: 96px; }
.nav {
  min-height: 44px;
  padding: 11px 14px;
}

/* Cards get more room to breathe: the old padding was tuned for the smaller
   type and looks cramped now that the text is a step larger. */
.card { padding: 22px 24px; }
.page-grid { gap: 24px; }

/* ---------- Motion ---------- */
/* A short, soft spring on the things that respond to a click. Long enough to
   read as deliberate, short enough that it never delays the interface. */
:root { --spring: cubic-bezier(0.22, 1.15, 0.36, 1); }

.btn {
  transition: transform 0.18s var(--spring), background 0.2s var(--ease),
              border-color 0.2s var(--ease), box-shadow 0.2s var(--ease), opacity 0.2s var(--ease);
}
.btn:hover:not(:disabled) { transform: translateY(-1.5px); }
.btn:active:not(:disabled) { transform: translateY(0) scale(0.975); }

.card {
  transition: border-color 0.24s var(--ease), box-shadow 0.24s var(--ease), transform 0.24s var(--ease);
}
.card:hover { border-color: var(--line-2); }

.acct-card { transition: transform 0.22s var(--spring), border-color 0.22s var(--ease), box-shadow 0.22s var(--ease); }
.acct-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); }

.nav, .cb-nav .nav { transition: background 0.2s var(--ease), color 0.2s var(--ease), transform 0.18s var(--spring); }
.cb-nav .nav:active { transform: scale(0.96); }

/* Switching tabs: the old 0.24s fade got a little rise, so the eye follows the
   new view in rather than having it appear underneath the cursor. */
.view.active { animation: viewRise 0.34s var(--spring); }
@keyframes viewRise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* Cards stagger in behind the view, which reads as the page assembling itself
   instead of one hard cut. Six is enough -- past that the delay is noticeable
   as sluggishness rather than polish. */
.view.active > .page-grid > .card,
.view.active > .card {
  animation: cardRise 0.42s var(--spring) backwards;
}
.view.active > .page-grid > .card:nth-child(1) { animation-delay: 0.03s; }
.view.active > .page-grid > .card:nth-child(2) { animation-delay: 0.07s; }
.view.active > .page-grid > .card:nth-child(3) { animation-delay: 0.11s; }
.view.active > .page-grid > .card:nth-child(4) { animation-delay: 0.15s; }
.view.active > .page-grid > .card:nth-child(5) { animation-delay: 0.19s; }
.view.active > .page-grid > .card:nth-child(6) { animation-delay: 0.22s; }
@keyframes cardRise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- iPad and other tablets ---------- */
/* The gap the old breakpoints left: between 900px and 1100px the layout was
   still running desktop rules on a screen that cannot hold them, which is what
   made an iPad in portrait feel broken. */
@media (min-width: 561px) and (max-width: 1024px) {
  .content { max-width: none; padding: 22px 22px 56px; }
  .page-grid.two-col,
  .page-grid.three-col { grid-template-columns: minmax(0, 1fr); }
  .account-grid { grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); }
  .stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The tab strip scrolls rather than wrapping onto a second line, so the bar
     keeps its height and the tabs stay where muscle memory expects them. */
  .cb-nav {
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .cb-nav::-webkit-scrollbar { display: none; }
  .cb-nav .nav { flex: 0 0 auto; }
}

/* ---------- Phones ---------- */
@media (max-width: 560px) {
  .content { max-width: none; padding: 18px 16px 48px; }
  .page-grid { gap: 16px; }
  .page-grid.two-col,
  .page-grid.three-col { grid-template-columns: minmax(0, 1fr); }
  .card { padding: 18px 16px; }
  .stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .account-grid { grid-template-columns: minmax(0, 1fr); }

  .cb-nav {
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 2px;
  }
  .cb-nav::-webkit-scrollbar { display: none; }
  .cb-nav .nav { flex: 0 0 auto; }

  /* Rows that were built as side-by-side columns have to stack, or the labels
     truncate to nothing on a 390px screen. */
  .field-row, .row-actions, .add-account-row { flex-wrap: wrap; }
  .add-account-row input { flex: 1 1 100%; }

  /* iOS zooms the whole page in when it focuses an input under 16px. Pinning
     the font size here is the standard way to stop that jump. */
  input:where(:not([type=checkbox]):not([type=radio]):not([type=range])),
  select,
  textarea { font-size: 16px; }

  /* Tables cannot shrink past their content, so let them scroll on their own
     rather than forcing the whole page sideways. */
  .table-wrap, .sb-table { overflow-x: auto; }
}

/* Landscape phones and small tablets: the same stacking, without wasting the
   extra width a landscape screen does have. */
@media (max-width: 900px) and (orientation: landscape) {
  .stat-row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ---------- Motion, opted out ---------- */
/* Anyone who has asked their OS for less motion gets none of the above. The
   existing reduced-motion block is earlier in the file; this covers what this
   pass added. */
@media (prefers-reduced-motion: reduce) {
  .view.active,
  .view.active > .page-grid > .card,
  .view.active > .card { animation: none; }
  .btn:hover:not(:disabled),
  .btn:active:not(:disabled),
  .acct-card:hover,
  .cb-nav .nav:active { transform: none; }
}


/* ---------- Getting started card ---------- */
/* Deliberately louder than a normal card: it is the first thing a new customer
   should read, and it disappears for good once they are set up. */
.gs-card {
  background:
    linear-gradient(140deg, rgba(99, 102, 241, 0.16), rgba(99, 102, 241, 0.04)),
    var(--panel);
  border: 1px solid rgba(139, 143, 248, 0.32);
  border-radius: var(--radius);
  padding: 24px 26px;
  margin-bottom: 22px;
  animation: cardRise 0.45s var(--spring) backwards;
}
.gs-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; margin-bottom: 18px;
}
.gs-head h2 {
  margin: 0; font-size: 21px; font-weight: 680; letter-spacing: -0.02em; color: var(--text);
}

.gs-steps {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px;
}
.gs-steps li { display: flex; gap: 13px; align-items: flex-start; }
.gs-num {
  flex: 0 0 auto;
  width: 30px; height: 30px; border-radius: 50%;
  background: linear-gradient(145deg, var(--primary), #4f46e5);
  color: #fff; font-size: 15px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
}
.gs-steps b { display: block; font-size: 16px; font-weight: 640; color: var(--text); margin-bottom: 5px; }
.gs-steps p { margin: 0; font-size: 14.5px; line-height: 1.6; color: var(--text-2); }

/* A button that reads as a link -- it navigates the app rather than submitting
   anything, but it has to look like the words around it. */
.gs-link {
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; font-weight: 640; color: var(--primary-2);
  border-bottom: 1px solid rgba(139, 143, 248, 0.4);
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}
.gs-link:hover { color: #fff; border-color: #fff; }

.gs-foot {
  margin: 20px 0 0; padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: 14.5px; color: var(--muted); line-height: 1.6;
}

/* On anything narrower than a laptop the three steps become one column -- side
   by side they squeeze to two words a line. */
@media (max-width: 1024px) {
  .gs-steps { grid-template-columns: minmax(0, 1fr); gap: 16px; }
  .gs-card { padding: 20px; }
}
@media (max-width: 560px) {
  .gs-head { flex-wrap: wrap; }
  .gs-head h2 { font-size: 19px; }
  .gs-card { padding: 18px 16px; }
}
