:root {
  --bg: #0a0d14;
  --panel: rgba(255,255,255,.04);
  --line: rgba(255,255,255,.09);
  --text: #e8ecf5;
  --muted: #78839b;
  --red: #ff4d5e;
  --red-glow: rgba(255,77,94,.55);
  --blue: #38bdf8;
  --blue-glow: rgba(56,189,248,.55);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* The `hidden` attribute only sets `display: none` from the UA stylesheet, so
   ANY author rule that sets `display` on the same element silently wins and the
   element stays on screen. Both panels toggled by JS (.metrics, .replaybar) set
   display, so without this they are never actually hidden -- .metrics is an
   inset:0 overlay, so it covered the arena and the mode buttons entirely. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font: 500 14px/1.45 ui-sans-serif, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}


/* ------------------------------------------------------------------ portal
 * A thin tab strip above every project's page. Deliberately not an iframe
 * shell: each project stays a normal standalone page that works on its own,
 * and adding a third is one <a> plus one nginx location block. The strip is
 * duplicated into each project's index.html rather than served from a shared
 * asset, because the projects are separate deployments with separate
 * lifecycles -- a shared file would couple their release cadence for the sake
 * of twenty lines of markup. */
.portal {
  display: flex; align-items: center; gap: 4px;
  padding: 0 14px; height: 38px;
  background: #05070c;
  border-bottom: 1px solid rgba(120,150,200,0.14);
  position: sticky; top: 0; z-index: 40;
}
.portal-brand {
  font-weight: 700; font-size: 12px; letter-spacing: 0.14em;
  color: #6f86b5; margin-right: 10px;
}
.portal-tab {
  display: inline-flex; align-items: center; height: 26px;
  padding: 0 12px; border-radius: 6px;
  font-size: 13px; text-decoration: none;
  color: #8ea3c9; transition: background 0.12s, color 0.12s;
}
.portal-tab:hover { background: rgba(120,150,200,0.10); color: #cfdcf2; }
.portal-tab.active { background: rgba(120,170,255,0.16); color: #eaf1ff; }
@media (max-width: 640px) {
  .portal { padding: 0 8px; height: 34px; }
  .portal-brand { display: none; }
}

body::before {
  content: "";
  position: fixed; inset: 0;
  background:
    radial-gradient(900px 600px at 20% -10%, rgba(255,77,94,.10), transparent 60%),
    radial-gradient(900px 600px at 80% 110%, rgba(56,189,248,.10), transparent 60%);
  pointer-events: none;
}

.shell {
  /* minus the portal strip above */
  --portal-h: 38px;
  position: relative;
  /* dvh tracks the shrinking/growing mobile browser chrome; height:100% against
     a fixed viewport leaves the bottom row under the address bar on phones.
     The 100% line is the fallback for browsers without dvh. */
  height: 100%;
  height: calc(100dvh - var(--portal-h));
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: 14px;
  padding: 18px clamp(14px, 3vw, 34px) 16px;
  padding-bottom: max(16px, env(safe-area-inset-bottom));
  max-width: 1180px;
  margin: 0 auto;
}

/* Below this the fixed rows (HUD + footer) leave the arena no usable height,
   so give the page back its scroll rather than crushing the stage to nothing.
   Browser chrome -- tab strip, address bar, toolbars -- eats real estate that
   a pure "phone proportions" assumption does not account for. */
@media (max-height: 560px) {
  html, body { overflow: auto; }
  .shell { height: auto; min-height: calc(100dvh - var(--portal-h)); }
  .stage { min-height: 320px; }
}

/* ---------- HUD ---------- */
.hud {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 18px;
}

/* Stacked, not inline: laid out as one flex ROW every element sat on the
   HUD's horizontal centreline, which put the speed readouts level with the
   timer and reading as part of the centre block. Stacking them above the
   team badge and pinning each to its own outer edge puts them where they
   belong -- the page's top-left and top-right corners, clear of the middle. */
.hud-side {
  display: flex; flex-direction: column; gap: 8px; min-width: 0;
  align-items: flex-start;
}
.hud-right { align-items: flex-end; }

/* One agent per row: a track showing that agent's share of ITS OWN side's
   top speed (Red and Blue have different caps, so a shared scale would read
   effort as advantage), plus the raw value, which stays comparable between
   the sides. Fixed width and deliberately NOT flex:1 -- growing to fill the
   column is what let these creep back toward the centre. */
.speeds { display: grid; gap: 5px; flex: 0 0 auto; width: 172px; }
.spd { display: flex; align-items: center; gap: 8px; }
.spd-name {
  font-size: 9.5px; color: var(--muted); letter-spacing: .06em;
  width: 20px; flex: none; font-variant-numeric: tabular-nums;
}
.spd-track {
  flex: 1 1 auto; height: 9px; border-radius: 999px;
  background: rgba(255,255,255,.10); overflow: hidden; min-width: 40px;
  border: 1px solid rgba(255,255,255,.07);
}
/* display:block is load-bearing, not decoration. The fill is a <span>, and an
   inline box ignores width/height outright -- so the track (a flex item, and
   therefore blockified) rendered while the fill inside it collapsed to
   nothing, leaving a bar that was permanently empty however fast the agent
   was actually going. The min-width keeps a slow-but-moving agent visible. */
.spd-fill {
  display: block;
  height: 100%; border-radius: 999px; min-width: 3px;
  transition: width .12s linear;
}
.spd-fill.zero { min-width: 0; }
.speeds.red .spd-fill { background: var(--red); box-shadow: 0 0 8px var(--red-glow); }
.speeds.blue .spd-fill { background: var(--blue); box-shadow: 0 0 8px var(--blue-glow); }
.spd-val {
  font-size: 10.5px; color: var(--text); font-variant-numeric: tabular-nums;
  width: 32px; text-align: right; flex: none;
}
.spd.out { opacity: .32; }

/* Narrow screens stack the HUD instead of hiding the speed bars. Hiding them
   below 760px meant the feature was invisible on exactly the device most
   likely to be used to check it. */
@media (max-width: 760px) {
  /* On a phone the 1fr/auto/1fr grid has no room to be three columns, so the
     whole HUD collapses into one centred stack -- and the speed readouts came
     along with it, bunched in the middle while the top-left and top-right of
     the screen sat completely empty. Flow layout cannot put them in those
     corners here, so they are lifted out of it and pinned there directly. */
  .hud { position: relative; gap: 8px; }
  .hud-side { gap: 6px; align-items: center; }
  /* Level with the timer and the team names rather than jammed against the
     top edge, and given room to breathe. */
  .speeds {
    position: absolute; top: 50%; transform: translateY(-50%);
    width: 138px; gap: 9px; z-index: 2;
  }
  #speeds-red { left: 0; }
  #speeds-blue { right: 0; }
  .spd { gap: 7px; }
  .spd-name { width: 16px; }
  .spd-val { width: 26px; }
  .spd-track { min-width: 24px; height: 8px; }
  .team { padding: 6px 10px; }
  .team-sub { max-width: 140px; }
}

.team {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 15px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  backdrop-filter: blur(6px);
}
.team-b { flex-direction: row; text-align: right; }

.team-name { font-size: 12.5px; font-weight: 700; letter-spacing: .07em; }
.team-sub {
  font-size: 10.5px; color: var(--muted); letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
  max-width: 210px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.dot { width: 11px; height: 11px; border-radius: 50%; flex: none; }
.team-a .dot { background: var(--red); box-shadow: 0 0 14px var(--red-glow); }
.team-b .dot { background: var(--blue); box-shadow: 0 0 14px var(--blue-glow); }

.hud-center { text-align: center; }
.timer {
  font-size: 40px; font-weight: 750; letter-spacing: -.02em; line-height: 1;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 26px rgba(255,255,255,.16);
}
.timer.urgent { color: #ffd166; text-shadow: 0 0 26px rgba(255,209,102,.4); }
.timer-label { font-size: 9.5px; letter-spacing: .22em; color: var(--muted); margin-top: 4px; }
.series {
  margin-top: 7px; font-size: 13px; font-weight: 700;
  color: var(--muted); font-variant-numeric: tabular-nums; letter-spacing: .06em;
}

/* ---------- stage ---------- */
.stage { position: relative; display: grid; place-items: center; min-height: 0; }
canvas { display: block; border-radius: 16px; }

.banner {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  pointer-events: none;
  opacity: 0; transition: opacity .35s ease;
}
.banner.show { opacity: 1; }
.banner span {
  padding: 16px 40px;
  font-size: 30px; font-weight: 800; letter-spacing: .1em;
  border-radius: 16px;
  background: rgba(10,13,20,.74);
  border: 1px solid var(--line);
  backdrop-filter: blur(10px);
  transform: translateY(6px) scale(.97);
  transition: transform .35s cubic-bezier(.2,.8,.3,1);
}
.banner.show span { transform: none; }
.banner.red span  { color: var(--red);  box-shadow: 0 0 46px var(--red-glow); }
.banner.blue span { color: var(--blue); box-shadow: 0 0 46px var(--blue-glow); }

/* Overlay controls live in one wrapping row spanning the stage, so the mode
   pill and the connection badge can never sit on top of each other however
   narrow the viewport gets. */
.stage-top {
  position: absolute; top: 12px; left: 14px; right: 14px;
  z-index: 6;                 /* above .metrics (5): see .modes */
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 8px; flex-wrap: wrap;
  pointer-events: none;       /* only the controls themselves are clickable */
}
.stage-top > * { pointer-events: auto; }

.conn {
  display: flex; align-items: center; gap: 7px;
  font-size: 10.5px; letter-spacing: .1em; color: var(--muted);
  background: rgba(10,13,20,.5);
  padding: 5px 10px; border-radius: 999px; border: 1px solid var(--line);
}
.conn-dot { width: 6px; height: 6px; border-radius: 50%; background: #ffd166; }
.conn.live .conn-dot { background: #4ade80; box-shadow: 0 0 9px rgba(74,222,128,.75); }
.conn.down .conn-dot { background: var(--red); }

/* ---------- mode toggle ---------- */
/* Positioning and stacking are owned by .stage-top. The z-index there is what
   keeps these clickable above .metrics -- that full-bleed overlay used to
   cover its own way out, leaving a reload as the only exit. */
.modes {
  display: flex; gap: 4px; flex-wrap: wrap;
  background: rgba(10,13,20,.5);
  border: 1px solid var(--line);
  border-radius: 999px; padding: 3px;
}
.mode-btn {
  font: inherit; font-size: 10.5px; font-weight: 700; letter-spacing: .12em;
  color: var(--muted); background: transparent; border: 0; cursor: pointer;
  padding: 5px 13px; border-radius: 999px; transition: color .18s, background .18s;
  /* A comfortable touch target; on a phone these were 24px tall pills. */
  min-height: 32px;
}
.mode-btn:hover { color: var(--text); }
.mode-btn.active { color: #0a0d14; background: var(--text); }

/* Recording is a toggle, not a view, so it sits slightly apart from the
   LIVE/REPLAY/METRICS group and never takes the .active treatment. */
.rec-btn { display: inline-flex; align-items: center; gap: 6px; margin-left: 4px; }
.rec-btn::before {
  content: ""; width: 1px; height: 13px;
  background: var(--line); margin-right: 4px;
}
.rec-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: #55607a; transition: background .18s;
}
.rec-btn.recording { color: #ff6b7d; }
.rec-btn.recording .rec-dot { background: #ff4d5e; animation: recpulse 1.1s infinite; }
.rec-btn[disabled] { opacity: .4; cursor: not-allowed; }
@keyframes recpulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }

/* ---------- replay bar ---------- */
.replaybar {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 11px 14px 13px;
  display: grid; gap: 9px;
}
.rb-row { display: flex; align-items: center; gap: 9px; }

.replaybar select {
  font: inherit; font-size: 11.5px;
  color: var(--text); background: rgba(255,255,255,.05);
  border: 1px solid var(--line); border-radius: 8px;
  padding: 6px 9px; cursor: pointer;
}
#replay-select { flex: 1; min-width: 0; }
.replaybar select:focus-visible,
.rb-btn:focus-visible,
#rb-scrub:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

.rb-btn {
  font: inherit; font-size: 11px; color: var(--text);
  background: rgba(255,255,255,.07); border: 1px solid var(--line);
  border-radius: 8px; padding: 6px 12px; cursor: pointer; min-width: 42px;
}
.rb-btn:hover { background: rgba(255,255,255,.13); }

.rb-time {
  font-size: 11.5px; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.rb-meta {
  font-size: 10.5px; color: var(--muted); letter-spacing: .03em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

#rb-scrub {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 5px; border-radius: 999px;
  background: rgba(255,255,255,.12); cursor: pointer;
}
#rb-scrub::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--text); border: 0; box-shadow: 0 0 10px rgba(255,255,255,.35);
}
#rb-scrub::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--text); border: 0;
}

/* ---------- footer ---------- */
.status { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
.chips { display: flex; gap: 8px; flex-wrap: wrap; }
.chip {
  font-size: 11px; color: var(--muted); letter-spacing: .05em;
  background: var(--panel); border: 1px solid var(--line);
  padding: 5px 11px; border-radius: 999px;
}
.chip b { color: var(--text); font-variant-numeric: tabular-nums; }
.legend { font-size: 10.5px; color: var(--muted); letter-spacing: .05em; }

@media (max-width: 720px) {
  .hud { grid-template-columns: 1fr; justify-items: center; gap: 10px; }
  .hud-right { justify-content: center; }
  .timer { font-size: 32px; }
  .legend { display: none; }
}

/* ---------- phones ----------
   The arena is square and sized to the smaller side of its container, so on a
   narrow portrait screen every row above and below it directly shrinks the
   playfield. Reclaim that space, and grow the tap targets: 10.5px pills with
   5px padding are well under the ~44px touch target phones need. */
@media (max-width: 560px) {
  .shell { gap: 8px; padding: 10px 10px 8px; }

  /* stack the two teams above the arena without a third full row */
  .hud { gap: 6px; }
  .timer { font-size: 26px; }
  .timer-label { display: none; }
  .team-name { font-size: 11px; }
  .team-sub { max-width: 150px; }

  .modes { top: 8px; left: 8px; gap: 2px; padding: 2px; }
  .mode-btn { font-size: 11px; padding: 9px 14px; letter-spacing: .08em; }

  .replaybar { padding: 9px 10px 11px; gap: 8px; }
  .rb-row { flex-wrap: wrap; gap: 6px; }
  #replay-select { flex: 1 1 100%; order: -1; }
  .rb-btn { padding: 10px 16px; font-size: 13px; }
  .replaybar select { padding: 9px 10px; font-size: 12.5px; }
  /* a thin range track is hard to grab with a thumb */
  #rb-scrub { height: 26px; }

  /* keep the two most useful chips; the rest just eat arena height */
  .status { gap: 8px; }
  .chip { font-size: 10px; padding: 4px 9px; }
  .chip:nth-child(n+3) { display: none; }

  .m-canvas { height: 86px; }
  .m-kpi { padding: 4px 7px; }
}

/* Landscape phone: vertical space is the scarce axis, so drop the chrome
   that costs height rather than width. */
@media (max-height: 460px) and (orientation: landscape) {
  .shell { gap: 6px; padding: 6px 10px; }
  .hud { grid-template-columns: 1fr auto 1fr; gap: 10px; }
  .timer { font-size: 22px; }
  .timer-label, .status { display: none; }
}

/* ---------- metrics dashboard ---------- */
/* Overlays the arena rather than resizing it, so switching modes never
   reflows the canvas the renderer is sized against. */
.metrics {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px 12px;
  overflow-y: auto;
  background: linear-gradient(180deg, rgba(10,13,20,.97), rgba(10,13,20,.99));
  backdrop-filter: blur(2px);
}

/* The .modes pill floats over this panel's top-left corner. Reserving room
   with a hard-coded padding was wrong: the pill's width changes with its
   label text and font, so the number was only ever right on one screen.
   Reserving a whole row instead is width-independent and cannot collide. */
.m-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; flex: 0 0 auto;
  padding-top: 42px;
}

/* A second, explicit way out of the panel (the .modes pill is the first).
   Sits with the run title so it reads as "leave this view", not a filter. */
.m-back {
  align-self: flex-start;
  font: inherit; font-size: 11px; font-weight: 600; letter-spacing: .04em;
  color: var(--text); background: rgba(255,255,255,.07);
  border: 1px solid var(--line); border-radius: 8px;
  padding: 5px 11px; cursor: pointer; margin-bottom: 2px;
  transition: background .18s;
}
.m-back:hover { background: rgba(255,255,255,.14); }
.m-back:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.m-title { display: flex; flex-direction: column; gap: 3px; }
.m-run {
  font-size: 13px; font-weight: 650; letter-spacing: .04em;
}
.m-sub { font-size: 10.5px; color: var(--muted); letter-spacing: .03em; }

.m-kpis { display: flex; flex-wrap: wrap; gap: 6px; }
.m-kpi {
  display: flex; flex-direction: column; gap: 1px;
  padding: 5px 9px; border-radius: 7px;
  background: var(--panel); border: 1px solid var(--line);
  font-size: 9px; color: var(--muted); letter-spacing: .06em; text-transform: uppercase;
}
.m-kpi b {
  font-size: 12.5px; color: var(--text); letter-spacing: .01em;
  text-transform: none; font-variant-numeric: tabular-nums;
}

/* Filters/controls sit in one row above the charts they scope, never inside
   a card. */
.m-controls {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  flex: 0 0 auto; padding: 2px 0 2px;
}
.m-ctl {
  display: flex; align-items: center; gap: 9px;
  font-size: 10.5px; color: var(--muted); letter-spacing: .05em;
}
.m-ctl input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: clamp(90px, 18vw, 170px); height: 4px; border-radius: 999px;
  background: rgba(255,255,255,.14); cursor: pointer;
}
.m-ctl input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--text); border: 0;
}
.m-ctl input[type="range"]::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--text); border: 0;
}
.m-ctl b {
  color: var(--text); font-variant-numeric: tabular-nums;
  font-size: 11px; min-width: 30px;
}
.m-ctl-note { font-size: 10px; color: var(--muted); letter-spacing: .03em; }

/* Wider minimum than the old 260px: at 260 the charts were ~104px tall
   thumbnails, too small to read a trend off. */
.m-grid {
  display: grid; gap: 12px; flex: 1 1 auto;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  align-content: start;
}

.m-card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; padding: 11px 13px 9px;
  display: flex; flex-direction: column; gap: 7px;
}
/* The headline chart (Red vs Blue win rate) takes the full row -- it is the
   one number the whole run is judged on. */
.m-card.m-wide { grid-column: 1 / -1; }
.m-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.m-card-title { font-size: 12.5px; font-weight: 650; letter-spacing: .03em; }
.m-card-legend { display: flex; gap: 11px; flex-wrap: wrap; }
.m-tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10.5px; color: var(--muted); letter-spacing: .03em;
}
.m-tag i { width: 12px; height: 2px; border-radius: 1px; display: inline-block; }
.m-tag b {
  color: var(--text); font-variant-numeric: tabular-nums; font-weight: 650;
}
.m-canvas { width: 100%; height: 190px; display: block; cursor: crosshair; }
.m-card.m-wide .m-canvas { height: 240px; }
.m-card-hint { font-size: 10px; color: var(--muted); letter-spacing: .02em; }

.m-foot {
  flex: 0 0 auto; font-size: 10px; color: var(--muted);
  letter-spacing: .03em; padding-top: 2px;
}

@media (max-width: 620px) {
  .m-grid { grid-template-columns: 1fr; }
  .m-canvas, .m-card.m-wide .m-canvas { height: 160px; }
  /* The .modes pill wraps to its own line on narrow screens, so the header
     no longer has to reserve room beside it. */
  .m-head { padding-left: 0; padding-top: 44px; }
}
