/*
 * The panel's one stylesheet. No inline style anywhere: the CSP forbids it.
 *
 * GeneralsX @feature Claude 09/09/2026
 *
 * The tokens are wh1ter0se.dev's, copied rather than approximated - the same values
 * tools/oauth/worker.js copies, and for the same reason it gives: an operator should be able to
 * tell at a glance that they are on their own property and not on a lookalike.
 */
:root {
	--bg: #0a0a0b;
	--ink: #f2f2f4;
	--ink-2: #9c9ca6;
	--ink-3: #8a8a94;
	--accent: #ff4d6d;
	--accent-hi: #ff6b84;
	--rule: #1e1e23;
	--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
	--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
* { box-sizing: border-box; }
body {
	margin: 0; background: var(--bg); color: var(--ink);
	font: 16px/24px var(--sans);
	-webkit-font-smoothing: antialiased;
}
.top {
	display: flex; gap: 16px; align-items: center; flex-wrap: wrap;
	padding: 14px clamp(18px, 4vw, 44px); border-bottom: 1px solid var(--rule);
	font: 500 12px/1 var(--mono); letter-spacing: .09em; text-transform: uppercase;
	color: var(--ink-3);
}
.brand { color: var(--ink); }
.who { color: var(--ink-2); }
.nav { margin-left: auto; display: flex; gap: 14px; align-items: center; }
.nav a, .link { color: var(--ink-3); text-decoration: none; }
.nav a:hover, .link:hover { color: var(--accent); }
main { max-width: 54rem; padding: clamp(18px, 4vw, 44px); }
h1 { margin: 0 0 18px; font: 600 clamp(26px, 4vw, 38px)/1.05 var(--mono); letter-spacing: -.03em; }
p { margin: 0 0 14px; color: var(--ink-2); }
code { font: 13px/20px var(--mono); color: var(--ink); }
.muted { color: var(--ink-3); font-size: 13.5px; }
.meta { display: block; color: var(--ink-3); font: 12.5px/20px var(--mono); }
button {
	min-height: 40px; padding: 0 18px; background: var(--accent); color: #0a0a0b;
	border: 1px solid var(--accent); font: 650 14px/40px var(--sans); cursor: pointer;
}
button:hover { background: var(--accent-hi); border-color: var(--accent-hi); }
button:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
button.link { background: none; border: 0; color: var(--ink-3); font: 500 12px/1 var(--mono);
	letter-spacing: .09em; text-transform: uppercase; padding: 0; min-height: 0; }
button.danger { background: none; border: 1px solid var(--rule); color: var(--ink-3);
	font: 500 12.5px/34px var(--sans); min-height: 34px; padding: 0 12px; }
button.danger:hover { border-color: var(--accent); color: var(--accent); }
form.inline { display: inline; }
input[type="text"] {
	background: #121216; border: 1px solid var(--rule); color: var(--ink);
	font: 14px/38px var(--mono); padding: 0 10px; min-height: 40px; width: 100%; max-width: 30rem;
}
ul.creds { list-style: none; padding: 0; margin: 0 0 22px; }
ul.creds li { padding: 12px 0; border-top: 1px solid var(--rule); }
.alarm {
	margin: 0; padding: 14px clamp(18px, 4vw, 44px);
	background: #2a1116; border-bottom: 1px solid var(--accent); color: var(--ink);
	font-size: 14.5px;
}
.alarm ul { margin: 8px 0 0; padding-left: 18px; color: var(--ink-2); font-size: 13.5px; }
.status { min-height: 24px; color: var(--accent); font: 13.5px/24px var(--mono); }
label { display: block; margin: 12px 0 4px; color: var(--ink-3); font-size: 13px; }

/* APPENDED below plan C's panel.css - this is not the whole file.
   GeneralsX @feature Claude 09/16/2026
   C's Task 20 already declares the nine site :root tokens (--bg --ink --ink-2 --ink-3 --accent
   --accent-hi --rule --mono --sans) byte-for-byte from tools/oauth/worker.js:348-351 (the CSS
   const; it was :334-337 before the moderation-snapshot fix e5991f9e4 moved it), plus
   *{box-sizing} and the body rule. Re-declaring any of them here would win on cascade order and
   silently restyle plan C's sign-in page - the ownership defect again, one stylesheet down.

   GeneralsX @bugfix Claude 09/16/2026 EVERY RULE BELOW IS SCOPED TO `.gx-console`, and the first
   attempt was not. It claimed to be "additive" and was measured not to be: `main`, `h1`, `button`,
   `input[type=text]`, `.alarm`, `.muted` and `.brand` were all re-declared bare, later in the file
   than plan C's copies and at equal or higher specificity, so they won on C'S OWN pages - which
   are built by lib/shell.js and load this same stylesheet. The worst of them was
   `.alarm{color:var(--accent)}`: recovery.js:156 renders the pending-credential-reset banner as
   `<div class="alarm" role="alert">` over `background:#2a1116`, so C's #f2f2f4 became #ff4d6d on
   dark red - about 3.5:1 - on the most security-relevant text in the panel. The old note argued
   the density was safe because it sat on `main` rather than `body`; C's pages are `<main><h1>...`
   too, so that mitigation did nothing. The scope class is emitted by lib/panel-shell.js's gxShell
   and by nothing else, which is what makes "additive" true rather than asserted.

   The one deliberately unscoped rule is the :root token pair. --ok and --warn are NEW names the
   site does not define (unbounded `grep -rn -- '--ok:|--warn:' tools/` exits 1), so declaring them
   globally adds two tokens and overrides nothing. test-panel-header.mjs enforces both halves of
   that: every other selector must carry the scope, and this :root may declare only properties
   plan C's half does not. */
:root{--ok:#5ad19a;--warn:#ffc857}
.gx-console header.top{display:flex;gap:18px;align-items:center;flex-wrap:wrap;
  padding:12px clamp(14px,3vw,28px);border-bottom:1px solid var(--rule)}
.gx-console .brand{font:600 13px/1 var(--mono);letter-spacing:-.02em;color:var(--ink)}
.gx-console header.top nav{display:flex;gap:14px;flex:1}
.gx-console header.top nav a{color:var(--ink-2);text-decoration:none;font:500 13px/1 var(--sans)}
.gx-console header.top nav a[aria-current=page]{color:var(--accent)}
.gx-console header.top nav a:hover{color:var(--ink)}
.gx-console .lag{font:500 12px/1 var(--mono);letter-spacing:.04em;color:var(--ink-3)}
.gx-console .lag.alarm{color:var(--accent)}
.gx-console main{padding:clamp(14px,3vw,28px);max-width:1400px;font:14px/21px var(--sans)}
.gx-console h1{margin:0 0 6px;font:600 22px/1.15 var(--mono);letter-spacing:-.03em}
.gx-console h2{margin:26px 0 8px;font:600 15px/1.2 var(--mono);letter-spacing:-.02em}
.gx-console p.sub{margin:0 0 18px;color:var(--ink-2)}
.gx-console .alarm{color:var(--accent)}
.gx-console .muted{color:var(--ink-3)}
.gx-console .partial{color:var(--warn)}
.gx-console table{border-collapse:collapse;width:100%;margin:0 0 20px;font-size:13px}
.gx-console th,.gx-console td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--rule);
  vertical-align:top}
.gx-console th{color:var(--ink-3);font:500 11px/1 var(--mono);letter-spacing:.08em;
  text-transform:uppercase}
.gx-console td.mono,.gx-console th.mono{font-family:var(--mono)}
.gx-console td.text{white-space:pre-wrap;word-break:break-word;max-width:52ch}
.gx-console form.gx{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0 0 16px}
.gx-console input[type=text],.gx-console input[type=search],.gx-console select{background:#131318;
  color:var(--ink);border:1px solid var(--rule);padding:7px 9px;font:13px/1.2 var(--sans);
  min-width:11rem}
.gx-console input:focus-visible,.gx-console select:focus-visible,
.gx-console button:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
.gx-console button{background:var(--accent);color:#0a0a0b;border:1px solid var(--accent);
  padding:7px 14px;font:650 13px/1.2 var(--sans);cursor:pointer}
.gx-console button:hover{background:var(--accent-hi);border-color:var(--accent-hi)}
.gx-console button.quiet{background:transparent;color:var(--ink-2);border-color:var(--rule)}
.gx-console button.quiet:hover{color:var(--ink);border-color:var(--ink-3)}
.gx-console .result{font:500 12px/1 var(--mono);color:var(--ink-2)}
.gx-console .pill{display:inline-block;padding:1px 7px;border:1px solid var(--rule);
  border-radius:2px;font:500 11px/17px var(--mono);letter-spacing:.04em}
/* GeneralsX @bugfix Claude 17/09/2026 /players' "in room" pill, which was `applied` until the
   console retired that word. Same green; the claim is still inferred from game_event. */
.gx-console .pill.in_room{color:var(--ok);border-color:var(--ok)}
/* GeneralsX @bugfix Claude 17/09/2026 /players' "timed out" and "no end recorded" pills. They drew
   `pill partial` with no pill rule, so only the text took `.partial`'s amber and the border stayed
   the plain rule colour. */
.gx-console .pill.partial{color:var(--warn);border-color:var(--warn)}
/* GeneralsX @feature Claude 16/09/2026 The pills the truth model draws (spec §5.1, §5.3). /players
   draws its "in room" pill as `in_room` (above), and /moderation draws neither `pending` nor
   `applied` any more (GeneralsX @bugfix Claude 17/09/2026: the `.pill.pending` rule went with them;
   no surface draws it). Only `in_force` is green here, and it is drawn only from the relay's own
   dated answer. What the relay has not confirmed yet is amber. What nobody can vouch for -
   `unknown`, including a claim panel.js aged past its bound - and a relay enforcing nothing are
   the alarm colour: an unknown must never look calmer than something merely waiting. */
.gx-console .pill.in_force{color:var(--ok);border-color:var(--ok)}
.gx-console .pill.checking{color:var(--warn);border-color:var(--warn)}
.gx-console .pill.not_at_relay{color:var(--warn);border-color:var(--warn);border-style:dashed}
.gx-console .pill.not_enforced{color:var(--accent);border-color:var(--accent);border-style:dashed}
.gx-console .pill.unknown{color:var(--accent);border-color:var(--accent)}
/* GeneralsX @bugfix Claude 17/09/2026 A sanction relay X lists and the store does not hold
   (/moderation's "Enforced at the relay but not in the store"). It had no rule and drew as a bare
   pill. Amber and dashed like `not_at_relay`, because it is the same disagreement seen from the
   other side: a dated relay fact the store does not back. */
.gx-console .pill.relay_only{color:var(--warn);border-color:var(--warn);border-style:dashed}
/* An action whose value the store no longer holds - reversed by a later action, or changed with no
   action row (GeneralsX @bugfix Claude 17/09/2026: this said "an action a later one reversed", which
   an edit outside the console is not). Neither green nor amber: it is not waiting for anything and
   it is not in force, it is history - so it reads in the same ink as the rest of the row and only
   the word carries the meaning. */
.gx-console .pill.superseded{color:var(--ink-3);border-color:var(--rule);text-decoration:line-through}
/* GeneralsX @bugfix Claude 17/09/2026 (PMd fix round 1, final review P3) NO SCRIPT, NO CLAIM. Only
   public/panel.js ages a relay-derived element (spec §5.2), so until its gxAgeStart has run the first
   tick and added `gx-aging` to <body>, every element carrying data-gx-age-ms is hidden and an UNKNOWN
   placeholder stands in its place. A page whose script never arrives or never runs keeps the
   placeholders, never a dated claim nobody ages. visibility, not display, so the layout does not move
   when the script reveals the page; the placeholder is the element's own ::after, visible inside its
   hidden host. Pinned by test-panel-modview.mjs's PMd no-script checks. */
.gx-console:not(.gx-aging) [data-gx-age-ms]{visibility:hidden;position:relative}
.gx-console:not(.gx-aging) [data-gx-age-ms]::after{visibility:visible;content:"unknown";position:absolute;
  left:0;top:0;white-space:nowrap;padding:inherit;color:var(--accent)}
.gx-console:not(.gx-aging) p[data-gx-age-ms]::after{content:"UNKNOWN - this page's script has not run, so nothing ages what the relay answered; reload.";
  right:0;white-space:normal}
.gx-console .note{color:var(--ink-3);font-size:12.5px;line-height:19px;margin:0 0 18px;
  padding-top:12px;border-top:1px solid var(--rule)}
