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

body {
  background: #1a1a2e;
  color: #e0e0e0;
  font-family: 'Segoe UI', system-ui, sans-serif;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  padding: 1rem;
}

.container {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.main-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: start;
  gap: 1rem;
  width: 100%;
}

.tabs-panel {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  background: #16213e;
  border: 1px solid #2a2a4a;
  border-radius: 12px;
  padding: 0.8rem;
  max-height: var(--tabs-max-h, none);
}

.tabs-header {
  font-size: 0.85rem;
  font-weight: 600;
  color: #c0c0d0;
  padding-bottom: 0.3rem;
  border-bottom: 1px solid #2a2a4a;
}

.replay-tabs {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: none;
}
.replay-tabs::-webkit-scrollbar { display: none; }

.replay-tab {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: #0f3460;
  border: 1px solid #2a2a5a;
  border-radius: 6px;
  padding: 0.4rem 0.5rem;
  font-size: 0.8rem;
  color: #e0e0e0;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
  user-select: none;
}

.replay-tab:hover {
  background: #173a6b;
}

.replay-tab:focus-visible {
  outline: 2px solid #e879a0;
  outline-offset: -2px;
}

.replay-tab.active {
  background: #2a2a5a;
  border-color: #e879a0;
}

.replay-tab-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.replay-tab-remove,
.replay-tab-share {
  background: transparent;
  color: #a0a0b0;
  border: none;
  border-radius: 3px;
  width: 1.4rem;
  height: 1.4rem;
  flex-shrink: 0;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}

.replay-tab-remove:hover {
  background: #3a3a6a;
  color: #ff6b6b;
}

.replay-tab-share:hover {
  background: #3a3a6a;
  color: #e879a0;
}

.canvas-column {
  grid-column: 2;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: 1280px;
}

.sidebar {
  grid-column: 3;
  justify-self: start;
  width: 280px;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.left-panel {
  grid-column: 1;
  justify-self: end;
  width: 280px;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  min-height: 0;
}

.input-panel-wrap {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.input-panel {
  background: #16213e;
  border: 1px solid #2a2a4a;
  border-radius: 12px;
  padding: 1.2rem;
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.tab-content {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.tab-content.hidden {
  visibility: hidden;
}

.panel-tabs {
  display: flex;
  justify-content: center;
  margin-top: -1px;
}

.panel-tab {
  background: #0f1c35;
  border: 1px solid #2a2a4a;
  border-radius: 0;
  color: #a0a0b0;
  padding: 0.4rem 1.3rem;
  font-size: 0.85rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
  position: relative;
  z-index: 1;
}

.panel-tab + .panel-tab {
  margin-left: -1px;
}

.panel-tab:hover:not(.active) {
  background: #16213e;
  color: #c0c0d0;
}

.panel-tab.active {
  background: #2a2a5a;
  color: #e879a0;
  border-color: #e879a0;
  cursor: default;
  z-index: 2;
}

.auth-row {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.auth-state {
  font-size: 0.85rem;
  color: #c0c0d0;
  word-break: break-word;
}

.auth-btn {
  background: #e879a0;
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 0.45rem 0.8rem;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}

.auth-btn:hover {
  background: #d4609a;
}

.auth-btn.hidden {
  display: none;
}

.input-panel input[type="text"] {
  background: #0f3460;
  border: 1px solid #2a2a5a;
  border-radius: 6px;
  color: #e0e0e0;
  padding: 0.5rem 0.8rem;
  font-size: 0.85rem;
  width: 100%;
  font-family: inherit;
}

.input-panel input[type="text"]::placeholder {
  color: #707080;
}

/* Mobile Safari/Chrome auto-zoom the viewport when focusing an input whose
   font-size is below 16px, which leaves the page zoomed-in after the keyboard
   closes. Bump to 16px on touch devices to suppress that. Desktop (hover-
   capable pointer) keeps the original 0.85rem size. */
@media (hover: none) and (pointer: coarse) {
  .input-panel input[type="text"] {
    font-size: 16px;
  }
  /* Match-view dual-canvas layout isn't mobile-optimized yet — grey out the
     tab on touch devices so desktop users can still test the feature. */
  .panel-tab[data-target="match"] {
    opacity: 0.4;
    cursor: not-allowed;
    pointer-events: none;
  }
  .panel-tab[data-target="match"]::after {
    content: "desktop only";
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 3px;
    font-size: 0.6rem;
    font-weight: 500;
    font-style: italic;
    letter-spacing: 0.02em;
    color: #8a8aa0;
    white-space: nowrap;
    pointer-events: none;
  }
}

#fetch-btn,
#match-fetch-btn {
  background: #e879a0;
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 0.6rem 1.5rem;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  align-self: center;
  transition: background 0.15s ease;
  letter-spacing: 0.03em;
}

#fetch-btn:hover:not(:disabled),
#match-fetch-btn:hover:not(:disabled) {
  background: #d4609a;
}

#fetch-btn:disabled,
#match-fetch-btn:disabled {
  background: #7a4060;
  cursor: not-allowed;
  opacity: 0.6;
}

.file-row {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.file-row label {
  font-size: 0.9rem;
  font-weight: 600;
  color: #c0c0d0;
}

.file-row .required {
  color: #e879a0;
}

.file-row .optional {
  color: #808090;
}

.file-drop {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 2.3rem;
  padding: 0.45rem 0.8rem;
  background: #0f3460;
  border: 1px dashed #2a2a5a;
  border-radius: 6px;
  color: #7a7a90;
  font-size: 0.85rem;
  cursor: pointer;
  text-align: center;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.file-drop:hover {
  background: #143a6b;
  border-color: #3a3a6a;
  color: #b0b0c0;
}

.file-drop.dragover {
  background: #2a2a5a;
  border-style: solid;
  border-color: #e879a0;
  color: #e879a0;
}

.file-drop.has-file {
  border-style: solid;
  color: #e0e0e0;
}

.file-drop input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.file-drop-text {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
}

.file-row .skin-select {
  background: #0f3460;
  border: 1px solid #2a2a5a;
  border-radius: 6px;
  color: #e0e0e0;
  font-size: 0.85rem;
  padding: 0.4rem 0.6rem;
  width: 100%;
}

#load-btn {
  background: #e879a0;
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 0.6rem 1.5rem;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  align-self: center;
  margin-top: 0.2rem;
  transition: background 0.15s ease;
  letter-spacing: 0.03em;
}

#load-btn:hover {
  background: #d4609a;
}

#load-btn:disabled {
  background: #7a4060;
  cursor: not-allowed;
}

.status {
  min-height: 1.5rem;
  font-size: 0.85rem;
  color: #e0e0e0;
  text-align: center;
}

.load-progress {
  align-self: center;
  width: 70%;
  max-width: 260px;
  height: 4px;
  margin-top: 0.55rem;
  background: rgba(232, 121, 160, 0.15);
  border-radius: 2px;
  overflow: hidden;
  display: none;
}

.load-progress.active {
  display: block;
}

.load-progress-bar {
  width: 40%;
  height: 100%;
  background: linear-gradient(90deg, transparent 0%, #e879a0 50%, transparent 100%);
  animation: load-progress-slide 1.2s ease-in-out infinite;
}

@keyframes load-progress-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}

.player-controls {
  display: flex;
  align-items: center;
  gap: 1rem;
  width: 100%;
}

.player-controls.hidden {
  display: none;
}

.play-pause-btn {
  background: #e879a0;
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 0.45rem 0;
  font-size: 1.1rem;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s ease;
  line-height: 1;
  width: 2.4rem;
  text-align: center;
}

.play-pause-btn:hover {
  background: #d4609a;
}

.scrub-bar-wrap {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
  height: 16px;
}

.scrub-bar {
  width: 100%;
  cursor: pointer;
  height: 4px;
  margin: 0;
  position: relative;
  z-index: 1;
}

.scrub-markers {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 12px;
  pointer-events: none;
  z-index: 2;
}

.scrub-marker {
  position: absolute;
  top: 0;
  width: 2px;
  height: 12px;
  transform: translateX(-50%);
  border-radius: 1px;
}

.scrub-marker.miss {
  background: #ff4d4d;
}

.scrub-marker.sliderbreak {
  background: #ffdd33;
}

.scrub-marker.miss.partner {
  background: #3d9bff;
}

.scrub-marker.sliderbreak.partner {
  background: #8fd2ff;
}

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  background: #2a2a4a;
  border-radius: 2px;
}

input[type="range"]::-moz-range-track {
  height: 4px;
  background: #2a2a4a;
  border-radius: 2px;
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 3px;
  height: 16px;
  background: #e879a0;
  border: none;
  border-radius: 1px;
  margin-top: -6px;
  cursor: pointer;
}

input[type="range"]::-moz-range-thumb {
  width: 3px;
  height: 16px;
  background: #e879a0;
  border: none;
  border-radius: 1px;
  cursor: pointer;
}

.time-display {
  font-size: 0.85rem;
  color: #a0a0b0;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  min-width: 7ch;
  text-align: right;
}

.share-at-time-btn {
  background: transparent;
  color: #a0a0b0;
  border: 1px solid #2a2a5a;
  border-radius: 4px;
  width: 1.8rem;
  height: 1.8rem;
  flex-shrink: 0;
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.share-at-time-btn:hover {
  background: #3a3a6a;
  color: #e879a0;
  border-color: #e879a0;
}
.share-at-time-btn.hidden { display: none; }

.render-options {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 1.5rem;
  width: 100%;
  background: #16213e;
  border: 1px solid #2a2a4a;
  border-radius: 8px;
  padding: 0.6rem 1rem;
}

/* Match mode: audio-swap is visible in the middle column. Switch to
   1fr auto 1fr so the swap control sits at the panel's true horizontal
   center regardless of left/right group widths. In normal mode the
   middle column stays `auto` (collapsed) so the left/right groups take
   their natural width instead of being crammed into half the panel. */
.render-options:has(#audio-swap:not(.hidden)) {
  grid-template-columns: 1fr auto 1fr;
}

.render-options.hidden {
  display: none;
}

.opts-left,
.opts-right {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  min-width: 0;
}
.opts-left  { justify-self: start; justify-content: flex-start; }
.opts-right { justify-self: end;   justify-content: flex-end;   }

.option-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: #c0c0d0;
  cursor: pointer;
  user-select: none;
}

.option-toggle input[type="checkbox"] {
  accent-color: #e879a0;
  width: 15px;
  height: 15px;
  cursor: pointer;
}

.option-dim {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: #c0c0d0;
}

.option-dim input[type="range"] {
  accent-color: #e879a0;
  width: 110px;
  cursor: pointer;
}

.option-dim span {
  font-size: 0.8rem;
  color: #a0a0b0;
  font-variant-numeric: tabular-nums;
  min-width: 3.5ch;
  text-align: right;
}

.volume-stack {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.volume-stack .option-dim {
  margin-left: 0;
}

.vol-label {
  width: 4.5rem;
  display: inline-block;
}

.option-offset {
  gap: 0.35rem;
}

.option-offset span {
  min-width: 5ch;
}

.offset-btn {
  background: transparent;
  border: 1px solid #2a2a4a;
  border-radius: 5px;
  color: #c0c0d0;
  font-size: 0.85rem;
  line-height: 1;
  padding: 0.2rem 0.4rem;
  cursor: pointer;
  font-family: inherit;
  flex-shrink: 0;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.offset-btn:hover {
  background: #2a2a5a;
  color: #e879a0;
  border-color: #e879a0;
}

.offset-btn:active {
  background: #3a3a6a;
}

.offset-reset {
  font-size: 0.95rem;
  padding: 0.15rem 0.35rem;
}

.sync-btn {
  background: transparent;
  border: 1px solid #2a2a4a;
  border-radius: 6px;
  color: #c0c0d0;
  font-size: 0.95rem;
  line-height: 1;
  padding: 0.25rem 0.45rem;
  cursor: pointer;
  font-family: inherit;
  flex-shrink: 0;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.sync-btn:hover {
  background: #2a2a5a;
  color: #e879a0;
  border-color: #e879a0;
}

.sync-btn:active {
  background: #3a3a6a;
}

/* Audio swap — two musical-note buttons flanking a swap arrow. Lives in
   the middle `auto` column of the render-options grid (`1fr auto 1fr`),
   which centers it within the panel. The INNER layout uses another
   `1fr auto 1fr` grid so the swap button sits at the element's exact
   center regardless of how wide each note's username runs — and since
   the element itself is horizontally centered in the panel (which is
   centered in the canvas column, same as the canvas stack), that center
   lines up with the vertical gap between the two canvases. */
.audio-swap {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.5rem;
  justify-self: center;
}
.audio-swap.hidden { display: none; }

.audio-swap > .audio-swap-note:first-of-type { justify-self: end; }
.audio-swap > .audio-swap-note:last-of-type  { justify-self: start; }

.audio-swap-note {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.6rem;
  background: transparent;
  border: 1px solid #2a2a4a;
  border-radius: 6px;
  color: #a0a0b0;
  font-size: 0.85rem;
  font-family: inherit;
  cursor: pointer;
  max-width: 11rem;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.audio-swap-note:hover {
  background: #2a2a5a;
  color: #e879a0;
  border-color: #e879a0;
}
.audio-swap-note.active {
  background: rgba(232, 121, 160, 0.18);
  color: #e879a0;
  border-color: #e879a0;
  box-shadow: 0 0 0 1px rgba(232, 121, 160, 0.5) inset;
}

.audio-swap-icon {
  font-size: 1.05rem;
  line-height: 1;
  flex-shrink: 0;
}

.audio-swap-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-style: italic;
  font-weight: 600;
}

.audio-swap-btn {
  background: transparent;
  border: 1px solid #2a2a4a;
  border-radius: 6px;
  color: #c0c0d0;
  font-size: 1.05rem;
  line-height: 1;
  padding: 0.3rem 0.55rem;
  cursor: pointer;
  font-family: inherit;
  flex-shrink: 0;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.audio-swap-btn:hover {
  background: #2a2a5a;
  color: #e879a0;
  border-color: #e879a0;
}
.audio-swap-btn:active {
  background: #3a3a6a;
}

.info-panel {
  position: relative;
  background: #16213e;
  border: 1px solid #2a2a4a;
  border-radius: 12px;
  padding: 1rem 1.1rem 1.1rem;
  overflow: hidden;
  flex: 0 1 auto;
  min-height: 0;
}

.info-header {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  border-bottom: 1px solid #2a2a4a;
  padding-bottom: 0.55rem;
  margin-bottom: 0.7rem;
}

.info-title {
  font-size: 1.05rem;
  font-weight: 600;
  color: #e879a0;
  letter-spacing: 0.02em;
  line-height: 1;
}

.info-version {
  font-size: 0.7rem;
  color: #808095;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

.info-body {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  font-size: 0.78rem;
  color: #c0c0d0;
  line-height: 1.4;
}

.info-body p {
  margin: 0;
}

.info-author {
  color: #a0a0b0;
}

.info-author strong {
  color: #e0e0e0;
  font-weight: 600;
}

.info-notes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  color: #a0a0b0;
  font-size: 0.75rem;
}

.info-notes li {
  position: relative;
  padding-left: 0.8rem;
}

.info-notes li::before {
  content: "•";
  position: absolute;
  left: 0.1rem;
  color: #e879a0;
}

.info-tips {
  color: #a0a0b0;
  font-size: 0.75rem;
}

.info-tips-label {
  color: #e879a0;
  font-weight: 600;
}

.info-key {
  display: inline-block;
  min-width: 1.2rem;
  padding: 0 0.3rem;
  border: 1px solid #2a2a5a;
  border-radius: 4px;
  background: #0f3460;
  color: #e0e0e0;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.78rem;
  line-height: 1.3;
  text-align: center;
}

.info-tip-sep {
  color: #4a4a6a;
  margin: 0 0.15rem;
}

.info-unsupported {
  color: #a0a0b0;
  font-size: 0.75rem;
}

.info-unsupported-label {
  color: #e879a0;
  font-weight: 600;
}

.info-contact {
  margin-top: 0.15rem;
  color: #c0c0d0;
}

.info-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.1rem;
}

.info-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.35rem;
  background: #0f3460;
  border: 1px solid #2a2a5a;
  border-radius: 6px;
  color: #e0e0e0;
  text-decoration: none;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.info-link:hover {
  background: #2a2a5a;
  border-color: #e879a0;
  color: #e879a0;
}

.info-link-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.info-horse {
  position: absolute;
  right: 24px;
  bottom: -4px;
  width: 70px;
  height: auto;
  opacity: 0.18;
  pointer-events: none;
  filter: saturate(0.9);
}

#replay-canvas,
#replay-canvas-2 {
  /* Explicit CSS width/height (matching the HTML width/height attributes that
     set the backing store) so the canvas has a CSS box `zoom` can scale.
     Firefox's `zoom` implementation doesn't always pick up the intrinsic size
     from the HTML attributes alone — the canvas would stay 1280×720 inside a
     zoomed-larger column and end up offset. Chrome derives the CSS size from
     the attributes either way. The narrow-viewport `@media (max-width: 1880px)`
     and dual-canvas rules override these with `width: 100%` / explicit dual
     widths via later cascade. */
  width: 1280px;
  height: 720px;
  border: 2px solid #2a2a4a;
  border-radius: 8px;
  background: #0a0a1a;
  display: block;
}

#replay-canvas.hidden,
#replay-canvas-2.hidden {
  display: none;
}

/* ─── Responsive: stack layout + scale canvas on narrow viewports ──────────────
   The native desktop layout needs ≈1872px (1280 canvas + 2×280 side panels +
   gaps). Below that we collapse the 3-column grid into a single stack and let
   the canvas scale down to viewport width via CSS (backing store stays
   1280×720 — browser downscales the bitmap). */
@media (max-width: 1880px) {
  body { padding: 0.6rem; }

  .main-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.8rem;
  }

  .canvas-column,
  .left-panel,
  .sidebar {
    grid-column: auto;
    justify-self: center;
    width: 100%;
    max-width: 1280px;
  }

  /* Stack order: canvas first (primary content), loaded-replay tabs, then
     input + info panels. */
  .canvas-column { order: 1; }
  .sidebar       { order: 2; }
  .left-panel    { order: 3; }

  #replay-canvas,
  #replay-canvas-2 {
    width: 100%;
    height: auto;
    max-width: 1280px;
  }

  /* JS ties the tabs-panel cap to canvas-column height — meaningful only in
     the 3-column layout. Release it when stacked. */
  .tabs-panel { max-height: none; }
}

/* Small phones: tighten padding and wrap controls onto multiple rows. */
@media (max-width: 600px) {
  body { padding: 0.4rem; }
  .main-row { gap: 0.6rem; }
  .input-panel { padding: 0.9rem; }
  .tabs-panel { padding: 0.6rem; }
  .player-controls { gap: 0.6rem; }
  .time-display { min-width: 6ch; font-size: 0.8rem; }
  .info-panel { padding: 0.9rem 0.9rem 2.8rem; }
  .info-horse { width: 56px; right: 14px; }

  /* Collapse render-options' 3-column grid (auto 1fr auto — or 1fr auto 1fr
     in match mode) into a single column so the toggle row, volume stack and
     bg-dim/offset row stack vertically instead of trying to share one line.
     Also forces the `:has(#audio-swap:not(.hidden))` override into the same
     single-column layout. */
  .render-options,
  .render-options:has(#audio-swap:not(.hidden)) {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.6rem;
    padding: 0.55rem 0.7rem;
  }
  .opts-left,
  .opts-right {
    justify-self: stretch;
    justify-content: flex-start;
    gap: 0.6rem 1rem;
  }
  .opts-left  { column-gap: 0.9rem; }
  .option-dim input[type="range"] { width: 100px; }
  .vol-label { width: auto; min-width: 3.4rem; }
}

/* Phone landscape: viewport height is ~390px on iPhone, so the replay is the
   focus. Canvas + scrub bar fill the viewport; render-options reorders below
   them (user scrolls down to tweak settings) and uses a compact stacked
   layout so bg-dim + offset aren't squeezed. `svh` is the "small" viewport
   height (URL bar visible) — using it keeps canvas + scrub bar on-screen
   even when Safari's chrome expands. */
@media (orientation: landscape) and (max-height: 560px) {
  body { padding: 0.25rem; }
  .main-row { gap: 0.3rem; }
  .canvas-column { gap: 0.3rem; }

  /* Canvas + scrub bar first; render-options last within canvas-column. */
  #render-options { order: 1; }

  .render-options,
  .render-options:has(#audio-swap:not(.hidden)) {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.35rem;
    padding: 0.4rem 0.6rem;
  }
  .opts-left,
  .opts-right {
    justify-self: stretch;
    justify-content: flex-start;
    gap: 0.35rem 0.9rem;
  }
  .option-toggle,
  .option-dim { font-size: 0.78rem; gap: 0.3rem; }
  .option-toggle input[type="checkbox"] { width: 13px; height: 13px; }
  .option-dim input[type="range"] { width: 95px; }
  .option-dim span { min-width: 2.8ch; font-size: 0.72rem; }
  .vol-label { width: auto; min-width: 3rem; }
  .volume-stack { gap: 0.2rem; }
  .offset-btn { padding: 0.1rem 0.3rem; font-size: 0.8rem; }
  .sync-btn { padding: 0.15rem 0.35rem; font-size: 0.85rem; }

  /* Canvas: fill the viewport minus just the scrub bar + tiny buffer so the
     replay is the dominant element on screen. aspect-ratio picks the width. */
  #replay-canvas,
  #replay-canvas-2 {
    align-self: center;
    width: auto;
    max-width: 100%;
    height: auto;
    max-height: calc(100vh - 50px);
    max-height: calc(100svh - 50px);
    aspect-ratio: 16 / 9;
  }

  .player-controls { gap: 0.5rem; }
  .time-display { min-width: 5ch; font-size: 0.75rem; }
  .play-pause-btn { font-size: 1rem; width: 2rem; padding: 0.3rem 0; }
}

/* Prototype — dual-canvas match replay viewing.
   In single mode each canvas renders at its native 1280×720. In dual mode the
   stack goes side-by-side and each canvas is visually halved (backing store
   unchanged — the browser downscales the bitmap). */
.canvas-stack { display: flex; justify-content: center; gap: 8px; }
.canvas-frame { position: relative; display: block; line-height: 0; }
.canvas-frame.hidden { display: none; }
.canvas-stack.dual #replay-canvas,
.canvas-stack.dual #replay-canvas-2 {
  width: 640px;
  height: 360px;
}

/* Player-name label — bottom-right corner of each canvas. Mimics the osu!
   tournament client's player-name plate: Exo bold italic, off-white with a
   subtle blue tint, soft drop-shadow so it stays readable against the bg. */
.player-label {
  position: absolute;
  bottom: 14px;
  right: 20px;
  color: #dce2ee;
  font-family: 'Exo', 'Segoe UI', sans-serif;
  font-style: italic;
  font-weight: 700;
  font-size: 26px;
  letter-spacing: 0.03em;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.85), 0 0 10px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  line-height: 1;
}
.player-label.hidden { display: none; }
.canvas-stack.dual .player-label { font-size: 18px; bottom: 10px; right: 14px; }
/* Dual mode: tint labels so they match the scrub-bar marker colors —
   soft off-white red for canvas1 (red misses), soft off-white blue for canvas2. */
.canvas-stack.dual #player-label-1 { color: #f3cfd1; }
.canvas-stack.dual #player-label-2 { color: #ccdcf5; }

/* Tournament-client-style dual score readout — appears below the scrub bar
   when a real match pair is active. Left canvas right-aligns to center, right
   canvas left-aligns; the leading side bumps 2px + bold italic. */
.match-score-strip {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 28px;
  align-items: flex-end;
  width: 100%;
  padding: 0 4px;
  pointer-events: none;
}
.match-score-strip.hidden { display: none; }

.match-score-side {
  display: flex;
  flex-direction: column;
  line-height: 1;
}
.match-score-side-left  { align-items: flex-end;  }
.match-score-side-right { align-items: flex-start; }

.match-score-diff {
  font-family: 'Exo', 'Segoe UI', sans-serif;
  font-style: italic;
  font-weight: 600;
  font-size: 13px;
  color: #ff9bb3;
  letter-spacing: 0.04em;
  line-height: 1;
  margin-bottom: 4px;
  min-height: 13px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  font-variant-numeric: tabular-nums;
}

.match-score {
  font-family: 'Exo', 'Segoe UI', sans-serif;
  font-style: italic;
  font-weight: 500;
  font-size: 32px;
  color: #dce2ee;
  letter-spacing: 0.02em;
  line-height: 1;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.75);
  font-variant-numeric: tabular-nums;
  transition: font-size 0.12s ease, font-weight 0.12s ease, color 0.12s ease;
}
.match-score.leading {
  font-weight: 800;
  font-size: 34px;
  color: #ffffff;
}

/* Fail overlay — centered over the partner canvas when that player has no
   replay. We still paint the beatmap background underneath so the pair stays
   visually balanced. */
.fail-overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #ffd8d8;
  font-family: 'Exo', 'Segoe UI', sans-serif;
  font-style: italic;
  font-weight: 700;
  font-size: 44px;
  letter-spacing: 0.05em;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.9);
  background: rgba(20, 10, 20, 0.55);
  padding: 0.8rem 1.6rem;
  border-radius: 10px;
  pointer-events: none;
  white-space: nowrap;
  line-height: 1;
}
.fail-overlay.hidden { display: none; }
.canvas-stack.dual .fail-overlay { font-size: 30px; padding: 0.5rem 1rem; }

/* ─── Match-view mode ────────────────────────────────────────────────────────
   When a match pair is loaded the single-replay shell (input panel, loaded-
   replays sidebar, info panel) becomes dead weight. Adding `.match-view` to
   <body> collapses the 3-column grid down to a single column dedicated to
   the two canvases, with a slim top strip carrying the match title + pair
   switcher. Each dual canvas stretches toward ≈50vw (capped at its 1280px
   backing store) so the replays dominate the viewport instead of occupying
   the centre third. */
body.match-view .left-panel,
body.match-view .sidebar {
  display: none;
}
body.match-view .main-row {
  grid-template-columns: minmax(0, 1fr);
}
body.match-view .canvas-column {
  grid-column: 1;
  justify-self: center;
  width: 100%;
  max-width: none;
}
body.match-view .canvas-stack.dual {
  gap: 12px;
}
body.match-view .canvas-stack.dual #replay-canvas,
body.match-view .canvas-stack.dual #replay-canvas-2 {
  width: min(calc(50vw - 24px), 1280px);
  height: auto;
  aspect-ratio: 16 / 9;
}
body.match-view .canvas-stack.dual .player-label {
  font-size: clamp(18px, 1.6vw, 28px);
  bottom: clamp(10px, 1vw, 16px);
  right:  clamp(14px, 1.4vw, 22px);
}
body.match-view .canvas-stack.dual .fail-overlay {
  font-size: clamp(30px, 3vw, 54px);
}

/* Slim strip above the canvases — room title, current pair, change-pair
   toggle, exit match view. The drawer drops from the strip with the match
   playlist so users can switch maps without leaving the mode. */
.match-strip {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  flex-wrap: wrap;
  background: #16213e;
  border: 1px solid #2a2a4a;
  border-radius: 8px;
  padding: 0.5rem 0.8rem;
  width: 100%;
  position: relative;
}
.match-strip.hidden { display: none; }

.match-strip-info {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.9rem;
  color: #c0c0d0;
  overflow: hidden;
}

.match-strip-room {
  color: #e879a0;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 0;
  max-width: 30ch;
}

.match-strip-sep { color: #5a5a75; flex-shrink: 0; }

.match-strip-current {
  color: #e0e0e0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.match-strip-actions {
  display: flex;
  gap: 0.4rem;
  flex-shrink: 0;
}

.match-strip-btn {
  background: #0f3460;
  border: 1px solid #2a2a5a;
  border-radius: 6px;
  color: #e0e0e0;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.35rem 0.8rem;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
  line-height: 1;
}
.match-strip-btn:hover {
  background: #2a2a5a;
  border-color: #e879a0;
  color: #e879a0;
}

.match-strip-back {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.match-strip-back-arrow { font-size: 0.95rem; line-height: 1; }

/* ─── Match overview ────────────────────────────────────────────────────────
   Overlaid on top of canvas1 when a MatchSession is active (before a pair is
   loaded). The canvas below is already painted with the first-picked map's
   gameplay background at 55% dim; this overlay layers the match metadata on
   top with a subtle gradient scrim for consistent text contrast. */
.match-overview {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  padding: 1.4rem 1.8rem;
  color: #e8e8f0;
  font-size: 0.95rem;
  line-height: 1.3;
  overflow: hidden;
  border-radius: 8px;
  background: linear-gradient(
    180deg,
    rgba(10, 10, 20, 0.55) 0%,
    rgba(10, 10, 20, 0.25) 35%,
    rgba(10, 10, 20, 0.55) 100%
  );
}
.match-overview.hidden { display: none; }

.match-overview-header {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.match-overview-title {
  font-size: 1.4rem;
  font-weight: 700;
  color: #ffffff;
  letter-spacing: 0.01em;
  margin: 0;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.85), 0 0 16px rgba(0, 0, 0, 0.55);
}

.match-overview-meta {
  font-size: 0.85rem;
  color: #c8c8dc;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

.match-overview-players {
  display: flex;
  align-items: stretch;
  gap: 1rem;
  font-size: 0.9rem;
}

.match-overview-player {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.85rem;
  background: rgba(15, 52, 96, 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(42, 42, 90, 0.85);
  border-radius: 10px;
  min-width: 16rem;
  flex: 0 0 auto;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
.match-overview-player.leader { border-color: #e879a0; }

.match-overview-player-avatar {
  width: 52px;
  height: 52px;
  border-radius: 8px;
  object-fit: cover;
  flex-shrink: 0;
  background: rgba(0, 0, 0, 0.4);
}
.match-overview-player-avatar-placeholder {
  background: rgba(30, 30, 60, 0.7);
  border: 1px dashed rgba(180, 180, 220, 0.3);
}

.match-overview-player-body {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
  flex: 1 1 auto;
}

.match-overview-player-topline {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
}

.match-overview-player-flag {
  width: 22px;
  height: auto;
  border-radius: 2px;
  flex-shrink: 0;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.match-overview-player-name {
  color: #ffffff;
  font-weight: 700;
  font-size: 1rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.match-overview-player-stats {
  color: #c4c8e0;
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}

.match-overview-player-ranks {
  color: #9fa8c8;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}

/* Scoreboard between the two player cards in a 1v1 overview. Expands to
   fill the gap and stays centered; the winner of each stat gets `.leading`
   (bold + brighter) to echo the live dual-score strip. */
.match-overview-summary {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.35rem;
  padding: 0.5rem 0.85rem;
  min-width: 10rem;
  font-variant-numeric: tabular-nums;
  color: #dce2ee;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

.match-overview-summary-score {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  column-gap: 0.8rem;
  font-family: 'Exo', 'Segoe UI', sans-serif;
  font-style: italic;
  font-size: 2.2rem;
  font-weight: 600;
  line-height: 1;
}
.match-overview-summary-wins {
  color: #c8cede;
  transition: color 0.12s ease, font-weight 0.12s ease;
}
.match-overview-summary-wins:first-child { text-align: right; }
.match-overview-summary-wins:last-child  { text-align: left;  }
.match-overview-summary-wins.leading {
  color: #ffffff;
  font-weight: 800;
}
.match-overview-summary-dash {
  color: #9fa8c8;
  font-weight: 500;
}

.match-overview-summary-stat {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  column-gap: 0.8rem;
  font-size: 0.85rem;
}
.match-overview-summary-stat-label {
  color: #8d93ad;
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.match-overview-summary-stat-val {
  color: #c8cede;
  font-style: italic;
  transition: color 0.12s ease, font-weight 0.12s ease;
}
.match-overview-summary-stat-val.left  { text-align: right; }
.match-overview-summary-stat-val.right { text-align: left;  }
.match-overview-summary-stat-val.leading {
  color: #ffffff;
  font-weight: 700;
}

.match-overview-maps {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  /* grow 0 so the panel only extends as far as the maps themselves — the
     translucent scrim stops at the last map rather than reaching the bottom
     of the overview. Still shrinkable + scrollable when a long match pushes
     past the available height. */
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 0.55rem;
  background: rgba(10, 10, 20, 0.55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border: 1px solid rgba(42, 42, 74, 0.55);
  border-radius: 8px;
  scrollbar-width: none;
}
.match-overview-maps::-webkit-scrollbar { display: none; }

.match-overview-map {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.5rem 0.75rem;
  background: rgba(26, 26, 46, 0.82);
  border: 1px solid rgba(42, 42, 74, 0.7);
  border-radius: 5px;
  font-size: 0.85rem;
}
.match-overview-map.current {
  border-color: #e879a0;
  background: rgba(52, 22, 48, 0.85);
}

.match-overview-map-index {
  color: #a0a0c0;
  font-variant-numeric: tabular-nums;
  min-width: 1.5rem;
  text-align: right;
  flex-shrink: 0;
}

.match-overview-map-title {
  color: #e8e8f0;
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.match-overview-map-scores {
  color: #b4b4cc;
  font-variant-numeric: tabular-nums;
  font-size: 0.8rem;
  white-space: nowrap;
  flex-shrink: 0;
}
.match-overview-map-score.leading {
  color: #ffffff;
  font-weight: 700;
}

.match-overview-map-load {
  background: rgba(42, 42, 90, 0.9);
  color: #e8e8f0;
  border: 1px solid rgba(58, 58, 106, 0.9);
  border-radius: 4px;
  padding: 0.3rem 0.85rem;
  font-size: 0.8rem;
  font-family: inherit;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.match-overview-map-load:hover:not(:disabled) {
  background: rgba(58, 58, 106, 1);
  border-color: #e879a0;
}
.match-overview-map-load:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.match-overview-map-share {
  background: transparent;
  color: #a0a0b0;
  border: 1px solid rgba(58, 58, 106, 0.9);
  border-radius: 4px;
  width: 1.8rem;
  height: 1.8rem;
  margin-left: 0.25rem;
  font-size: 0.9rem;
  line-height: 1;
  flex-shrink: 0;
  cursor: pointer;
  padding: 0;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.match-overview-map-share:hover {
  background: rgba(58, 58, 106, 1);
  color: #e879a0;
  border-color: #e879a0;
}

/* Match tab (prototype) */
.match-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-height: 300px;
  overflow-y: auto;
  margin-top: 0.5rem;
}
.match-header {
  font-size: 0.85rem;
  color: #a0a0c0;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid #2a2a4a;
}
.match-item {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.4rem 0.5rem;
  background: #1a1a2e;
  border: 1px solid #2a2a4a;
  border-radius: 4px;
  font-size: 0.8rem;
}
.match-item-title { color: #e0e0e0; font-weight: 600; }
.match-item-score { color: #c0c0d0; padding-left: 0.5rem; }
.match-item-load {
  align-self: flex-start;
  margin-top: 0.2rem;
  padding: 0.2rem 0.6rem;
  background: #2a2a5a;
  color: #e0e0e0;
  border: 1px solid #3a3a6a;
  border-radius: 3px;
  cursor: pointer;
  font-size: 0.75rem;
}
.match-item-load:hover:not(:disabled) { background: #3a3a6a; }
.match-item-load:disabled { opacity: 0.4; cursor: not-allowed; }
