: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 */
.login { min-height: 100vh; display: grid; place-items: center; padding: 16px; }
.login-card { width: 100%; max-width: 400px; display: grid; gap: 14px; }
.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; }
