/* The Discord panel, the stylesheet.

   One stylesheet for every brand; class prefix sx-.

   Read tokens.css first. Nothing in this file picks a colour, a size or a radius;
   it only arranges them. */

/* tokens.css is linked from base.html, ahead of this file, rather than imported
   here. An @import cannot carry the build stamp that busts a browser's cache, so
   importing it meant a deploy could change the palette and nobody would see it
   until they cleared their cache by hand. */

*, *::before, *::after { box-sizing: border-box; touch-action: manipulation; }

/* Any class that sets display beats the hidden attribute on specificity alone,
   which is how the unread badge sat there reading 0. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--text-body);
  font: var(--type-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--text-link); text-decoration: none; transition: var(--tr-color); }
a:hover { color: var(--text-link-hover); }

a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: var(--focus);
  outline-offset: var(--focus-offset);
  border-radius: var(--radius-sm);
}

/* Case and tracking are the brand's (tokens.css); these are the fallback. */
h1 { font: var(--type-title); letter-spacing: var(--h1-tracking, 0.02em); text-transform: var(--h1-case, uppercase); margin: 0; color: var(--fg-1); }
h2 { font: var(--type-section); margin: 0; color: var(--fg-1); }
h3 { font: var(--type-card-title); margin: 0; color: var(--fg-1); }
p { margin: 0; text-wrap: pretty; }

table { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }
th { text-align: left; }
code, kbd, samp { font: var(--type-data-sm); color: var(--fg-2); }

::placeholder { color: var(--text-faint); opacity: 1; }
::selection { background: var(--accent-tint); }

::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-thumb { background: var(--c-250); border-radius: var(--radius-pill); border: 3px solid var(--surface-page); }
::-webkit-scrollbar-track { background: transparent; }

.sx-mono, .num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.sx-serif { font-family: var(--font-serif); }

/* --- icons -------------------------------------------------------------------
   Every symbol is an SVG from icon() in app/web.py, never a typed character:
   the webfonts leave arrows, crosses and chevrons out, so each machine drew
   them in its own symbol font (Sigve, 27 September 2026). Beside text an icon
   has a line box of its own, .ico-line, sharing the text's line: there middle
   is the baseline plus half the x-height of the font in use, and 0.5cap the
   middle of the capitals, so the icon sits on the letters whatever the font.
   No number per font. --ico is the icon's size; a dot or a mark beside text
   takes the same line box. The negative margins keep the icon out of the
   height of that line box, so an icon taller than a line set solid
   (line-height 1, as in the buttons) cannot push the box, and with it the
   text's baseline, off centre in a flex row; with them the capitals' rule
   measures from the icon's middle. */
.ico { --ico: 1em; display: inline-block; width: var(--ico); height: var(--ico); flex: none; vertical-align: -0.125em; }
.ico-line { display: inline-block; flex: none; }
.ico-line > .ico { margin-block: calc(var(--ico) / -2); }
.ico-line > .ico-text { vertical-align: middle; }
.ico-line > .ico-caps { vertical-align: 0.5cap; }

/* --- the rail ---------------------------------------------------------------
   Eleven destinations plus the account in one horizontal row is a row nobody
   reads. They live in a fixed left rail instead, grouped under quiet headings,
   with the machine's state, the theme and who you are pinned to the bottom. */

/* --- which Discord this is --------------------------------------------------
   Two deploys serve this panel, one per server, and they are otherwise
   identical. Amber, meaning what amber means everywhere else here: notice this.
   The strip stays at the top while the page scrolls, and the rail sits under
   it at exactly the height that is left. In normal flow it pushed a full-height
   rail 33px past the window at the top of every page, and the last row, who
   you are and Log out, could not be scrolled into view (Sigve, 27 September
   2026). Opaque, because the page scrolls underneath it. */

body:has(.sx-instance) { --instance-h: 33px; }
.sx-instance {
  position: sticky; top: 0; z-index: 20;
  height: var(--instance-h);
  display: grid;
  place-items: center;
  background: linear-gradient(var(--attend-tint), var(--attend-tint)), var(--surface-page);
  color: var(--status-attend);
  border-bottom: var(--rule-soft);
  font: var(--type-state);
}
body:has(.sx-instance) .sx-shell,
body:has(.sx-instance) .centre { min-height: calc(100dvh - var(--instance-h)); }
@media (min-width: 1081px) {
  body:has(.sx-instance) .sx-rail { top: var(--instance-h); height: calc(100dvh - var(--instance-h)); }
}

.sx-shell { display: flex; min-height: 100dvh; }

/* --- between pages ----------------------------------------------------------
   A click in the rail loads the next page, and the browser draws it as one
   view: the page crossfades instead of blanking, and the rail and the strip,
   the same on both sides, hold still (Sigve, 27 September 2026: "the whole
   page reloads... super annoying"). Chrome, Edge and Safari; any other browser
   loads the page as before. base.html keeps the rail's scroll. */
@view-transition { navigation: auto; }
.sx-rail { view-transition-name: rail; }
.sx-instance { view-transition-name: strip; }
::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation-duration: 150ms; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none; }
}

.sx-rail {
  width: var(--rail-w);
  flex: none;
  background: var(--rail-bg);
  color: var(--rail-fg);
  padding: var(--sp-9) var(--sp-7) var(--sp-7);
  display: flex;
  flex-direction: column;
  gap: var(--sp-9);
  position: sticky;
  top: 0;
  height: 100dvh;
  overflow-y: auto;
}

.sx-brand {
  font: var(--fw-medium) var(--fs-lead) / var(--lh-tight) var(--font-serif);
  color: var(--rail-fg);
  letter-spacing: var(--tr-title);
  padding-inline: var(--sp-5);
}
.sx-brand span { color: var(--rail-fg-muted); }
.sx-brand:hover { color: var(--rail-fg); }
.sx-rail::-webkit-scrollbar-thumb { background: var(--rail-rule); border-color: var(--rail-bg); }

/* --- a list that drops under a field ----------------------------------------
   The member picker (templates/_member_pick.html). It floats, so nothing under
   it jumps while results come and go; each row is a member as Discord shows
   one: the picture, the name in the server, the username. */
.sx-member-pick { position: relative; display: block; }
.sx-member-pick input[type="text"] { width: 100%; }
.sx-member-pick .sx-member-face {
  position: absolute; left: var(--sp-5); bottom: calc((var(--ctl) - 26px) / 2);
  pointer-events: none;
}
.sx-member-pick:not(.chosen) .sx-member-face { display: none; }
.sx-member-pick.chosen input { padding-left: calc(26px + 2 * var(--sp-5)); }
.sx-drop {
  display: block;
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 40;
  background: var(--surface-raised);
  border: var(--edge-control);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-float);
  max-height: 60vh; overflow-y: auto;
  margin: 0; padding: var(--sp-3) 0; list-style: none;
}
.sx-drop-hit {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5);
  font: var(--type-meta); color: var(--fg-2); cursor: pointer;
}
.sx-drop-hit:hover, .sx-drop-hit[aria-selected="true"] { background: var(--surface-page); color: var(--fg-1); }
.sx-drop-hit strong { color: var(--fg-1); font-weight: var(--fw-medium, 500); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sx-drop-hit .dim { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sx-drop-empty { display: block; padding: var(--sp-4) var(--sp-5); font: var(--type-meta); color: var(--fg-3); }

.sx-nav { display: flex; flex-direction: column; gap: var(--sp-1); }
/* A line of text with its icon in it, not a flex row: the icon stands on the
   capitals of this very line (0.5cap from its own middle), for any font. */
.sx-navhead {
  font: var(--type-navhead, var(--fw-medium) var(--fs-micro) / var(--lh-flat) var(--font-mono));
  text-transform: uppercase;
  letter-spacing: var(--navhead-tracking, 0.08em);
  color: var(--rail-head);
  padding: var(--sp-7) var(--sp-5) var(--sp-4);
}
/* The brand's own section carries the org's mark instead of a drawn icon
   (brand.toml nav_mark, set inline as --nav-mark). The mark is a shape, not a
   stroke, so it is a mask filled with the heading's own colour: one file,
   every theme, no second colour to keep in step. */
.sx-navmark {
  display: inline-block;
  width: 13px;
  height: 13px;
  background: currentColor;
  -webkit-mask: var(--nav-mark) center / contain no-repeat;
  mask: var(--nav-mark) center / contain no-repeat;
}

.sx-nav-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  font: var(--type-meta);
  color: var(--rail-fg-muted);
  padding: var(--sp-5) var(--sp-5);
  border-radius: var(--radius-sm);
  transition: var(--tr-color);
}
.sx-nav-link:hover { color: var(--rail-fg); background: var(--rail-hover); }
.sx-nav-link[aria-current="page"] { color: var(--rail-fg); background: var(--rail-active); }

/* What's new at the foot of the rail, a link like the rest; the count is the
   entries this account has not opened, in the brand's accent. */
.sx-news { align-self: stretch; }
.sx-news-n {
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--radius-pill);
  background: var(--accent); color: #fff; font: var(--type-data-sm);
  display: inline-grid; place-items: center;
}

/* What's new reads like a blog: posts one after another in a centred
   reading column, the page's title and lede centred with them, a rule
   between posts, the brand's accent on the area and the request. */
.news { max-width: 720px; margin: 0 auto; }
main:has(.news) .sx-pagehead { max-width: 720px; margin-left: auto; margin-right: auto; }
.post { padding: var(--stack-lg) 0; }
.post + .post { border-top: var(--rule); }
.post:first-child { padding-top: 0; }
.post-meta { font: var(--type-meta); color: var(--text-faint); }
.post-area { color: var(--accent); font-weight: var(--fw-medium); }
.post-title { margin: var(--sp-4) 0 var(--sp-5); font-size: 1.75rem; }
.post-title a { color: inherit; text-decoration: none; }
.post-title a:hover { text-decoration: underline; }
.post-intro { font: var(--type-lede); color: var(--text-prose); margin: 0 0 var(--sp-8); }
.post-media { display: block; width: 100%; aspect-ratio: 16 / 9; background: #111214; border-radius: var(--radius); object-fit: cover; cursor: zoom-in; }
.post-text { margin: var(--sp-8) 0 var(--sp-7); color: var(--text-body); }
.post-link { font: var(--type-label); color: var(--accent); text-decoration: none; }
.post-link:hover { text-decoration: underline; }
/* A shared What's new link without a session: the post as a teaser, alone
   in the reading column, and the way to sign in. */
.post-preview { max-width: 720px; margin: 0 auto; padding: var(--stack-lg) var(--page-gutter); }
.post-preview .sx-mark { display: block; margin-bottom: var(--stack); }
.post-preview .post-media { margin-bottom: var(--sp-8); }
.post-list { list-style: none; padding: 0; margin: 0 0 var(--sp-8); display: grid; gap: var(--sp-5); }
.post-list a { font: var(--type-card-title); color: var(--fg-1); }

/* Pinned to the bottom of the rail, so the destinations never have to share a
   line with the account. */
.sx-railfoot {
  margin-top: auto;
  padding-top: var(--sp-7);
  border-top: var(--border) solid var(--rail-rule);
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  align-items: flex-start;
}
.sx-who { display: flex; align-items: center; gap: var(--sp-5); width: 100%; }
.sx-handle { font: var(--type-meta); color: var(--rail-fg); overflow: hidden; text-overflow: ellipsis; text-decoration: none; }
/* The name opens your own profile: the name and role your answers carry. */
a.sx-handle:hover, a.sx-handle[aria-current] { color: var(--rail-fg); text-decoration: underline; }
.sx-signout { font: var(--type-meta); color: var(--rail-fg-faint); margin-left: auto; }
.sx-signout:hover { color: var(--rail-fg); }
.sx-avatar {
  width: 26px; height: 26px; flex: none;
  border-radius: var(--radius-pill);
  background: var(--rail-hover);
  overflow: hidden;
}
.sx-avatar img { width: 100%; height: 100%; display: block; object-fit: cover; }

.sx-main { flex: 1; min-width: 0; }

/* Only below the breakpoint, where the rail slides over the page. */
.sx-railtoggle { display: none; }
.sx-scrim {
  position: fixed; inset: 0; z-index: 25;
  background: rgba(0, 0, 0, 0.5);
}

/* --- connection status -----------------------------------------------------
   The machine's state, in the bar, on every page. The dot breathes while a sync
   is in flight and is the only animation in the system. */

.sx-conn { display: inline-flex; align-items: center; gap: var(--sp-4); font: var(--type-state); color: var(--rail-fg-muted); white-space: nowrap; }
.sx-dot { display: inline-block; vertical-align: middle; margin-block: -4px; width: 8px; height: 8px; border-radius: var(--radius-pill); background: var(--status-off); flex: none; }
.sx-dot[data-state="connected"] { background: var(--status-live); }
.sx-dot[data-state="unreachable"] { background: var(--status-halt); }
.sx-dot[data-state="working"] { background: var(--status-attend); animation: sx-breathe 1.6s ease-in-out infinite; }
@keyframes sx-breathe { 0%, 100% { opacity: 1 } 50% { opacity: 0.35 } }

/* --- theme toggle ---------------------------------------------------------- */

.sx-themetoggle {
  height: var(--ctl-sm);
  padding: 0 var(--sp-6);
  border-radius: var(--radius-btn);
  background: transparent;
  border: var(--border) solid var(--rail-rule);
  cursor: pointer;
  color: var(--rail-fg);
  font: var(--type-state);
  transition: var(--tr-color);
}
.sx-themetoggle:hover { background: var(--rail-hover); color: var(--rail-fg); }
/* One half at a time, decided by the theme itself so there is no flash and
   nothing for JS to keep in step. */
.sx-themetoggle > span { display: none; align-items: center; gap: var(--sp-4); }
.sx-themetoggle .ico { --ico: 15px; }
.sx-themetoggle .to-light { display: inline-flex; }
[data-theme="light"] .sx-themetoggle .to-light { display: none; }
[data-theme="light"] .sx-themetoggle .to-dark { display: inline-flex; }

/* --- page ------------------------------------------------------------------ */

main {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--stack-lg) var(--page-gutter) var(--sp-13);
}
footer { padding: var(--stack-lg) 0 0; margin: 0; max-width: none; }

/* Title and lede are one block, so the gap under the title never depends on
   whether the page happens to have a lede. */
.sx-pagehead { margin-bottom: var(--stack); }
.sx-pagehead .lede {
  font: var(--type-lede);
  color: var(--text-prose);
  max-width: 68ch;
  margin-top: var(--sp-6);
}

h2 { margin-top: var(--stack-lg); margin-bottom: var(--sp-7); }
.sx-pagehead + h2, h1 + h2 { margin-top: 0; }

/* --- panels ----------------------------------------------------------------
   A card is a surface lifted off a darker page by tone alone. No outline, no
   drop shadow, no coloured edge. If two things need separating, use space. */

.panel {
  background: var(--surface-panel);
  border-radius: var(--radius);
  padding: var(--sp-9);
  margin-bottom: var(--stack);
}
.panel.scroll { padding: var(--sp-6) var(--sp-5); }
/* Not a field grid: its negative margin is what takes its last row's space
   back. :where() keeps the old weight, so form > .actions:last-child still wins. */
.panel > :last-child:where(:not(.fields)) { margin-bottom: 0; }

/* --- notices ---------------------------------------------------------------
   Documentation, not alerts. They never dismiss and never animate. */

.panel.note {
  background: var(--surface-raised);
  font: var(--type-lede);
  color: var(--text-prose);
  padding: var(--sp-8) var(--sp-9);
}
.panel.note strong { color: var(--fg-1); font-weight: var(--fw-medium); }
.panel.note p + p { margin-top: var(--sp-6); }
.panel.note[data-tone="attend"] strong { color: var(--status-attend); }
.panel.note[data-tone="halt"] strong { color: var(--status-halt); }

/* --- stat cards ------------------------------------------------------------
   Serif figures. A zero is never tinted: an empty queue is good news. */

.cards {
  display: grid;
  /* Four across: the dashboard's eight tiles make two full rows and the
     support page's four make one, and nothing wraps as a lone orphan. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-6);
  margin-bottom: var(--stack);
}
@media (max-width: 700px) {
  .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.card {
  background: var(--surface-panel);
  border-radius: var(--radius);
  padding: var(--sp-8);
  display: block;
}
/* The stat tiles are doors now; the lift on hover is the whole affordance,
   because a coloured number must keep meaning state, not link. */
a.card { transition: var(--tr-color); }
a.card:hover { background: var(--surface-raised); }
.card .n {
  font: var(--type-figure);
  letter-spacing: var(--tr-figure);
  color: var(--fg-1);
  font-variant-numeric: tabular-nums;
}
.card .n.warn { color: var(--status-attend); }
.card .l { font: var(--type-meta); color: var(--text-label); margin-top: var(--sp-5); }

/* --- tables ----------------------------------------------------------------
   Rules exist inside tables and nowhere else. The whole row warms on hover, with
   the end cells rounded so the highlight is a soft block, not a full-bleed band. */

/* Capped, because an uncapped table pours down the page and reads as an
   endless feed; Sigve asked four times before this line existed. The data
   rows are capped server-side too; this is the visual ceiling for whatever
   still renders. */
.scroll { overflow-x: auto; max-height: 60vh; overflow-y: auto; }
/* A table wider than a phone scrolls inside its panel. Without this the
   widest table set the whole page's width, /channels at 968 px on a 390 px
   screen. */
.sx-table-wrap { overflow-x: auto; }
.sx-table-wrap, .scroll { scrollbar-width: thin; }
/* The answer on a dashboard row (somebody waiting for panel access): the
   buttons side by side at the right, stacked on a phone rather than scrolled
   out of sight, so this cell opts out of the phone's one-line cells below. */
td.sx-answer { text-align: right; white-space: normal; }
.sx-answer form { margin: 2px 0 2px var(--sp-3); }

/* Not sticky. Tables sit inside an overflow-x wrapper, and a sticky header
   positions against its scroll container rather than the viewport, which parked
   the header row in the middle of the table. */
/* Headers and sum rows may break onto two lines, and a long name in a cell
   may break mid-word. Table layout still hands every column its one-line
   width when the panel has room; these only decide what gives when it does
   not, which used to be the panel's edge: /channels 16 px too wide at full
   width, 250 px at 1100. Channel names keep td.nowrap. */
thead th {
  font: var(--type-colhead-panel, var(--fw-medium) var(--fs-micro) / var(--lh-flat) var(--font-mono));
  text-transform: var(--colhead-case, uppercase);
  letter-spacing: var(--colhead-tracking, 0.06em);
  color: var(--text-label);
  padding: var(--sp-5) var(--cell-pad-x);
  border-bottom: var(--rule);
  background: var(--surface-panel);
}
/* Only a bare name cell may break mid-word: a member called one 30-letter
   word held /members 35 px past its panel. Letting every cell break did the
   same job and let the table squeeze ids and dates into three lines where it
   had room. A person with their picture (who()) and a channel (chan()) stay
   whole: broken mid-word the dashboard read "RUS/H" and "#💬/heroic-cs"
   (25 September 2026), and a table too narrow for them scrolls in its panel. */
td.name { overflow-wrap: anywhere; }
.who, .chan { white-space: nowrap; }
th.num, td.num, td.id, .id { white-space: nowrap; }
@media (max-width: 1280px) {
  :root { --cell-pad-x: var(--sp-5); }
}
tbody td {
  font: var(--type-cell);
  color: var(--text-body);
  padding: var(--sp-6) var(--cell-pad-x);
  height: var(--row);
  border-bottom: var(--rule-soft);
  vertical-align: middle;
  transition: var(--tr-color);
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover > td { background: var(--surface-row-hover); }
tbody tr:hover > td:first-child { border-start-start-radius: var(--radius-md); border-end-start-radius: var(--radius-md); }
tbody tr:hover > td:last-child { border-start-end-radius: var(--radius-md); border-end-end-radius: var(--radius-md); }

/* A configuration table carries four numeric inputs and a description, which at
   full cell padding pushes the last columns out of the panel. Inside a table both
   tighten; outside one they keep the roomy defaults. */
.scroll td, .scroll th { padding-inline: var(--cell-pad-x-tight); }
td input[type="number"] { width: 84px; }
td input[type="text"], td select { width: 100%; min-width: 150px; }
td .dim, td .muted { display: block; max-width: 42ch; }

td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.num { font: var(--type-data); color: var(--fg-1); }
td.muted, .muted { color: var(--text-label); }
td.dim, .dim { color: var(--text-faint); font: var(--type-meta); }
td.dim { font: var(--type-data-sm); }
/* A cell holding a list of ids or a reason wraps inside the card; without
   this an audit row ran past the panel's edge. */
td.wrap { max-width: 42ch; overflow-wrap: anywhere; white-space: normal; }
/* The dashboard's lists sit two to a row, where a subject breaks between words;
   letting it break anywhere turned "Turnering" into "Turnerin g". */
.scroll td.wrap { overflow-wrap: normal; }
/* A table with many columns keeps 10 px beside each cell, never more than the
   page's own: at full padding the mod log's six columns left the note 90 px,
   "+Givea/ways" down 37 lines, and /channels ran 32 px past its panel (Sigve,
   27 September 2026). A note takes what the other columns leave and never less
   than 12ch; only a token longer than the column itself breaks inside, like a
   deleted message's "in⁠#channel" glued by a word joiner. */
table.dense td, table.dense th { padding-inline: min(var(--cell-pad-x), var(--sp-5)); }
td.note { width: 100%; max-width: none; min-width: 12ch; white-space: normal; overflow-wrap: anywhere; }
/* A long deleted message on the mod log: three lines, the rest on a click. */
details.said > summary {
  list-style: none; cursor: pointer;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
details.said > summary::-webkit-details-marker { display: none; }
details.said[open] > summary { display: block; }
/* An edited message: what it said before, then after, each on its own line
   behind its label, and the link back to the message under them. */
.sx-edit { display: flex; gap: var(--sp-3); align-items: baseline; min-width: 0; }
.sx-edit + .sx-edit { margin-top: var(--sp-2); }
.sx-edit > .says, .sx-edit > details { min-width: 0; }
.sx-edit-label { flex: none; min-width: 5.5ch; font: var(--type-meta); color: var(--text-faint); }
.sx-edit-link { display: inline-block; margin-top: var(--sp-2); font: var(--type-meta); }

/* --- tags ------------------------------------------------------------------
   State is said in words, in the colour of those words. Pills, never stripes. */

/* Every tag is a chip, the plain one included: a role, a channel or an emoji
   reads as a pill on the raised surface, and a state takes its own colour. */
.tag {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  font: var(--type-tag);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius-pill);
  white-space: nowrap;
  background: var(--surface-raised); color: var(--text-body);
}
.tag.ok   { background: var(--live-tint);   color: var(--status-live); }
.tag.wait, .tag.attend { background: var(--attend-tint); color: var(--status-attend); }
.tag.off  { background: var(--surface-raised); color: var(--status-off); }
.tag.halt { background: var(--halt-tint);   color: var(--status-halt); }
.sx-rail .tag.wait { background: var(--sx-yellow); color: var(--sx-purple-deep); }

/* Keyword shortcuts on the search page. A tag somebody can press, so it takes
   the pill's shape and the accent when it is the one in force. */
.chips { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin: var(--sp-6) 0 var(--stack); }
a.tag {
  background: var(--surface-raised); color: var(--text-label);
  text-decoration: none; transition: var(--tr-color);
}
a.tag:hover { color: var(--text-title); }
/* The x on a removable chip: quiet until it is pointed at, then the halt red,
   because it deletes. It is a button, so it sheds every button rule, the
   42 px height and the red hover fill included (Sigve, 25 September 2026:
   the x drew as a tall red bar beside the role). The x is an 11 px icon,
   the ink of the character it replaces, so a chip keeps its width and a row
   of chips wraps where it did. */
button.tag-x, td button.tag-x {
  height: auto; padding: 0 0 0 var(--sp-1); background: none; border: 0;
  color: var(--fg-3); cursor: pointer;
  vertical-align: middle;   /* in its .ico-line: on the middle of the chip's letters */
  margin-block: -5.5px;     /* half its height: out of the line box's height, as an icon */
}
button.tag-x .ico { --ico: 11px; }
button.tag-x:hover { background: none; color: var(--status-halt); }
/* A chip picker (templates/_pick.html): the shared menu button covers the
   chip, while the plus, label and chevron keep the chip's familiar look. */
.sx-pick .sx-pick-addbox {
  position: relative;
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font: var(--type-tag); color: var(--text-link);
  padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-5);
  border: var(--border) solid transparent;   /* the select's own box: same height as before */
  border-radius: var(--radius-pill);
  background: var(--surface-raised);
  white-space: nowrap;
}
/* Plus and chevron as large as the character and the arrow they replace, so
   the chip keeps its width. */
.sx-pick .sx-pick-addbox .ico-line:first-child .ico { --ico: 11px; }
.sx-pick .sx-pick-addbox .ico-line:last-of-type { color: var(--fg-3); margin-left: var(--sp-3); }
.sx-pick .sx-pick-addbox .ico-line:last-of-type .ico { --ico: 16px; }
.sx-pick .sx-pick-addbox:hover, .sx-pick .sx-pick-addbox:has(.sx-select-button:focus) { background: var(--surface-input-focus); }
.sx-pick .sx-pick-addbox:has(.sx-select-button:focus-visible) { outline: var(--focus); outline-offset: var(--focus-offset); }
.sx-pick .sx-pick-addbox:has(.sx-select-button:disabled) { opacity: 0.8; cursor: not-allowed; }
.sx-pick .sx-pick-addbox > .sx-select { position: absolute; inset: 0; width: 100%; height: 100%; }
.sx-pick .sx-pick-addbox > select.sx-pick-add {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0;
  opacity: 0; cursor: pointer;
}
.sx-pick .sx-pick-addbox .sx-select-button {
  width: 100%; height: 100%; padding: 0; border: 0; background: none; color: transparent;
}
.sx-pick .sx-pick-addbox .sx-select-button:focus-visible { outline: none; }
.sx-pick .sx-pick-addbox .sx-select-arrow { display: none; }
/* In a form grid the picker takes a label's look without being a label. */
.fields .sx-pick-field { font: var(--type-label); color: var(--text-label); }
.fields .sx-pick-field .chips { margin: var(--sp-5) 0 0; }
/* A role's own colour, as Discord puts it on the role: a dot at the front,
   on a chip and on a switch that picks the role alike. */
.tag .dot, .sx-switch .dot { display: inline-block; vertical-align: middle; margin-block: -4px; width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--dot, var(--fg-3)); }
/* An emoji in a chip is a square picture, not an avatar. */
img.emoji { width: 18px; height: 18px; object-fit: contain; flex: none; }
/* A line that says something failed, outside a chip. */
td.halt, span.halt { color: var(--status-halt); }
a.tag.on { background: var(--brand-tint); color: var(--text-link); }

/* A heading with an action against it, sharing the row. */
.rowhead {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: var(--sp-6); margin: var(--stack) 0 var(--sp-6);
}

/* Role kind reads as a word, not a colour: rolling, permanent, synced. */
.kind { font: var(--type-meta); color: var(--text-label); }

/* --- unknown ---------------------------------------------------------------
   A reading the panel cannot take: a member who joined before the bot did has no
   history, and rendering that as 0 says they did nothing. Achromatic and hatched
   so it can never read as a status or a number. */

.unknown {
  display: inline-flex; align-items: center; gap: var(--sp-4);
  font: var(--type-state);
  color: var(--unknown-fg);
  background: var(--hatch);
  padding: var(--sp-3) var(--sp-6);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

/* --- buttons ---------------------------------------------------------------
   A pill. One accent fill per view; the decisions on a queue row are soft tinted
   pills rather than outlines, because an outline in every row turns the page into
   a grid of boxes. */

button, .btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--sp-5);
  height: var(--ctl);
  padding: 0 var(--sp-8);
  font: var(--type-button);
  background: var(--accent);
  color: var(--accent-ink);
  border: var(--border) solid transparent;
  border-radius: var(--radius-btn);
  white-space: nowrap;
  cursor: pointer;
  transition: var(--tr-color);
}
button:hover, .btn:hover { background: var(--accent-hover); color: var(--accent-ink); }
button:active, .btn:active { background: var(--accent-press); }
button:disabled { opacity: 0.4; cursor: not-allowed; }
/* Support sees every setting it cannot change. The browser's own disabled
   grey is too faint to read in the dark theme, so the value keeps the body
   colour and only the edge and the cursor say it is locked. */
input:disabled, select:disabled, textarea:disabled {
  color: var(--text-body); -webkit-text-fill-color: var(--text-body);
  opacity: 0.8; cursor: not-allowed;
}

button.ghost, .btn.ghost {
  background: transparent;
  color: var(--fg-2);
  border: var(--edge-control);
}
button.ghost:hover, .btn.ghost:hover { background: var(--surface-raised); color: var(--fg-1); }

button.confirm { background: var(--live-tint); color: var(--status-live); }
button.confirm:hover { background: var(--live-tint); color: var(--status-live); filter: brightness(1.25); }

button.danger { background: var(--halt-tint); color: var(--status-halt); }
button.danger:hover { background: var(--halt-tint); color: var(--status-halt); filter: brightness(1.25); }

/* Inside a table row the decisions shrink, or the row grows to fit them. */
td button { height: var(--ctl-sm); padding: 0 var(--sp-6); font-size: var(--fs-small); }
/* A row with a button is as tall as one without: its cell gives up padding
   so the 34 px button fits the 56 px row instead of stretching it to 63. */
tbody td:has(> form > button, > button) { padding-block: var(--sp-5); }
/* The same for a segmented control, 2 px taller for its border, and a person
   or a control sits on the row's centre line, not on the text baseline: on
   the baseline a person held the row at 61 px and sat 3 px high. */
tbody td:has(> .seg) { padding-block: calc(var(--sp-5) - 1px); }
td > .who, td > .seg { vertical-align: middle; }

.actions { display: flex; gap: var(--sp-5); align-items: center; margin-top: var(--sp-8); flex-wrap: wrap; }

/* --- trending nades: the MVP shortlist -------------------------------------- */
.trending { display: flex; flex-direction: column; gap: var(--sp-4); margin-top: var(--sp-6); }
.trend-row {
  display: flex; align-items: center; gap: var(--sp-6);
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface-raised);
  border-radius: var(--radius-md);
}
.trend-row img {
  width: 72px; height: 44px; flex: none;
  object-fit: cover; border-radius: var(--radius-sm);
  background: var(--surface-page);
}
.trend-meta { flex: 1; min-width: 0; }
.trend-title { color: var(--fg-1); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.trend-sub { color: var(--fg-3); font-size: var(--fs-small); margin-top: 1px; }
.trend-use { flex: none; height: var(--ctl-sm); padding: 0 var(--sp-6); font-size: var(--fs-small); }

/* --- fields ----------------------------------------------------------------
   Never background: transparent on a native control. The browser paints the
   select popup, the date picker and autofill from the element's own colours, not
   from the wrapper, so a transparent select gives a white list in the dark theme. */

input, select, textarea {
  font: var(--type-body);
  color: var(--fg-1);
  background: var(--surface-input);
  border: var(--border) solid transparent;
  border-radius: var(--radius-sm);
  padding: 0 var(--sp-6);
  height: var(--ctl);
  transition: var(--tr-color);
}
textarea { height: auto; padding: var(--sp-6); line-height: var(--lh-body); resize: vertical; }

/* The open native popup reads the element's own surface and text colours. */
select {
  background-color: var(--surface-input);
}
option { background: var(--surface-input); color: var(--fg-1); }

/* Shared select-only combobox. The native element keeps its form value and
   validation, but the app draws the visible control and its scrollable list. */
.sx-select { position: relative; display: block; min-width: 0; width: 100%; }
.sx-select > select.sx-select-native {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: 0;
  opacity: 0; pointer-events: none;
}
.sx-select-button {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-5);
  width: 100%; height: var(--ctl); min-width: 0; padding: 0 var(--sp-6);
  font: var(--type-body); text-align: left; color: var(--fg-1);
  background: var(--surface-input); border: var(--border) solid transparent;
  border-radius: var(--radius-sm); cursor: pointer;
}
.sx-select-button:hover, .sx-select-button[aria-expanded="true"] { background: var(--surface-input-focus); color: var(--fg-1); }
.sx-select-button:disabled { opacity: 0.8; cursor: not-allowed; }
.sx-select:has(.sx-select-error) .sx-select-button { border-color: var(--status-halt); }
.sx-select-error { display: block; margin-top: var(--sp-2); font: var(--type-meta); color: var(--status-halt); }
.sx-select-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sx-select-arrow {
  width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
.sx-select-menu {
  position: fixed; z-index: 1000; overflow: auto; overscroll-behavior: contain;
  padding: var(--sp-2); background: var(--surface-raised); color: var(--fg-1);
  border: var(--edge-control); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-float);
}
.sx-select-option {
  padding: var(--sp-3) var(--sp-5); border-radius: var(--radius-sm);
  font: var(--type-body); color: var(--fg-1); cursor: pointer; overflow-wrap: anywhere;
}
.sx-select-option:hover, .sx-select-option.active { background: var(--surface-input-focus); }
.sx-select-option[aria-selected="true"] { background: var(--surface-input-focus); font-weight: var(--fw-medium, 500); }
.sx-select-menu[aria-multiselectable="true"] .sx-select-option { padding-left: var(--sp-10); position: relative; }
.sx-select-menu[aria-multiselectable="true"] .sx-select-option::before {
  content: ""; position: absolute; left: var(--sp-3); top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; border: var(--edge-control); border-radius: var(--radius-sm);
}
.sx-select-menu[aria-multiselectable="true"] .sx-select-option[aria-selected="true"]::before { background: var(--fg-1); border-color: var(--fg-1); }

input:hover, select:hover, textarea:hover,
input:focus, select:focus, textarea:focus { background: var(--surface-input-focus); }

input[type="number"] {
  font: var(--type-data);
  text-align: right;
  width: var(--num-w);
  -moz-appearance: textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

input[type="checkbox"] {
  width: 20px; height: 20px; padding: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
input[type="file"] { height: auto; padding: var(--sp-5) var(--sp-6); }

/* Fields side by side share three rows of this grid: the words, the box and
   the hint. Each field is a subgrid over them, so a label that wraps to a
   second line takes the words beside it down too, and every box and hint in
   the row stays on one line (Sigve, 1 October 2026, after a box on Matches
   sat a line below its two neighbours, and five pages more like it). The
   space between fields is a margin: a row gap would also open between a
   field's own rows. The negative margin takes the last row's back. */
.fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); column-gap: var(--sp-8);
  margin-bottom: calc(-1 * var(--sp-8));
}
.fields > * { grid-row: span 3; margin: 0 0 var(--sp-8); }
/* A field holding a sentence gets the row to itself. Equal columns put a 1900
   character reply in a 300px box next to a two digit number in the same width. */
.fields > .wide { grid-column: 1 / -1; }

/* The bot's picture beside its name, rather than stacked above it, so the one
   form on that page fits without scrolling. */
.sx-identity { display: flex; align-items: flex-start; gap: var(--sp-8); }
.sx-botpic {
  width: 72px; height: 72px; flex: none;
  border-radius: var(--radius-pill);
  background: var(--surface-raised);
  object-fit: cover;
  display: block;
}
@media (max-width: 860px) { .sx-identity { flex-wrap: wrap; } }

/* A single flip reads as a sentence with a box, not as a labelled field. */
.sx-switch { display: flex; align-items: center; gap: var(--sp-5); font: var(--type-body); color: var(--text-body); cursor: pointer; }
.fields label { display: block; font: var(--type-label); color: var(--text-label); }
.fields > label, .fields > .sx-pick-field { display: grid; grid-template-rows: subgrid; }
.fields input, .fields select, .fields textarea { display: block; width: 100%; margin-top: var(--sp-5); }
.fields .sx-select { margin-top: var(--sp-5); }
td .sx-select { min-width: 150px; }
form.row .sx-select { flex: none; min-width: 200px; }
/* A switch is a box beside its words, not a full-width box above them: the
   field rules would otherwise stretch the checkbox across the column. */
.fields label.sx-switch { display: flex; align-items: center; gap: var(--sp-5); }
.fields label.sx-switch input { display: inline-block; width: auto; margin-top: 0; }
/* A switch among fields stands on their boxes' line, as tall as a box, and
   leaves the words row above it empty. */
.fields > label.sx-switch:not(.wide) { display: grid; }
.sx-switch-line { grid-row: 2; display: flex; align-items: center; gap: var(--sp-5); min-height: var(--ctl); margin-top: var(--sp-5); }
/* A field with a button of its own (the bot's nickname) keeps it in the hint
   row, under the hint, so the field still has three pieces. */
.sx-field-foot { display: grid; justify-items: start; gap: var(--sp-5); }
/* A private automatic reply: the count turns red past Discord's 150, and the
   messages senders flagged sit under the rule. */
[data-count][data-tone="halt"] { color: var(--status-halt); }
.sx-flags { margin-top: var(--sp-6); }

form.row { display: flex; gap: var(--sp-5); align-items: center; flex-wrap: wrap; }
/* Rows of one-line forms stacked a field's distance apart, and that far
   below the line above them (a member's Timeout, Nickname, Kick and Ban).
   Not .fields: its rules move a box down. */
.sx-rows { display: grid; gap: var(--sp-8); margin-top: var(--sp-8); }
.sx-rows > form.row { margin-bottom: 0; }
/* A member's account checks: the request button, each check's line and its
   Approve a small step apart instead of touching. */
.sx-checks > :is(p, form):not(:first-child) { margin-top: var(--sp-5); }
form.row input[type="text"], form.row .sx-member-pick { min-width: 220px; flex: 1 1 220px; }
/* Flex would otherwise shrink a 104px number field to fit, and a right-aligned
   amount in a 50px box is unreadable. */
form.row input[type="number"], form.row select { flex: none; }
form.row select { min-width: 200px; }

/* --- empty states ----------------------------------------------------------
   Ruled paper, and they explain what will fill them. Never behind live data. */

.empty {
  background: var(--grid-8);
  background-size: var(--grid-8-size);
  border-radius: var(--radius-md);
  padding: var(--sp-11) var(--sp-9);
  text-align: center;
  color: var(--text-label);
  font: var(--type-lede);
}
/* Under 1280 px a table panel gives most of its side padding to the table:
   the channel list needs it at 1100 and 768. */
@media (max-width: 1280px) {
  .panel.sx-table-wrap { padding-inline: var(--sp-5); }
}
/* On a phone a table panel gives its padding to the table: the support queue
   fits a 390 px screen instead of scrolling 10 px. After the block above, or
   its .panel padding wins on source order. */
@media (max-width: 480px) {
  .panel.sx-table-wrap { padding-inline: var(--sp-4); }
}
/* On a phone a table keeps each cell on one line and scrolls sideways inside
   its panel. Squeezed to 300 px, a four-column table broke names, times and
   statuses a letter per line. Free text (a subject, a reason, a note) opts back
   into wrapping with td.wrap, at a width that still reads as a line. */
@media (max-width: 700px) {
  td, th { white-space: nowrap; }
  .who a, td.name { overflow-wrap: normal; }
  td.wrap { white-space: normal; min-width: 22ch; overflow-wrap: normal; }
  /* A note under a cell's main text: as wide as it needs, never past 30ch. */
  td .dim, td .muted { white-space: normal; width: max-content; max-width: 30ch; }
}

/* --- signed out and waiting ------------------------------------------------ */

.centre {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--page-gutter);
  background: var(--sx-purple);
}
.centre .sx-mark { width: 56px; height: 56px; margin: 0 auto var(--sp-7); display: block; }
.centre .panel {
  width: 100%;
  max-width: var(--page-narrow);
  border-radius: var(--radius-lg);
  padding: var(--sp-11);
  text-align: center;
  margin-bottom: var(--sp-7);
}
.centre h1 { font: var(--type-section); margin-bottom: var(--sp-6); }
.centre .actions { justify-content: center; }

/* --- the public board ------------------------------------------------------
   No rail, no session, nothing but the standings. */

.sx-public {
  max-width: var(--page-narrow);
  margin: 0 auto;
  padding: var(--sp-13) var(--page-gutter) var(--sp-12);
}
.sx-public .empty { margin-top: var(--stack); }

/* --- support DMs -----------------------------------------------------------
   A face and a bubble per message, the member on the left and the bot on the
   right, so a moderator can see who said what down the side of the thread
   rather than by reading the timestamps. */

/* Capped, not endless. The panel holds the conversation and scrolls inside
   itself, so the reply box under it stays where it was however long the thread
   gets. 60vh leaves the composer and the send button on screen together on a
   laptop, which is the pair somebody actually uses. */
.chat {
  display: flex; flex-direction: column; gap: var(--sp-6);
  max-height: 60vh; overflow-y: auto; scrollbar-gutter: stable;
}

/* The member's other cases, one line under the case bar. */
.sx-othercases { margin: 0 0 var(--sp-6); display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-4); }

/* Top aligned, so the face sits beside the name it belongs to rather than
   drifting to the bottom of a long message. */
.dm { display: flex; gap: var(--sp-5); align-items: flex-start; max-width: 80%; }
.dm-out { flex-direction: row-reverse; align-self: flex-end; }

.face {
  width: 36px; height: 36px; flex: none;
  border-radius: var(--radius-pill);
  background: var(--surface-raised);
  object-fit: cover;
}
.face.sm { width: 26px; height: 26px; }
/* A profile picture is round everywhere, the way Discord draws it. The radius
   sits on the wrapper, so the wrapper has to clip the picture inside it; a
   radius alone left every face in the panel square. */
.face, .sx-botpic, .sx-avatar { overflow: hidden; }
.face { display: inline-block; vertical-align: middle; }
.face img, .sx-botpic img {
  width: 100%; height: 100%; display: block; object-fit: cover; border-radius: inherit;
}
/* The member's name in a list, with their face against it. */
.who { display: inline-flex; align-items: center; gap: var(--sp-4); }

.bubble {
  padding: var(--sp-5) var(--sp-6);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  min-width: 0;
}
/* Ours is tinted with the panel's own accent, theirs is not, which is the one
   difference that survives being read quickly. */
.dm-out .bubble { background: var(--brand-tint); }
.bubble .dim { font: var(--type-meta); margin-bottom: var(--sp-3); }
.bubble .dim b { color: var(--text-label); font-weight: 600; }

/* A picture somebody sent is the message, not a footnote to it: it fills the
   bubble and opens at full size in a new tab. Capped in height so one long
   screenshot cannot push the rest of the thread off the page. */
.bubble .shot { display: block; margin-top: var(--sp-4); }
.bubble .shot img {
  display: block;
  max-width: 100%; max-height: 420px;
  border-radius: var(--radius-sm);
  border: var(--border) solid var(--border-hairline);
}
/* The bubble sizes to its content, so a wide screenshot needs the row to be
   allowed to grow past the 80% a line of text is capped at. */
.dm:has(.shot) { max-width: 100%; }
.dm:has(.shot) .bubble { max-width: 560px; }

/* A conversation's state and what you can do about it, on one row: the state
   on the left, the actions on the right, wrapping under each other on a phone. */
.sx-casebar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--sp-6); margin-bottom: var(--stack);
}
.sx-casestate, .sx-caseactions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); }
.sx-caseactions form { margin: 0; }

/* The queue's status cell: the state, who has it, and who is in it now. */
.sx-state { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-4); }
.sx-here { display: block; margin-top: var(--sp-3); font: var(--type-meta); color: var(--status-attend); }
.sx-here[hidden] { display: none; }

/* What app/markup.py produced. Line breaks arrive as <br>, so pre-wrap is only
   here to keep runs of spaces somebody typed on purpose. */
.says { white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text-body); }
.says a { color: var(--text-link); }
/* The hairline is what makes a snippet read as one in the light theme, where
   the page surface and the bubble are near enough the same value. */
.says code {
  font: var(--type-data-sm);
  background: var(--surface-page);
  border: var(--border) solid var(--border-hairline);
  border-radius: var(--radius-sm);
  padding: 1px 5px;
}
.says pre.code {
  margin: var(--sp-4) 0 0;
  padding: var(--sp-5);
  background: var(--surface-page);
  border: var(--border) solid var(--border-hairline);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  white-space: pre;
}
.says pre.code code { background: none; border: 0; padding: 0; }
.says blockquote {
  margin: var(--sp-3) 0;
  padding-left: var(--sp-5);
  border-left: 3px solid var(--border-strong);
  color: var(--text-prose);
}
.says h3, .says h4, .says h5 { margin: var(--sp-4) 0 var(--sp-2); color: var(--text-title); }
.says h3 { font-size: 1.15em; } .says h4 { font-size: 1.06em; } .says h5 { font-size: 1em; }
/* Discord's subtext is a line of its own, and inline would let it run on from
   whatever came before it. */
.says small { display: block; color: var(--text-meta); }
.says > :first-child { margin-top: 0; }
/* Discord hides a spoiler until it is clicked. Focusable, so it opens from the
   keyboard as well as the mouse. */
.says .spoiler {
  background: var(--border-strong); color: transparent;
  border-radius: var(--radius-sm); cursor: pointer;
}
.says .spoiler:hover, .says .spoiler:focus { color: var(--text-body); }

/* --- footer ---------------------------------------------------------------- */

footer { font: var(--type-data-sm); color: var(--text-faint); }

/* --- narrow ----------------------------------------------------------------
   The rail stops being affordable somewhere under a laptop. It slides in over
   the page instead, behind a Menu button, which is the phone case the brief
   asked for: approvals on a phone, configuration at a desk. */

@media (max-width: 1080px) {
  .sx-rail {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    z-index: 30;
    transform: translateX(-100%);
    transition: transform 200ms ease-out;
    box-shadow: var(--shadow-float);
  }
  .sx-rail.open { transform: none; }
  .sx-railtoggle {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-4);
    height: var(--ctl-sm);
    margin: var(--sp-7) 0 0 var(--page-gutter);
    padding: 0 var(--sp-6);
    background: transparent;
    color: var(--fg-2);
    border: var(--edge-control);
    font: var(--type-state);
  }
  .sx-railtoggle:hover { background: var(--surface-raised); color: var(--fg-1); }
  .sx-railtoggle .ico { --ico: 18px; }
  main { padding-top: var(--sp-8); }
}

@media (min-width: 1081px) {
  /* The scrim only exists for the sliding rail. */
  .sx-scrim { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .sx-rail { transition: none; }
}

@media (max-width: 860px) {
  :root {
    --page-gutter: var(--page-gutter-sm);
    --fs-title: 28px;
    --ctl: var(--hit);
    /* Tighter than a laptop's: at 768 px the channel table needs every pixel. */
    --cell-pad-x: var(--sp-4);
  }
  .panel { padding: var(--sp-7); }
  /* A bubble capped at 80% of a phone is a column two words wide. */
  .dm { max-width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* --- announcement preview --------------------------------------------------
   A rough Discord message, drawn from the same renderer the inbox uses. Rough
   on purpose: it is here to check the words and the formatting, not to be a
   pixel copy of a client nobody controls. */

/* Write on the left, watch on the right. The preview stays where it is while
   the form scrolls, because the question it answers is asked from every field
   and not only from the last one. */
.sx-compose {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: var(--sp-9);
  align-items: start;
}
.sx-compose-form { min-width: 0; }
.sx-compose-side {
  position: sticky;
  /* Clear of the top of the viewport by the page's own gutter, so it never
     looks glued to the browser chrome, and of the server strip when there is one. */
  top: calc(var(--instance-h, 0px) + var(--sp-9));
  min-width: 0;
}
/* After the rules above, or they win on source order and the 380 px preview
   column survives on a phone, 38 px past its edge. */
@media (max-width: 1080px) {
  /* One column, and the preview stops sticking: on a narrow screen it would
     cover the field somebody is typing in. */
  .sx-compose { grid-template-columns: minmax(0, 1fr); }
  .sx-compose-side { position: static; }
}

/* Discord's own values, not the panel's, and hard-coded rather than themed:
   this block is a picture of another product, and it looks wrong in daylight
   for the same reason a screenshot of Discord would. Their type is gg sans,
   which is theirs and not redistributable, so Inter stands in, a webfont
   base.html loads: a stack of machine fonts drew it differently on every PC. */
.dc {
  background: #313338;
  border-radius: var(--radius-md);
  padding: 16px;
  margin-bottom: var(--stack);   /* the same air every panel keeps below it */
  font-family: "Inter", "gg sans", "Noto Sans", "Helvetica Neue", Arial, sans-serif;
  overflow-x: auto;
}
.dc-msg { display: flex; gap: 16px; align-items: flex-start; }
.dc-avatar { width: 40px; height: 40px; border-radius: 50%; flex: none; }
.dc-body { min-width: 0; flex: 1; }
.dc-head { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.dc-name { color: #f2f3f5; font-size: 1rem; font-weight: 500; }
.dc-app {
  background: #5865f2; color: #fff;
  font-size: 0.625rem; font-weight: 500;
  padding: 0 4px; border-radius: 3px; text-transform: uppercase;
}
.dc-time { color: #949ba4; font-size: 0.75rem; }
.dc-dim { color: #949ba4; }
.dc-text {
  color: #dbdee1; font-size: 1rem; line-height: 1.375;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.dc-text a, .dc-embed-desc a { color: #00a8fc; }
.dc-text strong, .dc-embed-desc strong { font-weight: 700; }
.dc-text code, .dc-embed-desc code {
  background: #1e1f22; color: #dbdee1;
  border: 0; border-radius: 3px; padding: 1px 3px;
  font-family: "JetBrains Mono", Consolas, "Courier New", monospace; font-size: 0.85em;
}
.dc-text pre.code, .dc-embed-desc pre.code {
  background: #2b2d31; border: 1px solid #1e1f22; color: #dbdee1;
  border-radius: 4px; padding: 8px; margin: 4px 0 0;
}
.dc-text blockquote, .dc-embed-desc blockquote {
  border-left: 4px solid #4e5058; margin: 4px 0; padding-left: 12px; color: #dbdee1;
}
.dc-text h3, .dc-text h4, .dc-text h5 { color: #f2f3f5; margin: 8px 0 4px; }
.dc-text .spoiler, .dc-embed-desc .spoiler { background: #4e5058; }

.dc-embed {
  margin-top: 8px;
  max-width: 432px;              /* Discord's own ceiling on an embed */
  background: #2b2d31;
  border-left: 4px solid var(--accent);
  border-radius: 4px;
  padding: 8px 16px 16px 12px;
}
.dc-embed-title { color: #00a8fc; font-size: 1rem; font-weight: 600; margin-top: 8px; }
.dc-embed-desc { color: #dbdee1; font-size: 0.875rem; line-height: 1.375; margin-top: 8px; white-space: pre-wrap; }
/* A poster is a file on the message, above the embed: Discord shows an
   attachment at most about 400 px tall and keeps its shape. */
.dc-attach { margin: 4px 0 6px; }
.dc-attach-img { display: block; width: auto; height: auto; max-width: 100%; max-height: 400px; border-radius: 8px; }
.dc-embed-img { margin-top: 16px; max-width: 100%; border-radius: 4px; display: block; }
.dc-embed-footer { color: #949ba4; font-size: 0.75rem; margin-top: 8px; }
.dc-embed-title:empty, .dc-embed-desc:empty, .dc-embed-footer:empty { display: none; }
.dc-text .emoji, .dc-embed-desc .emoji {
  width: 1.375em; height: 1.375em; vertical-align: -0.3em; object-fit: contain;
}
/* The answer buttons under a game post, drawn the way Discord draws its
   secondary buttons: outside the embed, on the message itself. */
.dc-buttons { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; max-width: 432px; }
.dc-btn {
  background: #4e5058; color: #fff; border-radius: 8px;
  padding: 6px 16px; font-size: 0.875rem; font-weight: 500; line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
/* The card under a staff answer (templates/_reply.html): the person's picture
   and name on the embed's author line. */
.dc-embed-author {
  color: #f2f3f5; font-size: 0.875rem; font-weight: 600; margin-top: 8px;
  display: flex; align-items: center; gap: 8px;
}
.dc-embed-author img { width: 24px; height: 24px; border-radius: 50%; flex: none; }
/* Discord's small text (a line starting -#), its link blue, and the blurple
   of a primary button, for the About me preview. */
.dc-small { color: #949ba4; font-size: 0.75rem; line-height: 1.33; }
.dc-link { color: #00a8fc; }
.dc-btn.primary { background: #5865f2; }
/* About me's steps: the place, the question with its kind and the tools on
   one line; the options or roles under it, the form behind Edit. */
.sx-step-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-5); }
.sx-step-n { font: var(--type-data-sm); color: var(--text-label); min-width: 2ch; }
.sx-step-title { flex: 1; min-width: 12rem; }
.sx-step-tools { display: flex; gap: var(--sp-3); margin: 0; }
.sx-step .chips { margin-top: var(--sp-4); }
.sx-step-edit { margin-top: var(--sp-4); }
.sx-step-edit > summary { cursor: pointer; color: var(--text-label); width: fit-content; }
/* An achievement card: the words on the left, the art as the thumbnail on the right. */
.dc-ach { display: flex; gap: 16px; align-items: flex-start; }
.dc-ach-text { flex: 1; min-width: 0; }
.dc-ach-art { width: 80px; height: 80px; flex: none; margin-top: 8px; border-radius: 4px; object-fit: cover; }
/* Discord draws an embed title without a link in white, and a card's title has none. */
.dc-ach .dc-embed-title { color: #f2f3f5; }
.dc-next { margin-top: 16px; }
/* The line over a reply: the spine from the avatar up to a small copy of the
   message it answers. */
.dc-replied {
  position: relative; display: flex; align-items: center; gap: 4px;
  margin: 0 0 4px 56px; color: #b5bac1; font-size: 0.875rem; line-height: 1.375;
}
.dc-replied::before {
  content: ""; position: absolute; left: -36px; top: 50%; width: 30px; height: 10px;
  border: solid #4e5058; border-width: 2px 0 0 2px; border-top-left-radius: 6px;
}
.dc-replied img { width: 16px; height: 16px; border-radius: 50%; flex: none; }
.dc-replied-name { color: #f2f3f5; font-weight: 500; }
.dc-replied em { color: #b5bac1; }
/* A <t:…> date: Discord sets it on a faint pill. */
.dc-stamp { background: rgba(255, 255, 255, 0.06); border-radius: 3px; padding: 0 2px; }
/* Files on a direct message, under the words and above the card, the way
   Discord draws them: pictures as one mosaic no wider than 550 px, one picture
   at most 350 px tall, any other file as a row with its name and size. */
.dc-files { display: grid; gap: 4px; margin-top: 4px; max-width: 550px; }
.dc-gallery {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 4px;
  border-radius: 8px; overflow: hidden;
}
.dc-gallery img {
  grid-column: span 2; display: block; width: 100%; aspect-ratio: 1;
  object-fit: cover; background: #2b2d31;
}
.dc-gallery[data-n="1"] img:first-child { grid-column: span 6; aspect-ratio: 2; }
.dc-gallery[data-n="2"] img:nth-child(-n+2),
.dc-gallery[data-n="4"] img { grid-column: span 3; aspect-ratio: 3 / 2; }
.dc-gallery[data-n="one"] { display: block; width: fit-content; max-width: 100%; }
.dc-gallery[data-n="one"] img { width: auto; max-width: 100%; max-height: 350px; aspect-ratio: auto; }
.dc-file {
  display: flex; align-items: center; gap: 8px; max-width: 432px; padding: 16px;
  background: #2b2d31; border: 1px solid #1e1f22; border-radius: 8px;
}
.dc-file > span { display: flex; flex-direction: column; min-width: 0; }
.dc-file-name { color: #00a8fc; font-size: 1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dc-file-size { color: #949ba4; font-size: 0.75rem; }
.sx-reply-preview { margin-top: var(--sp-8); }
.sx-reply-preview > p.dim { margin: 0 0 var(--sp-4); }
/* The chosen files beside Attach files, each a chip whose x takes it out. */
.sx-files { display: flex; flex-wrap: wrap; gap: var(--sp-3); min-width: 0; }
.sx-files:empty { display: none; }
.sx-files .tag { white-space: normal; overflow-wrap: anywhere; }

.sx-preview { padding: var(--sp-7); }
.pv-embed {
  margin-top: var(--sp-5);
  padding: var(--sp-6);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--surface-page);
}
.pv-title { font: var(--type-card-title); color: var(--text-link); margin-bottom: var(--sp-4); }
.pv-embed .dim:empty, .pv-title:empty { display: none; }
.says .emoji, .pv-title .emoji {
  width: 1.35em; height: 1.35em;
  vertical-align: -0.3em;
  object-fit: contain;
}

/* --- the content bank gallery ------------------------------------------
   Each item is a real card: one surface, one border, image bleeding to the
   card's edges, the caption row pinned under it. Used items fade as a whole
   card, so the state reads without moving anything. */
.bank-map-head {
  margin: var(--sp-6) 0 var(--sp-3);
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
}
.bank-map-head:first-child { margin-top: 0; }
.bank-grid + .bank-grid { margin-top: var(--sp-6); }
.bank-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--sp-6);
}
.bank-card {
  margin: 0;
  background: var(--surface-raised);
  border: var(--edge-control);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.bank-card img {
  display: block;
  width: 100%;
  height: 112px;
  object-fit: cover;
  background: var(--surface-page);
}
.bank-card figcaption {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  /* The contests cards put two buttons beside the name; without the wrap they
     crush the caption into a sliver instead of taking a second row. */
  flex-wrap: wrap;
}
.bank-card.used { opacity: 0.5; }
/* A template with no picture previews its edge colour instead; the tint is the
   stored colour with alpha, set inline by the page. */
.tpl-blank { height: 112px; display: grid; place-items: center; }

/* The rewards page's tick, sat in the chat gutter so choosing reads as part of
   reading. Sized to the row's face, not the 16px browser default. */
.rw-pick { display: flex; align-items: center; padding-right: var(--sp-4); cursor: pointer; }
.rw-pick input { width: 18px; height: 18px; accent-color: var(--accent); cursor: pointer; }

/* The add tile: the counter-interaction to Remove, drawn as one more card in
   the same grid. Dashed, because it is a place to put something, not a thing. */
.bank-add {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 158px;
  border-style: dashed;
  background: transparent;
  cursor: pointer;
  text-align: center;
  padding: var(--sp-5);
  color: var(--fg-2);
}
.bank-add:hover { background: var(--surface-raised); color: var(--fg-1); }
.bank-add-plus { font-size: 28px; line-height: 1; }

/* The question bank's add row, under the same table its Removes live in. */
.bank-add-row {
  display: flex;
  align-items: flex-end;
  gap: var(--sp-6);
  flex-wrap: wrap;
  margin-top: var(--sp-6);
  padding: 0 var(--cell-pad-x, var(--sp-5));
}
.bank-add-row label { display: flex; flex-direction: column; gap: var(--sp-2); }
.bank-add-row button { flex: none; }

/* --- sum row ---------------------------------------------------------------
   The total sits in the header, under the column names, because these tables
   are as long as the server is busy and a total you scroll to is not a total. */
tr.sumrow th {
  background: var(--surface-raised);
  color: var(--text-title);
  font: var(--type-data);
  border-bottom: var(--rule);
}
tr.sumrow th.dim, tr.sumrow th .dim { font: var(--type-meta); color: var(--text-meta); }

/* --- two columns -----------------------------------------------------------
   The dashboard reads as two conversations side by side on a desk monitor: what
   the server is doing on the left, what waits for a person on the right. One
   column below the same breakpoint the rail collapses at, because two 500px
   columns are two unreadable ones. */
.two { display: grid; gap: var(--stack-lg); align-items: start; }
@media (min-width: 1081px) { .two { grid-template-columns: 1fr 1fr; } }
/* The newest members beside the event log: the log has four columns and
   needs about 540 px, so it takes two thirds, and the pair stacks until the
   page has room for that. In halves it scrolled at every width from 1100 to
   1440 (measured on dev, 25 September 2026). */
.two.log, .two.lead { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 1240px) {
  .two.log { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
  /* The open tickets beside the schedule, the other way round: with the
     opener and the time on one line, halves ran past the panel (measured on
     27 September 2026). */
  .two.lead { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
}
.two > div > h2:first-child, .two > div > .rowhead:first-child { margin-top: 0; }
/* A grid item's min width is its content's by default, so a wide table in
   one column pushed the whole page wider than a phone. */
.two > * { min-width: 0; }


/* --- the mark ---------------------------------------------------------------
   The wordmark set in Geist Black, cream on the black rail, beside the helmet
   tile the tab shows. Both wordmark files stay in the page for a rail that
   is not black; this one is, so only the light one shows. */
.sx-brand { display: flex; align-items: center; gap: var(--brand-gap, var(--sp-5)); }
.sx-logo { height: var(--logo-height, 22px); width: auto; display: block; }
/* The same mark as the tab, in front of the wordmark. */
.sx-mark { width: 36px; height: 36px; border-radius: 8px; flex: none; }
.sx-logo-light { display: none; }
.sx-brand-text { font: var(--type-meta); color: var(--rail-fg-muted); align-self: flex-end; padding-bottom: 4px; }
.sx-brand:hover .sx-brand-text { color: var(--sx-yellow); }

/* The section icons take the site's yellow, with the headings they sit in. */
.sx-navhead svg { color: var(--rail-head); }
.sx-navhead .ico { --ico: 13px; }
/* Half the 13 px out of the line box above and below, so the heading keeps
   its height, and the middle on the capitals. */
.sx-navhead .ico, .sx-navhead .sx-navmark { vertical-align: 0.5cap; margin: -6.5px var(--sp-3) -6.5px 0; }

/* En rad som er en lenke: hele flaten svarer på musa, og tastaturet ser den. */
tr.sx-rowlink { cursor: pointer; }
tr.sx-rowlink:hover td { background: var(--surface-row-hover); }
tr.sx-rowlink:focus-visible { outline: var(--focus); outline-offset: calc(var(--focus-offset) * -1); }

/* --- the guide preview -------------------------------------------------------
   Discord draws an embed about 430px wide; a guide with six categories in
   that width is a tall thin column. The preview widens it and gives each
   category the breathing room the real thing has. */
/* The box hugs the embed instead of stretching across the page with the
   card in one corner and nothing in the rest. */
.dc-wide { width: fit-content; max-width: 100%; padding: 24px; }
.dc-wide .dc-msg { display: block; }
.dc-wide .dc-embed { max-width: 720px; margin-top: 0; padding: 16px 20px 18px; }
.dc-field-name { color: #f2f3f5; font-weight: 600; font-size: 0.875rem; margin-top: 16px; }
/* The role guide: a message per section, the banner first, then its embed
   and, on the last one, the picker's menus, the way Discord stacks them. */
.dc-guide { display: flex; flex-direction: column; gap: 20px; width: 520px; max-width: 100%; }
.dc-guide-msg .dc-embed { margin-top: 8px; }
.dc-banner { display: block; width: 100%; height: auto; border-radius: 8px; }
/* A giveaway post: the fields run side by side where Discord puts them
   inline, and the picture keeps its own shape (templates/_giveaway_preview.html). */
.dc-fields { display: flex; flex-wrap: wrap; gap: 0 24px; }
.dc-field { min-width: 0; }
.dc-field.wide { flex-basis: 100%; }
.dc-embed-thumb { float: right; max-width: 80px; max-height: 80px; margin: 8px 0 0 16px; border-radius: 4px; }
.dc-embed-img { width: auto; height: auto; max-height: 520px; }
/* A select menu's closed bar. The placeholder is one line, cut with an
   ellipsis as Discord cuts it, and the chevron at the right stands on its
   letters: the two share a baseline, and the icon's own line box puts it on
   the middle of the x-height, whatever the font. */
.dc-select {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  max-width: 432px; margin-top: 8px; padding: 10px 12px;
  background: #1e1f22; border: 1px solid #1e1f22; border-radius: 8px;
  color: #b5bac1; font-size: 0.9375rem;
}
.dc-select > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dc-select .ico { --ico: 20px; }
/* Channels & Roles, drawn the way Discord's Customize tab draws it: each
   question a dark card, its answers a grid of tiles with emoji, name and the
   small line under it. */
/* A set width: inside the preview box, which hugs its content, the grid
   would otherwise shrink to one tile a row. */
.dc-ob { display: flex; flex-direction: column; gap: 16px; width: 720px; max-width: 100%; }
.dc-ob-head { color: #f2f3f5; font-weight: 600; font-size: 1rem; }
.dc-ob-prompt { background: #2b2d31; border-radius: 8px; padding: 12px 16px 14px; }
.dc-ob-title { color: #f2f3f5; font-weight: 600; font-size: 0.9375rem; margin-bottom: 10px; }
.dc-ob-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; }
.dc-ob-option {
  display: flex; align-items: flex-start; gap: 10px;
  background: #383a40; border: 1px solid #3f4147; border-radius: 8px; padding: 10px 12px;
}
.dc-ob-emoji { width: 20px; flex: 0 0 20px; font-size: 1.1rem; line-height: 1.2; text-align: center; }
.dc-ob-emoji img { display: block; }
.dc-ob-name { display: block; color: #f2f3f5; font-weight: 600; font-size: 0.875rem; }
.dc-ob-desc { display: block; color: #b5bac1; font-size: 0.75rem; margin-top: 2px; }
.dc-ob-note { color: #949ba4; font-size: 0.75rem; margin-top: 10px; }
.dc-field-value { margin-top: 6px; line-height: 1.5; }
/* A mention, drawn the way Discord's client draws it, wherever a message is
   rendered: blurple at 30 % behind #c9cdfb, solid blurple on hover. */
.mention, .dc-chan {
  color: #c9cdfb; background: rgba(88, 101, 242, 0.3);
  border-radius: 3px; padding: 0 2px; font-weight: 500;
  transition: var(--tr-color); cursor: default;
}
.mention:hover, .dc-chan:hover { background: #5865f2; color: #fff; }
.says .emoji, .dc-text .emoji { width: 1.375em; height: 1.375em; vertical-align: -0.3em; object-fit: contain; }

/* --- rhythm between blocks ----------------------------------------------------
   Every block that can be followed by another keeps the same air below it as a
   panel does. Measured across all seventeen pages on 1 September 2026: a search
   row, an action row and a two-column grid each sat flush against whatever came
   next, and the last block on five pages touched the footer. */
.actions { margin-bottom: var(--stack); }
form.row { margin-bottom: var(--stack); }
.two { margin-bottom: var(--stack); }
.sx-identity { margin-bottom: var(--stack); }
form > .actions:last-child { margin-bottom: var(--stack); }

/* --- folded lists ----------------------------------------------------------
   A table with data-fold shows that many rows and keeps the rest behind one
   chevron row. The sum in the header still counts everything. */
tr.fold td { padding: var(--sp-4) var(--cell-pad-x); text-align: center; border-bottom: 0; }
.fold-btn {
  height: var(--ctl-sm); padding: 0 var(--sp-7); background: transparent;
  border: var(--edge-control); color: var(--text-label); font: var(--type-meta);
}
.fold-btn:hover { background: var(--surface-raised); color: var(--text-title); }
.fold-btn .ico-line { margin-left: var(--sp-3); }
/* Centred under a phone table wider than the screen, the button sat half past
   the panel's edge. Pinned left, it stays in view while the rows scroll. */
@media (max-width: 700px) {
  tr.fold td { text-align: left; }
  .fold-btn { position: sticky; left: 0; }
}

/* --- segmented control ---------------------------------------------------------
   A binary choice is two buttons side by side, both visible, the active one
   lit. Never one button that swaps its label: that one always reads as the
   state it is about to leave. */
.seg { display: inline-flex; border: var(--edge-control); border-radius: var(--radius-btn); overflow: hidden; }
.seg form { margin: 0; }
.seg-btn {
  height: var(--ctl-sm); padding: 0 var(--sp-6); border: 0; border-radius: 0;
  background: transparent; color: var(--text-label); font: var(--type-meta);
}
.seg-btn:hover { background: var(--surface-raised); color: var(--text-title); }
/* After the rule above, or it loses on source order. */
@media (max-width: 1280px) { .seg-btn { padding-inline: var(--sp-5); } }
.seg-btn[aria-pressed="true"], .seg-btn[aria-current="true"] { background: var(--accent); color: var(--accent-ink); cursor: default; }
/* A half that is a link (the panel view, which has an address of its own). */
a.seg-btn { display: inline-flex; align-items: center; justify-content: center; }
.seg-btn[data-tone="ok"][aria-pressed="true"]   { background: var(--live-tint); color: var(--status-live); }
.seg-btn[data-tone="halt"][aria-pressed="true"] { background: var(--halt-tint); color: var(--status-halt); }
.busy { opacity: 0.6; pointer-events: none; }
td.nowrap, th.nowrap { white-space: nowrap; }

/* the eye: one button, the icon says which state it is in */
.ctl { display: inline-flex; align-items: center; gap: var(--sp-4); }
/* One line on a desktop. Under 1280 px the eye may drop under the switch,
   rather than holding the whole table wider than its panel. */
@media (max-width: 1280px) { .ctl { flex-wrap: wrap; } }
/* On a phone the table scrolls sideways anyway, so the controls keep one line
   instead of stacking every channel row three buttons tall. */
@media (max-width: 700px) { .ctl { flex-wrap: nowrap; } }
.eye { margin: 0; }
.eye button {
  width: var(--ctl-sm); height: var(--ctl-sm); padding: 0; display: inline-grid; place-items: center;
  border: var(--edge-control); border-radius: var(--radius-btn); background: transparent; color: var(--text-label);
}
.eye button:hover { background: var(--surface-raised); color: var(--text-title); }
.eye button[aria-pressed="false"] { color: var(--text-faint); border-style: dashed; }
.eye button[aria-pressed="true"] .off, .eye button[aria-pressed="false"] .on { display: none; }

/* category rows in the channel table, same groups as the Discord sidebar */
tr.grouprow th {
  padding-top: var(--sp-7); padding-bottom: var(--sp-3);
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint); border-bottom: 0;
}
tr.grouprow:first-child th { padding-top: var(--sp-4); }

/* --- dashboard charts ----------------------------------------------------------
   One series per chart, so the accent is the only colour and the title is the
   legend. Gridlines are hairlines a step off the surface; every label wears a
   text token, never the series colour. */
.chart { display: block; margin-top: var(--sp-6); overflow: visible; }
.chart .grid { stroke: var(--border-hairline); stroke-width: 1; }
.chart .tick { font: var(--fw-medium) 15px / 1 var(--font-sans); fill: var(--text-faint); }
.chart .value { font: var(--fw-bold) 16px / 1 var(--font-sans); fill: var(--text-title); }
.chart .note { font: var(--fw-regular) 17px / 1 var(--font-sans); fill: var(--text-label); }
.chart .bar { fill: var(--accent); }
.chart .bar:hover { fill: var(--accent-hover); }
.chart .zero { fill: var(--border-strong); }
.chart .bar.prev { fill: var(--border-strong); }
.chart .bar.prev:hover { fill: var(--text-faint); }
.legend { display: flex; gap: var(--sp-8); margin-top: var(--sp-4); font: var(--type-meta); color: var(--text-label); }
.legend span { display: inline-flex; align-items: center; gap: var(--sp-3); }
.legend .sw { width: 10px; height: 10px; border-radius: 2px; background: var(--accent); display: inline-block; }
.legend .sw.prev { background: var(--border-strong); }
.chart .line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart .fill { fill: var(--accent); opacity: 0.1; }
.chart .dot { fill: var(--accent); stroke: var(--surface-panel); stroke-width: 2; }
.chart .hit { fill: transparent; }
.chart .hit:hover { fill: var(--accent-tint); }
.panel > h3 + .muted { margin-top: var(--sp-3); font: var(--type-meta); }

/* Horizontal bars for a short list of kinds: label, track, count. */
.hbars { display: grid; gap: var(--sp-5); margin-top: var(--sp-7); }
.hbar { display: grid; grid-template-columns: minmax(120px, 1fr) 3fr auto; align-items: center; gap: var(--sp-6); }
.hbar-label { font: var(--type-meta); color: var(--text-body); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hbar-track { height: 12px; background: var(--surface-raised); border-radius: 4px; overflow: hidden; }
.hbar-fill { display: block; height: 100%; background: var(--accent); border-radius: 0 4px 4px 0; min-width: 2px; }
.hbar-n { font: var(--type-data-sm); color: var(--text-title); min-width: 2.5ch; text-align: right; }

/* Paired rows: both cells stretch to the taller one, so an empty queue never
   leaves a hole beside a full table. */
.two.even { align-items: stretch; }
.two.even > div { display: flex; flex-direction: column; min-width: 0; }
.two.even > div > .panel { flex: 1; margin-bottom: 0; }
.two.even .rowhead { margin-top: 0; }
.panel > .empty { margin: 0; height: 100%; display: grid; place-items: center; min-height: 120px; }
.panel.scroll:has(> .empty) { display: flex; }
.panel.scroll:has(> .empty) > .empty { flex: 1; }

/* The support inbox: one card per conversation. Who, state and when on top,
   then what it is about, then the latest message. Cards like the stat tiles
   above them, with the same hover. */
.sx-inbox {
  list-style: none; margin: 0 0 var(--stack); padding: 0;
  display: grid; gap: var(--sp-5);
}
.sx-thread {
  display: flex; align-items: flex-start; gap: var(--sp-7);
  padding: var(--sp-8) var(--sp-9);
  background: var(--surface-panel);
  border-radius: var(--radius);
  color: inherit;
  transition: var(--tr-color);
}
.sx-thread:hover { color: inherit; background: var(--surface-raised); }
.sx-thread:focus-visible { outline: var(--focus); outline-offset: var(--focus-offset); border-radius: var(--radius); }
.sx-thread-body { flex: 1; min-width: 0; display: grid; gap: var(--sp-4); }
.sx-thread-top { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-4); }
/* State, unread and who has it sit on the right, beside the time. */
.sx-thread-tags { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-4); margin-left: auto; }
.sx-thread-who { font: var(--type-data); color: var(--text-link); overflow-wrap: anywhere; }
.sx-thread:hover .sx-thread-who { color: var(--text-link-hover); }
.sx-thread-when { font: var(--type-meta); color: var(--text-faint); white-space: nowrap; }
.sx-thread-subject { font: var(--type-data); color: var(--text-title); overflow-wrap: anywhere; }
/* Two lines of the message, then an ellipsis: enough to know what it says. */
.sx-thread-says {
  color: var(--text-body); overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.sx-thread .sx-here { margin-top: 0; }
.sx-inbox-empty { padding: var(--sp-8) var(--sp-9); background: var(--surface-panel); border-radius: var(--radius); }
.sx-inbox > li.fold { padding-top: var(--sp-3); }
/* On a phone the time stays beside the name, without the date, and the tags
   take the right of the line under it. Wrapping let the time fall onto a line
   of its own. */
@media (max-width: 480px) {
  .sx-thread { padding: var(--sp-7); gap: var(--sp-5); }
  .sx-thread-top { display: grid; grid-template-columns: 1fr auto; align-items: baseline; }
  .sx-thread-tags { grid-column: 1 / -1; justify-content: flex-end; }
  .sx-thread-when { grid-row: 1; grid-column: 2; }
  .sx-thread-clock { display: none; }
}

/* A match's own picture: the thumbnail, the upload and the way back to the
   drawn card, under the match it belongs to. */
.match-own { display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-5); }
.match-own-img { width: 100%; border-radius: var(--radius-sm); display: block; }
.match-own-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-3); margin: 0; }
.match-own-actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.match-own-actions form, .match-own-row form { margin: 0; }
.match-own label.btn { cursor: pointer; }
.match-own-row { flex-direction: row; flex-wrap: wrap; align-items: center; margin-top: 0; }
.match-own-thumb { height: 40px; width: auto; border-radius: 4px; display: block; }
/* The disclosure reads as something to press, the way the daily page's does. */
.match-own-row summary { cursor: pointer; color: var(--text-label); white-space: nowrap; }
.match-own-row details[open] { flex-basis: 100%; }

/* Queue actions sit outside the case link so closing never opens the chat. */
.sx-queue-card { background: var(--surface-panel); border-radius: var(--radius); }
.sx-queue-card > .sx-thread { border-radius: var(--radius) var(--radius) 0 0; }
.sx-queue-action { display: flex; justify-content: flex-end; margin: 0; padding: 0 var(--sp-9) var(--sp-6); }
@media (max-width: 640px) {
  .sx-queue-action { padding: 0 var(--sp-7) var(--sp-6); }
}

/* Basic keeps daily work visible. Hidden controls still submit their values. */
body:not(.sx-advanced) .adv { display: none !important; }
.sx-view { display: flex; flex-shrink: 0; width: 100%; margin: 0 0 20px; }
.sx-view .seg-btn { flex: 1; }

/* The wheel video's dialog on Giveaways (static/wheel.js): the canvas as big
   as the window allows, square, on the night ground the video is drawn on. */
.gw-wheel { width: min(640px, calc(100vw - 32px)); max-width: none; padding: var(--sp-8);
            border: 0; border-radius: var(--radius); background: var(--surface-panel);
            color: var(--text-body); }
.gw-wheel::backdrop { background: rgba(0, 0, 0, .6); }
.gw-wheel-head { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-5); }
.gw-wheel canvas { display: block; width: min(100%, calc(100dvh - 260px)); height: auto; aspect-ratio: 1;
                   margin: 0 auto; border-radius: var(--radius-sm); background: #0a0a0a; }
@media (max-width: 560px) { .gw-wheel { padding: var(--sp-6); } }

/* --- roadmap (templates/roadmap.html) ----------------------------------------
   One <details> per month. The current month opens; the others carry their
   count on the summary line until somebody opens them. */
.rm-month { margin-bottom: var(--sp-6); }
.rm-month > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-6);
}
.rm-month > summary::-webkit-details-marker { display: none; }
.rm-title { font: var(--type-section); color: var(--text-title); }
.rm-meta { display: inline-flex; align-items: center; gap: var(--sp-5); }
.rm-bar { height: 4px; border-radius: 2px; background: var(--surface-raised); margin: var(--sp-6) 0 var(--sp-5); overflow: hidden; }
.rm-bar i { display: block; height: 100%; background: var(--accent); }
.rm-row {
  display: flex; align-items: center; gap: var(--sp-6);
  min-height: var(--row-dense); border-top: var(--rule-soft);
  font: var(--type-body); color: var(--text-body);
}
.rm-row form { display: contents; }
.rm-row .rm-text { flex: 1; }
.rm-row.done .rm-text { color: var(--text-faint); text-decoration: line-through; }
.rm-add { margin-top: var(--sp-6); }

/* --- the match card's designer (Matches, brands/heroic/static/matchdesign.js)
   One canvas: the card the bot draws, each piece a button over it at the
   design's own point, and the save beside the card. Under it an inspector of
   small cards whose rows put the label on the left and the control on one
   right edge, 44px a row. */
.mc-designer { display: flex; flex-direction: column; gap: var(--sp-8); margin-bottom: var(--stack); }
.mc-designer .panel { margin: 0; }
.mc-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
              gap: var(--sp-6); max-width: 960px; margin: 0 auto var(--sp-7); }
.mc-actions { display: flex; align-items: center; gap: var(--sp-5); margin-left: auto; }
.mc-state { font: var(--type-meta); color: var(--text-label); }
.mc-stage { position: relative; max-width: 960px; aspect-ratio: 1200 / 420; margin: 0 auto;
            border-radius: var(--radius-sm); overflow: hidden; background: var(--surface-raised);
            touch-action: none; user-select: none; -webkit-user-select: none; }
.mc-img { display: block; width: 100%; height: 100%; }
button.mc-handle, button.mc-handle:hover, button.mc-handle:active {
  position: absolute; display: block; height: auto; padding: 0; margin: 0;
  background: transparent; color: inherit; border: 2px dashed transparent; border-radius: 6px;
  cursor: grab; transition: border-color 0.12s, background-color 0.12s;
}
/* The pieces show while the pointer is on the card, so the card itself reads clean. */
.mc-stage:hover .mc-handle { border-color: rgba(208, 33, 60, 0.3); }
@media (hover: none) { button.mc-handle { border-color: rgba(208, 33, 60, 0.3); } }
button.mc-handle:hover, button.mc-handle:focus-visible, button.mc-handle.dragging, button.mc-handle.picked {
  border-color: var(--accent); background: rgba(208, 33, 60, 0.08);
}
.mc-handle span {
  position: absolute; left: 4px; top: 4px; padding: 1px 8px; border-radius: 4px;
  background: var(--accent); color: var(--on-brand); font: var(--type-label);
  white-space: nowrap; opacity: 0; pointer-events: none;
}
.mc-handle:hover span, .mc-handle:focus-visible span, .mc-handle.dragging span, .mc-handle.picked span { opacity: 1; }
button.mc-handle.dragging { cursor: grabbing; }
.mc-guide { position: absolute; top: 0; bottom: 0; left: 50%; border-left: 1px dashed var(--accent); pointer-events: none; }
.mc-hint { max-width: 960px; margin: var(--sp-6) auto 0; font: var(--type-meta); color: var(--text-label); }
.mc-inspector { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr));
                gap: var(--sp-8); align-items: start; }
.mc-col { display: flex; flex-direction: column; gap: var(--sp-8); }
.mc-card { padding: var(--sp-8) var(--sp-9) var(--sp-6); }
.mc-card h3 { margin: 0 0 var(--sp-4); }
.mc-line {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-6);
  min-height: 44px; border-top: var(--rule-soft);
  font: var(--type-body); color: var(--text-body); cursor: pointer;
}
.mc-card h3 + .mc-line { border-top: 0; }
.mc-line input[type="color"] {
  flex: none; width: 44px; height: 28px; padding: 2px; border-radius: 6px;
  border: var(--border) solid var(--border-soft); background: var(--surface-input); cursor: pointer;
}
.mc-line input[type="checkbox"] { flex: none; margin: 0 12px 0 0; }
.mc-line select, .mc-line .sx-select { flex: none; width: 140px; }
.mc-line select, .mc-line .sx-select-button { height: 36px; }
.mc-picture { cursor: default; }
.mc-picture-tools { display: inline-flex; align-items: center; gap: var(--sp-4); }
.mc-thumb-wrap { display: inline-flex; align-items: center; gap: var(--sp-2); }
.mc-thumb { display: block; width: 63px; height: 22px; object-fit: cover; border-radius: 3px; border: var(--border) solid var(--border-soft); }
.mc-picture .btn { height: 32px; padding: 0 var(--sp-6); }
.mc-foot { margin: var(--sp-4) 0 0; font: var(--type-meta); color: var(--text-label); }
.mc-presets { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-5); margin: var(--sp-6) 0; }
button.mc-preset, button.mc-preset:hover {
  flex-direction: column; align-items: stretch; height: auto; padding: var(--sp-4); gap: var(--sp-4);
  background: transparent; color: var(--text-body); font: var(--type-label);
  border: var(--border) solid var(--border-soft); border-radius: var(--radius-sm);
}
button.mc-preset:hover { background: var(--surface-raised); color: var(--fg-1); }
button.mc-preset[aria-pressed="true"] { border-color: var(--accent); color: var(--fg-1); }
.mc-preset-card { position: relative; display: block; aspect-ratio: 1200 / 420; border-radius: 3px; overflow: hidden; background: var(--p-body); }
.mc-preset-card i { position: absolute; left: 0; right: 0; top: 0; height: 22%; background: var(--p-bar); }
.mc-preset-card b { position: absolute; top: 56%; width: 20%; height: 10%; border-radius: 2px; background: var(--p-ink); }
.mc-preset-card b:first-of-type { left: 11%; }
.mc-preset-card b:last-of-type { right: 11%; }
button.mc-reset { width: 100%; margin-bottom: var(--sp-4); }

/* A phone reads each wide table row as a card. The column headings are copied
   into data-label by mobile-tables.js, so every value keeps its meaning. */
@media (max-width: 639px) {
  .sx-table-wrap:has(.sx-mobile-cards), .scroll:has(.sx-mobile-cards) {
    overflow-x: visible;
    max-height: none;
  }
  table.sx-mobile-cards, table.sx-mobile-cards tbody { display: block; width: 100%; min-width: 0 !important; }
  table.sx-mobile-cards thead { display: block; }
  table.sx-mobile-cards thead tr:not(.sumrow), table.sx-mobile-cards tr[data-mobile-head] { display: none; }
  table.sx-mobile-cards tbody tr:not([hidden]) {
    display: flex; flex-direction: column; min-width: 0;
    padding: var(--sp-6) 0; border-bottom: var(--rule-soft);
  }
  table.sx-mobile-cards tbody tr[hidden] { display: none; }
  table.sx-mobile-cards tbody tr[data-mobile-group] { display: block; color: var(--text-label); }
  table.sx-mobile-cards thead tr.sumrow { display: flex; flex-direction: column; padding: var(--sp-6) 0; border-bottom: var(--rule-soft); }
  table.sx-mobile-cards tbody td, table.sx-mobile-cards tbody th, table.sx-mobile-cards thead tr.sumrow th {
    display: flex; justify-content: space-between; align-items: center;
    gap: var(--sp-6); width: 100% !important; min-width: 0 !important;
    padding: var(--sp-3) 0; border: 0; text-align: right;
    white-space: normal; overflow-wrap: anywhere;
  }
  table.sx-mobile-cards tbody td::before, table.sx-mobile-cards thead tr.sumrow th::before {
    content: attr(data-label); flex: none; max-width: 45%;
    color: var(--text-label); font: var(--type-meta); text-align: left;
  }
  table.sx-mobile-cards tbody td[data-mobile-id] { order: -3; }
  table.sx-mobile-cards tbody td[data-mobile-status] { order: -2; }
  table.sx-mobile-cards tbody td[data-mobile-title] { order: -1; color: var(--text-title); font-weight: 700; }
  table.sx-mobile-cards tbody td[colspan], table.sx-mobile-cards tbody th[colspan] { display: block; text-align: left; }
  table.sx-mobile-cards tbody td[colspan]::before { display: none; }
  table.sx-mobile-cards tbody td:has(.ctl) { display: block; text-align: left; }
  table.sx-mobile-cards tbody td:has(.ctl)::before { display: block; max-width: none; margin-bottom: var(--sp-3); }
  table.sx-mobile-cards tbody td:has(> .tag) { display: block; text-align: left; }
  table.sx-mobile-cards tbody td:has(> .tag)::before { display: block; max-width: none; margin-bottom: var(--sp-3); }
  table.sx-mobile-cards tbody td .ctl { flex-wrap: wrap; max-width: 100%; }
  table.sx-mobile-cards thead tr.sumrow th:first-child { color: var(--text-title); font-weight: 700; }
  table.sx-mobile-cards thead tr.sumrow th:first-child::before,
  table.sx-mobile-cards tbody tr[data-mobile-group] th::before { display: none; }
}

.card .n { white-space: nowrap; }
@media (max-width: 639px) { .card .n { font-size: min(30px, 8vw); } }

@media (pointer: coarse) {
  :is(button, a[href], summary, input:not([type="hidden"]), select, textarea, label.sx-switch, label.sx-pick-addbox, label.btn) {
    min-width: 48px; min-height: 48px;
  }
  :is(input, select, textarea) { font-size: max(16px, var(--fs-body, 16px)); }
  :is(input[type="checkbox"], input[type="radio"]) { width: 48px; height: 48px; flex: none; }
  summary { display: flex; align-items: center; }
  a[href]:not(.card):not(.sx-brand):not(.sx-thread) { display: inline-flex; align-items: center; }
  button.tag-x, td button.tag-x { height: 48px; min-width: 48px; margin-block: 0; padding: 0; justify-content: center; }
}

@media (max-width: 639px) {
  /* The draggable name handles sit near the canvas edge. Keep their full touch
     area outside the preview without changing the preview's aspect ratio. */
  .mc-stage {
    width: calc(100vw - 32px); max-width: none;
    margin-left: calc(50% - (100vw - 32px) / 2);
    margin-bottom: 28px; overflow: visible;
  }
  .mc-img { border-radius: var(--radius-sm); }
}

/* Felles valpil: nettlesaren si pil kan ikkje flyttast med padding. */
:root { --valpil: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e6e7e9' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); }
[data-theme="light"] { --valpil: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23343a40' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); }
select:not([multiple]):not([size]),
select:not([multiple])[size="1"] {
  appearance: none !important;
  background-image: var(--valpil) !important;
  background-position: right 12px center !important;
  background-repeat: no-repeat !important;
  background-size: 16px 16px !important;
  padding-inline: 12px 40px !important;
}
.gw-preview { margin-top: var(--sp-6); }
