/* The admin console's stylesheet. One file, from 'self', since the CSP (S1)
   allows no inline style; no JavaScript anywhere (A3).

   ---- Fonts ----

   The game's own faces (A2), copied byte for byte into static/fonts and served
   by the console at fixed /fonts/ routes; font-src 'self' is what keeps them
   local. `swap`, so text is never invisible while a face loads. */
@font-face { font-family: "Cinzel"; font-style: normal; font-weight: 600; font-display: swap; src: url("/fonts/Cinzel-600.woff2") format("woff2"); }
@font-face { font-family: "Cinzel"; font-style: normal; font-weight: 700; font-display: swap; src: url("/fonts/Cinzel-700.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap; src: url("/fonts/Inter-400.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/Inter-500.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap; src: url("/fonts/Inter-600.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 700; font-display: swap; src: url("/fonts/Inter-700.woff2") format("woff2"); }

/* ---- Tokens ----

   Torchlit, the game's theme (A2). The page tokens below are copied from
   web/src/styles/app.css — its :root, and its
   `@media (prefers-color-scheme: light)` block — and must be kept in step with
   it by hand. Not its [data-theme] selectors: the console has no theme toggle,
   which would need script, so it follows the system and nothing else.

   --danger and --danger-ink are the console's own (A6): a crimson, distinct in
   hue from the ember --accent and from --red (an orange), for the actions that
   cannot be undone or that reach a player. theme_test.go holds every
   text/ground pair here to 4.5:1 in both schemes, and the danger hue apart. */
:root {
  --bg: #14110d;
  --panel: #1f1a14;
  --ink: #efe6d4;
  --ink-dim: #b5a68c;
  --line: #3a3127;
  /* A control's edge: 3:1 on both grounds, which --line deliberately is not. */
  --line-strong: #806b54;
  --accent: #f2a541;
  --accent-ink: #1a1208;
  /* Status text. The Okabe-Ito hues are tuned for fills; these are the
     versions that hold 4.5:1 as text on the page and panel grounds. */
  --error-ink: #e0662a;
  --warn-ink: var(--orange);
  --ok-ink: var(--green);
  --info-ink: #56b4e9;
  --danger: #ff6b85;
  --danger-ink: #1a0a0e;

  --font-display: Cinzel, Georgia, serif;
  --font-body: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Okabe-Ito, as the game: the chart series and the lock tints, so a lock
     looks the same in both consoles. */
  --blue: #0072b2;
  --orange: #e69f00;
  --green: #009e73;
  --red: #d55e00;
  --purple: #cc79a7;

  color-scheme: dark;
}
/* Torchlit light: parchment, the ember darkened until it holds 4.5:1. */
@media (prefers-color-scheme: light) {
  :root {
    --bg: #f3ead8;
    --panel: #fbf6ec;
    --ink: #2a2118;
    --ink-dim: #5e4f3c;
    --line: #d8c9ad;
    --line-strong: #857257;
    --accent: #8f4f00;
    --accent-ink: #fff8ec;
    --error-ink: #a33f00;
    --warn-ink: #7f5300;
    --ok-ink: #006b4c;
    --info-ink: #00629a;
    --danger: #b0213f;
    --danger-ink: #fff8ec;
    color-scheme: light;
  }
}
/* Charts.
   Sized by their viewBox rather than by pixels, so the geometry computed in
   charts.go stays the only place any of it lives and the card decides the
   width. The marks are thin and the axis is a hairline one shade off the
   surface: a dashed grid reads as a threshold when it is only a grid. */
.chart { width: 100%; height: auto; display: block; margin: 0.4rem 0 0.6rem; overflow: visible; }
.chart--channels { margin-bottom: 0.9rem; }
.chart__label { fill: var(--ink); font-size: 13px; dominant-baseline: middle; }
.chart__value { fill: var(--ink-dim); font-size: 12px; font-variant-numeric: tabular-nums; }
.chart__tick  { fill: var(--ink-dim); font-size: 11px; text-anchor: middle; }
/* Dark ink rather than white. These fills are mid-tone by design, and white on
   the lighter two is around 2:1 — under any floor worth having. Near-black
   clears 4.4:1 on the darkest of the four and more on the rest. */
.chart__inside { fill: #14161a; font-size: 12px; font-weight: 600; }
.chart__value--zero { opacity: 0.45; }
.chart__axis  { stroke: var(--line); stroke-width: 1; }
.chart__line  { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* A surface-coloured ring, so overlapping markers stay countable without a
   border drawn around every mark. */
.chart__dot   { stroke: var(--panel); stroke-width: 2; }

/* A legend is always present for two or more series, so identity never rests
   on colour alone. */
/* The swatch and its label are one thing, so they do not wrap apart. */
.key-cell { white-space: nowrap; }
.key {
  display: inline-block; width: 0.7rem; height: 0.7rem;
  border-radius: 0.2rem; margin-right: 0.25rem; vertical-align: -1px;
}
.key--guest, .key--arrived, .key--ads { background: var(--blue); }
.key--user, .key--search { background: var(--green); }
.key--moved, .key--referral { background: var(--orange); }
.key--direct { background: var(--purple); }

/* Where players are: the map beside its list, stacked on a narrow screen. The
   outlines are the surface's hairline colour, and a country with runs is the
   first series colour at a log-scaled opacity, so the shading is one hue. */
.geo { display: grid; gap: 1rem; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: start; }
@media (max-width: 52rem) { .geo { grid-template-columns: minmax(0, 1fr); } }
.worldmap { width: 100%; height: auto; display: block; }
.worldmap path { fill: var(--line); stroke: var(--panel); stroke-width: 0.4; }
.worldmap path.worldmap__on { fill: var(--blue); }
.worldmap path:hover { stroke: var(--ink); stroke-width: 1; }
.key--geo { fill: var(--blue); }
.geo__head { font-size: 1rem; margin: 0 0 0.4rem; }
.geo__list { list-style: none; margin: 0 0 0.4rem; padding: 0; display: grid; gap: 0.4rem; }
.geo__row {
  display: grid; grid-template-columns: minmax(0, 9rem) minmax(2rem, 1fr) auto;
  gap: 0.5rem; align-items: center; font-variant-numeric: tabular-nums;
}
.geo__name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.geo__track { display: block; width: 100%; height: 0.6rem; background: var(--bg); border-radius: 0.2rem; overflow: hidden; }
.geo__fill { fill: var(--blue); }
.geo__val { text-align: right; white-space: nowrap; }

/* The one number a card leads with. Proportional figures, not tabular: equal
   widths make a display-size number look loose. */
.num--hero { font-size: 2.2rem; font-weight: 700; margin: 0.2rem 0; font-variant-numeric: proportional-nums; }

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 15px/1.5 var(--font-body);
}
main { max-width: 68rem; margin: 0 auto; padding: 1rem; }
a { color: inherit; text-decoration-color: var(--line-strong); text-underline-offset: 0.15em; }
a:hover { color: var(--accent); text-decoration-color: currentColor; }
/* Headings in the display face. Cinzel is all small capitals, so it gets a
   touch of tracking and stays on headings: a paragraph of it is hard going. */
h1, h2, h3 { margin: 0 0 0.5rem; font-family: var(--font-display); font-weight: 700; letter-spacing: 0.02em; line-height: 1.25; }
h1 { font-size: 1.5rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; font-weight: 600; }
code, kbd, pre { font-family: var(--font-mono); font-size: 0.9em; }
code { background: var(--bg); border: 1px solid var(--line); border-radius: 0.25rem; padding: 0 0.25rem; }

/* A visible focus ring everywhere, in the ember: it holds 8:1 on the dark
   grounds and 5:1 on parchment, where the palette's blue would not. */
:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* ---- The frame ----

   The top bar is always there, at every width (A7): the accent stripe along
   its top, the console's name, the Admin tag, the phone's Menu, who is signed
   in and Sign out. It stays put as the page scrolls. */
.bar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: 0.4rem 0.75rem; flex-wrap: wrap;
  padding: 0.5rem 1rem; background: var(--panel);
  border-top: 4px solid var(--accent); border-bottom: 1px solid var(--line);
}
.bar__home {
  font-family: var(--font-display); font-weight: 700; letter-spacing: 0.02em;
  color: var(--accent); text-decoration: none;
}
/* The Admin tag: a filled ember pill, so it reads as a label and not a link. */
.bar__tag {
  padding: 0.05rem 0.55rem; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-size: 0.75rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
}
.bar__who { margin-left: auto; color: var(--ink-dim); font-size: 0.9em; overflow-wrap: anywhere; }
.inline-form { margin: 0; }

/* The sidebar (A1), which on a phone is the menu: a <details> whose summary
   is the Menu button and whose nav drops down under the top bar, over the
   page. No script; the browser opens and closes it. */
.menu > summary {
  list-style: none; cursor: pointer;
  padding: 0.3rem 0.75rem; border: 1px solid var(--line-strong); border-radius: 0.375rem;
  color: var(--ink); font-weight: 600;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary:hover, .menu[open] > summary { border-color: var(--accent); }
.nav {
  position: absolute; top: 100%; left: 0; right: 0;
  max-height: calc(100vh - 6rem); overflow-y: auto;
  padding: 0.75rem 1rem 1rem; background: var(--panel); border-bottom: 1px solid var(--line);
}
.nav__group {
  margin: 1rem 0 0.3rem; color: var(--ink-dim);
  font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase;
}
.nav__group:first-child { margin-top: 0; }
.nav__list { list-style: none; margin: 0; padding: 0; }
.nav__list a {
  display: block; padding: 0.35rem 0.6rem; border-left: 3px solid transparent;
  border-radius: 0 0.3rem 0.3rem 0; color: var(--ink); text-decoration: none;
}
.nav__list a:hover { color: var(--accent); background: var(--bg); }
/* Where you are: the ember, on the page ground, with an edge. */
.nav__list a[aria-current="page"] {
  color: var(--accent); background: var(--bg); border-left-color: var(--accent); font-weight: 600;
}

/* From 1024px the nav is a fixed column on the left, shown whether or not the
   details is open, and the summary is hidden: one nav in the markup at every
   width. A closed <details> hides its content through its ::details-content
   part (content-visibility: hidden in the UA stylesheet), which an author rule
   overrides; Chromium 131, Safari 18.4 and Firefox 143 have it. A browser
   without it fails @supports and keeps the phone layout at every width, Menu
   and all, rather than a sidebar it could not open. The bar is a fixed height
   here so the column can start right under it. */
@supports selector(::details-content) {
  @media (min-width: 1024px) {
    .bar { height: 3.5rem; flex-wrap: nowrap; }
    .menu { margin-right: -0.75rem; }
    .menu > summary { display: none; }
    .menu::details-content { content-visibility: visible; }
    .nav {
      position: fixed; top: 3.5rem; bottom: 0; left: 0; right: auto; width: 14rem;
      max-height: none; padding: 1rem 0.75rem;
      border-bottom: 0; border-right: 1px solid var(--line);
    }
    .page--nav { margin-left: 14rem; }
  }
}

.card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 0.5rem; padding: 0.9rem 1rem; margin-bottom: 1rem;
}

/* Buttons, as the game's: outlined by default, no fill, the page's ink and
   the control edge, the accent on hover. .secondary is the same thing said
   explicitly. .primary is the one main action of a form, filled with the
   ember. A quiet one is outlined in the hairline and dimmed. A danger one
   (A6) is crimson text and edge, filled crimson under the pointer: the
   loudest of them, so an irreversible action never reads as the screen's
   ordinary next step. */
button, .button {
  font: inherit; font-weight: 500; line-height: 1.3;
  padding: 0.4rem 0.75rem; border-radius: 0.375rem;
  border: 1px solid var(--line-strong); background: transparent; color: var(--ink);
  cursor: pointer; text-decoration: none; display: inline-block;
}
button:hover:not(:disabled), .button:hover { color: var(--ink); border-color: var(--accent); }
button:focus-visible, .button:focus-visible { border-color: var(--accent); }
button.primary, .button.primary {
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent); font-weight: 600;
}
button.primary:hover:not(:disabled), .button.primary:hover { color: var(--accent-ink); filter: brightness(1.08); }
button.quiet, .button.quiet { color: var(--ink-dim); border-color: var(--line); }
button.quiet:hover:not(:disabled), .button.quiet:hover { color: var(--ink); border-color: var(--line-strong); }
button.danger, .button.danger {
  color: var(--danger); border-color: var(--danger); font-weight: 600;
}
button.danger:hover:not(:disabled), .button.danger:hover,
button.danger:focus-visible, .button.danger:focus-visible {
  background: var(--danger); color: var(--danger-ink); border-color: var(--danger);
}
button:disabled { opacity: 0.45; cursor: not-allowed; }

/* The Listen page's mood buttons: the one that is playing is pressed, and
   stays filled under the pointer, where a button's hover would put the
   page's ink on the ember. */
.listen-mood { margin: 0 0.25rem 0.35rem 0; }
button.listen-mood[aria-pressed=true],
button.listen-mood[aria-pressed=true]:hover:not(:disabled) {
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
}

input[type=text], textarea, select {
  font: inherit; width: 100%; padding: 0.4rem 0.55rem; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line-strong); border-radius: 0.375rem;
}
input[type=text]:focus, textarea:focus, select:focus { border-color: var(--accent); }
input::placeholder, textarea::placeholder { color: var(--ink-dim); opacity: 1; }
input[type=checkbox] { accent-color: var(--accent); width: 1rem; height: 1rem; vertical-align: -2px; }
label { display: block; font-weight: 600; margin-top: 0.6rem; }
.hint { color: var(--ink-dim); font-size: 0.9em; margin: 0.15rem 0 0; }
.quiet { color: var(--ink-dim); }

/* Two numbers that only mean anything together, on one line. The inputs are
   full width by default, so each needs a flex basis rather than a row of its
   own, and min-width:0 so they can shrink below their content. */
.pair { display: flex; align-items: center; gap: 0.5rem; }
.pair input[type=text] { flex: 1 1 0; min-width: 0; }

label.inline { display: inline-block; font-weight: 400; margin: 0 0.6rem 0.2rem 0; }
label.inline input[type=text] { width: 4.5em; }
table.tuning-classes caption { caption-side: top; text-align: left; font-weight: 600; margin-top: 0.6rem; }

table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 0.45rem 0.55rem; border-bottom: 1px solid var(--line); vertical-align: top; }
th { color: var(--ink-dim); font-weight: 600; font-size: 0.9em; }
thead th { border-bottom-color: var(--line-strong); }
td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Status is never carried by colour alone: every pill spells out its state. */
.pill {
  display: inline-block; padding: 0.05rem 0.45rem; border-radius: 1rem;
  font-size: 0.85em; border: 1px solid currentColor;
}
/* Status text uses the -ink tokens, never the bare hues: --red is under
   4.5:1 on the dark panel and far under it on parchment. */
.pill--open { color: var(--ok-ink); }
.pill--locked { color: var(--ink-dim); }
.pill--missing { color: var(--error-ink); }

.flash {
  padding: 0.6rem 0.8rem; border-radius: 0.4rem; border: 1px solid currentColor;
  border-left-width: 4px; background: var(--panel); margin: 0 0 1rem;
}
.flash--ok { color: var(--ok-ink); }
.flash--error { color: var(--error-ink); }

/* A grid of cells rather than a <pre>, so a lock can carry its own numeral and
   tint. One span per cell: a 40x40 floor is 1600 of them, the same order as the
   player's own map, on a page a handful of people open. */
.map {
  font-family: var(--font-mono);
  font-size: 12px; line-height: 1; overflow-x: auto;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: 0.35rem; padding: 0.6rem; margin: 0;
  width: max-content; max-width: 100%;
}
.map__row { display: flex; }
.map .c {
  display: inline-grid; place-items: center;
  width: 1ch; height: 1.15em; flex: 0 0 auto;
}
/* Emoji are wider than a monospace cell, so the grid gets a wider track rather
   than letting the glyphs decide and shear every row out of line. */
.map--emoji .c { width: 1.25em; font-size: 11px; }

.map .c-wall { color: var(--line); }
.map .c-door { color: var(--ink-dim); }
.map .c-entry, .map .c-way { color: var(--ok-ink); }

/* The lock language, shared with the game: numeral plus tint, never tint alone,
   and the door wears a plate the key does not. */
.map .c-key { font-weight: 700; color: var(--lock); }
.map .c-lock {
  font-weight: 700; color: var(--bg); background: var(--lock);
  border-radius: 2px;
}
.map .l1 { --lock: var(--orange); }
.map .l2 { --lock: var(--blue); }
.map .l3 { --lock: var(--purple); }

.toggle { font-size: 0.75rem; font-weight: 400; margin-left: 0.6rem; }
.matches { list-style: none; margin: 0.75rem 0 0; padding: 0; }
.matches li { padding: 0.35rem 0; border-bottom: 1px solid var(--line); }
.matches li:last-child { border-bottom: 0; }
.legend { color: var(--ink-dim); font-size: 0.9em; }
.grid2 { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
.muted { color: var(--ink-dim); }
.foot {
  max-width: 68rem; margin: 0 auto; padding: 1rem; border-top: 1px solid var(--line);
  color: var(--ink-dim); font-size: 0.85em; font-variant-numeric: tabular-nums;
}
/* A table wider than its card scrolls inside the card rather than widening the
   page: the classes page's by-day table has a column per character played. */
.scroll { overflow-x: auto; }
.scroll th, .scroll td { white-space: nowrap; }
/* A number's heading sits over the number. Scoped rather than global so the
   pages that already lay out their headings another way are left as they are. */
.numeric th.num { text-align: right; }
.legend__item { margin-right: 0.75rem; white-space: nowrap; }
.mono { font-family: var(--font-mono); font-size: 0.95em; }

/* What used to be inline style attributes, which the console's CSP (S1)
   refuses: style-src is 'self' only. */
.friends__row { display: flex; gap: .5rem; }
.flash--spaced { margin-top: .75rem; }

/* The weekly adventure's screens. Model-written text keeps its line breaks
   as text (S6): pre-line, never markup. */
.prose { white-space: pre-line; }
.flag { color: var(--error-ink); font-size: 0.9em; margin-top: 0.2rem; }
.facts { display: grid; grid-template-columns: max-content 1fr; gap: 0.2rem 0.8rem; margin: 0.4rem 0; }
.facts dt { color: var(--ink-dim); }
.facts dd { margin: 0; }
.scenes { display: grid; gap: 0.8rem; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); margin: 0.6rem 0; }
.scene { margin: 0; }
.scene img { display: block; width: 100%; height: auto; border: 1px solid var(--line); border-radius: 0.3rem; background: var(--bg); }
.scene__none { display: flex; align-items: center; justify-content: center; aspect-ratio: 8 / 5;
  border: 1px dashed var(--line); border-radius: 0.3rem; color: var(--ink-dim); font-size: 0.9em; }
.scene figcaption { font-size: 0.85em; margin-top: 0.3rem; }
