:root {
  --bg: #f6f7f9;
  --panel: #ffffff;
  --panel-2: #f0f2f5;
  --text: #1b1f24;
  --muted: #646d78;
  --border: #dde1e6;
  --accent: #5865f2;
  --accent-text: #ffffff;
  --danger: #d93a3a;
  --ok: #2f9e5b;
  --warn: #c98a14;
  --code-bg: #f0f2f5;
  --shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #15171b;
    --panel: #1d2026;
    --panel-2: #252931;
    --text: #e6e8eb;
    --muted: #9099a4;
    --border: #2f343c;
    --accent: #7b86ff;
    --accent-text: #0e1016;
    --danger: #f06767;
    --ok: #4cc27f;
    --warn: #e3ab3b;
    --code-bg: #14161a;
    --shadow: none;
  }
}

* { box-sizing: border-box; }
html, body { margin: 0; }
body {
  background: var(--bg);
  color: var(--text);
  font: 14px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
[hidden] { display: none !important; }
h1 { font-size: 22px; margin: 0 0 6px; }
h2 { font-size: 18px; margin: 0; }
h3 { font-size: 13px; margin: 22px 0 8px; text-transform: uppercase;
     letter-spacing: .04em; color: var(--muted); }
code, pre, .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; }
.muted { color: var(--muted); }
.small { font-size: 12px; }

/* EverythingBot logo (favicon + login + sidebar) */
.logo { width: 76px; height: 76px; border-radius: 16px; justify-self: center;
        box-shadow: var(--shadow); }
.brand-logo { width: 22px; height: 22px; border-radius: 5px;
              vertical-align: -5px; margin-right: 7px; }

input, select, textarea, button {
  font: inherit; color: inherit;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 6px; padding: 7px 10px; min-width: 0;
}
input:focus, select:focus, textarea:focus, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
textarea { resize: vertical; font-family: inherit; }
button { cursor: pointer; background: var(--panel-2); }
button:hover:not(:disabled) { border-color: var(--muted); }
button:disabled { opacity: .5; cursor: default; }
button.primary { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }
button.danger { color: var(--danger); }
.small-btn { padding: 4px 8px; font-size: 12px; }
label { display: grid; gap: 4px; font-size: 13px; }

.row { display: flex; gap: 8px; align-items: center; }
.row.wrap { flex-wrap: wrap; }
.row.between { justify-content: space-between; }
.grow-1 { flex: 1 1 160px; }
.grow-3 { flex: 3 1 220px; }

.card { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px; box-shadow: var(--shadow); }

/* login */
/* sign-in dialog, opened over the page by the Log in button */
.login { position: fixed; inset: 0; z-index: 40; display: grid; place-items: center; padding: 16px;
         overflow-y: auto; background: rgba(0, 0, 0, .55); }
.login-card { position: relative; width: 100%; max-width: 400px; display: grid; gap: 14px; }
.login-close { position: absolute; top: 8px; right: 8px; width: 30px; height: 30px; padding: 0;
               font-size: 20px; line-height: 1; background: transparent; border-color: transparent;
               color: var(--muted); }
.login-close:hover:not(:disabled) { color: var(--text); border-color: var(--border); }

/* Signed out ("guest") vs signed in: the page is one shell, <body class="guest"> switches it.
   auth-only = needs a token (hidden for guests); guest-only / user-only = the Log in button
   and the who/Log out footer. */
body.guest .auth-only,
body.guest .user-only,
body:not(.guest) .guest-only { display: none !important; }
/* a guest has no console, so the command picker IS the page: never collapsed for them
   (their saved collapsed/open choice is left alone for when they log in) */
body.guest .cmd-picker.collapsed { display: grid; }
#login-open { white-space: nowrap; flex: 0 0 auto; }
.login-card form { display: grid; gap: 12px; }
.login-card form.row { display: flex; }
.login-card form.row input { flex: 1; }
.scopes { border: 1px solid var(--border); border-radius: 6px; display: flex; gap: 14px; flex-wrap: wrap; padding: 8px 10px; }
.scopes legend { font-size: 12px; color: var(--muted); padding: 0 4px; }
.scopes label { display: flex; align-items: center; gap: 5px; }
.paste summary { cursor: pointer; color: var(--muted); font-size: 13px; margin-bottom: 8px; }
.msg { margin: 0; min-height: 1.4em; }
.msg.err { color: var(--danger); }
.msg.ok { color: var(--ok); }

/* shell */
.shell { display: grid; grid-template-columns: 200px 1fr; min-height: 100vh; }
.side { background: var(--panel); border-right: 1px solid var(--border); padding: 14px 10px; display: flex; flex-direction: column; gap: 2px; position: sticky; top: 0; height: 100vh; }
.brand { font-weight: 700; padding: 4px 10px 14px; }
.side a { color: var(--text); text-decoration: none; padding: 7px 10px; border-radius: 6px; }
.side a:hover { background: var(--panel-2); }
.side a.active { background: var(--accent); color: var(--accent-text); }
.side-foot { margin-top: auto; display: grid; gap: 6px; padding: 10px; border-top: 1px solid var(--border); }
.side-foot #who { overflow-wrap: anywhere; }
.content { padding: 20px 24px 40px; min-width: 0; max-width: 1200px; }
.view-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
.view > .row { margin-bottom: 12px; }

.toast { position: fixed; right: 16px; bottom: 16px; max-width: min(420px, calc(100vw - 32px)); margin: 0; padding: 10px 14px; border-radius: 8px; background: var(--text); color: var(--bg); z-index: 10; }
.toast.err { background: var(--danger); color: #fff; }

.pill { font-size: 12px; padding: 3px 9px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted); }
.pill.ok { color: var(--ok); border-color: var(--ok); }
.pill.warn { color: var(--warn); border-color: var(--warn); }
.pill.err { color: var(--danger); border-color: var(--danger); }

.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.tile { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; }
.tile .k { font-size: 12px; color: var(--muted); }
.tile .v { font-size: 20px; font-weight: 600; font-variant-numeric: tabular-nums; margin-top: 2px; overflow-wrap: anywhere; }
.tile .s { font-size: 12px; color: var(--muted); }
.bar { height: 5px; background: var(--panel-2); border-radius: 3px; margin-top: 8px; overflow: hidden; }
.bar > span { display: block; height: 100%; background: var(--accent); }
.bar.hot > span { background: var(--warn); }

/* console */
.run-form { display: grid; gap: 8px; margin-bottom: 12px; }
.output { display: grid; gap: 8px; }
.output:empty::before { content: "No output yet."; color: var(--muted); }
.item { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; overflow-wrap: anywhere; }
.item.text { white-space: pre-wrap; }
.item.error { border-color: var(--danger); color: var(--danger); white-space: pre-wrap; }
.item.meta { background: transparent; border-style: dashed; color: var(--muted); font-size: 12px; }
.item .who { font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.item .file-head { font-weight: 600; }
/* a captured file that is an image (a generated picture, a render) is shown,
   not just named - see the file branch of renderItem */
.item img { display: block; max-width: 100%; max-height: 420px; border-radius: 6px;
            margin-top: 6px; background: var(--panel-2); }
.embed { border-left: 4px solid var(--border); padding-left: 10px; display: grid; gap: 6px; }
.embed .title { font-weight: 600; }
.embed .desc { white-space: pre-wrap; }
.embed .fields { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px; }
.embed .field .n { font-weight: 600; font-size: 13px; }
.embed .field .fv { white-space: pre-wrap; font-size: 13px; }
.embed .footer { font-size: 12px; color: var(--muted); }
.embed img { max-width: 100%; max-height: 320px; border-radius: 6px; }
.components { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.modal-form { display: grid; gap: 8px; }
.modal-form textarea { font: inherit; color: inherit; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 7px 10px; min-height: 70px; }
#run-input { margin-top: 10px; }

/* commands */
.cmd-picker { display: grid; gap: 12px; }
.cmd-picker.collapsed { display: none; }
.split { display: grid; grid-template-columns: minmax(200px, 320px) 1fr; gap: 12px; align-items: start; }
.list { list-style: none; margin: 0; padding: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; max-height: 70vh; overflow: auto; }
.list li { padding: 8px 12px; border-bottom: 1px solid var(--border); cursor: pointer; }
.list li:last-child { border-bottom: 0; }
.list li:hover { background: var(--panel-2); }
.list li.active { background: var(--panel-2); box-shadow: inset 3px 0 var(--accent); }
.list .n { font-weight: 600; }
.list .d { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.detail { display: grid; gap: 10px; position: sticky; top: 16px; }
.detail pre { background: var(--code-bg); padding: 10px; border-radius: 6px; white-space: pre-wrap; margin: 0; }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size: 13px; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; overflow-wrap: anywhere; }

/* tables */
.table-wrap { overflow-x: auto; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; }
table { border-collapse: collapse; width: 100%; font-size: 13px; }
th, td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; }
td.wrap { white-space: normal; overflow-wrap: anywhere; min-width: 200px; }
tr:last-child td { border-bottom: 0; }
th { color: var(--muted); font-weight: 500; background: var(--panel-2); }
td.num { font-variant-numeric: tabular-nums; text-align: right; }
td .row { gap: 4px; }
.empty td { color: var(--muted); text-align: center; }

.logs { background: var(--code-bg); border: 1px solid var(--border); border-radius: 10px; padding: 12px; max-height: 420px; overflow: auto; white-space: pre-wrap; margin: 0; }

/* footer credit */
.foot { font-size: 11px; color: var(--muted); margin: 0; }
.foot a { color: var(--muted); text-decoration: none; border-bottom: 1px dotted currentColor; }
.foot a:hover { color: var(--accent); border-bottom-color: var(--accent); }
.foot .sep { opacity: .6; margin: 0 5px; }

/* autocomplete (console intellisense) */
.ac-wrap { position: relative; display: flex; flex-direction: column; }
.ac { position: absolute; top: 100%; left: 0; right: 0; z-index: 30;
      background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
      box-shadow: 0 8px 24px rgba(0, 0, 0, .18); max-height: 300px; overflow: auto; }
.ac-hint { padding: 4px 10px; font-size: 11px; color: var(--muted);
           border-bottom: 1px solid var(--border); }
.ac-item { padding: 6px 10px; cursor: pointer; }
.ac-item + .ac-item { border-top: 1px solid var(--border); }
.ac-item.hover, .ac-item:hover { background: var(--panel-2); }
.ac-item .ac-name { font-weight: 600; }
.ac-item .ac-desc { font-size: 12px; color: var(--muted);
                    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* live help text under the console form */
.help-strip { display: grid; gap: 2px; background: var(--code-bg);
              border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px;
              margin-bottom: 8px; }
.help-strip .usage { font-weight: 600; overflow-wrap: anywhere; }
.help-strip .d { color: var(--muted); white-space: pre-wrap; }

/* help page */
/* help page */

/* applets */
.applet-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.applet-card { display: grid; gap: 10px; }
.applet-card-head { display: flex; justify-content: space-between; gap: 10px; align-items: flex-start; }
.applet-card-head .n { font-weight: 600; overflow-wrap: anywhere; }
.applet-card-head .d { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
.applet-card-form { border-top: 1px solid var(--border); padding-top: 10px; }
.applet-scope { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 10px 12px; }
.applet-scope strong { overflow-wrap: anywhere; }
.applet-scope select { min-width: 160px; }

/* shared command-output dock (#applet-output): substantial output from a config
   applet - which has no panel of its own - lands here instead of vanishing */
.applet-output { display: grid; gap: 8px; margin-top: 14px; }
.applet-output .item { margin: 0; }
.applet-output-title { font-size: 11px; font-weight: 600; text-transform: uppercase;
                      letter-spacing: .04em; color: var(--muted); }

/* applet sections (Server Moderation etc.) */
.applet-section { display: grid; gap: 10px; margin-bottom: 14px; }
.applet-section-title { font-weight: 600; }

/* The apple applet is a picture of an apple and wants the whole window, not the
   reading-width column the rest of the dashboard uses. Scoped to that one
   applet, and only while its view is the visible one, so nothing else moves.
   These are CLASSES on purpose: the page CSP is style-src 'self', so an inline
   style="" attribute is silently dropped and the element falls back to body
   font size. */
.content:has(#view-applets:not([hidden]) .apple-max) { max-width: none; }
.apple-max { display: block; width: 100%; height: auto; border-radius: 14px;
             border: 1px solid var(--border); background: var(--panel-2); }
.apple-say { font-size: 22px; text-align: center; margin: 0 0 14px; }
.apple-emoji { font-size: min(40vw, 420px); line-height: 1; text-align: center; }
.applet-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* applet group headings on the Applets home grid (one grid per group) */
.applet-group { display: grid; gap: 8px; margin-bottom: 18px; }
.applet-group-title { font-size: 13px; font-weight: 600; text-transform: uppercase;
                      letter-spacing: .04em; color: var(--muted); }

/* one row inside a listSection card: description left, actions right */
.applet-list-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
                   justify-content: space-between; }
.applet-list-row .applet-row { flex: 0 0 auto; }

/* one action per row: [input] [input] [button] */
.applet-inline-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.applet-inline-form > input,
.applet-inline-form > .picker { flex: 1 1 160px; min-width: 0; }
.applet-inline-form > button { flex: 0 0 auto; }
.applet-inline-form > input { padding: 6px 8px; border: 1px solid var(--border);
                              border-radius: 6px; background: var(--panel-2); color: var(--text); }
.applet-inline-form > select { padding: 6px 8px; border: 1px solid var(--border);
                               border-radius: 6px; background: var(--panel-2); color: var(--text); }

/* label + input pair */
.applet-field { display: grid; gap: 4px; }

/* Code Runner's editor: a transparent <textarea> stacked over a highlighted <pre>.
   BOTH layers must share every metric below or the caret drifts off the text - that is the
   whole trick. (The textarea keeps the real value and the caret; the <pre> is decoration.) */
.applet-inline-form > .code-row { flex: 1 1 100%; width: 100%; min-width: 0; }
.code-editor { display: grid; width: 100%; }
.code-editor > .code-hl,
.code-editor > .code-input {
  grid-area: 1 / 1;
  margin: 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: transparent;          /* each layer sets its own below */
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.45;
  letter-spacing: 0;
  tab-size: 4;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow: auto;
}
/* the <pre> carries the visible background; the textarea must be SEE-THROUGH or it hides it */
.code-editor > .code-hl { pointer-events: none; background: var(--code-bg); }
/* BOTH layers must be the SAME BOX, not just the same font: an auto-height <pre> grows
   instead of scrolling, so the coloured copy sits still while the textarea scrolls under it.
   The applet also copies the textarea's client box onto the <pre> (ResizeObserver) so a user
   drag on the resize handle keeps them aligned. */
.code-editor > .code-hl {
  height: 46vh;
  min-height: 320px;
  max-height: 72vh;
  border-color: transparent;
}
.code-editor > .code-input {
  background: transparent;            /* MUST stay see-through: this layer paints OVER the pre */
  color: transparent;                 /* the <pre> behind shows the coloured copy */
  -webkit-text-fill-color: transparent;
  caret-color: var(--text);
  resize: vertical;
  height: 46vh;                        /* it is the point of the card */
  min-height: 320px;
  max-height: 72vh;
}
.code-editor > .code-input::placeholder { color: var(--muted); }
/* the selection is painted by the textarea ON TOP of the <pre>, so it has to be see-through */
.code-editor > .code-input::selection { background: rgba(88, 101, 242, .35); }

/* token colours - Prism's token types (the vendor bundle is not themed by Prism's own CSS,
   which is light-only; these are all vars so they follow the dark/light theme). The second
   block is the fallback tokenizer's classes. */
.code-hl .token.comment,
.code-hl .token.prolog,
.code-hl .token.doctype,
.code-hl .token.cdata { color: var(--muted); font-style: italic; }
.code-hl .token.punctuation { color: var(--muted); }
.code-hl .token.string,
.code-hl .token.char,
.code-hl .token.attr-value,
.code-hl .token.regex,
.code-hl .token.triple-quoted-string { color: var(--ok); }
.code-hl .token.number,
.code-hl .token.boolean,
.code-hl .token.constant,
.code-hl .token.symbol { color: var(--warn); }
.code-hl .token.keyword,
.code-hl .token.atrule,
.code-hl .token.rule,
.code-hl .token.important,
.code-hl .token.builtin { color: var(--accent); font-weight: 600; }
.code-hl .token.function,
.code-hl .token.class-name,
.code-hl .token.macro,
.code-hl .token.function-definition { font-weight: 600; }
.code-hl .token.tag,
.code-hl .token.selector,
.code-hl .token.attr-name,
.code-hl .token.property,
.code-hl .token.variable { color: var(--accent); }
.code-hl .token.operator,
.code-hl .token.entity,
.code-hl .token.url { color: var(--text); }
.code-hl .token.deleted { color: var(--danger); }
.code-hl .token.inserted { color: var(--ok); }
.code-hl .token.italic { font-style: italic; }
.code-hl .token.bold { font-weight: 700; }

.code-hl .tok-com { color: var(--muted); font-style: italic; }
.code-hl .tok-str { color: var(--ok); }
.code-hl .tok-num { color: var(--warn); }
.code-hl .tok-kw { color: var(--accent); font-weight: 600; }
.code-hl .tok-fn { font-weight: 600; }
.code-hl .tok-const { color: var(--danger); }

/* typeahead picker (user search) */
.picker { position: relative; display: block; }
/* An author rule beats the UA sheet, so `.picker { display: block }` would KEEP a [hidden]
   picker on screen (the admin-only "execute as" box is exactly that). */
.picker[hidden] { display: none; }
.picker input { width: 100%; padding: 6px 8px; border: 1px solid var(--border);
                border-radius: 6px; background: var(--panel-2); color: var(--text); }
.picker select { width: 100%; }
.picker-menu { position: absolute; z-index: 5; left: 0; right: 0; top: calc(100% + 2px);
               display: grid; max-height: 240px; overflow-y: auto;
               background: var(--panel); border: 1px solid var(--border);
               border-radius: 6px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35); }
.picker-opt { text-align: left; padding: 6px 8px; border: 0; border-radius: 0;
              background: transparent; color: var(--text); cursor: pointer; }
.picker-opt:hover { background: var(--panel-2); }

/* result lists (bans, allowances) */
.applet-list { display: grid; gap: 6px; }
.applet-list-item { background: var(--panel-2); border: 1px solid var(--border);
                    border-radius: 8px; padding: 8px 10px; display: grid; gap: 8px; }
.applet-list-who .n { font-weight: 600; overflow-wrap: anywhere; }

/* label/value grid for one record (Applets.stats) - the readable alternative
   to a long "a | b | c" line */
.applet-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
                gap: 6px 12px; }
.applet-stat { display: grid; gap: 1px; min-width: 0; }
.applet-stat .k { font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em;
                  color: var(--muted); }
.applet-stat .v { font-size: 13px; font-weight: 600; overflow-wrap: anywhere; }
.applet-list-item .applet-list-row { align-items: start; }

/* game applets (Tetris / Block Blast) --------------------------------------- */
/* captured command output lands here: boards are ASCII embeds, so keep the
   text tight and scrollable instead of letting it wrap into mush */
.game-board { display: grid; gap: 8px; margin: 12px 0; }
.game-board .item.embed { max-width: 100%; }
.game-board .item.embed .desc { white-space: pre; overflow-x: auto;
                                font-family: var(--mono, ui-monospace, Consolas, monospace);
                                line-height: 1.15; font-size: 13px; }
.game-board .item.text { white-space: pre; overflow-x: auto;
                         font-family: var(--mono, ui-monospace, Consolas, monospace);
                         line-height: 1.15; font-size: 13px; }

/* one-tap move pad */
.game-pad { display: flex; flex-wrap: wrap; gap: 6px; }
.game-pad button { min-width: 108px; }

/* ---- 2048 (and any future tile grid): the playable board ------------------
   Tiles are dumb blocks; the applet only swaps text + a t<value> class, so the board is one
   CSS grid and a repaint never re-creates nodes (focus and the pop animation survive). */
.grid2048 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  width: 100%;
  max-width: 440px;
  aspect-ratio: 1 / 1;
  margin: 10px 0;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel-2);
  touch-action: none;      /* a swipe on the board must not scroll the page */
  user-select: none;
  outline: none;
}
.grid2048:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.grid2048.busy { opacity: .72; }         /* a move is in flight */
.grid2048.finished { opacity: .55; }     /* over: only Start/New do anything */
.grid2048 .tile {
  display: grid;
  place-items: center;
  border-radius: 8px;
  background: var(--code-bg);
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: clamp(15px, 5vw, 28px);
  transition: background-color .12s ease, color .12s ease;
}
@keyframes tile2048pop { from { transform: scale(.86); } to { transform: scale(1); } }
.grid2048 .tile.changed { animation: tile2048pop .18s ease-out; }

/* the classic palette (fixed colours on purpose: tiles are objects, not chrome) */
.grid2048 .t2 { background: #eee4da; color: #776e65; }
.grid2048 .t4 { background: #ede0c8; color: #776e65; }
.grid2048 .t8 { background: #f2b179; color: #f9f6f2; }
.grid2048 .t16 { background: #f59563; color: #f9f6f2; }
.grid2048 .t32 { background: #f67c5f; color: #f9f6f2; }
.grid2048 .t64 { background: #f65e3b; color: #f9f6f2; }
.grid2048 .t128 { background: #edcf72; color: #f9f6f2; }
.grid2048 .t256 { background: #edcc61; color: #f9f6f2; }
.grid2048 .t512 { background: #edc850; color: #f9f6f2; }
.grid2048 .t1024 { background: #edc53f; color: #f9f6f2; }
.grid2048 .t2048 { background: #edc22e; color: #f9f6f2; }
.grid2048 .tbig { background: #3c3a32; color: #f9f6f2; }

/* ---- chess board (the Chess applet) -------------------------------------
   The pieces are the cburnett-style PNGs the bot already ships for its Discord boards, kept in
   dashboard/pieces/ and served at /static/pieces/. Squares are plain divs, so a click/drag
   target is the square itself (the <img> is pointer-events:none). */
.chessboard {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  /* BOTH axes get 1fr: with only the columns sized, the rows are content-sized (the piece
     images' intrinsic height) and the squares come out rectangular however square the outer
     box is. 1fr rows + 1fr columns + aspect-ratio: 1 = 8x8 true squares. */
  grid-template-rows: repeat(8, 1fr);
  width: 100%;
  max-width: 480px;
  aspect-ratio: 1 / 1;
  margin: 8px 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  user-select: none;
  touch-action: manipulation;
}
.chessboard .sq { position: relative; display: grid; place-items: center; cursor: pointer;
                  min-width: 0; min-height: 0; }
.chessboard .sq > * { max-width: 100%; max-height: 100%; }
.chessboard .sq.light { background: #eeeed2; }
.chessboard .sq.dark { background: #769656; }
.chessboard .sq.last { box-shadow: inset 0 0 0 3px rgba(255, 214, 0, .6); }
.chessboard .sq.sel { box-shadow: inset 0 0 0 3px var(--accent); }
.chessboard .sq.check { box-shadow: inset 0 0 0 3px var(--danger); }
.chessboard .pc { width: 86%; height: 86%; object-fit: contain; pointer-events: none;
                 -webkit-user-drag: none; }
.chessboard .dot { width: 26%; height: 26%; border-radius: 50%; background: rgba(20, 20, 20, .3);
                  pointer-events: none; }
.chessboard .cap { position: absolute; inset: 5%; border: 4px solid rgba(20, 20, 20, .3);
                    border-radius: 50%; pointer-events: none; }
.chessboard.busy { opacity: .68; cursor: progress; }

.promo { display: flex; gap: 6px; align-items: center; margin: 4px 0; }
.promo-btn { padding: 3px; background: var(--panel-2); border: 1px solid var(--border);
             border-radius: 8px; cursor: pointer; }
.promo-btn img { width: 30px; height: 30px; display: block; }

/* eval bar: white fills from the left, black keeps the rest */
.eval-bar { height: 8px; border-radius: 999px; background: #3a3a3a; overflow: hidden;
            margin: 4px 0 10px; max-width: 480px; }
.eval-bar > span { display: block; height: 100%; width: 50%; background: #f2f2f2;
                   transition: width .25s ease; }
.chess-side { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start;
              justify-content: space-between; }
.chess-moves { display: flex; gap: 8px; flex-wrap: wrap; max-width: 340px;
               font-family: ui-monospace, Consolas, monospace; font-size: 12px; }
.chess-moves .mv { background: var(--panel-2); border: 1px solid var(--border);
                   border-radius: 6px; padding: 2px 6px; }

/* block blast cell grid */
.cell-grid { display: inline-grid; gap: 3px; margin-top: 8px; }
.cell-row { display: flex; gap: 3px; align-items: center; }
.cell-row-n, .cell-corner { width: 16px; text-align: center; font-size: 11px;
                            color: var(--muted); }
.cell-col { width: 26px; text-align: center; font-size: 11px; color: var(--muted); }
.cell-btn { width: 26px; height: 26px; padding: 0; font-size: 11px;
            border: 1px solid var(--border); border-radius: 4px;
            background: var(--panel-2); color: var(--muted); cursor: pointer; }
.cell-btn:hover { background: var(--panel); color: var(--text); }
/* farming: a clickable plot grid -------------------------------------------- */
.plot-grid { display: grid; gap: 6px; justify-content: start; }
.plot-row { display: flex; gap: 6px; }
.plot { width: 104px; height: 84px; display: grid; gap: 2px; align-content: center;
        justify-items: center; padding: 4px; border: 1px solid var(--border);
        border-radius: 8px; background: var(--panel-2); color: var(--text);
        cursor: pointer; }
.plot:hover { background: var(--panel); }
.plot.empty { border-style: dashed; color: var(--muted); }
.plot.ready { border-color: var(--accent); }
.plot.selected { outline: 2px solid var(--accent); outline-offset: 1px; }
.plot .plot-icon { font-size: 16px; line-height: 1; }
.plot .plot-name { font-size: 11px; font-weight: 600; overflow-wrap: anywhere;
                  text-align: center; }
.plot .plot-bar { font-family: var(--mono, ui-monospace, Consolas, monospace);
                 font-size: 10px; letter-spacing: -1px; color: var(--muted); }
.plot .plot-pct { font-size: 10px; color: var(--muted); }
.plot .plot-items { font-size: 10px; }

/* the body of the template you are filling in */
.tpl-preview { background: var(--code-bg); border: 1px solid var(--border);
               border-radius: 6px; padding: 8px 10px; margin: 0; white-space: pre-wrap;
               font-size: 12px; max-height: 180px; overflow: auto; }

/* sandboxed markup preview (Render applet): a data: URL frame, no scripts */
.preview-slot { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.preview-frame { display: block; width: 100%; height: 260px; border: 0;
                 background: #1b1b1f; }

/* sprite collection grid (Pokemon): images straight from a public CDN */
.sprite-grid { display: grid; gap: 8px;
               grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
.sprite-card { display: grid; gap: 2px; justify-items: center; padding: 6px 4px;
               border: 1px solid var(--border); border-radius: 8px;
               background: var(--panel-2); text-align: center; }
.sprite-card img { width: 64px; height: 64px; image-rendering: pixelated; }
.sprite-card .sprite-name { font-size: 11px; font-weight: 600; overflow-wrap: anywhere; }
.sprite-card .sprite-qty { font-size: 11px; color: var(--muted); }

/* sorting arcade (Bogo): a canvas the user drags bars on ---------------------- */
.sort-canvas { display: block; width: 100%; max-width: 760px; height: auto;
               background: var(--panel-2); border: 1px solid var(--border);
               border-radius: 8px; cursor: grab; touch-action: none;
               user-select: none; -webkit-user-select: none; }
.sort-canvas:active { cursor: grabbing; }
.sort-hud { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.sort-hud .sort-label { font-size: 10.5px; text-transform: uppercase;
                        letter-spacing: .04em; color: var(--muted); }
.sort-hud .sort-stat { font-family: var(--mono, ui-monospace, Consolas, monospace);
                       font-size: 15px; font-weight: 600; margin-right: 12px;
                       font-variant-numeric: tabular-nums; }

/* the "a cat/bird is here right now" banner */
.applet-banner { border-radius: 8px; padding: 10px 12px; font-weight: 600; }
.applet-banner.ready { background: rgba(90, 200, 120, 0.12);
                       border: 1px solid rgba(90, 200, 120, 0.45); }

.safety-form { max-width: 640px; display: grid; gap: 10px; }
.safety-form label { display: grid; gap: 4px; grid-template-columns: 1fr; }

.crumbs { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 12px; }
.crumbs .sep { color: var(--muted); }
.crumbs button { padding: 3px 8px; font-size: 13px; }
.crumbs button[disabled] { opacity: 1; color: var(--text); cursor: default; }
.help-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }
.help-card { display: grid; gap: 3px; text-align: left; background: var(--panel); }
.help-card .t { font-weight: 600; overflow-wrap: anywhere; }
.help-card .d { font-size: 12px; color: var(--muted); }

/* instances table on the overview */
.inst-avatar { width: 20px; height: 20px; border-radius: 50%; vertical-align: -5px; margin-right: 6px; }
.inst-act { font-size: 11px; color: var(--muted); }

/* overview -> you (GET /api/me: the signed-in user's profile + mutual servers) */.me-head { display: flex; gap: 14px; align-items: flex-start; background: var(--panel);
           border: 1px solid var(--border); border-radius: 10px; padding: 14px;
           box-shadow: var(--shadow); }
.me-avatar { width: 56px; height: 56px; border-radius: 50%; flex: 0 0 auto;
             object-fit: cover; background: var(--panel-2); }
.me-avatar-empty { display: grid; place-items: center; font-size: 22px; font-weight: 600;
                   color: var(--muted); }
.me-id { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.me-name { font-size: 18px; font-weight: 600; display: flex; gap: 6px;
           align-items: center; flex-wrap: wrap; }
.me-id code { font-size: 11.5px; }
.me-icon-empty { display: inline-grid; place-items: center; font-size: 11px;
                 font-weight: 600; color: var(--muted); background: var(--panel-2); }
/* the "12d 4h ago" line under a join date */
.me-sub { display: block; }
/* a role chip; --chip is set from JS (the role's colour) - never an inline style=,
   which the dashboard's CSP blocks */
.role-chip { display: inline-flex; align-items: center; font-size: 11px;
             padding: 1px 8px; border-radius: 999px; white-space: nowrap;
             border: 1px solid var(--border); background: var(--panel-2); }
.role-chip::before { content: ""; width: 7px; height: 7px; border-radius: 50%;
                     background: var(--chip, var(--muted)); margin-right: 5px;
                     flex: 0 0 auto; }

#sess-token code { display: block; overflow-wrap: anywhere; font-size: 12px;
                   margin-bottom: 6px; }

/* batches card */
.batch-item { background: var(--panel-2); border: 1px solid var(--border);
              border-radius: 8px; padding: 8px 10px; display: grid; gap: 4px; }
.batch-item + .batch-item { margin-top: 6px; }
.batch-item .n { font-weight: 600; }
.batch-item .d { font-size: 12px; color: var(--muted); }
.param-form { display: grid; gap: 8px; }
.param-form label { display: grid; gap: 3px; }

@media (max-width: 760px) {
  .shell { grid-template-columns: 1fr; }
  .side { position: static; height: auto; flex-direction: row; flex-wrap: wrap; border-right: 0; border-bottom: 1px solid var(--border); padding: 8px; }
  .brand { width: 100%; padding: 4px 8px 6px; }
  .side-foot { margin: 0; width: 100%; border-top: 0; display: flex; align-items: center; gap: 10px; padding: 6px 8px 0; }
  .content { padding: 16px; }
  .split { grid-template-columns: 1fr; }
  .list { max-height: 40vh; }
  .detail { position: static; }
}

/* ---------------------------------------------------------------------------
   public form page (/f/<id>, served by web_dashboard for form.html + form.js)

   Same palette as the dashboard (the `:root` vars above), so a shared link does
   not look like a different product. No inline style anywhere: the page runs under
   the dashboard's CSP (style-src 'self').
   --------------------------------------------------------------------------- */

.fp-body { min-height: 100vh; }
.fp-main { max-width: 720px; margin: 0 auto; padding: 24px 16px 60px; }

.fp-head { display: flex; gap: 14px; align-items: center; margin-bottom: 16px; }
.fp-logo { width: 42px; height: 42px; border-radius: 10px; flex: 0 0 auto; }
.fp-head h1 { font-size: 22px; margin: 0; }
.fp-head p { margin: 2px 0 0; }

.fp-card { padding: 18px; }

.fp-q { margin-bottom: 18px; }
.fp-q:last-child { margin-bottom: 0; }
.fp-label { display: block; font-weight: 600; margin-bottom: 4px; }
.fp-req { color: var(--danger); }
.fp-help { margin: 0 0 6px; }
.fp-input { width: 100%; }
.fp-input:focus-visible, .fp-choice input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.fp-choice { display: flex; align-items: center; gap: 8px; padding: 6px 0; cursor: pointer; }
.fp-choice input { accent-color: var(--accent); }

/* rating: real radios, drawn as a row of tiles (keyboard + screen readers work) */
.fp-rating { display: flex; flex-wrap: wrap; gap: 6px; }
.fp-rating-opt { display: inline-flex; position: relative; }
.fp-rating-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.fp-rating-opt span { display: inline-grid; place-items: center; min-width: 40px; height: 38px;
                      border: 1px solid var(--border); border-radius: 8px; background: var(--panel);
                      cursor: pointer; font-variant-numeric: tabular-nums; }
.fp-rating-opt input:checked + span { border-color: var(--accent); color: var(--accent);
                                      box-shadow: inset 0 0 0 1px var(--accent); }
.fp-rating-opt input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

.fp-nav { display: flex; align-items: center; gap: 10px; margin-top: 18px; }
/* whichever of Next/Submit is visible sits on the right */
.fp-nav .primary { margin-left: auto; }

.fp-error { white-space: pre-line; color: var(--danger); margin: 12px 0 0; font-size: 13px; }
.fp-banner { padding: 10px 12px; border-radius: 8px; background: var(--panel-2);
             border: 1px solid var(--border); margin-bottom: 14px; white-space: pre-line;
             font-size: 13px; }
.fp-banner.warn { border-color: var(--warn); color: var(--warn); }
.fp-banner.err { border-color: var(--danger); color: var(--danger); }

/* a section header inside a form (h3 is uppercased globally for view headings) */
.fp-section { text-transform: none; letter-spacing: 0; font-size: 14px; font-weight: 600;
              margin: 0 0 10px; padding-top: 10px; color: var(--text);
              border-top: 1px solid var(--border); }
.fp-q:first-child .fp-section { padding-top: 0; border-top: 0; }

.fp-foot { text-align: center; margin-top: 20px; }

/* the honeypot: parked off-screen rather than display:none, because bots that skip
   hidden inputs happily fill this one - and a filled one is exactly what we flag */
.fp-hp { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px;
         overflow: hidden; }

/* ---------------------------------------------------------------------------
   Discord tab (chat_view.js): a tiny Discord client for the owner.

   Three columns - pickers | messages + composer | tools - collapsing to two
   below 1100px and to one below 820px. Everything is drawn from the theme vars,
   so it follows light/dark with the rest of the dashboard. Rows are <button>s
   on purpose: they are keyboard reachable and need no ARIA wiring.
   --------------------------------------------------------------------------- */

.chat-shell {
  display: grid;
  grid-template-columns: minmax(190px, 250px) minmax(0, 1fr) minmax(190px, 260px);
  gap: 12px;
  align-items: start;
}
@media (max-width: 1100px) {
  .chat-shell { grid-template-columns: minmax(180px, 220px) minmax(0, 1fr); }
  .chat-tools { grid-column: 1 / -1; }
}
@media (max-width: 820px) {
  .chat-shell { grid-template-columns: 1fr; }
}

.chat-side { display: grid; gap: 8px; align-content: start; min-width: 0; }
.chat-select, .chat-filter { width: 100%; }
.chat-side label { display: grid; gap: 3px; }

/* pickers */
.chat-list { display: grid; gap: 2px; max-height: 62vh; overflow: auto; padding-right: 2px; }
.chat-cat { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
            color: var(--muted); margin: 10px 0 2px; }
.chat-sel-tag { font-size: 12px; font-weight: 600; overflow-wrap: anywhere; margin-bottom: 4px; }
.chat-row { display: grid; gap: 1px; width: 100%; text-align: left; font: inherit;
            color: var(--text); background: transparent; border: 1px solid transparent;
            border-radius: 7px; padding: 5px 8px; cursor: pointer; }
.chat-row:hover:not(:disabled) { background: var(--panel-2); border-color: var(--border); }
.chat-row.active { background: var(--panel-2); border-color: var(--accent); }
.chat-row-name { font-size: 13px; overflow-wrap: anywhere; }
.chat-row-sub { font-size: 11px; color: var(--muted); overflow-wrap: anywhere; }

/* messages + composer */
.chat-main { display: grid; grid-template-rows: auto minmax(220px, 58vh) auto;
             gap: 10px; min-width: 0; }
.chat-head { display: flex; gap: 10px; justify-content: space-between;
             align-items: flex-start; border-bottom: 1px solid var(--border);
             padding-bottom: 8px; }
.chat-head-main { min-width: 0; }
.chat-topic { overflow-wrap: anywhere; }
.chat-msgs { display: grid; gap: 10px; align-content: start; overflow-y: auto; padding-right: 4px; }
.chat-msg { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 8px; }
.chat-msg.mine .chat-author { color: var(--accent); }
.chat-avatar { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; }
.chat-avatar-empty { display: grid; place-items: center; background: var(--panel-2);
                     border: 1px solid var(--border); font-size: 12px; font-weight: 600;
                     color: var(--muted); }
.chat-body { min-width: 0; }
.chat-meta { display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; }
.chat-author { font-weight: 600; font-size: 13px; }
.chat-badge { font-size: 9.5px; letter-spacing: .05em; padding: 1px 5px; border-radius: 4px;
              background: var(--accent); color: var(--accent-text); }
.chat-when { font-size: 11px; color: var(--muted); }
.chat-reply { font-size: 11.5px; color: var(--muted); border-left: 2px solid var(--border);
              padding-left: 6px; }
.chat-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-media { display: grid; gap: 6px; }
.chat-img { max-width: 100%; max-height: 320px; border-radius: 8px; }
.chat-file { font-size: 12.5px; overflow-wrap: anywhere; }
.chat-reactions { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 2px; }
.chat-chip { font-size: 12px; padding: 1px 7px; border-radius: 999px;
             border: 1px solid var(--border); background: var(--panel-2);
             color: var(--text); cursor: pointer; }
.chat-chip.me { border-color: var(--accent); color: var(--accent); }

/* per-message actions: quiet until the row is hovered/focused */
.chat-actions { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 3px;
                opacity: .35; transition: opacity .12s; }
.chat-msg:hover .chat-actions, .chat-msg:focus-within .chat-actions { opacity: 1; }
.chat-act { font-size: 10.5px; padding: 1px 6px; border-radius: 5px;
            border: 1px solid var(--border); background: transparent;
            color: var(--muted); cursor: pointer; }
.chat-act:hover:not(:disabled) { color: var(--text); border-color: var(--muted); }
.chat-act.danger { color: var(--danger); border-color: var(--danger); }

.chat-composer { display: grid; gap: 6px; border-top: 1px solid var(--border); padding-top: 8px; }
.chat-composer-bar { font-size: 12px; }
.chat-input { width: 100%; min-height: 62px; resize: vertical; font: inherit; }
.chat-reply-chip { display: flex; gap: 8px; align-items: center; justify-content: space-between;
                   font-size: 11.5px; color: var(--muted); border: 1px dashed var(--border);
                   border-radius: 7px; padding: 3px 8px; }

/* tools */
.chat-tools { display: grid; gap: 10px; align-content: start; }
.chat-tool { border: 1px solid var(--border); border-radius: 10px; padding: 10px;
             background: var(--panel); }
.chat-tool summary { cursor: pointer; font-size: 12.5px; font-weight: 600; margin-bottom: 6px; }
.chat-tool .row { margin-bottom: 4px; }
.chat-user-out { display: grid; gap: 6px; }
.chat-user-card { display: grid; gap: 3px; padding: 10px; }
.chat-user-name { font-weight: 600; overflow-wrap: anywhere; }
.chat-roles { display: flex; gap: 4px; flex-wrap: wrap; }
.chat-role { font-size: 11px; padding: 1px 7px; border-radius: 999px;
             border: 1px solid var(--border); color: var(--muted); }
.warn-text { color: var(--warn); }

/* mentions, custom emoji and the `@`/`:` completion popup (chat_view.js).
   Custom emoji are CDN images sized in em, so they sit on the text baseline the
   way Discord's do; mention pills are plain spans (no innerHTML anywhere). */
.chat-emoji { height: 1.375em; width: auto; vertical-align: -0.3em; margin: 0 1px; }
.chat-mention { background: var(--panel-2); color: var(--accent); font-weight: 600;
                border-radius: 5px; padding: 0 4px; }
.chat-mention.role { background: transparent; color: var(--text);
                     border: 1px solid var(--border); }
.chat-channel-ref { background: var(--panel-2); color: var(--muted);
                    border-radius: 5px; padding: 0 4px; }
.chat-chip-n { font-size: 11.5px; margin-left: 3px; }

/* the listbox hangs off the textarea, so the wrapper owns the positioning */
.chat-input-wrap { position: relative; }
.chat-ac { position: absolute; left: 0; right: 0; bottom: calc(100% + 4px); z-index: 20;
           display: grid; gap: 1px; max-height: 240px; overflow-y: auto; padding: 4px;
           background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
           box-shadow: 0 8px 24px rgba(0, 0, 0, .25); }
.chat-ac[hidden] { display: none; }
.chat-ac-item { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; gap: 8px;
                align-items: center; text-align: left; font: inherit; color: var(--text);
                background: transparent; border: 1px solid transparent; border-radius: 7px;
                padding: 3px 6px; cursor: pointer; }
.chat-ac-item:hover { background: var(--panel-2); border-color: var(--border); }
.chat-ac-item.active { background: var(--panel-2); border-color: var(--accent); }
.chat-ac-item .chat-avatar { width: 22px; height: 22px; font-size: 11px; }
.chat-ac-label { font-size: 12.5px; white-space: nowrap; overflow: hidden;
                 text-overflow: ellipsis; }
.chat-ac-sub { font-size: 10.5px; color: var(--muted); }
.chat-ac-char { font-size: 16px; text-align: center; }

/* forum posts (a forum's posts ARE threads) + thread state badges */
.chat-post { display: grid; gap: 3px; text-align: left; font: inherit; color: var(--text);
             background: var(--panel-2); border: 1px solid var(--border);
             border-radius: 10px; padding: 8px 10px; cursor: pointer; }
.chat-post:hover:not(:disabled) { border-color: var(--muted); }
.chat-post.archived { opacity: .65; }
.chat-post-head { display: flex; gap: 8px; align-items: baseline; justify-content: space-between; }
.chat-post-title { font-size: 13.5px; overflow-wrap: anywhere; }
.chat-post-meta { font-size: 11px; color: var(--muted); }
.chat-post-preview { font-size: 12.5px; color: var(--muted); white-space: pre-wrap;
                     overflow-wrap: anywhere; }

/* forwarded messages: the original is shipped inline as a snapshot, so the card
   is drawn from that copy (the source channel may be unreadable or the original
   deleted). A left rule + label, like Discord's. */
.chat-forward { border-left: 3px solid var(--border); border-radius: 0 8px 8px 0;
                background: var(--panel-2); padding: 6px 9px; display: grid; gap: 4px;
                margin-top: 2px; }
.chat-forward-head { font-size: 11px; font-weight: 600; color: var(--muted);
                     letter-spacing: .02em; }
.chat-forward-body { font-size: 13px; white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-forward-when { font-size: 10.5px; color: var(--muted); }
.chat-forward .chat-img { max-height: 240px; }

/* typing indicators: the incoming line under the channel name, and the
   user-id completion listbox that drops DOWN inside the tools panel */
.chat-typing { font-size: 11.5px; color: var(--muted); font-style: italic;
               min-height: 0; }
.chat-ac-inline { top: calc(100% + 2px); bottom: auto; }
