/* BHPanel admin UI.
   Self-contained: no web fonts, no CDN, nothing this panel does not serve
   itself. An admin page that depends on someone else's origin can be changed
   or broken by someone else's outage. */

/* ── theme ────────────────────────────────────────────────────────────────────
   One set of names, three ways of choosing between them, and NO JavaScript in
   any of them - the panel serves a Content-Security-Policy of
   `default-src 'none'` with no script source at all, which is a property worth
   more than a toggle that animates.
     1. light is the default, because that is what a panel looks like at 10am
     2. `prefers-color-scheme: dark` follows the operating system on its own
     3. html.theme-dark / html.theme-light is an explicit choice, rendered by
        the server from a cookie, and beats the operating system either way
   Written twice for dark rather than once, because a media query cannot be
   overridden by a class that appears earlier in the cascade.
   ───────────────────────────────────────────────────────────────────────────*/
:root {
  --bg: #f4f6f9;
  --panel: #ffffff;
  --panel-2: #f1f4f8;
  --line: #e2e8f0;
  --line-soft: #eef1f6;
  --text: #1a2432;
  --dim: #64748b;
  --accent: #2563eb;
  --accent-soft: #e8efff;
  --accent-ink: #ffffff;
  --ok: #16a34a;
  --ok-soft: #dcfce7;
  --warn: #d97706;
  --warn-soft: #fef3c7;
  --bad: #dc2626;
  --bad-soft: #fee2e2;
  --info: #0891b2;

  /* The sidebar keeps its own pair so it stays dark in light mode - the rail
     is the one part that reads better as a constant. */
  --side-bg: #16202e;
  --side-bg-2: #1d2937;
  --side-text: #cbd5e1;
  --side-text-on: #ffffff;

  --radius: 10px;
  --radius-sm: 7px;
  --shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 2px 8px rgba(15, 23, 42, .06);
  --shadow-lift: 0 4px 16px rgba(15, 23, 42, .10);
}

/* Follow the operating system, unless an explicit choice was made. */
@media (prefers-color-scheme: dark) {
  html:not(.theme-light) {
    --bg: #0d1520;
    --panel: #151f2e;
    --panel-2: #1c2839;
    --line: #2a3849;
    --line-soft: #223044;
    --text: #e3eaf3;
    --dim: #8fa3ba;
    --accent: #5b93f7;
    --accent-soft: #1b2c4a;
    --accent-ink: #0d1520;
    --ok: #3fb950;
    --ok-soft: #14301f;
    --warn: #d9a441;
    --warn-soft: #33270f;
    --bad: #f2645a;
    --bad-soft: #3a1a1a;
    --info: #38b6cc;
    --side-bg: #0a1119;
    --side-bg-2: #131d29;
    --shadow: 0 1px 2px rgba(0, 0, 0, .30), 0 2px 10px rgba(0, 0, 0, .28);
    --shadow-lift: 0 4px 18px rgba(0, 0, 0, .40);
  }
}

/* An explicit choice, which outranks the operating system in both directions. */
html.theme-dark {
  --bg: #0d1520;
  --panel: #151f2e;
  --panel-2: #1c2839;
  --line: #2a3849;
  --line-soft: #223044;
  --text: #e3eaf3;
  --dim: #8fa3ba;
  --accent: #5b93f7;
  --accent-soft: #1b2c4a;
  --accent-ink: #0d1520;
  --ok: #3fb950;
  --ok-soft: #14301f;
  --warn: #d9a441;
  --warn-soft: #33270f;
  --bad: #f2645a;
  --bad-soft: #3a1a1a;
  --info: #38b6cc;
  --side-bg: #0a1119;
  --side-bg-2: #131d29;
  --shadow: 0 1px 2px rgba(0, 0, 0, .30), 0 2px 10px rgba(0, 0, 0, .28);
  --shadow-lift: 0 4px 18px rgba(0, 0, 0, .40);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: none; }   /* the pointer says it is a link; see shell.css */

/* ── header ─────────────────────────────────────────────────────────────── */

.top {
  display: flex; align-items: center; gap: 24px;
  padding: 0 20px; height: 52px;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.brand { font-weight: 600; color: var(--text); letter-spacing: .2px; }
.top nav { display: flex; gap: 18px; flex: 1; }
.top nav a { color: var(--dim); padding: 4px 0; border-bottom: 2px solid transparent; }
.top nav a.on { color: var(--text); border-bottom-color: var(--accent); }
.top nav a:hover { color: var(--text); text-decoration: none; }
.me { display: flex; align-items: center; gap: 14px; font-size: 14px; }

main { max-width: 1080px; margin: 0 auto; padding: 26px 20px 60px; }
main.centred { max-width: 420px; padding-top: 8vh; }

h1 { font-size: 22px; margin: 0 0 4px; font-weight: 600; }
h2 { font-size: 16px; margin: 0 0 12px; font-weight: 600; }
.sub { color: var(--dim); margin: 0 0 22px; font-size: 14px; }

/* ── cards and grids ────────────────────────────────────────────────────── */

.card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 18px; margin-bottom: 18px;
}
.grid { display: grid; gap: 14px; }
.grid.c2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid.c4 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }

.stat .n { font-size: 26px; font-weight: 600; }
.stat .l { color: var(--dim); font-size: 13px; }

/* ── tables ─────────────────────────────────────────────────────────────── */

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
  text-align: left; color: var(--dim); font-weight: 500; font-size: 12px;
  text-transform: uppercase; letter-spacing: .4px;
  padding: 8px 10px; border-bottom: 1px solid var(--line);
}
td { padding: 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
tr:last-child td { border-bottom: none; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }

/* ── usage bars ─────────────────────────────────────────────────────────── */

/* Wide enough that one percent is a visible pixel. At 80px the difference
   between 2% and 4% was 1.6px and 3.2px - a real difference the eye could
   not find, which is what made thirteen accounts look identical. */
.bar { height: 6px; background: var(--panel-2); border-radius: 3px; overflow: hidden; min-width: 140px; }
.bar > i { display: block; height: 100%; background: var(--ok); }
.bar.warn > i { background: var(--warn); }
.bar.critical > i { background: var(--bad); }
.bar.none > i { background: var(--line); width: 100%; }

/* ── pills ──────────────────────────────────────────────────────────────── */

.pill {
  display: inline-block; padding: 2px 9px; border-radius: 11px;
  font-size: 12px; font-weight: 600; line-height: 1.6;
}
.pill.ok      { background: rgba(63,185,80,.15);  color: var(--ok); }
.pill.warn    { background: rgba(210,153,34,.15); color: var(--warn); }
.pill.bad     { background: rgba(248,81,73,.15);  color: var(--bad); }
.pill.neutral { background: var(--panel-2);       color: var(--dim); }

/* ── forms ──────────────────────────────────────────────────────────────── */

label { display: block; font-size: 13px; color: var(--dim); margin-bottom: 5px; }
/* ⚠ Matched by EXCLUSION, not by listing the types.
 *
 * This WAS `input[type=text], input[type=password], input[type=number]`, and an
 * <input> with no type attribute matches none of them - the browser treats it
 * as text, the stylesheet did not. Such a field got no width, no padding and no
 * font-size, so it rendered at the browser's default ~175px beside a full-width
 * neighbour with its text jammed against the border. 45 fields over 15 pages
 * were wrong that way, in both panels; type=email was never listed either.
 *
 * shell.css carries this same warning above its own copy of this selector. That
 * is the point: the trap was found once, in the colour rule, and the sizing
 * rule right here was left standing. Keep the two exclusion lists in step.
 *
 * The :where() is load-bearing, not decoration. A bare :not([type=...]) scores
 * a full attribute selector, so eight of them would total 0,8,1 and beat
 * `form.row label input { width: auto }` (0,1,3) - silently stretching every
 * one-line control row on the site. :where() contributes zero, so the single
 * :not() left outside it holds this at 0,1,1: exactly what input[type=text]
 * scored, so every existing override keeps the relationship it already had. */
input:not([type=checkbox]):where(:not(
  [type=radio], [type=submit], [type=button], [type=reset], [type=file],
  [type=range], [type=color], [type=image], [type=hidden]
)),
select, textarea {
  width: 100%; padding: 8px 10px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; font-size: 14px;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
/* Capped by default. A control fills its .field, so without a ceiling here a
 * one-line email or domain box stretched the entire width of the card - about
 * 1000px on a desktop - which is neither readable nor a sensible target. The
 * hint wraps at the same measure, which is the other half of the improvement.
 * .wide opts out, and w-xs/w-sm/w-md tighten further; .wide was already being
 * written in the markup before it meant anything. */
.field { margin-bottom: 14px; max-width: 460px; }
.field.wide { max-width: none; }
.hint { color: var(--dim); font-size: 12px; margin-top: 5px; }
.row { display: flex; gap: 12px; flex-wrap: wrap; }
.row > .field { flex: 1 1 140px; }

/* The grid's gap owns the spacing between its items. .card carries an 18px
 * margin-bottom for stacking, and inside a grid that lands on top of the gap -
 * measured 18px of margin under a 14px gap. .widgets already resets it for the
 * dashboard; every other .grid needed the same. */
.grid > .card { margin-bottom: 0; }

button, .btn {
  padding: 8px 15px; border-radius: var(--radius); border: 1px solid var(--line);
  background: var(--panel-2); color: var(--text);
  font: inherit; font-size: 14px; cursor: pointer;
}
button:hover, .btn:hover { border-color: var(--accent); text-decoration: none; }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.danger  { background: transparent; border-color: var(--bad); color: var(--bad); }
button.danger:hover { background: var(--bad); color: #fff; }
button.link {
  background: none; border: none; color: var(--accent); padding: 0;
  font-size: 14px; cursor: pointer;
}
button.link:hover { text-decoration: none; color: var(--accent); }
.actions { display: flex; gap: 10px; align-items: center; margin-top: 16px; }

/* ── flashes ────────────────────────────────────────────────────────────── */

.flash {
  padding: 11px 14px; border-radius: var(--radius); margin-bottom: 16px;
  border: 1px solid var(--line); font-size: 14px;
}
.flash.ok    { border-color: var(--ok);   background: rgba(63,185,80,.10); }
.flash.error { border-color: var(--bad);  background: rgba(248,81,73,.10); }
.flash.warn  { border-color: var(--warn); background: rgba(210,153,34,.10); }

/* ── misc ───────────────────────────────────────────────────────────────── */

.danger-zone { border-color: var(--bad); }
.danger-zone h2 { color: var(--bad); }
.muted { color: var(--dim); }
.empty { text-align: center; padding: 40px 20px; color: var(--dim); }
details summary { cursor: pointer; color: var(--dim); font-size: 13px; }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px; font-size: 14px; }
.kv dt { color: var(--dim); }
.kv dd { margin: 0; }

/* ── CSP-safe utilities ─────────────────────────────────────────────────────
   The panel sends `style-src 'self'` with no 'unsafe-inline', so a style=""
   attribute is silently DROPPED by the browser. That is not a cosmetic problem:
   a usage bar whose width attribute is ignored renders at its default 100%, so
   every account looks completely full. Everything visual therefore has to be a
   class. (A CSP nonce would not help — nonces apply to <style> elements, never
   to style attributes.) */

/* Usage-bar fill, in 5% steps. Precise enough to read, and no inline style.
 *
 * ⚠ EVERY WIDTH HERE IS SCOPED `.bar > i`, and that is not tidiness.
 *
 * The default below has to exist: <i> is a block child of a fixed-width parent,
 * so with no width at all it fills the track and every account reads as 100%
 * used. But `.bar > i` scores 0,1,1 — a class and a type — while a bare `.w50`
 * scores 0,1,0. The default therefore BEAT every width class regardless of
 * source order, and the bars rendered empty on the accounts page: a visible
 * track with nothing in it, on a table whose whole job is showing usage.
 *
 * Scoping each width the same way makes them 0,2,1 and puts them back in
 * charge. Do not "simplify" these back to bare classes.
 *
 * ⚠ AND THERE ARE TWO STRUCTURES, not one. The tables use `.bar > i`; the
 * dashboard, the customer pages and the usage cards use `.meter .fill`. Scoping
 * to `.bar > i` alone silently removed every width from the second family - and
 * because `.fill` is a block child with no width of its own, those bars did not
 * go empty, they went FULL. Every meter on the dashboard read 100%. If a new
 * structure is added, it must be added here too.
 */
.bar > i { width: 0; }
.bar > i.w0,.meter .fill.w0{width:0}
.bar > i.w1,.meter .fill.w1{width:1%}
.bar > i.w2,.meter .fill.w2{width:2%}
.bar > i.w3,.meter .fill.w3{width:3%}
.bar > i.w4,.meter .fill.w4{width:4%}
.bar > i.w5,.meter .fill.w5{width:5%}
.bar > i.w6,.meter .fill.w6{width:6%}
.bar > i.w7,.meter .fill.w7{width:7%}
.bar > i.w8,.meter .fill.w8{width:8%}
.bar > i.w9,.meter .fill.w9{width:9%}
.bar > i.w10,.meter .fill.w10{width:10%}
.bar > i.w11,.meter .fill.w11{width:11%}
.bar > i.w12,.meter .fill.w12{width:12%}
.bar > i.w13,.meter .fill.w13{width:13%}
.bar > i.w14,.meter .fill.w14{width:14%}
.bar > i.w15,.meter .fill.w15{width:15%}
.bar > i.w16,.meter .fill.w16{width:16%}
.bar > i.w17,.meter .fill.w17{width:17%}
.bar > i.w18,.meter .fill.w18{width:18%}
.bar > i.w19,.meter .fill.w19{width:19%}
.bar > i.w20,.meter .fill.w20{width:20%}
.bar > i.w21,.meter .fill.w21{width:21%}
.bar > i.w22,.meter .fill.w22{width:22%}
.bar > i.w23,.meter .fill.w23{width:23%}
.bar > i.w24,.meter .fill.w24{width:24%}
.bar > i.w25,.meter .fill.w25{width:25%}
.bar > i.w26,.meter .fill.w26{width:26%}
.bar > i.w27,.meter .fill.w27{width:27%}
.bar > i.w28,.meter .fill.w28{width:28%}
.bar > i.w29,.meter .fill.w29{width:29%}
.bar > i.w30,.meter .fill.w30{width:30%}
.bar > i.w31,.meter .fill.w31{width:31%}
.bar > i.w32,.meter .fill.w32{width:32%}
.bar > i.w33,.meter .fill.w33{width:33%}
.bar > i.w34,.meter .fill.w34{width:34%}
.bar > i.w35,.meter .fill.w35{width:35%}
.bar > i.w36,.meter .fill.w36{width:36%}
.bar > i.w37,.meter .fill.w37{width:37%}
.bar > i.w38,.meter .fill.w38{width:38%}
.bar > i.w39,.meter .fill.w39{width:39%}
.bar > i.w40,.meter .fill.w40{width:40%}
.bar > i.w41,.meter .fill.w41{width:41%}
.bar > i.w42,.meter .fill.w42{width:42%}
.bar > i.w43,.meter .fill.w43{width:43%}
.bar > i.w44,.meter .fill.w44{width:44%}
.bar > i.w45,.meter .fill.w45{width:45%}
.bar > i.w46,.meter .fill.w46{width:46%}
.bar > i.w47,.meter .fill.w47{width:47%}
.bar > i.w48,.meter .fill.w48{width:48%}
.bar > i.w49,.meter .fill.w49{width:49%}
.bar > i.w50,.meter .fill.w50{width:50%}
.bar > i.w51,.meter .fill.w51{width:51%}
.bar > i.w52,.meter .fill.w52{width:52%}
.bar > i.w53,.meter .fill.w53{width:53%}
.bar > i.w54,.meter .fill.w54{width:54%}
.bar > i.w55,.meter .fill.w55{width:55%}
.bar > i.w56,.meter .fill.w56{width:56%}
.bar > i.w57,.meter .fill.w57{width:57%}
.bar > i.w58,.meter .fill.w58{width:58%}
.bar > i.w59,.meter .fill.w59{width:59%}
.bar > i.w60,.meter .fill.w60{width:60%}
.bar > i.w61,.meter .fill.w61{width:61%}
.bar > i.w62,.meter .fill.w62{width:62%}
.bar > i.w63,.meter .fill.w63{width:63%}
.bar > i.w64,.meter .fill.w64{width:64%}
.bar > i.w65,.meter .fill.w65{width:65%}
.bar > i.w66,.meter .fill.w66{width:66%}
.bar > i.w67,.meter .fill.w67{width:67%}
.bar > i.w68,.meter .fill.w68{width:68%}
.bar > i.w69,.meter .fill.w69{width:69%}
.bar > i.w70,.meter .fill.w70{width:70%}
.bar > i.w71,.meter .fill.w71{width:71%}
.bar > i.w72,.meter .fill.w72{width:72%}
.bar > i.w73,.meter .fill.w73{width:73%}
.bar > i.w74,.meter .fill.w74{width:74%}
.bar > i.w75,.meter .fill.w75{width:75%}
.bar > i.w76,.meter .fill.w76{width:76%}
.bar > i.w77,.meter .fill.w77{width:77%}
.bar > i.w78,.meter .fill.w78{width:78%}
.bar > i.w79,.meter .fill.w79{width:79%}
.bar > i.w80,.meter .fill.w80{width:80%}
.bar > i.w81,.meter .fill.w81{width:81%}
.bar > i.w82,.meter .fill.w82{width:82%}
.bar > i.w83,.meter .fill.w83{width:83%}
.bar > i.w84,.meter .fill.w84{width:84%}
.bar > i.w85,.meter .fill.w85{width:85%}
.bar > i.w86,.meter .fill.w86{width:86%}
.bar > i.w87,.meter .fill.w87{width:87%}
.bar > i.w88,.meter .fill.w88{width:88%}
.bar > i.w89,.meter .fill.w89{width:89%}
.bar > i.w90,.meter .fill.w90{width:90%}
.bar > i.w91,.meter .fill.w91{width:91%}
.bar > i.w92,.meter .fill.w92{width:92%}
.bar > i.w93,.meter .fill.w93{width:93%}
.bar > i.w94,.meter .fill.w94{width:94%}
.bar > i.w95,.meter .fill.w95{width:95%}
.bar > i.w96,.meter .fill.w96{width:96%}
.bar > i.w97,.meter .fill.w97{width:97%}
.bar > i.w98,.meter .fill.w98{width:98%}
.bar > i.w99,.meter .fill.w99{width:99%}
.bar > i.w100,.meter .fill.w100{width:100%}
/* A bar with no limit set is a full grey track, not a full green one — "no
   limit" and "completely full" must never look the same. */
.bar.none > i { width: 100%; }

/* spacing + sizing utilities */
.mt-0   { margin-top: 0; }
.mt-1   { margin-top: 6px; }
.mt-2   { margin-top: 8px; }
.mb-2   { margin-bottom: 14px; }
.mb-3   { margin-bottom: 18px; }
.tight  { margin-top: -8px; }
/* ...except after something with no bottom margin to eat.
 *
 * The -8px is there to pull a note up into the space a paragraph or heading
 * leaves behind it. These elements leave none - a <table> and a <form> have no
 * UA margin, dl.facts sets `margin: 0` outright, and summary.act keeps only
 * .15rem - so the note is dragged onto the element above instead. Measured at
 * -8px after a table (services), a form.row (my/php, my/sites) and a dl.facts
 * (my/ftp), and -6px after a summary.act (my/sites): five places, four
 * different tags, which is why this is keyed on the property they share rather
 * than on the pages that happen to show it.
 *
 * :where() keeps this at .tight's own 0,1,0 so it wins on order alone, and any
 * page rule that already overrides .tight still does. */
:where(table, dl, form, summary, pre, fieldset) + .tight { margin-top: 4px; }
/* ...except directly under a form control.
 *
 * .tight pulls a hint up into the bottom margin of whatever precedes it, which
 * works under a heading or a paragraph. A control has NO bottom margin, so the
 * -8px lands the hint text ON the input - measured at exactly -8px of overlap
 * on the SSH keys page, in 16 places across both panels. Keyed on the adjacency
 * that causes it rather than on the pages that happen to show it today. */
input + .hint.tight,
select + .hint.tight,
textarea + .hint.tight { margin-top: 4px; }

/* A hint written as a <span> rather than a <p>, sitting under a control.
 *
 * .hint sets margin-top, which an inline box ignores, so the span rode up
 * alongside the input instead of sitting under it - measured at 27px of
 * overlap on the customer DNS page. .grid-3 cells never showed it because
 * they are flex columns, where every child gets its own line for free.
 *
 * Scoped to a DIRECT child of .field on purpose: mail.php puts a hint BETWEEN
 * two inputs to read "info@domain - delivers to - someone@domain", and that
 * one has to stay inline. It lives inside a .row within the .field, so the
 * child combinator leaves it alone. Hidden inputs are excluded for the same
 * reason - the span after one of those is somebody else's row. */
.field > input:not([type=hidden]) + .hint,
.field > select + .hint,
.field > textarea + .hint { display: block; margin-top: 4px; }
.tighter{ margin-top: -6px; margin-bottom: 14px; }
.pt-0   { padding-top: 0; }
.w-xs   { max-width: 280px; }
.w-sm   { max-width: 320px; }
.w-md   { max-width: 460px; }
.grow-0 { flex: 0 0 auto; }
.bottom { align-items: flex-end; }
.truncate {
  max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}


/* ── mail page ──────────────────────────────────────────────────────────────
   A few classes the mail page needs. Kept here rather than inline because the
   panel's CSP is style-src 'self' with no unsafe-inline: an inline style
   attribute is silently dropped, which is how every usage bar once rendered
   full. */

/* A row of controls that belong to one action, wrapping on a narrow screen
   instead of overflowing the table cell. */
.tight-row {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  align-items: center;
  margin-bottom: .35rem;
}
.tight-row:last-child { margin-bottom: 0; }

/* Values meant to be copied: a generated password, a DKIM record. */
.selectable { user-select: all; }
.selectable.big { font-size: 1.05rem; letter-spacing: .02em; }

/* A DKIM value is a few hundred characters and must not stretch the table. */
code.wrap {
  display: block;
  overflow-wrap: anywhere;
  word-break: break-all;
  white-space: pre-wrap;
  max-width: 46em;
}

/* The one-time password panel, which the admin has to notice. */
.card.highlight {
  border-color: var(--accent, #2563eb);
  box-shadow: 0 0 0 1px var(--accent, #2563eb) inset;
}

label.inline {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  font-weight: normal;
}

/* A compact table. NOT called "grid": that class already means display:grid in
   this stylesheet, and putting it on a <table> turns the table into a CSS grid
   container - rows and columns stop behaving like a table while still looking
   almost right, which is a slow thing to notice. */
table.tight td, table.tight th { vertical-align: top; padding: 6px 8px; }

/* Field widths. Classes rather than a style attribute: the panel's CSP is
   style-src 'self' with no unsafe-inline, so an inline width is dropped and the
   field silently renders at its default size. */
.w-num     { width: 7em; }
.w-confirm { width: 16em; }
.w-wide    { min-width: 20em; flex: 1 1 20em; }

/* ─────────────────────────────────────────────────────────────────────────────
   BHPanel identity.
   
   Overrides the tokens above rather than editing them, so the starting point
   stays readable and this is one block to revisit.
   
   The panel used to look like every generated admin template: white rounded
   cards floating on light grey, one saturated blue doing every job, the same
   padding everywhere. That reads as unconsidered even when the information is
   right, and a hosting panel is something people trust with a business.
   
   The accent is the BiswasHost brand blue, #0D6EFD, with #0948B3 for the
   pressed and darker states - the same pair the BH modules use, so the panel
   belongs to the same product family rather than being its own island.
   
   Worth being explicit, because it looks like the colour that was here before
   and is not: the old #2563EB was a framework default nobody chose. Using the
   brand deliberately also means the panel stops reading as generated for the
   right reason - not because the hue is unusual, but because everything around
   it is considered. That work is the density, the type and the hairlines below,
   which is where the character actually comes from. A colour on its own has
   never made a tool look designed.
   
   Corners much sharper, because soft everything is the template signature. No
   shadows at all; a hairline states an edge without pretending the page is a
   stack of paper. A near-black rail so the accent has somewhere to land.
   
   Dark is written twice on purpose: a media query cannot be overridden by a
   class that appears earlier in the cascade.
   ───────────────────────────────────────────────────────────────────────────*/
:root {
  --bg: #f2f3f5;
  --panel: #ffffff;
  --panel-2: #f5f6f8;
  --line: #dfe3e8;
  --line-soft: #ebeef1;
  --text: #16181d;
  --dim: #6b7280;

  --accent: #0d6efd;
  --accent-strong: #0948b3;
  --accent-soft: #e7f0ff;
  --accent-ink: #ffffff;

  --side-bg: #14161c;
  --side-bg-2: #1c1f27;
  --side-text: #a8adb8;
  --side-text-on: #ffffff;

  --radius: 4px;
  --radius-sm: 3px;
  --shadow: none;
  --shadow-lift: none;
}

:root:not([data-theme="light"]) {
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0e1013;
    --panel: #16191f;
    --panel-2: #1c2027;
    --line: #272c35;
    --line-soft: #1f242b;
    --text: #e6e8ec;
    --dim: #8b919d;

    --accent: #4d94ff;
    --accent-strong: #0d6efd;
    --accent-soft: #10233d;
    --accent-ink: #14161c;

    --side-bg: #0b0d10;
    --side-bg-2: #14171c;
  }
}

:root[data-theme="dark"] {
  --bg: #0e1013;
  --panel: #16191f;
  --panel-2: #1c2027;
  --line: #272c35;
  --line-soft: #1f242b;
  --text: #e6e8ec;
  --dim: #8b919d;

  --accent: #4d94ff;
  --accent-strong: #0d6efd;
  --accent-soft: #10233d;
  --accent-ink: #14161c;

  --side-bg: #0b0d10;
  --side-bg-2: #14171c;
}

/* A row of action buttons sitting BETWEEN two paragraphs.
 *
 * ⚠ It has its own class because the obvious one is wrong. `.tight` is
 * margin-top:-8px, and it exists to pull a hint paragraph up under a heading —
 * so `class="row tight"` around two buttons pulled them 8px into the text
 * above, and the `.tight` paragraph that followed pulled itself 8px back into
 * the buttons. Overlapping text at BOTH edges, on every page that used it.
 * Measured: the row ended at y=371 and the next paragraph began at y=363. */
.btn-row { margin-top: 10px; margin-bottom: 12px; }

/* ───────────────────────────────────────────────────────────────────────────
 * The terminal.
 *
 * ⚠ Colours are CLASSES, never inline styles. The panel sends style-src 'self'
 * with no 'unsafe-inline', so a style="color:…" attribute is dropped by the
 * browser in silence: the text renders unstyled and nothing appears in the
 * console to say why. The renderer emits .tf0-.tf255 / .tb0-.tb255 instead, and
 * the sixteen that a shell actually uses are spelled out below.
 *
 * ⚠ No webfont. font-src is absent from the policy, so it falls back to
 * default-src 'none' and every @font-face fails. The stack below is whatever
 * the machine already has.
 * ───────────────────────────────────────────────────────────────────────────*/

.term-card { padding-bottom: 12px; }

.term-bar {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 10px; flex-wrap: wrap;
}
.term-end { margin-left: auto; }

.term-status { font-size: 12px; color: var(--dim); }
.term-status.live::before { content: "\25CF"; color: #3fb950; margin-right: 5px; }
.term-status.ended { color: #d9534f; }
.term-status.ended::before { content: "\25CF"; margin-right: 5px; }

.term { position: relative; }

.term-screen {
  background: #0b0d10;
  color: #d5d8dd;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 10px 12px;
  height: 62vh;
  min-height: 260px;
  overflow-y: auto;
  overflow-x: hidden;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
  font-size: 13px;
  line-height: 1.35;
  white-space: pre;
  tab-size: 8;
}

/* Each row is its own element so a line that is all spaces still occupies a
 * line, and so a selection breaks where the screen breaks. */
.tline { min-height: 1.35em; }

/* Visually hidden, NEVER display:none. A hidden input cannot take focus, and
 * then every keystroke goes nowhere — the terminal looks frozen. */
.term-input {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* Ten characters of the screen's own font, parked off-screen so the script can
 * measure one character. Off-screen rather than hidden, because something with
 * display:none has no width to measure. */
.term-probe {
  position: absolute; left: -9999px; top: 0;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
  font-size: 13px; line-height: 1.35; white-space: pre;
}

.tcur { background: #d5d8dd; color: #0b0d10; }
.tbold { font-weight: 700; }
.tdim { opacity: 0.7; }
.tul { text-decoration: underline; }
.tinv { background: #d5d8dd; color: #0b0d10; }

.tf0 { color: #4f5561; } .tf1 { color: #f7768e; } .tf2 { color: #9ece6a; }
.tf3 { color: #e0af68; } .tf4 { color: #7aa2f7; } .tf5 { color: #bb9af7; }
.tf6 { color: #7dcfff; } .tf7 { color: #c0caf5; }
.tf8 { color: #737b8a; } .tf9 { color: #ff9daf; } .tf10 { color: #b9e88a; }
.tf11 { color: #f2c98a; } .tf12 { color: #9cb9ff; } .tf13 { color: #d0b8ff; }
.tf14 { color: #a3e2ff; } .tf15 { color: #ffffff; }

.tb0 { background: #0b0d10; } .tb1 { background: #f7768e; }
.tb2 { background: #9ece6a; } .tb3 { background: #e0af68; }
.tb4 { background: #7aa2f7; } .tb5 { background: #bb9af7; }
.tb6 { background: #7dcfff; } .tb7 { background: #c0caf5; }
.tb8 { background: #2a2f3a; } .tb9 { background: #ff9daf; }
.tb10 { background: #b9e88a; } .tb11 { background: #f2c98a; }
.tb12 { background: #9cb9ff; } .tb13 { background: #d0b8ff; }
.tb14 { background: #a3e2ff; } .tb15 { background: #ffffff; }
