:root {
  --bg: #0b1017;
  --panel: #131b26;
  --panel-2: #18222f;
  --line: #26313f;
  --text: #e6edf7;
  --dim: #8ea0bb;
  --red: #e0413f;
  --blue: #2f7de1;
  --accent: #ffd94a;
  --coral: #f2b13c;
  --algae: #4ec08a;
  --good: #46c98b;
  --warn: #f0a63c;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.35), 0 8px 24px -12px rgba(0, 0, 0, 0.6);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: radial-gradient(1200px 600px at 20% -10%, #16202d 0%, var(--bg) 60%);
  color: var(--text);
  font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { margin: 0; font-weight: 650; letter-spacing: 0.01em; }
h2 { font-size: 15px; }
h3 { font-size: 12px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.08em; margin: 14px 0 8px; }
p { margin: 0 0 8px; }
a { color: var(--text); text-decoration: none; }
button { font: inherit; }
code { font-family: var(--mono); font-size: 0.92em; color: var(--accent); }

/* Scores and clocks are read like a scoreboard: digits must not jitter as they change. */
.score strong, .timer, .chip, .events, .margin, td.mono, th.mono, .robot-title, .inventory-strip {
  font-variant-numeric: tabular-nums;
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 7px; }
:focus:not(:focus-visible) { outline: none; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--line);
  background: rgba(11, 16, 23, 0.85);
  backdrop-filter: blur(8px);
  position: sticky; top: 0; z-index: 20;
}
.brand h1 { font-size: 16px; }
.brand .sub { color: var(--dim); font-size: 12px; margin: 2px 0 0; }
.topbar-actions { display: flex; align-items: center; gap: 8px; }

.layout {
  display: grid;
  grid-template-columns: minmax(320px, 380px) minmax(0, 1fr) minmax(260px, 320px);
  gap: 14px;
  padding: 14px 18px 28px;
  align-items: start;
}
/* Grid and flex items carry min-width:auto, so one wide child (the roster grid) stretches the whole
   track instead of shrinking: below ~754px the page grew sideways and every panel scrolled. These
   zeroes let the columns follow the viewport instead. */
.layout > * { min-width: 0; }
.setup { grid-column: 1; }
.field-panel { grid-column: 2; }
/* The read-out rail is one column with tabs inside it, as on the 2026 page: it sticks while the FIELD
   stays put, and scrolls in place rather than pushing the page six screens long. */
.output { grid-column: 3; position: sticky; top: 66px; max-height: calc(100vh - 82px); overflow-y: auto; }

@media (max-width: 1240px) {
  .layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .setup, .field-panel, .output { grid-column: 1 / -1; position: static; max-height: none; }
  /* Three panels that each span the full width are one long column, and with SETUP first the field plus the
     transport row (Run / Play / scrubber / event steps / Export / Import) sat at y = 3,260…3,523 in a 4,629 px
     document at the 510 px preview width — measured, not guessed. That is what "there are no replay controls"
     reads as. The field is what you come back to after a run, so it goes first once the grid is no longer
     three columns; nothing about the wide layout changes. */
  .field-panel { order: -1; }
}
@media (max-width: 720px) {
  .layout { grid-template-columns: minmax(0, 1fr); }
}

.panel {
  container-type: inline-size;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px;
  box-shadow: var(--shadow);
}
.panel-head { margin-bottom: 10px; }
.hint { color: var(--dim); font-size: 12px; }

/* ---- tabbed output column, as the 2026 page has it ----------------------------------------
   Six headings stacked in one rail read as a scroll, and the reading aid nobody asked for hides the
   one that answers the question. The tabs are a segmented control that scrolls sideways instead of
   wrapping, with the active one carried by an underline rather than a filled box (a filled box on a
   dark panel reads as a button, not as "you are here"). */
.tabs {
  display: flex; gap: 2px; border-bottom: 1px solid var(--line); padding-bottom: 0;
  overflow-x: auto; scrollbar-width: none; margin-bottom: 4px;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  background: transparent; border: none; border-bottom: 2px solid transparent; color: var(--dim);
  padding: 6px 10px; cursor: pointer; font-size: 12px; white-space: nowrap; margin-bottom: -1px;
}
.tab:hover { color: var(--text); }
.btn.cancel, .chip.error { color: var(--warn); border-color: var(--warn); }
.tab.active { color: var(--text); border-bottom-color: var(--accent); font-weight: 600; }
.tab-body { padding-top: 10px; }
.tab-body h3 { margin: 16px 0 6px; font-size: 13px; }
.tab-body h3:first-child { margin-top: 0; }

.btn {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 7px 12px;
  cursor: pointer;
  font-size: 13px;
}
.btn:hover { border-color: #3b4a5e; }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--blue); border-color: var(--blue); color: #fff; }
.btn.ghost { background: transparent; }
.btn.small { padding: 4px 9px; font-size: 12px; }

.chip {
  border: 1px solid var(--line);
  background: var(--panel-2);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 12px;
  color: var(--dim);
}
.chip.busy { color: var(--accent); border-color: #4a4324; }
.chip.done { color: var(--good); border-color: #24402f; }

.setup-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); gap: 12px; }
.setup-grid > * { min-width: 0; }
.alliance-block { border: 1px solid var(--line); border-radius: 10px; padding: 10px; background: var(--panel-2); }
.alliance-block.red { border-left: 3px solid var(--red); }
.alliance-block.blue { border-left: 3px solid var(--blue); }
.alliance-block h3 { margin-top: 0; }
.alliance-block h4 { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.08em; margin: 14px 0 4px; }
.inv-label.blue { color: var(--blue); }
.inv-label.red { color: var(--red); }

.roster { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.robo {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px;
  background: #141d29;
}
.robot-title { display: flex; justify-content: space-between; align-items: baseline; gap: 6px; font-size: 13px; }
.robot-title b { font-weight: 650; }
.robot-title span { color: var(--dim); font-size: 11px; }
.specs { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 4px 8px; margin-top: 6px; }
.specs label { display: flex; justify-content: space-between; align-items: center; gap: 6px; font-size: 11px; color: var(--dim); min-width: 0; }
.specs label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.specs input {
  width: 100%;
  max-width: 64px;
  min-width: 44px;
  background: #0f1620;
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 6px;
  padding: 3px 5px;
  font-family: var(--mono);
  font-size: 11px;
}

.cages { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.cages label { font-size: 11px; color: var(--dim); display: flex; align-items: center; gap: 4px; }

/* HUMAN PLAYER block: the same control style as a ROBOT spec row, one notch tighter, because it sits
   inside a 215px alliance card and the four fields have to stay on two lines. */
.hp-row { margin-top: 10px; }
.specs.hp {
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 2px 6px;
  margin-top: 0;
  border-top: 1px solid var(--line);
  padding-top: 6px;
}
.specs.hp label { font-size: 11px; }
.specs.hp input { max-width: 48px; min-width: 40px; }
.hp-head { grid-column: 1 / -1; font-size: 10px; letter-spacing: 0.07em; color: var(--dim); }

.pick, .check { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-size: 12px; color: var(--dim); }
.pick select, .pick input[type="number"] {
  background: #0f1620; border: 1px solid var(--line); color: var(--text);
  border-radius: 6px; padding: 4px 6px; font-size: 12px; font-family: var(--mono);
}
/* One preset dropdown per ROBOT card. The name of the machine it stands for sits under the picker so the
   card says whose ledger is on the field, and "edited on this card" is what appears once it is nobody's. */
.pick.preset { flex-wrap: wrap; margin: 2px 0 6px; }
.pick.preset select { flex: 1 1 120px; min-width: 0; }
.preset-note { flex: 1 1 100%; font-size: 10px; color: var(--dim); line-height: 1.35; }
.check input { accent-color: var(--blue); }

.advanced { margin-top: 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel-2); }
.advanced summary { cursor: pointer; padding: 8px 10px; font-size: 12px; color: var(--dim); }
.adv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 8px; padding: 4px 10px 10px; }
.adv-grid > * { min-width: 0; }

.run-row { display: flex; align-items: center; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
/* The two batch inputs live in the button row, where `.pick` would otherwise spread them across the line
   and right-align their labels against the far edge of the panel. */
.run-row .pick { justify-content: flex-start; gap: 6px; }
.run-row .pick input[type="number"] { width: 74px; }
/* The autosave and the link, one line under the run buttons. The status text is the only readout, so it takes
   the remaining width and truncates rather than pushing the buttons onto their own lines. */
.store-row { display: flex; align-items: center; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.store-row .hint { flex: 1; min-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The receipt an imported CSV leaves behind, styled like the other collapsed block on the panel. The list is
   the file's own row numbers, so it wraps instead of truncating. */
.import-report { margin-top: 8px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel-2); }
.import-report summary { cursor: pointer; padding: 8px 10px; font-size: 12px; color: var(--dim); }
.import-report ul { margin: 0; padding: 4px 10px 10px 28px; font-size: 11px; color: var(--dim); line-height: 1.45; }
.import-report li { margin: 2px 0; }
.progress { flex: 1; min-width: 140px; height: 16px; border: 1px solid var(--line); border-radius: 8px; position: relative; overflow: hidden; background: #0f1620; }
.progress .bar { height: 100%; width: 0%; background: var(--blue); transition: width 0.15s linear; }
.progress span { position: absolute; inset: 0; display: grid; place-items: center; font-size: 11px; color: var(--text); }

.scoreboard { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 12px; margin-bottom: 8px; }
.score { display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 0; }
.score .teams { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.score.red .tag { color: var(--red); }
.score.blue .tag { color: var(--blue); }
.score .tag { font-size: 11px; letter-spacing: 0.14em; }
.score strong { font-size: 34px; line-height: 1.1; }
.score .teams { font-size: 11px; color: var(--dim); font-family: var(--mono); }
/* The itemised ledger under the field: two columns, one per ALLIANCE, six scoring rows each. It reads
   left-to-right in the same order as the scoreboard, and each row carries its own itemisation so a 6-point
   gap can be traced to the column that produced it. */
.score-detail { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 10px; }
.sd-col { border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; min-width: 0; background: var(--panel); }
.sd-col.blue h4 { color: var(--blue); }
.sd-col.red h4 { color: var(--red); }
.sd-col h4 { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin: 0 0 6px; font-size: 12px; letter-spacing: 0.12em; }
.sd-live { font-size: 20px; color: var(--text); }
.sd-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; font-size: 12px; padding: 2px 0; }
.sd-k { color: var(--dim); font-size: 10.5px; letter-spacing: 0.1em; min-width: 84px; }
.sd-row b { font-family: var(--mono); font-weight: 600; }
.sd-sub { color: var(--dim); font-size: 11px; font-family: var(--mono); }
.sd-final { border-top: 1px solid var(--line); margin-top: 4px; padding-top: 5px; }
@media (max-width: 720px) { .score-detail { grid-template-columns: minmax(0, 1fr); } }
.clock { text-align: center; min-width: 0; }
/* The middle clock track is `auto`, so it takes its max-content and squeezes the two score columns to
   nothing on a narrow panel. The panel width, not the viewport, decides this: the preview surface can be
   any size at any viewport, so it is a container query. */
@container (max-width: 660px) {
  .scoreboard { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .clock { grid-column: 1 / -1; order: -1; }
}
.clock .period { font-size: 11px; letter-spacing: 0.14em; color: var(--accent); }
.clock .timer { font-size: 22px; font-weight: 650; }
.clock .margin { font-size: 12px; color: var(--dim); }
.inventory-strip { display: flex; justify-content: center; gap: 8px; font-size: 11px; color: var(--dim); margin: 4px 0; flex-wrap: wrap; }
.inventory-strip b { color: var(--text); font-weight: 600; }

.foul-strip { display: flex; gap: 6px; flex-wrap: wrap; min-height: 20px; margin-bottom: 6px; }
.foul {
  font-size: 11px; font-family: var(--mono);
  border: 1px solid #4a2b2b; background: #221618; color: #f0a0a0;
  border-radius: 6px; padding: 2px 6px;
}

.canvas-wrap { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: #0a1018; }
/* The field is 690 x 317 in plus a 26 in margin, so the canvas keeps that ratio and ui.js sizes the
   backing store to the box x devicePixelRatio. A fixed 1380x680 bitmap got downsampled by the browser,
   which is what made the branches and the CAGE plates look smeared. */
canvas { display: block; width: 100%; aspect-ratio: 742 / 369; }

/* ---- immersive playback -------------------------------------------------------------------
   `:fullscreen` (the Fullscreen button, key F) hands the FIELD panel to the browser and the chrome goes
   away. `body.fill` is the same rules without the browser: an embedded preview panel rejects the request
   with "Permissions check failed", and a button that only ever tried the API looked dead. In either mode
   ui.js measures the height that is actually left over and gives the canvas an explicit pixel box, so all
   these rules have to do is keep the panel from scrolling and centre the picture in what is left. */
body.fill { overflow: hidden; }
body.fill .field-panel,
.field-panel:fullscreen {
  display: flex;
  flex-direction: column;
  padding: 12px 16px 14px;
  overflow: hidden;
}
body.fill .field-panel { position: fixed; inset: 0; z-index: 60; border-radius: 0; }
.field-panel:fullscreen { background: linear-gradient(180deg, var(--panel) 0%, var(--panel-2) 100%); }
.field-panel:fullscreen .panel-head,
body.fill .field-panel .panel-head { margin-bottom: 6px; }
/* The line under the heading is the first thing a viewer has already read, and in either mode it is
   height the replay wants. */
.field-panel:fullscreen #field-sub,
body.fill .field-panel #field-sub { display: none; }
/* The detailed ledger is the two boxes under the transport bar. Like the hint line — and like the 2026
   page's legend, which is dropped for the same reason — it is a reading aid for the page, not part of the
   picture, and the same six columns are on the Read-out rail the whole time. The live REEF line under it
   is the same kind of thing, so it goes with it. */
.field-panel:fullscreen .score-detail,
body.fill .field-panel .score-detail,
.field-panel:fullscreen #rp-live,
body.fill .field-panel #rp-live { display: none; }
/* `flex-basis: 0`, not `auto`: with `auto` the wrap sizes to its content, the content is a canvas whose
   height is a percentage of the wrap, and the cycle resolves to the bitmap's own height — a picture taller
   than the panel, with the REEF and the BARGE running off the bottom of the screen. */
body.fill .canvas-wrap,
.field-panel:fullscreen .canvas-wrap { flex: 1 1 0; min-height: 0; display: grid; place-items: center; }
body.fill #field,
.field-panel:fullscreen #field { border-radius: 8px; max-width: 100%; max-height: 100%; }

.transport { display: flex; align-items: center; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.scrub-wrap { flex: 1; min-width: 180px; position: relative; }
.scrub-wrap input[type="range"] { width: 100%; margin: 0; accent-color: var(--blue); }
.scrub-marks { display: flex; position: relative; height: 10px; overflow: hidden; }
.scrub-marks i { position: absolute; top: 0; width: 3px; height: 8px; background: var(--warn); cursor: pointer; }
.scrub-marks i.climb { background: var(--good); }
.scrub-marks i.fumble { background: var(--dim); }
/* A CORAL that fell off a BRANCH is the colour of the piece it dropped, not of a foul. */
.scrub-marks i.drop { background: #f1eee4; }
.speed { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--dim); }
.speed select { background: #0f1620; border: 1px solid var(--line); color: var(--text); border-radius: 6px; padding: 3px 5px; font-size: 12px; }
.kbd { font-family: var(--mono); font-size: 11px; border: 1px solid var(--line); border-radius: 4px; padding: 0 4px; color: var(--dim); }

.tbl { width: 100%; border-collapse: collapse; font-size: 12px; }
.tbl th, .tbl td { text-align: left; padding: 4px 6px; border-bottom: 1px solid #1d2734; }
.tbl th { color: var(--dim); font-weight: 600; font-size: 11px; }
.tbl td.mono, .tbl th.mono { font-family: var(--mono); text-align: right; }
.tbl tr:last-child td { border-bottom: none; }
.swatch { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }

/* The batch's two histograms. The `canvas` rule above sizes the FIELD by aspect ratio and it is an
   element selector, so a chart has to take its own box back or it inherits the field's 2:1 shape. */
canvas.chart { width: 100%; height: 132px; aspect-ratio: auto; display: block; background: #0f1620; border: 1px solid var(--line); border-radius: 10px; margin: 2px 0; }
/* The single-MATCH charts. The output panel is the container, so this asks the panel how wide it is, not the
   window: at the ~510 px the in-app preview gives, six band rows still need the whole width to themselves. */
.charts { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; margin: 6px 0 2px; }
canvas.chart.tall { height: 176px; }
@container (min-width: 820px) { .charts { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.rp-list { display: grid; gap: 8px; }
.rp-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 3px 8px; align-items: center; font-size: 12px; }
.rp-row small { color: var(--dim); }
.rp-row .rp-bar { grid-column: 1 / -1; height: 7px; border-radius: 999px; overflow: hidden; background: #0f1620; border: 1px solid var(--line); }
.rp-row .rp-bar > i { display: block; height: 100%; }
.rp-row .rp-bar.blue > i { background: var(--blue); }
.rp-row .rp-bar.red > i { background: var(--red); }
.delta-line { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 12px; color: var(--dim); margin-top: 6px; }
.delta-cell { font-family: var(--mono); }
.delta-cell[data-dir="up"] { color: var(--good); }
.delta-cell[data-dir="down"] { color: var(--warn); }

.output .events { max-height: 260px; overflow: auto; font-size: 11px; font-family: var(--mono); color: var(--dim); display: flex; flex-direction: column; gap: 2px; }
.output .events b { color: var(--text); font-weight: 600; }
/* Every ledger row is a seek now, so it has to look like one and give room to hit: a row is 11 px of text. */
.output .events .ev { cursor: pointer; padding: 1px 4px; margin: 0 -4px; border-radius: 4px; }
.output .events .ev:hover { background: var(--panel-2); color: var(--text); }
/* One tab body is on screen at a time and the rail already scrolls, so the body gets its own ceiling:
   without it the Measured-data tab made the sticky rail jump as the page grew. */
.tab-body { max-height: calc(100vh - 230px); overflow-y: auto; }
@media (max-width: 1240px) { .output .tab-body { max-height: none; } }

.results-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.card { border: 1px solid var(--line); border-radius: 10px; padding: 10px; background: var(--panel-2); }
.card .k { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.08em; }
.card .v { font-size: 20px; font-family: var(--mono); }
.card .n { font-size: 11px; color: var(--dim); }

.rules-grid, .data-grid { display: grid; gap: 14px; }
.rules-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.data-grid { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.data-grid ul { margin: 0; padding-left: 18px; font-size: 12px; color: var(--dim); }
.data-grid li { margin-bottom: 6px; }
.data-grid b { color: var(--text); }
.data-grid p { font-size: 12px; color: var(--dim); }
