/*
 * DeckRemote - the modernist sheet.
 *
 * One 900px page of paper, sections separated by rules rather than floating
 * cards, sharp corners, one accent colour and headings set heavy. Every colour,
 * space and font is a variable so a page can be built out of tokens the way the
 * mockups are.
 *
 * No web fonts on purpose. This app is built to keep working with the uplink
 * down, and a stylesheet that blocks on fonts.googleapis.com is a page that
 * renders bare at exactly the wrong moment.
 */

:root {
  /* ---- neutrals: warm paper rather than grey, so the accent stays cold ---- */
  --color-neutral-100: #faf9f7;
  --color-neutral-200: #f2f0ec;
  --color-neutral-300: #e6e2db;
  --color-neutral-400: #d2ccc2;
  --color-neutral-500: #a8a196;
  --color-neutral-600: #736c62;
  --color-neutral-700: #45403a;
  --color-neutral-800: #29251f;
  --color-neutral-900: #16140f;

  --color-bg: #fffefb;
  --color-surface: var(--color-neutral-200);
  --color-divider: var(--color-neutral-400);
  --color-text: var(--color-neutral-900);
  --color-text-muted: var(--color-neutral-600);

  --color-accent: #1f4ed8;
  --color-accent-700: #1739a8;
  --color-accent-100: #e6ecfd;

  /* State. Kept separate from the accent: "playing" and "this is yours" are
     different facts and the operator reads them at a glance. */
  --color-good: #1a7a4a;
  --color-good-100: #e2f2e9;
  --color-warn: #a4530b;
  --color-warn-100: #fbeedd;
  --color-bad: #b3261e;
  --color-bad-100: #fbe6e4;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 40px;
  --space-8: 48px;

  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-heading: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --shadow-md: 0 10px 30px rgba(22, 20, 15, .13);

  /* Legacy names, still referenced from inline styles and the display screen. */
  --bg: var(--color-bg);
  --panel: var(--color-bg);
  --panel-2: var(--color-surface);
  --line: var(--color-divider);
  --text: var(--color-text);
  --muted: var(--color-text-muted);
  --accent: var(--color-accent);
  --accent-dim: var(--color-accent-700);
  --good: var(--color-good);
  --warn: var(--color-warn);
  --bad: var(--color-bad);
  --radius: 0px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--color-neutral-300);
  color: var(--color-text);
  font: 15px/1.55 var(--font-body);
  -webkit-text-size-adjust: 100%;
}

/* ---------- typography ---------- */

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: 800;
  letter-spacing: -.015em;
  line-height: 1.15;
  color: var(--color-text);
}
h1 { font-size: 27px; }
h2 { font-size: 19px; }
h3 { font-size: 17px; }
h6 {
  font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--color-text-muted); margin: 0 0 var(--space-2);
}
a { color: var(--color-accent-700); }

/*
 * `hidden` has to outrank the sheet. The browser's own [hidden] rule lives in
 * the user-agent origin, so *any* author rule setting a display beats it - and
 * this sheet sets one on `button`, `.pill`, `.row` and `.crumbs`. Every hidden
 * button on the site was showing regardless: "Clear uploads" with public skate
 * off, "Clear this slot" on a brand new cart, the cache pill with nothing to
 * report. One line here rather than a variant per component.
 */
[hidden] { display: none !important; }
.text-muted { color: var(--color-text-muted); }
.text-muted a { color: inherit; }
.caps { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--color-text-muted); }
.strike { text-decoration: line-through; }

/* ---------- the sheet ---------- */

/*
 * `.wrap` is the page. It was a full-width container in the old design and is
 * now the sheet itself, so every page inherits the frame without being touched.
 */
.wrap, .sheet {
  width: 900px; max-width: 100%;
  margin: var(--space-6) auto;
  background: var(--color-bg);
  box-shadow: var(--shadow-md);
}
@media (max-width: 940px) {
  .wrap, .sheet { margin: 0 auto; box-shadow: none; }
}

/*
 * Every top-level block of the sheet is separated from the next by a rule.
 * Doing it here rather than per-component means a page is just a stack of
 * blocks and nothing has to know what sits above it.
 */
.wrap > *, .sheet > *, .blocks > * { border-bottom: 2px solid var(--color-divider); }
.wrap > *:last-child, .sheet > *:last-child, .blocks > *:last-child { border-bottom: 0; }
.wrap > *[hidden], .sheet > *[hidden], .blocks > *[hidden] { border-bottom: 0; }

.section, .panel { padding: var(--space-5) var(--space-6); }
.section-head { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }
.section-head h2, .panel h2 { margin: 0 0 var(--space-3); font-size: 19px; }
.section-head h2 { margin-bottom: 0; }

/* Side-by-side blocks, a rule between them rather than a gutter. */
.cols { display: grid; grid-template-columns: 1fr 1fr; }
.cols-wide { grid-template-columns: 1.6fr 1fr; }
.cols > * { border-right: 2px solid var(--color-divider); min-width: 0; }
.cols > *:nth-child(2n), .cols > *:last-child { border-right: 0; }
/* Stacked blocks within one column keep their own rule between them. */
.stack > .panel + .panel, .cols > .stack > .panel + .panel { border-top: 2px solid var(--color-divider); }
.stack > .panel { margin-top: 0; }
@media (max-width: 820px) {
  .cols, .cols-wide { grid-template-columns: 1fr; }
  .cols > * { border-right: 0; border-bottom: 2px solid var(--color-divider); }
  .cols > *:last-child { border-bottom: 0; }
}

/* ---------- navigation ---------- */

.nav, header.bar {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-6);
  background: var(--color-bg);
  border-bottom: 2px solid var(--color-divider);
}
.nav-brand, header.bar h1 {
  font-family: var(--font-heading); font-size: 15px; font-weight: 800;
  letter-spacing: .16em; text-transform: uppercase; margin: 0;
  display: flex; align-items: center;
}
/*
 * The deck's own mark, in place of the wordmark. Bounded by height rather than
 * width so a tall crest and a wide banner both sit on the same baseline without
 * the header changing size, and so a logo nobody has cropped cannot push the
 * tabs off the sheet.
 */
.brandmark { height: 30px; width: auto; max-width: 200px; object-fit: contain; display: block; }
.nav a, nav.tabs a {
  color: var(--color-text-muted); text-decoration: none;
  font-size: 13px; font-weight: 600; letter-spacing: .04em;
  padding: var(--space-1) 0; border-bottom: 3px solid transparent;
}
.nav a:hover, nav.tabs a:hover { color: var(--color-text); }
.nav a[aria-current], nav.tabs a.active {
  color: var(--color-text); border-bottom-color: var(--color-accent);
}
nav.tabs { display: flex; gap: var(--space-4); }
.spacer { flex: 1; }

/* ---------- buttons ---------- */

button, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  font: 600 14px/1 var(--font-body);
  padding: 11px var(--space-4);
  min-height: 40px;
  border: 2px solid var(--color-neutral-900);
  border-radius: 0;
  background: var(--color-bg);
  color: var(--color-text);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
button:hover:not(:disabled), .btn:hover:not(:disabled) { background: var(--color-neutral-200); }
button:disabled, .btn:disabled { opacity: .4; cursor: not-allowed; }
button:focus-visible, .btn:focus-visible, a:focus-visible {
  outline: 3px solid var(--color-accent); outline-offset: 2px;
}

button.primary, .btn-primary {
  background: var(--color-accent); border-color: var(--color-accent); color: #fff;
}
button.primary:hover:not(:disabled), .btn-primary:hover:not(:disabled) {
  background: var(--color-accent-700); border-color: var(--color-accent-700);
}
.btn-secondary { background: var(--color-bg); }
button.danger, .btn-danger { border-color: var(--color-bad); color: var(--color-bad); }
button.danger:hover:not(:disabled), .btn-danger:hover:not(:disabled) {
  background: var(--color-bad); color: #fff;
}
.btn-ghost, button.ghost {
  border-color: transparent; background: none; color: var(--color-accent-700);
  padding-left: 0; padding-right: 0;
}
.btn-ghost:hover:not(:disabled), button.ghost:hover:not(:disabled) {
  background: none; text-decoration: underline;
}
.btn-icon {
  min-height: 32px; width: 32px; padding: 0; border-color: transparent; background: none;
}
.btn-icon:hover:not(:disabled) { background: var(--color-neutral-200); }
.btn-block { width: 100%; }
button.small, .btn-sm { min-height: 32px; padding: 6px var(--space-3); font-size: 13px; }
.soundbtn { min-height: 32px; padding: 6px var(--space-3); font-size: 13px; }

/* ---------- fields ---------- */

input, select, textarea, .input {
  font: 15px/1.4 var(--font-body);
  color: var(--color-text);
  background: var(--color-bg);
  border: 2px solid var(--color-neutral-900);
  border-radius: 0;
  padding: 9px var(--space-3);
  width: 100%;
  min-height: 40px;
}
input::placeholder, textarea::placeholder { color: var(--color-neutral-500); }
input:focus, select:focus, textarea:focus {
  outline: 3px solid var(--color-accent); outline-offset: 1px;
}
textarea { resize: vertical; line-height: 1.45; }
.field { display: flex; flex-direction: column; gap: var(--space-1); }
.field > label, label.label {
  font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--color-text-muted);
}
select.compact { width: auto; max-width: 160px; min-height: 32px; padding: 5px var(--space-2); font-size: 13px; }
code, .code {
  font-family: var(--font-mono); background: var(--color-surface);
  padding: 6px var(--space-3); letter-spacing: .1em; font-size: 13px;
}

/* ---------- rows and lists ---------- */

.row { display: flex; gap: var(--space-2); align-items: center; }
.row.wrap-row { flex-wrap: wrap; }
.stack > * + * { margin-top: var(--space-3); }
.grow { flex: 1; min-width: 0; }

ul.list { list-style: none; margin: 0; padding: 0; }
ul.list li {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--color-divider);
}
ul.list li .grow { flex: 1; min-width: 0; }
ul.list li .title {
  font-weight: 600; font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
ul.list li .sub {
  font-size: 12px; color: var(--color-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The one currently on the ice, marked with a rule down its left edge. */
li.current {
  border-left: 3px solid var(--color-accent); margin-left: -3px; padding-left: 11px;
}
.empty { color: var(--color-text-muted); font-size: 13px; padding: var(--space-4) 0; }
.who { font-size: 13px; color: var(--color-text-muted); }
.when { font-size: 12px; color: var(--color-text-muted); }
.logrow {
  display: flex; gap: var(--space-3); font-size: 13px;
  padding: var(--space-2) 0; border-top: 1px solid var(--color-divider);
}
.logrow .when { min-width: 130px; }
.logrow .who { min-width: 90px; color: var(--color-accent-700); }

.crumbs {
  display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center;
  font-size: 13px; color: var(--color-text-muted); margin-bottom: var(--space-3);
}
.crumbs button {
  background: none; border: none; padding: 2px 0; min-height: 0;
  color: var(--color-accent-700); font-size: 13px; text-decoration: underline;
}
.crumbs span.sep { color: var(--color-neutral-500); }

/* ---------- running order ---------- */

.entry .num, .posnum {
  font-family: var(--font-heading); font-weight: 800; font-size: 15px;
  color: var(--color-neutral-700); min-width: 24px;
  font-variant-numeric: tabular-nums;
}
.entry.played { opacity: .5; }
.entry.played .num { color: var(--color-neutral-600); }
.entry.current { opacity: 1; }
.entry.current .num { color: var(--color-accent); }
.entry .names { font-weight: 600; }
/* Operator notes: "wait for the announcer". Deliberately the loudest thing in
   the row - it is the bit that is easy to miss and expensive to miss. */
.entry .note {
  font-size: 12px; color: var(--color-warn); font-weight: 600; margin-top: 3px;
  white-space: pre-wrap; word-break: break-word;
}

/* ---------- tags ---------- */

.tag, .pill {
  display: inline-flex; align-items: center;
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 3px var(--space-2); border-radius: 0; white-space: nowrap;
  border: 1px solid var(--color-divider); color: var(--color-text-muted);
  background: var(--color-bg);
}
.tag-neutral, .pill { background: var(--color-surface); border-color: var(--color-surface); }
.tag-outline { background: none; border-color: var(--color-neutral-500); }
.tag-accent, .tag.mine, .pill.on {
  background: var(--color-accent-100); border-color: var(--color-accent-100); color: var(--color-accent-700);
}
.tag.shared { background: var(--color-surface); border-color: var(--color-neutral-500); }
.tag.public, .pill.warn {
  background: var(--color-warn-100); border-color: var(--color-warn-100); color: var(--color-warn);
}
.pill.off { background: var(--color-bad-100); border-color: var(--color-bad-100); color: var(--color-bad); }

/* ---------- now playing ---------- */

.now { padding: var(--space-5) var(--space-6); border-bottom: 2px solid var(--color-divider); }
.now .label {
  display: inline-flex; font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; padding: 3px var(--space-2);
  background: var(--color-accent-100); color: var(--color-accent-700);
  margin-bottom: var(--space-2);
}
.now .track { font-family: var(--font-heading); font-size: 27px; font-weight: 800; letter-spacing: -.015em; margin: 0 0 var(--space-1); }
.bigstart { min-height: 48px; font-size: 16px; }
.turnbox {
  padding: var(--space-4); background: var(--color-accent-100);
  border-left: 3px solid var(--color-accent); margin-bottom: var(--space-4);
}

/* ---------- progress ---------- */

.meter { height: 8px; background: var(--color-surface); }
.meter > span { display: block; height: 100%; background: var(--color-accent); }

/*
 * The seek bar on the playback page.
 *
 * Squared off like everything else on the sheet, and given a 32px hit strip so
 * a thumb lands on the thing rather than near it - this is worked from a phone
 * at the boards. The generic `input` rule is written for text boxes and would
 * otherwise draw a bordered box with a hairline through it.
 */
.seekbar { margin-top: var(--space-4); }
.seekbar .seektimes {
  font-size: 13px; color: var(--color-text-muted);
  font-variant-numeric: tabular-nums; margin-top: var(--space-1);
}
.seekbar input[type="range"] {
  -webkit-appearance: none; appearance: none;
  flex: 1; width: auto; min-height: 32px; height: 32px;
  padding: 0; border: 0; background: none;
}
.seekbar input[type="range"]::-webkit-slider-runnable-track {
  height: 8px; background: var(--color-surface); border: 1px solid var(--color-divider);
}
.seekbar input[type="range"]::-moz-range-track {
  height: 8px; background: var(--color-surface); border: 1px solid var(--color-divider);
}
.seekbar input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 26px; margin-top: -10px;
  background: var(--color-accent); border: 0; border-radius: 0;
}
.seekbar input[type="range"]::-moz-range-thumb {
  width: 14px; height: 26px;
  background: var(--color-accent); border: 0; border-radius: 0;
}
.seekbar input[type="range"]:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* No length to scrub against - a track nobody probed. The buttons either side
   still work, so the bar is greyed rather than taken away. */
.seekbar input[type="range"]:disabled { opacity: .45; }

/* ---------- toast ---------- */

.toast {
  position: fixed; left: 50%;
  bottom: calc(var(--space-5) + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  background: var(--color-neutral-900); color: var(--color-neutral-100);
  border: 0; border-radius: 0; padding: var(--space-3) var(--space-5);
  z-index: 100; max-width: 90vw; font-size: 14px;
  box-shadow: var(--shadow-md);
}
.toast.err { background: var(--color-bad); color: #fff; }
.err { color: var(--color-bad); font-size: 13px; min-height: 18px; margin-top: var(--space-2); }

/* ---------- the narrow sheet: sign in, public skate ----------
 * The pages reached without an account are the same sheet as the coach pages,
 * cut narrow and centred on the grey, with the same brand bar across the top.
 *
 * Their blocks are mutually exclusive states rather than a stack, so the rules
 * are drawn per block instead of by `.sheet > *`: whichever state is showing is
 * usually followed by hidden siblings, and the inherited rule would then lie
 * along the bottom edge of the sheet with nothing under it.
 *
 * The shadow stays on below 940px. On the coach pages the sheet fills a phone
 * and the shadow has nothing to fall on; here it is a 420px block on grey at
 * every width, and a deck is a bright building.
 */

/* `minmax(0, auto)` rather than a bare implicit column: a grid column sized by
   its contents will not shrink below them, so on a phone narrower than the
   420px sheet the block ran off the right of the screen and took the sign-in
   form with it. With a floor of 0 the column can give way and `max-width` on the
   sheet does the rest. */
.login-wrap {
  min-height: 100dvh; display: grid; grid-template-columns: minmax(0, auto);
  place-items: center; padding: var(--space-5);
}
.sheet-sm { width: 420px; max-width: 100%; margin: 0; box-shadow: var(--shadow-md); }
.sheet-sm > * { border-bottom: 0; }
.sheet-sm .bar { padding: var(--space-3) var(--space-5); }
.sheet-sm .panel { padding: var(--space-5); }
.sheet-sm .panel h2 { margin: 0 0 var(--space-2); }
.lede { color: var(--color-text-muted); font-size: 13px; margin: 0 0 var(--space-4); }

/* The block below the fold: the other way in, or what to do about the code. */
.foot-note {
  margin: 0; padding: var(--space-4) var(--space-5);
  border-top: 2px solid var(--color-divider);
  font-size: 13px; color: var(--color-text-muted);
}
.linkish {
  background: none; border: none; padding: 0; min-height: 0;
  color: var(--color-accent-700); cursor: pointer; font-size: 13px; text-decoration: underline;
}
.linkish:hover { background: none; }

/* ---------- display screen ----------
 * The same sheet as the coaches' pages, at deck scale.
 *
 * It was white-on-dark for a long time, on the argument that dark carries
 * further across the ice. That holds in a dim arena; under the lighting this
 * deck actually has, dark-on-light is the more legible pair, and it is what
 * lets the deck's own logo and photographs sit on the screen without a tile
 * behind them. Everything is still sized in vh, which is what does the real
 * work at that distance.
 */
body.display {
  overflow: hidden;
  background: var(--color-bg);
  color: var(--color-text);
}
.screen { height: 100dvh; display: flex; flex-direction: column; padding: 3vh 4vw; }
.screen .head {
  display: flex; align-items: center; gap: 16px; color: var(--color-text-muted);
  font-family: var(--font-heading); font-size: 2.2vh; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  padding-bottom: 2vh; border-bottom: 2px solid var(--color-divider);
}
/* Bounded by height, so any shape of logo keeps the header one band tall. */
.screen .head .brandmark { height: 7vh; width: auto; max-width: 34vw; object-fit: contain; }
.screen .nowbox { padding: 4vh 0 3vh; border-bottom: 2px solid var(--color-divider); }
.screen .nowbox .lbl {
  color: var(--color-accent); font-size: 2.4vh; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
}
.screen .nowbox .skater {
  font-family: var(--font-heading); font-size: 8vh; font-weight: 800;
  letter-spacing: -.02em; line-height: 1.05; margin: 1vh 0 0;
}
.screen .nowbox .meta { font-size: 3vh; color: var(--color-text-muted); margin-top: 1vh; }
.screen .uplbl {
  color: var(--color-text-muted); font-size: 2.2vh; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; margin: 3vh 0 1.5vh;
}
.screen ol { list-style: none; margin: 0; padding: 0; flex: 1; overflow: hidden; }
.screen ol li {
  display: flex; gap: 2vw; align-items: baseline;
  padding: 1.4vh 0; border-bottom: 1px solid var(--color-divider);
}
.screen ol li .n {
  font-family: var(--font-heading); color: var(--color-accent); font-size: 3.4vh; font-weight: 800;
  min-width: 4vh; font-variant-numeric: tabular-nums;
}
.screen ol li .who { font-size: 4vh; font-weight: 600; color: var(--color-text); }
.screen ol li .what { font-size: 2.6vh; color: var(--color-text-muted); margin-left: auto; }
.screen .lower { display: flex; gap: 4vw; flex: 1; min-height: 0; }
.screen .lower-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* The deck's own photographs, down the right. Sized as a share of the screen
   rather than of the photo, so a portrait snap and a landscape one occupy the
   same block and the skater information never shifts under them. */
.screen .side {
  width: 42%; display: flex; flex-direction: column; gap: 2vh; min-width: 0; margin-top: 3vh;
}
.screen .side.qronly { width: auto; }
.screen .photobox {
  position: relative; flex: 1; min-height: 0; overflow: hidden;
  background: var(--color-surface); border: 1px solid var(--color-divider);
}
.screen .photobox img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .9s ease;
}
.screen .photobox img.on { opacity: 1; }
.screen .dots { display: flex; gap: 1.2vh; justify-content: center; padding-top: 1.2vh; }
.screen .dots i { width: 1vh; height: 1vh; background: var(--color-neutral-400); display: block; }
.screen .dots i.on { background: var(--color-accent); }

.screen .qrbox {
  align-self: center; text-align: center;
  background: var(--color-surface); border: 1px solid var(--color-divider);
  padding: 2vh 2vh 1.5vh;
}
.screen .qrbox .qrlbl {
  color: var(--color-accent-700); font-size: 2vh; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; margin-bottom: 1.5vh;
}
.screen .qrbox img { width: 22vh; height: 22vh; display: block; background: #fff; padding: 1vh; }
.screen .qrbox .qrhint { color: var(--color-text-muted); font-size: 1.9vh; margin-top: 1.2vh; line-height: 1.3; }
.screen .foot {
  display: flex; align-items: center; gap: 12px; color: var(--color-text-muted);
  font-size: 2vh; padding-top: 2vh;
}
.dot { width: 1.4vh; height: 1.4vh; background: var(--color-neutral-400); display: inline-block; }
.dot.live { background: var(--color-good); }
.dot.house { background: var(--color-warn); }
.idle-big { font-size: 6vh; font-weight: 700; color: var(--color-text-muted); }

/* ---------- phones ----------
 * The coach pages are used standing at the boards, one-handed, in a glove. The
 * sheet above is drawn for 900px of paper; everything below reflows it for a
 * phone without any page having to know about it.
 *
 * Two breakpoints. 900px is where the header bar stops fitting on one line -
 * brand, four tabs, two status pills, a name and a sign-out button add up to
 * about 860px, and past that the sign-out button was simply off the side of the
 * screen with nothing to scroll it into view. 700px is the phone proper, where
 * the list rows have to stop being rows.
 */

/* The notch and the home indicator. The pages already ask for `viewport-fit=cover`
   so the sheet meets the edges; this keeps the content off the hardware. */
body {
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

@media (max-width: 900px) {
  /*
   * The bar wraps rather than overflowing. Ordering it explicitly means the
   * lines break in order of what an operator needs first: who you are and the
   * way out, then the tabs, then the deck player's status - and never the other
   * way round, which is what unordered wrapping would give on a logo of a
   * different width.
   */
  .nav, header.bar { flex-wrap: wrap; row-gap: var(--space-2); }
  .nav-brand, header.bar h1 { order: 1; }
  .nav .spacer, header.bar .spacer { order: 2; }
  .nav .who, header.bar .who { order: 3; }
  .nav > button, header.bar > button { order: 4; }
  .nav nav.tabs, header.bar nav.tabs { order: 5; }
  .nav .pill, header.bar .pill, .nav .tag, header.bar .tag { order: 6; }

  /* The tabs take a band of their own as soon as the bar wraps at all. Left to
     wrap on their own they end up trailing the sign-out button, and navigation
     that reads after the way out is navigation nobody finds. */
  .nav nav.tabs, header.bar nav.tabs { width: 100%; }
}

@media (max-width: 700px) {
  /* ---- the sheet ---- */

  /* 32px of margin on each side is a fifth of a phone. The rules between blocks
     still run the full width, so the page reads as the same sheet. */
  .section, .panel, .now, .nav, header.bar, .foot-note { padding-left: var(--space-4); padding-right: var(--space-4); }
  .section, .panel, .now { padding-top: var(--space-4); padding-bottom: var(--space-4); }
  .nav, header.bar { padding-top: var(--space-3); padding-bottom: var(--space-3); gap: var(--space-3); }

  /* ---- the bar ---- */

  /* The tabs can be swiped along their band if a fifth one is ever added, or
     the deck's name is set in a wide type. */
  .nav nav.tabs, header.bar nav.tabs {
    gap: var(--space-5);
    overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .nav nav.tabs::-webkit-scrollbar, header.bar nav.tabs::-webkit-scrollbar { display: none; }
  .nav a, nav.tabs a { padding: var(--space-2) 0; font-size: 14px; }
  /* A logo nobody has cropped must not push the sign-out button off the line. */
  .brandmark { height: 26px; max-width: 40vw; }
  /* Set tighter so the wordmark, the coach's name and the way out hold one line
     on a 360px phone instead of costing a whole extra band. */
  .nav-brand, header.bar h1 { font-size: 12px; letter-spacing: .1em; }
  .who { font-size: 12px; }
  .nav .pill, header.bar .pill { font-size: 10px; }

  /* ---- lists ---- */

  /*
   * A list row was a single line: number, title, tags, buttons. On a phone the
   * buttons won every time and the skater's name was ellipsed to "Emily ..." or
   * squeezed out of existence - which is the one thing on the row that has to be
   * readable. So the title claims the first line and everything that acts on it
   * wraps underneath.
   */
  /* A queue row carries an owner tag and four buttons on its second line, and at
     360px that is within about ten pixels of not fitting - hence the tighter
     column gap and the smaller tag, which buy the row back. */
  ul.list li { flex-wrap: wrap; row-gap: var(--space-2); column-gap: var(--space-2); padding: var(--space-3) 0; }
  ul.list li .tag, ul.list li .pill { font-size: 10px; padding: 3px var(--space-1); letter-spacing: .06em; }
  /* The title's line is filled by the title, so this only bites on the wrapped
     line: the buttons finish at the right edge, under the thumb, and read as
     belonging to the row above rather than starting a new one. */
  ul.list li { justify-content: flex-end; }
  ul.list li > .grow { flex: 1 1 calc(100% - 56px); }
  ul.list li .title, ul.list li .sub {
    white-space: normal; overflow: visible; overflow-wrap: anywhere;
  }
  /* Names run long. Two lines of a title is a name you can read; five is a row
     you have to scroll past. */
  ul.list li .title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  ul.list li .sub { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  /* The move-to-folder picker shares the action line with Edit and Delete. */
  select.compact { max-width: 44%; }

  /* ---- section heads ---- */

  /* The spacer means "push the rest to the right". On a phone there is no right
     to push to, so it means "put the rest on the next line" instead. */
  .section-head { flex-wrap: wrap; row-gap: var(--space-2); }
  .section-head > .spacer { flex-basis: 100%; height: 0; }
  .section-head h2 { flex: 0 1 auto; }

  /* ---- transport ---- */

  /* Play, Stop and Skip are pressed in a hurry, sometimes in a glove. */
  .now .track { font-size: 22px; }
  .now .row.wrap-row > button { flex: 1 1 auto; min-height: 44px; }
  .now .row.wrap-row { row-gap: var(--space-2); }

  /* ---- touching things ---- */

  /* 32px is a mouse target. The row buttons - the arrows, Play, the x - are the
     ones a thumb misses, so they get the full 44. */
  button, .btn { min-height: 44px; }
  button.small, .btn-sm, .soundbtn { min-height: 40px; min-width: 40px; padding: 8px var(--space-3); }
  .btn-icon, .nudge { min-height: 40px; width: 40px; }
  .crumbs button { min-height: 0; min-width: 0; padding: var(--space-1) 0; }
  .linkish { min-height: 0; }
  /* Ghost buttons are text, not targets with edges - full height on them just
     leaves a hole in the layout. */
  .btn-ghost, button.ghost { min-height: 0; }

  /* ---- fields ---- */

  /* Under 16px, Safari zooms the whole page in when a field takes focus and
     leaves it zoomed - so a coach searching for a skater ends up dragging the
     page back into place afterwards. 16px is the price of it not doing that. */
  input, select, textarea, .input { font-size: 16px; min-height: 44px; }
  select.compact { min-height: 40px; font-size: 14px; }
  input[type="checkbox"], input[type="radio"] { min-height: 0; }
  input[type="file"] { padding: var(--space-2); }
}

/*
 * The small old phones (a 320px SE is still a phone a coach owns). Nothing here
 * is broken without it - the row simply wraps a second time - but the queue
 * controls are worth keeping on one line, since that is the line the operator
 * reaches for between skaters.
 */
@media (max-width: 380px) {
  ul.list li > button.small, ul.list li > .btn-sm {
    min-width: 36px; padding-left: var(--space-2); padding-right: var(--space-2);
  }
  ul.list li .tag, ul.list li .pill { font-size: 9px; }
  .now .track { font-size: 20px; }
}

/* ==========================================================================
   Colour coding
   --------------------------------------------------------------------------
   A closed palette of nine, not a colour picker. The point of colour here is
   "that block is the Juveniles" at a glance across a deck, so what matters is
   that the nine are distinguishable and that every one of them is legible on
   both the coach pages and the dark wall screen - which free hex chosen on a
   bright phone reliably is not.

   Each colour is three values: a strong one for text and rules, a tint for
   fills, and a light variant used where the background is dark. Hues are spread
   evenly and lightness is held roughly constant across the set, so no single
   colour reads as louder or more urgent than its neighbours.
   ========================================================================== */
:root {
  --c-red:    #b3261e; --c-red-100:    #fbe6e4; --c-red-lift:    #ff8a80;
  --c-orange: #a4530b; --c-orange-100: #fbeedd; --c-orange-lift: #ffb26b;
  --c-amber:  #806309; --c-amber-100:  #f9efcd; --c-amber-lift:  #ffd166;
  --c-green:  #1a7a4a; --c-green-100:  #e2f2e9; --c-green-lift:  #6ee7a8;
  --c-teal:   #0f6d78; --c-teal-100:   #ddf0f2; --c-teal-lift:   #5fd3de;
  --c-blue:   #1f4ed8; --c-blue-100:   #e6ecfd; --c-blue-lift:   #8fb4ff;
  --c-violet: #6b3fc4; --c-violet-100: #eee8fc; --c-violet-lift: #c4a6ff;
  --c-pink:   #b02a72; --c-pink-100:   #fce6f1; --c-pink-lift:   #ff9ecb;
  --c-slate:  #4a5568; --c-slate-100:  #e8eaee; --c-slate-lift:  #b3bdcc;
}

/* A 4px rule down the leading edge - enough to group a run of rows without
   tinting them, which at ten entries a screen becomes noise. */
.c-red    { --swatch: var(--c-red);    --swatch-100: var(--c-red-100);    --swatch-lift: var(--c-red-lift); }
.c-orange { --swatch: var(--c-orange); --swatch-100: var(--c-orange-100); --swatch-lift: var(--c-orange-lift); }
.c-amber  { --swatch: var(--c-amber);  --swatch-100: var(--c-amber-100);  --swatch-lift: var(--c-amber-lift); }
.c-green  { --swatch: var(--c-green);  --swatch-100: var(--c-green-100);  --swatch-lift: var(--c-green-lift); }
.c-teal   { --swatch: var(--c-teal);   --swatch-100: var(--c-teal-100);   --swatch-lift: var(--c-teal-lift); }
.c-blue   { --swatch: var(--c-blue);   --swatch-100: var(--c-blue-100);   --swatch-lift: var(--c-blue-lift); }
.c-violet { --swatch: var(--c-violet); --swatch-100: var(--c-violet-100); --swatch-lift: var(--c-violet-lift); }
.c-pink   { --swatch: var(--c-pink);   --swatch-100: var(--c-pink-100);   --swatch-lift: var(--c-pink-lift); }
.c-slate  { --swatch: var(--c-slate);  --swatch-100: var(--c-slate-100);  --swatch-lift: var(--c-slate-lift); }

ul.list li.tinted { border-left: 4px solid var(--swatch); padding-left: var(--space-3); }

/* The swatch itself: a filled square, and a ring when it is the chosen one. */
.swatches { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
.swatch {
  width: 26px; height: 26px; min-height: 0; min-width: 0; padding: 0;
  border: 2px solid var(--color-neutral-400); background: var(--swatch, transparent);
  cursor: pointer; flex: 0 0 auto;
}
.swatch[aria-pressed="true"] { outline: 2px solid var(--color-text); outline-offset: 2px; }
/* "No colour" reads as an empty slot rather than a white square. */
.swatch.none {
  background: repeating-linear-gradient(45deg,
    var(--color-neutral-200) 0 5px, var(--color-neutral-300) 5px 10px);
}
.dot {
  display: inline-block; width: 9px; height: 9px; flex: 0 0 auto;
  background: var(--swatch, var(--color-neutral-400));
}

/* ==========================================================================
   Section headings inside a running order
   ========================================================================== */
li.grouphead {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3) 0 var(--space-2);
  border-bottom: 2px solid var(--swatch, var(--color-divider));
  margin-top: var(--space-4);
}
li.grouphead:first-child { margin-top: 0; }
li.grouphead .gname {
  font-family: var(--font-heading); font-weight: 800; font-size: 13px;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--swatch, var(--color-text));
}
li.grouphead .gcount { font-size: 11px; color: var(--color-text-muted); }

/* ==========================================================================
   Clip editor
   --------------------------------------------------------------------------
   Non-destructive: everything here writes numbers, never the upload. The
   waveform is decoded in the browser rather than rendered on the server - a
   Coolify box has no business running ffmpeg per page load, and a phone decodes
   a three-minute mp3 in about a second.
   ========================================================================== */
.clipeditor { border-top: 2px solid var(--color-divider); padding-top: var(--space-3); margin-top: var(--space-3); }
.clipeditor .wavewrap {
  position: relative; height: 96px; background: var(--color-neutral-800);
  overflow: hidden; touch-action: none; user-select: none; cursor: crosshair;
}
.clipeditor canvas { display: block; width: 100%; height: 100%; }
/* The trimmed-away ends, dimmed rather than hidden: a coach needs to see what
   they have cut in order to know they cut the right thing. */
.clipeditor .shade {
  position: absolute; top: 0; bottom: 0; background: rgba(22, 20, 15, .68);
  pointer-events: none;
}
.clipeditor .handle {
  position: absolute; top: 0; bottom: 0; width: 13px; margin-left: -6px;
  cursor: ew-resize; background: none; border: 0; padding: 0; min-height: 0; min-width: 0;
}
.clipeditor .handle::before {
  content: ""; position: absolute; left: 5px; top: 0; bottom: 0; width: 3px;
  background: var(--c-amber-lift);
}
.clipeditor .handle::after {
  content: ""; position: absolute; left: 0; top: 50%; margin-top: -13px;
  width: 13px; height: 26px; background: var(--c-amber-lift);
}
.clipeditor .handle:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
.clipeditor .playhead {
  position: absolute; top: 0; bottom: 0; width: 2px; background: #fff; pointer-events: none;
}
.clipeditor .waveinfo {
  display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap;
  margin-top: var(--space-2);
}
/* The number the trim usually exists for - programs have time limits. */
.clipeditor .runtime {
  font-family: var(--font-mono); font-size: 22px; font-weight: 700; letter-spacing: -.02em;
}
.clipeditor .runtime.over { color: var(--color-bad); }
.clipeditor .loading { color: var(--color-neutral-400); font-size: 13px; padding: var(--space-4); }
.clipeditor .fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: var(--space-2); margin-top: var(--space-3);
}
.clipeditor .fields label { display: block; font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--color-text-muted); margin-bottom: 2px; }
.clipeditor .fields input { width: 100%; font-family: var(--font-mono); }
.clipeditor .gainrow { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); }
.clipeditor .gainrow input[type="range"] { flex: 1; min-width: 80px; }
.clipeditor .gaindb { font-family: var(--font-mono); font-weight: 700; min-width: 62px; text-align: right; }
.clipeditor .inherit { font-size: 12px; color: var(--color-text-muted); }

/* ==========================================================================
   Cart wall
   --------------------------------------------------------------------------
   Instant-fire buttons. Big, square, colour-coded, and never more than one tap
   from wherever the operator already is.
   ========================================================================== */
.cartgrid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-2);
  margin-top: var(--space-3);
}
@media (max-width: 560px) { .cartgrid { grid-template-columns: repeat(2, 1fr); } }
.cart {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start;
  gap: var(--space-1); text-align: left;
  min-height: 76px; padding: var(--space-2) var(--space-3);
  border: 2px solid var(--swatch, var(--color-divider));
  background: var(--swatch-100, var(--color-surface));
  color: var(--swatch, var(--color-text));
  font-weight: 700; font-size: 14px; line-height: 1.2;
}
.cart .cartsub { font-size: 11px; font-weight: 600; opacity: .75; font-family: var(--font-mono); }
.cart.firing { background: var(--swatch, var(--color-text)); color: #fff; }
/* How much of the cart is left, as a bar across its foot. */
.cart .bar { position: absolute; left: 0; bottom: 0; height: 3px; background: currentColor; }
.cart.empty-slot {
  border-style: dashed; border-color: var(--color-neutral-400);
  background: none; color: var(--color-text-muted); font-weight: 500;
  align-items: center; justify-content: center;
}

/* ---------- which mode the deck is in ----------
   Mirrors the banner on the deck console, and says the same thing from the
   other end of the wire: during an event the transport on this page is dead on
   purpose, because the console on site owns it. That used to be thirteen
   pixels of warning text underneath the buttons it explained. */
.modebar {
  display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-4) var(--space-6);
  border-bottom: 2px solid var(--color-divider);
  border-left: 4px solid var(--color-divider);
  background: var(--color-surface);
}
.modebar #modeBarText {
  font-family: var(--font-heading); font-size: 17px; font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase; color: var(--color-text-muted);
}
.modebar .sub { font-size: 14px; color: var(--color-text-muted); }
/* An event is the state worth colouring: it is the one where this page stops
   being a set of controls and becomes a window onto the deck. */
.modebar.event { border-left-color: var(--color-accent); background: var(--color-accent-100); }
.modebar.event #modeBarText { color: var(--color-accent-700); }
.modebar.event .sub { color: var(--color-accent-700); }

/*
 * The support session: reads everything, writes nothing.
 *
 * The server refuses every write from one of these regardless of what the page
 * does, so this is honesty rather than enforcement - it stops somebody filling
 * in a form that was never going to be accepted. Buttons are dimmed rather than
 * hidden, because a page missing half its controls looks broken and a page with
 * greyed-out controls looks like what it is.
 */
.readonly-banner {
  position: sticky;
  top: 0;
  z-index: 50;
  padding: var(--space-2) var(--space-3);
  background: var(--color-warn-100);
  border-bottom: 1px solid var(--color-warn);
  color: #5a4506;
  font-size: 13px;
  text-align: center;
}
body.readonly button:not(#logout):not([data-readonly-ok]),
body.readonly input:not(#search):not([type=search]),
body.readonly select,
body.readonly textarea {
  opacity: .55;
  pointer-events: none;
}
/* Searching is a read, and it is most of what support is here to do. */
body.readonly #search { opacity: 1; pointer-events: auto; }
