/* NGS — ngs.telestrator.com
   Three visual languages in one sheet:
     1. the admin chrome (dashboard, games, plays, settings) — plain and dense
     2. .board-*  the LIVE THIS PLAY strip   — the default board theme (?theme=strip)
     3. .ps-*     the Play Stats panel       — the alternate board theme (?theme=panel);
                  its ?theme=blocks variant adds the .ps-cols/.ps-unit blocks (#10168)
   All board themes are live and selectable per URL (?theme=), because each was
   approved by different people. They are namespaced apart so neither can drift into
   the other, or into the admin pages. */

:root {
  --bg: #12151a;
  --panel: #1a1e25;
  --panel-2: #21262f;
  --line: #2c333e;
  --ink: #e7ebf0;
  --ink-dim: #9aa5b4;
  --ink-faint: #6b7686;
  --accent: #76e034;
  --accent-dim: #4f9c24;
  --red: #e5484d;
  --amber: #f5a524;
  --radius: 8px;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- top bar ---------- */
.topbar {
  display: flex; align-items: center; gap: 24px;
  padding: 0 20px; height: 56px;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.brand { font-weight: 700; font-size: 17px; color: var(--ink); letter-spacing: .5px; }
.brand span { color: var(--ink-faint); font-weight: 400; font-size: 13px; }
.brand:hover { text-decoration: none; }
.topbar nav { display: flex; gap: 4px; flex: 1; }
.topbar nav a {
  padding: 7px 12px; border-radius: 6px; color: var(--ink-dim); font-size: 14px;
}
.topbar nav a:hover { background: var(--panel-2); color: var(--ink); text-decoration: none; }
.topbar nav a.on { background: var(--panel-2); color: var(--accent); }
.topbar-right { display: flex; align-items: center; gap: 10px; }
.who { display: flex; flex-direction: column; line-height: 1.15; font-size: 13px; color: var(--ink); }
.who em { font-style: normal; font-size: 11px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .5px; }
.inline { display: inline; margin: 0; }

/* ---------- layout ---------- */
.wrap { max-width: 1180px; margin: 0 auto; padding: 24px 20px 60px; }
.wrap-narrow { max-width: 420px; padding-top: 80px; }
.foot {
  display: flex; justify-content: space-between;
  max-width: 1180px; margin: 0 auto; padding: 16px 20px 30px;
  color: var(--ink-faint); font-size: 12px;
}

h1 { font-size: 22px; margin: 0 0 4px; font-weight: 650; }
h2 { font-size: 16px; margin: 28px 0 10px; font-weight: 650; }
.sub { color: var(--ink-dim); font-size: 13px; margin: 0 0 20px; }

/* ---------- cards / panels ---------- */
.card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 16px 18px; margin-bottom: 16px;
}
.card h3 { margin: 0 0 12px; font-size: 14px; font-weight: 650; color: var(--ink-dim);
  text-transform: uppercase; letter-spacing: .6px; }

.grid { display: grid; gap: 14px; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }

.stat { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; }
.stat .k { font-size: 11px; text-transform: uppercase; letter-spacing: .8px; color: var(--ink-faint); }
.stat .v { font-size: 24px; font-weight: 700; margin-top: 4px; }
.stat .v.ok { color: var(--accent); }
.stat .v.bad { color: var(--red); }
.stat .v.warn { color: var(--amber); }
.stat .n { font-size: 12px; color: var(--ink-faint); margin-top: 2px; }

/* ---------- tables ---------- */
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); }
th { color: var(--ink-faint); font-size: 11px; text-transform: uppercase; letter-spacing: .6px; font-weight: 600; }
tbody tr:hover { background: var(--panel-2); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); font-size: 13px; }

/* ---------- forms / buttons ---------- */
label { display: block; font-size: 12px; color: var(--ink-dim); margin: 12px 0 4px; text-transform: uppercase; letter-spacing: .5px; }
input[type=text], input[type=password], input[type=email], select {
  width: 100%; padding: 9px 11px; background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: 6px; font-size: 14px; font-family: inherit;
}
input:focus, select:focus { outline: 2px solid var(--accent-dim); outline-offset: -1px; }
.btn {
  display: inline-block; padding: 8px 14px; border-radius: 6px; border: 1px solid var(--line);
  background: var(--panel-2); color: var(--ink); font-size: 14px; font-family: inherit;
  cursor: pointer; text-decoration: none;
}
.btn:hover:not(:disabled) { background: var(--line); text-decoration: none; }
/* A disabled button must look disabled: without this a greyed-out Save read as a live one
   that did nothing (preset editor, #10222). The hover rules are guarded rather than
   pointer-events switched off, so a disabled button's title tooltip stays reachable. */
.btn:disabled { opacity: .4; cursor: default; }
.btn-primary { background: var(--accent-dim); border-color: var(--accent-dim); color: #fff; font-weight: 600; }
.btn-primary:hover:not(:disabled) { background: var(--accent); color: #06210a; }
.btn-live { border-color: var(--accent-dim); color: var(--accent); }
.btn-quiet { background: transparent; color: var(--ink-faint); }
.btn-sm { padding: 5px 10px; font-size: 13px; }
.btn-row { display: flex; gap: 10px; margin-top: 18px; align-items: center; }

/* ---------- flash / badges ---------- */
.flash { padding: 11px 14px; border-radius: 6px; margin-bottom: 18px; font-size: 14px; border: 1px solid; }
/* The supervisor banner is toggled by today.php's refresh. .flash does not set `display`,
   so the UA sheet's [hidden] already wins - but only until someone gives .flash a
   `display`, at which point the banner would come back silently and permanently. Two
   classes of specificity, no !important. */
.flash[hidden] { display: none; }
/* Same trap, same cure (#10201): .inline and .btn DO set `display`, so an author-origin
   declaration beats the UA sheet's [hidden] whatever the specificity — the Replays
   page's Replay/Stop swap and its Board link toggle by the attribute. */
.inline[hidden], .btn[hidden] { display: none; }
/* The global `select` is a form-card control: full width, 9px padding, 14px text. In the
   Replays actions cell that fills the cell — pushing the row past the card edge — and stands
   taller than the btn-sm buttons beside it. Same override the preset editor's preview bar uses. */
#replays select { width: auto; padding: 5px 8px; font-size: 13px; vertical-align: middle; }
.flash-success { background: rgba(118,224,52,.10); border-color: var(--accent-dim); color: #b8f088; }
.flash-error { background: rgba(229,72,77,.10); border-color: var(--red); color: #ffb4b4; }

.badge { display: inline-block; padding: 2px 8px; border-radius: 20px; font-size: 11px;
  text-transform: uppercase; letter-spacing: .5px; font-weight: 600; }
.badge-on { background: rgba(118,224,52,.15); color: var(--accent); }
.badge-off { background: var(--panel-2); color: var(--ink-faint); }
.badge-err { background: rgba(229,72,77,.15); color: var(--red); }

.empty { color: var(--ink-faint); padding: 30px; text-align: center; font-size: 14px; }

/* ============================================================
   LIVE THIS PLAY board — matches the ticket 10021 mockup.
   Sized in vw/clamp so it scales to a control-room display.
   ============================================================ */
.board-page {
  background: #000; min-height: 100vh; margin: 0;
  display: flex; align-items: center; justify-content: center; padding: 3vh 2vw;
}
.board { width: 100%; max-width: 1240px; font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

.board-main {
  border: 2px solid #76e034; border-radius: 14px;
  background: #050505; padding: clamp(14px, 2.2vw, 26px) clamp(16px, 2.4vw, 30px);
}
.board-title {
  color: #76e034; font-weight: 800; letter-spacing: .06em;
  font-size: clamp(18px, 2.3vw, 30px); margin: 0 0 clamp(12px, 1.8vw, 22px);
}

.board-stats { display: flex; align-items: stretch; }
.board-stat { flex: 1; padding: 0 clamp(10px, 1.6vw, 22px); min-width: 0; }
.board-stat + .board-stat { border-left: 1px solid #33383c; }
.board-stat:first-child { padding-left: 0; }
.board-stat:last-child { padding-right: 0; }
.board-stat .k {
  color: #9b9b9b; font-size: clamp(9px, .92vw, 13px); line-height: 1.2;
  text-transform: uppercase; letter-spacing: .09em;
  /* A stat name wraps to a second line rather than losing its end: six tiles across
     left "BALL CARRIER TOP SP..." on air (#10210). Every label reserves the two lines
     so the values below stay on one baseline, and a third line is clamped. */
  min-height: 2.4em; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
}
.board-stat .v {
  color: #fff; font-weight: 700; margin-top: clamp(6px, .9vw, 12px);
  font-size: clamp(17px, 2.15vw, 30px); line-height: 1.15;
}
.board-stat .v.dim { color: #5a5a5a; font-weight: 600; }

/* situation line + personnel + substitutions strip */
.board-subs {
  display: flex; flex-wrap: wrap; margin-top: clamp(8px, 1.1vw, 14px);
  border-radius: 10px; overflow: hidden; min-height: clamp(56px, 7vw, 96px);
}
.board-subs.none { display: none; } /* nothing to draw: every cell hidden, or only an absent situation left (#10081, #10216) */
/* hide=subs: no dead space beside a lone personnel cell — with or without the situation
   line above it, so "last cell" rather than "only child". Scoped to the wide layout so it
   cannot outrank the <=720px column rule below (media queries add no specificity). */
@media (min-width: 721px) { .board-subs > .board-personnel:last-child { flex: 1; } }
.board-cell { display: flex; flex-direction: column; justify-content: center; align-items: center;
  padding: clamp(8px, 1.1vw, 15px) clamp(10px, 1.4vw, 18px); }
/* The game situation (#10216): one line across the top of the bar — down and distance at
   the ball, clock, score. Upper-cased here, so the model's line keeps the spelling the
   window rows use. Declared AFTER .board-cell: the element carries both classes at equal
   specificity, so this order is what lets the slimmer padding win. */
.board-situation { flex: 0 0 100%; background: #0b1622; color: #fff; font-weight: 700;
  font-size: clamp(13px, 1.5vw, 21px); letter-spacing: .05em; text-transform: uppercase;
  padding: clamp(6px, .8vw, 10px) clamp(10px, 1.4vw, 18px); border-bottom: 1px solid #1f2d3a; }
.board-personnel { flex: 0 0 34%; background: #121212; color: #fff;
  font-weight: 700; font-size: clamp(15px, 1.9vw, 26px); letter-spacing: .02em; }
.board-personnel.none { color: #4a4a4a; font-weight: 600; font-size: clamp(11px, 1.15vw, 15px);
  text-transform: uppercase; letter-spacing: .08em; text-align: center; }
.board-in  { flex: 1; background: #1f7a2e; }
.board-out { flex: 1; background: #a3221b; }
.board-cell .hd {
  color: #fff; font-size: clamp(10px, 1.02vw, 14px); font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; margin-bottom: 4px; opacity: .95;
}
.board-cell .who {
  color: #fff; font-weight: 700; font-size: clamp(11px, 1.22vw, 17px);
  line-height: 1.32; text-align: center; display: block;
}
.board-cell .none { color: rgba(255,255,255,.55); font-weight: 600; font-size: clamp(10px, 1.05vw, 14px); }

/* status line under the board */
.board-status {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: clamp(8px, 1.2vw, 14px); padding: 0 4px;
  color: #6a6a6a; font-size: clamp(10px, 1vw, 13px);
}
.board-status .live { color: #76e034; font-weight: 700; letter-spacing: .06em; }
.board-status .stale { color: #f5a524; font-weight: 700; letter-spacing: .06em; }
.board-status .down { color: #e5484d; font-weight: 700; letter-spacing: .06em; }
.board-status a { color: #6a6a6a; }

@media (max-width: 720px) {
  .board-stats { flex-wrap: wrap; gap: 14px 0; }
  .board-stat { flex: 0 0 50%; }
  .board-stat + .board-stat { border-left: none; }
  .board-subs { flex-direction: column; }
  .board-situation { flex: none; }
  .board-personnel { flex: none; padding: 12px; }
}

/* Additions since the original mockup: an outcome-tagged tile value (the "passed to"
   tile can read COMPLETE/INCOMPLETE), a demo banner, and a placeholder tile for when
   nothing has data yet. */
.board-demo {
  background: #8a5a00; color: #ffe9b8; text-align: center;
  font-size: clamp(10px, 1.05vw, 13px); font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; padding: 5px 10px; border-radius: 6px;
  margin-bottom: clamp(8px, 1.2vw, 14px);
}
.board-complete   { color: #76e034; }
.board-incomplete { color: #ff3b30; }

/* ============================================================
   Play Stats window — Armstrong's design (#10028).

   A floating panel meant to sit over live video, so it is translucent and dark, and
   every colour is chosen to stay legible over bright footage. Generic on purpose: it
   renders sections of label/value rows, which is the display model ported from the
   Unity tool, so it does not need to know which stats it is showing.
   ============================================================ */
.ps-page {
  background: #0b0d10;
  min-height: 100vh;
  margin: 0;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 4vh 2vw;
}

.ps-window {
  width: 100%;
  max-width: 620px;
  /* Never taller than the screen (#10228): the window is a browser source over video
     and nothing scrolls it, so content past the bottom is clipped at the window's edge
     (the overflow: hidden below) instead of falling below the display, where it is
     simply not on air. 100vh less the page's own 4vh padding top and bottom. What gets
     clipped is whatever the producer's order put last -- Substitutions by default, but a
     preset can put them anywhere -- so this is the backstop; the substitution cap
     (NGS_SUBS_MAX in ngs_display.php) is what keeps a tall list from pushing the
     sections after it off the screen. Not a scrolling body (a scrollbar on air) and not
     a scale-to-fit (unreadable at 3x). */
  max-height: calc(100vh - 8vh);
  background: rgba(18, 20, 23, .93);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 10px 40px rgba(0, 0, 0, .55);
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* Title bar, matching the windowed tool it is ported from. */
.ps-titlebar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(9px, 1.3vw, 14px) 16px;
  background: rgba(44, 48, 54, .96);
}
.ps-title {
  color: #f2f4f7;
  font-size: clamp(15px, 1.7vw, 21px);
  font-weight: 600;
  letter-spacing: .01em;
}
.ps-matchup {
  position: absolute;
  right: 16px;
  color: #8b939e;
  font-size: clamp(10px, 1.05vw, 13px);
  letter-spacing: .06em;
  text-transform: uppercase;
}

/* The game situation as the panel themes draw it (#10230): one line under the title bar,
   in place of the section of rows #10216 put first in the window -- that heading plus six
   rows moved the play section seven lines down, below the crop of a browser source sized
   before it existed, and the play was off air. One line costs the height of one row.
   Upper-cased here, like the strip's cell, so the model keeps the rows' spelling. Parts
   are separated by a glyph drawn in CSS (below), and a line too long for the window ellipses rather
   than wraps: it is one row by contract. `display` is set, so [hidden] is restated (the
   .flash trap above). The parts are separated by a bullet, not the middot the Timeouts
   value already uses between the two teams, so one glyph does not carry two meanings on
   the line (review, #10230). */
.ps-situation {
  display: block;
  padding: clamp(4px, .6vw, 8px) 16px;
  background: #0b1622;
  color: #fff;
  text-align: center;
  font-size: clamp(12px, 1.3vw, 16px);
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ps-situation[hidden] { display: none; }
.ps-sit-part + .ps-sit-part::before { content: "\2022"; margin: 0 .6em; color: #8b939e; }

.ps-demo-banner {
  background: #8a5a00;
  color: #ffe9b8;
  text-align: center;
  font-size: clamp(10px, 1.05vw, 13px);
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 5px 10px;
}

.ps-body { padding: clamp(10px, 1.5vw, 18px) clamp(14px, 2vw, 24px) clamp(16px, 2vw, 24px); }
/* Hidden when the situation banner is the only thing left to draw (#10230): .ps-body sets
   no `display`, so the UA sheet's [hidden] already applies — restated so a later `display`
   on .ps-body cannot bring the padded blank back silently (the .flash trap). */
.ps-body[hidden] { display: none; }

.ps-section { margin-bottom: clamp(10px, 1.5vw, 18px); }
.ps-section:last-child { margin-bottom: 0; }
/* A section whose heading a preset blanked continues the group above it: no gap (#10110). */
.ps-section.ps-continued { margin-top: calc(-1 * clamp(10px, 1.5vw, 18px)); }

/* Cyan section headings, as in the design. */
.ps-heading {
  color: #4fb3e8;
  font-size: clamp(15px, 1.85vw, 23px);
  font-weight: 700;
  margin: 0 0 clamp(2px, .5vw, 6px);
  letter-spacing: .01em;
}

/* Label left, value hard right. The dotted baseline is omitted deliberately - the
   design relies on alignment alone, and a leader line gets noisy over video. */
.ps-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: clamp(1px, .28vw, 4px) 0;
}
.ps-label {
  color: #b6bcc5;
  font-size: clamp(13px, 1.5vw, 19px);
  font-weight: 400;
  white-space: nowrap;
}
.ps-value {
  color: #fff;
  font-size: clamp(13px, 1.5vw, 19px);
  font-weight: 700;
  text-align: right;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Outcome tags, matching the Unity tool's complete/incomplete colours. */
.ps-complete   { color: #46d15e; }
.ps-incomplete { color: #ff3b30; }

/* ---------- blocks layout (?theme=blocks, né columns, #10168) ----------
   The Unity Play Stats tool's relayout (fingerworks 3c8745cf): substitutions as tinted
   In/Out columns, one player per line, and Personnel Changes as side-by-side
   offense/defense blocks. The tints are the Unity window's serialized defaults
   (NGSPlayStatsWindow._subInColor & co.) converted to rgba. */
.ps-cols {
  display: flex;
  flex-wrap: wrap;             /* four sub columns on a narrow board stack rather than clip */
  align-items: flex-start;     /* uneven columns line up at the top, as the Unity layout does */
  gap: clamp(8px, 1.6vw, 20px);
  padding: clamp(1px, .28vw, 4px) 0;
}
.ps-col {
  min-width: 4.5em;            /* the "N → N" floor: a bare "1" already reserves the space */
  padding: 3px 8px 5px;
  border-radius: 5px;
}
.ps-col-hd {
  color: #b6bcc5;
  font-size: clamp(13px, 1.5vw, 19px);
  font-weight: 700;
  white-space: nowrap;
}
.ps-col-p {
  color: #fff;
  font-size: clamp(13px, 1.5vw, 19px);
  font-weight: 400;
  white-space: nowrap;
}
.ps-col-in  { background: rgba(31, 115, 51, .55); }   /* players coming in: green */
.ps-col-out { background: rgba(140, 36, 36, .55); }   /* players going out: red */
/* The Substitutions section's own columns (a direct child of the section; the
   personnel ones sit inside .ps-unit) share the row rather than wrap: four columns
   with one eleven-deep left three on the first line and the fourth orphaned under a
   hole (#10210). The columns still hug their content -- one In list must not become
   a full-width block -- and only when the row would overflow do they give way, each
   in proportion to its width, a name that still cannot fit ellipsed instead of a
   column pushed to the next line. Narrow boards fall back to stacking below. */
.ps-section > .ps-cols { flex-wrap: nowrap; }
.ps-section > .ps-cols > .ps-col { flex: 0 1 auto; min-width: 0; }
.ps-section > .ps-cols > .ps-col .ps-col-hd,
.ps-section > .ps-cols > .ps-col .ps-col-p { overflow: hidden; text-overflow: ellipsis; }

.ps-units {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: clamp(6px, 1.2vw, 12px);
  padding: clamp(1px, .28vw, 4px) 0;
}
.ps-unit { padding: 5px 10px 7px; border-radius: 6px; }
.ps-unit-off { background: rgba(33, 82, 140, .5); }   /* offense: blue */
.ps-unit-def { background: rgba(128, 71, 31, .5); }   /* defense: orange */
.ps-unit-hd {
  color: #fff;
  font-size: clamp(13px, 1.5vw, 19px);
  font-weight: 700;
  white-space: nowrap;
  margin-bottom: 2px;
}
/* A gap, not a dash, after the abbr — so it doesn't read like the "→" in the package change. */
.ps-unit-team { margin-right: 1.2em; }
.ps-unit .ps-cols { gap: clamp(8px, 1.2vw, 16px); padding: 0; }
.ps-unit .ps-col { min-width: 3.5em; padding: 1px 6px 3px; }

@media (max-width: 520px) {
  .ps-section > .ps-cols { flex-wrap: wrap; }
  .ps-section > .ps-cols > .ps-col { flex: 1 1 40%; }
  .ps-matchup { display: none; }
  .ps-row { gap: 10px; }
  .ps-label, .ps-value { font-size: 13px; }
}

/* ---------- previous-play pane (?prev=1, #10175) ----------
   The panel themes gain a second panel beside the live one, the strip a second strip;
   both read in time order like a page — previous left, live right on the panels,
   previous above, live below on the strip (#10241). The stepping controls fade in on
   hover / focus so nothing extra is on air in a browser source, and stay visible while
   the pane is stepped back (.pv-on) so an operator cannot mistake a pinned pane for a
   following one. */
.ps-duo { display: flex; align-items: flex-start; justify-content: center; gap: 20px; width: 100%; max-width: 1280px; }
.ps-duo .ps-window { flex: 1 1 0; min-width: 0; }
@media (max-width: 900px) { .ps-duo { flex-wrap: wrap; } .ps-duo .ps-window { flex: 1 1 100%; } }
.ps-prev .ps-title { color: #8b939e; }

.board-stack { display: flex; flex-direction: column; gap: clamp(14px, 2.4vh, 26px); width: 100%; max-width: 1240px; }
.board-stack .board { width: 100%; }
/* The previous strip must not read as the live one: muted frame, muted title. */
.board-prev .board-main { border-color: #3c434b; }
.board-prev .board-title { color: #9aa5b4; text-transform: uppercase; }
.board-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0 0 clamp(12px, 1.8vw, 22px); }
.board-head .board-title { margin: 0; }

.pv-nav { display: inline-flex; align-items: center; gap: 6px; opacity: 0; transition: opacity .15s; }
.pv-nav.pv-on,
.ps-prev:hover .pv-nav, .ps-prev:focus-within .pv-nav,
.board-prev:hover .pv-nav, .board-prev:focus-within .pv-nav { opacity: 1; }
.ps-titlebar .pv-nav { position: absolute; left: 12px; }
.pv-btn { background: rgba(255, 255, 255, .08); border: 1px solid rgba(255, 255, 255, .18); color: #e7ebf0;
  border-radius: 5px; font: inherit; font-size: 15px; line-height: 1; padding: 3px 10px; cursor: pointer; }
.pv-btn:hover:not(:disabled) { background: rgba(255, 255, 255, .16); }
.pv-btn:disabled { opacity: .35; cursor: default; }
.pv-live { color: #76e034; border-color: rgba(118, 224, 52, .5); font-size: 11px; font-weight: 700;
  letter-spacing: .08em; padding: 5px 10px; }

.btn-demo { border-color: #8a5a00; color: #f5c264; }

/* ---------- board preset editor (#10148) ---------- */
.pe { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 16px; align-items: start; }
.pe-list .card { padding: 12px 14px; }
.pe-items { list-style: none; margin: 0; padding: 0; }
.pe-items a { display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  padding: 6px 8px; border-radius: 6px; color: var(--ink); font-size: 14px; }
.pe-items a:hover { background: var(--panel-2); text-decoration: none; }
.pe-items a.on { background: var(--panel-2); color: var(--accent); }
.pe-items em { font-style: normal; font-size: 11px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .5px; white-space: nowrap; }
.pe-head { padding: 12px 16px; }
.pe-head-row { display: flex; gap: 16px; align-items: flex-end; flex-wrap: wrap; }
.pe-name { flex: 1 1 260px; }
.pe-name label { margin-top: 0; }
.pe-shared { margin: 0 0 9px; }
.pe-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 1px; }
.pe-status { margin: 8px 0 0; min-height: 1.2em; }
.pe-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, 46%); gap: 16px; align-items: start; }
.pe-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin: -4px 0 10px; }
.pe-tab { background: transparent; border: 0; border-bottom: 2px solid transparent; color: var(--ink-dim);
  padding: 8px 12px; font: inherit; font-size: 14px; cursor: pointer; }
.pe-tab.on { color: var(--accent); border-bottom-color: var(--accent); }
.pe-tabnote { margin: 0 0 12px; }
.pe-section { border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 10px; background: var(--bg); }
.pe-section.off { opacity: .55; }
.pe-section-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pe-sec-label { margin: 0; flex: 1 1 auto; }
.pe-sec-label .mono { color: var(--ink-faint); font-size: 12px; }
.pe-title { display: flex; align-items: center; gap: 8px; flex: 1 1 260px; }
.pe-title input[type=text] { padding: 5px 8px; font-size: 13px; }
.pe-title label.check { margin: 0; white-space: nowrap; font-size: 12px; }
.pe-rows-tools { margin: 8px 0 4px 26px; }
.pe-rows { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: 18px; }
.pe-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; break-inside: avoid; font-size: 14px; }
.pe-row.off label { color: var(--ink-faint); }
.pe-row label.check { margin: 0; flex: 1 1 auto; }
.pe-row .badge { font-size: 10px; padding: 1px 6px; }
.pe-move { display: inline-flex; flex-direction: column; gap: 1px; }
.pe-move button { background: var(--panel-2); border: 1px solid var(--line); color: var(--ink-dim); font-size: 8px;
  line-height: 1; padding: 2px 4px; cursor: pointer; border-radius: 3px; }
.pe-move button:disabled { opacity: .3; cursor: default; }
.pe-move button:hover:not(:disabled) { color: var(--ink); }
.pe-preview { position: sticky; top: 12px; padding: 12px; }
/* Three rows (#10235): "Preview" + the Design and Data selects, the step controls, then
   "Open in a tab". One row ran past the card in the preview column once #10223 and #10224
   had filled it, and at the card's width the longer step labels cannot share a line with
   the button either, so the button has its own row in every case rather than only for
   those labels. A row wraps rather than overflows if the column is narrower still. */
.pe-preview-bar { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; font-size: 13px; color: var(--ink-dim); }
.pe-preview-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.pe-preview-bar select { width: auto; padding: 5px 8px; font-size: 13px; }
.pe-steps { gap: 6px; }   /* the step row (#10223); hidden as a whole on the live game */
.pe-steps[hidden] { display: none; }   /* an author display beats the UA [hidden], as .btn[hidden] above (#10201) */
.pe-steps .btn { padding: 3px 9px; line-height: 1.2; white-space: nowrap; }
.pe-preview-row > .btn-live { white-space: nowrap; }   /* "Open in a tab" (#10224), on its own row */
.pe-frame-wrap { border: 1px solid var(--line); border-radius: 6px; overflow: hidden; background: #000; }
.pe-frame-wrap iframe { display: block; border: 0; width: 100%; }
.pe-frame-wrap.is-panel iframe { height: 620px; }
.pe-frame-wrap.is-strip iframe { height: 300px; }
@media (max-width: 1100px) {
  .pe { grid-template-columns: 1fr; }
  .pe-body { grid-template-columns: 1fr; }
  .pe-preview { position: static; }
}

/* Inline checkbox row (login "keep me signed in"). Overrides the block `label`
   rule above, which is written for stacked form fields. */
label.check {
  display: flex; align-items: center; gap: 9px;
  margin: 16px 0 0; text-transform: none; letter-spacing: 0;
  font-size: 14px; color: var(--ink-dim); cursor: pointer;
}
label.check input { width: auto; margin: 0; accent-color: var(--accent-dim); }
