/* The palette and the chrome around the board.
 *
 * Small on purpose. `board.css` is the app; this is what an app on this
 * deployment looks like — a page that scrolls under a header that does not,
 * three theme buttons, a gate and a footer. A guest who has never seen this
 * before opens exactly one page, and everything here is on it.
 */

:root {
  --bg: #111417; --panel: #191d21; --line: #262c31;
  --text: #e7ecef; --dim: #97a3ad; --accent: #7fb3ff; --warn: #ffb86b;
  color-scheme: dark;
}

/* Three states, not two. No `data-theme` means "follow the system", which is
   what most people want and what the page starts as; the attribute is only
   written when somebody chooses. The light palette therefore appears twice —
   once for the system preference and once for an explicit choice — because
   CSS has no way to name a block and reuse it.
   `color-scheme` moves with the palette, or the browser draws its own form
   controls and scrollbars for the wrong one. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #fbfbfa; --panel: #fff; --line: #e3e5e8; --text: #1b1f23;
    --dim: #6a737d; --accent: #1f6feb; --warn: #b35900;
    color-scheme: light;
  }
}
:root[data-theme="light"] {
  --bg: #fbfbfa; --panel: #fff; --line: #e3e5e8; --text: #1b1f23;
  --dim: #6a737d; --accent: #1f6feb; --warn: #b35900;
  color-scheme: light;
}
:root[data-theme="dark"] {
  --bg: #111417; --panel: #191d21; --line: #262c31;
  --text: #e7ecef; --dim: #97a3ad; --accent: #7fb3ff; --warn: #ffb86b;
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* `hidden` means hidden, everywhere.
 *
 * The attribute's effect is a *user-agent* rule — `[hidden] { display: none }`
 * — so any author rule that sets `display` beats it, silently. That is a bug
 * with no symptom to search for: the JavaScript sets `hidden` correctly, the
 * element renders anyway, and nothing in either file is wrong on its own.
 * `!important` because the whole point is outranking a rule written later and
 * more specifically by somebody who was not thinking about this. */
[hidden] { display: none !important; }

body {
  margin: 0; background: var(--bg); color: var(--text);
  font-family: ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif;
  line-height: 1.55;
  font-size: 1.0625rem;
  display: flex; flex-direction: column;
  /* A header that stays put and a page that scrolls under it. `dvh` rather
     than `vh` so a phone's collapsing address bar does not leave a strip of
     the board below the fold with nothing to scroll it into view. */
  height: 100dvh;
  overflow: hidden;
}

header {
  display: flex; gap: .6rem; align-items: center; padding: .7rem 1rem;
  border-bottom: 1px solid var(--line);
  padding-top: max(.7rem, env(safe-area-inset-top));
  flex: none;
}
header strong { font-size: 1rem; }

/* Three states, and the default is one of them: grey is "nothing to say".
   `warn` is what a project at risk or stuck paints, and it was missing from
   this file for a release — `board.js` was setting the class all along, so
   every stuck project rendered the same grey as a healthy one and the health
   dot quietly meant nothing. */
.dot { width: .55rem; height: .55rem; border-radius: 50%; background: var(--dim); flex: none; }
.dot.live { background: #3fb950; }
.dot.warn { background: var(--warn); }

button {
  background: var(--panel); color: var(--text); border: 1px solid var(--line);
  border-radius: .5rem; padding: .35rem .7rem; cursor: pointer;
  /* rem, not inherit. The chrome is a fixed frame around a page of text. */
  font-family: inherit; font-size: .95rem;
}
button:hover:not(:disabled) { border-color: var(--dim); }
button:disabled { opacity: .5; cursor: default; }
button.on { border-color: var(--accent); color: var(--accent); }
button.quiet { background: none; border-color: transparent; color: var(--dim); }
button.quiet:hover:not(:disabled) { color: var(--text); border-color: var(--line); }

input, select, textarea {
  background: var(--bg); color: var(--text); border: 1px solid var(--line);
  border-radius: .5rem; padding: .4rem .6rem;
  font-family: inherit; font-size: 1rem; width: 100%;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

.meta { color: var(--dim); font-size: .9rem; margin: .3rem 0; }

/* ------------------------------------------------------------------ the gate */

/* Covers everything, because what is behind it is a shell with no data in it
   and showing a board's worth of empty frames behind a sign-in card reads as a
   page that failed to load. */
#gate, #pj-join {
  position: fixed; inset: 0; z-index: 20;
  background: var(--bg);
  display: flex; align-items: center; justify-content: center; padding: 1.5rem;
}
.gate-card {
  background: var(--panel); border: 1px solid var(--line); border-radius: .8rem;
  padding: 1.6rem; max-width: 26rem; width: 100%;
}
.gate-card h2 { margin: 0 0 .4rem; font-size: 1.25rem; }
.gate-card button { margin-top: .8rem; }
.gate-card form { display: flex; gap: .5rem; margin-top: .8rem; }
.gate-card form button { margin-top: 0; flex: none; }
#gate-error, #pj-join-error { color: var(--warn); }
/* A line of text that behaves like a link, because that is what it is for —
   the exception to the passkey, not a second equal choice. */
#gate-code-show {
  background: none; border: none; color: var(--accent); padding: .4rem 0;
  text-decoration: underline; cursor: pointer; font-size: .9rem;
}

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

/* The strip along the bottom: what you are running, and whose it is. It is the
   last child of a column-flex body, so `flex: none` is what keeps it a strip
   rather than something the board can push off the screen. */
#foot {
  flex: none;
  display: flex; flex-wrap: wrap; gap: .1rem .8rem;
  padding: .35rem 1rem;
  padding-bottom: max(.35rem, env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  color: var(--dim); font-size: .75rem; line-height: 1.4;
}
/* One at each end, rather than both in the middle. There is no settled
   convention for a version beside a notice — web footers centre a copyright,
   application status bars pin their readings to the edges — and centred, the
   two read as one sentence that does not parse. At the margins each is its own
   fact: what you are running on the left, whose it is on the right.

   `margin-left: auto` on the notice rather than `justify-content:
   space-between` on the row, because the row is not always two things. To a
   caller with no session `/api/health` deliberately answers without a version,
   and so does a health request that never came back — and `space-between` with
   one item has nothing to push against, so it drops the survivor on the left.
   This keeps each side its own whichever one is missing. */
#foot .foot-notice { margin-left: auto; }

/* --------------------------------------------------------------- dialogues */

dialog {
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: .8rem;
  padding: 1.2rem; max-width: 32rem; width: calc(100% - 2rem);
}
dialog::backdrop { background: rgba(0, 0, 0, .5); }
dialog h2 { margin: 0 0 .6rem; font-size: 1.15rem; }
/* The primary action is filled, so the thing you opened the dialog to do is
   the thing your eye lands on. Everything that is not that carries `quiet`.
   Note the specificity: this beats a bare class selector, so a row of small
   buttons inside a dialog has to be scoped by the dialog's id to escape it —
   `board.css` does exactly that, twice, and says why. */
dialog button:not(.quiet) {
  background: var(--accent); border-color: var(--accent); color: var(--bg);
}
dialog button:not(.quiet):hover:not(:disabled) { filter: brightness(1.1); }
