/*
 * Scoped under .fight-scheduler-app (the first-class page wrapper) so this
 * stylesheet cannot leak into the Portal shell (sidebar/footer/chrome) or
 * collide with Bootstrap's bare .modal.
 *
 * TWO CONSUMERS. Unlike every other app in the suite, this stylesheet is loaded
 * by both the operator page (inside the Portal shell) and view.twig, a
 * standalone public document. That page therefore links the token layer itself;
 * if it ever stops, every var() below resolves to nothing — silently, because an
 * unresolved custom property is not an error. FightSchedulerAdoptionTest guards
 * the pairing.
 *
 * It also means --bs-* is mostly unavailable here: only the radius scale,
 * --bs-border-color and the link colours come from portal-theme.css. The rest of
 * the Bootstrap layer is vendored Bootstrap, which the standalone page does not
 * load — so this app maps onto --portal-c-* and --portal-*, not onto
 * --bs-body-bg / --bs-tertiary-bg the way the shell-only apps did.
 *
 * The app used to declare its palette twice, once here and once under a private
 * [data-bs-theme="light"] block. Both are gone: what mapped onto a global token
 * now derives from it (and those already resolve both ways, so the light block
 * had nothing left to say), and eight properties had zero reads.
 */
.fight-scheduler-app {
  /* Page-private, because the Portal has no equivalent.
     --fs-accent-text is the one real swap left in the file: teal reads on the
     dark canvas but is unreadable on white, where it becomes brand purple.
     --fs-chrome-bg is the shared fill behind the time column, menu bar and
     cage-row header — faint on dark, and fully absent on light where those
     surfaces are borders only. */
  --fs-accent-text: var(--portal-accent-teal);
  --fs-magenta: var(--portal-accent);
  --fs-chrome-bg: var(--portal-c-tab-hover);

  /* The schedule's STATUS palette. These are the fills that tell an operator
     what state a fight is in, so they ride the Portal's solid button families —
     the only token group meant to be a filled block with predictable contrast.
     Fill and ink are BOTH theme-invariant on purpose: a fill that follows the
     theme under an ink that does not is exactly how a status block becomes
     unreadable in one theme only. */
  --fs-sched-bg: var(--portal-c-btn-warning-bg);
  --fs-sched-border: var(--portal-c-btn-warning-border-hover);
  --fs-sched-ink: var(--portal-c-brand);
  --fs-linked-bg: var(--portal-c-btn-success-bg);
  --fs-linked-border: var(--portal-c-btn-success-border-hover);
  --fs-event-bg: var(--portal-c-badge-info-color);
  --fs-event-border: var(--portal-c-badge-info-bg);
  --fs-conflict-bg: var(--portal-c-btn-danger-bg);
  --fs-conflict-border: var(--portal-c-btn-danger-border-hover);
  --fs-done-fg: var(--portal-c-badge-success-color);
  --fs-done-border: color-mix(in srgb, var(--portal-c-badge-success-color) 60%, transparent);
}

:root[data-bs-theme="light"] .fight-scheduler-app,
.fight-scheduler-app[data-bs-theme="light"] {
  --fs-accent-text: var(--portal-nhrl-purple);
  /* Borders only, no fill — 'transparent' is a keyword, not a literal. */
  --fs-chrome-bg: transparent;
}

.fight-scheduler-app,
.fight-scheduler-app * {
  box-sizing: border-box;
  font-family: var(--portal-font-body);
}

.fight-scheduler-app {
  /* Horizontal breathing room comes from Bootstrap .container-fluid on the
     wrapper (matching the other first-class pages); this adds the vertical gap. */
  padding-top: 1rem;
  padding-bottom: 1.5rem;
  font-family: var(--portal-font-body);
  color: var(--portal-c-text);
}

/* The canvas, for the STANDALONE public view only.
   On the operator page .fight-scheduler-app sits inside the Portal shell, which
   paints its own canvas — an adopted page lets that show through instead of
   dropping a flat panel over it. view.twig is on basePublic, which paints
   nothing for this family, so it opts in explicitly and fills the viewport. */
.fight-scheduler-app--standalone {
  min-height: 100vh;
  background-color: var(--portal-c-surface-opaque);
}

/* Top header: title left, True Finals right, top-aligned */
.app-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 4px;
}

.app-header h1 {
  margin: 0;
}

.app-header__right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.app-version {
  font-size: var(--portal-text-2xs);
  color: var(--portal-c-text-muted);
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  letter-spacing: 0.5px;
}

.fight-scheduler-app h1 {
  text-transform: uppercase;
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  color: var(--portal-c-text);
  font-size: 50px;
}

.sparky-logo {
  height: 50px;
  vertical-align: middle;
  margin-right: 10px;
}

.fight-scheduler-app h2 {
  text-transform: uppercase;
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  color: var(--portal-c-text);
  font-size: 35px;
}

.fight-scheduler-app a {
  color: var(--fs-accent-text);
  text-decoration: none;
}

.fight-scheduler-app a:hover {
  color: var(--fs-magenta);
  text-decoration: underline;
}

/* ===== Scheduler: Cage swimlanes + time axis ===== */
.scheduler {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  margin-top: 20px;
  border-radius: var(--bs-border-radius-lg);
  overflow: hidden;
  border: 1px solid var(--portal-c-edge-control-subtle);
  /* Left time strip uses the shared chrome fill; the grid area is faintly tinted */
  background: linear-gradient(to right, var(--fs-chrome-bg) 72px, var(--portal-c-row-hover) 72px);
  min-width: 720px;
  max-height: 70vh;
}

/* Fixed header row: always visible, never scrolls */
.scheduler__header {
  display: flex;
  flex-shrink: 0;
  height: 56px;
  border-bottom: 1px solid var(--portal-c-edge-control-subtle);
}

.scheduler__time-header {
  flex-shrink: 0;
  width: 72px;
  background: var(--fs-chrome-bg);
  border-right: 2px solid var(--portal-c-edge-control-subtle);
}

.scheduler__cage-headers {
  flex: 1;
  display: flex;
  min-width: 0;
}

.scheduler__body {
  display: flex;
  flex: 1;
  min-height: 0;
}

.scheduler__time-col {
  flex-shrink: 0;
  width: 72px;
  display: flex;
  flex-direction: column;
  background: var(--fs-chrome-bg);
  border-right: 2px solid var(--portal-c-edge-control-subtle);
  position: sticky;
  left: 0;
  z-index: 1;
}

.scheduler__time-row {
  height: 32px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  padding-left: 8px;
  font-size: var(--portal-text-md);
  color: var(--portal-c-text);
  font-weight: var(--portal-weight-label);
}

.scheduler__cages {
  flex: 1;
  display: flex;
  min-width: 0;
}

.scheduler__cage {
  flex: 1;
  min-width: 140px;
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--portal-c-edge-control-subtle);
}

.scheduler__cage:last-child {
  border-right: none;
}

.scheduler__cage-header {
  flex: 1;
  min-width: 140px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 6px 8px;
  background: var(--fs-chrome-bg);
  border-right: 1px solid var(--portal-c-edge-control-subtle);
  text-align: center;
}

.scheduler__cage-header:last-child {
  border-right: none;
}

.scheduler__cage-title {
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  font-size: var(--portal-text-base);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--portal-c-text);
}

.scheduler__cage-weight {
  font-size: var(--portal-text-xs);
  color: var(--portal-c-text-muted);
  margin-top: 2px;
}

.scheduler__cage-body {
  flex: 1;
  position: relative;
  min-height: 0;
}

.scheduler__cage-track {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-rows: repeat(var(--scheduler-minutes, 60), 32px);
  grid-template-columns: repeat(24, 1fr);
}

/* Green card color (saved for alternate use): bg var(--fs-linked-bg), border var(--fs-linked-border) */
.scheduler__fight {
  grid-column: 1;
  margin: 2px 4px;
  padding: 6px 8px;
  border-radius: var(--bs-border-radius-sm);
  font-size: var(--portal-text-md);
  line-height: 1.3;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background: var(--fs-sched-bg);
  color: var(--fs-sched-ink);
  border: 1px solid var(--fs-sched-border);
  box-sizing: border-box;
}
.scheduler__fight--draggable {
  cursor: grab;
}
.scheduler__fight--draggable:active {
  cursor: grabbing;
}

.scheduler__fight--linked {
  background: var(--fs-linked-bg);
  border-color: var(--fs-linked-border);
}

/* Completed fight: green border and text, no fill — a done fight recedes into
   the track rather than repainting the canvas colour on top of it. It used to
   restate the page background here, which only ever matched by coincidence. */
.scheduler__fight--done {
  background: transparent;
  border-color: var(--fs-done-border);
  color: var(--fs-done-fg);
}

.scheduler__fight--done .scheduler__fight-meta {
  color: var(--fs-done-fg);
}

.scheduler__fight--event {
  background: var(--fs-event-bg);
  color: var(--fs-sched-ink);
  border-color: var(--fs-event-border);
}

/* Overlapping conflict: pink, proportional width */
.scheduler__fight--conflict {
  background: var(--fs-conflict-bg);
  border-color: var(--fs-conflict-border);
  color: var(--fs-sched-ink);
}

.scheduler__fight-weight {
  font-size: 10px;
  font-weight: var(--portal-weight-strong);
  text-transform: uppercase;
  opacity: 0.7;
}

.scheduler__fight-label {
  font-weight: var(--portal-weight-strong);
  /* allow wrapping so we can show more of the fight name */
  white-space: normal;
  overflow: hidden;
  text-overflow: ellipsis;
}

.scheduler__fight-meta {
  font-size: var(--portal-text-xs);
  color: inherit;
  opacity: 0.9;
  margin-top: 2px;
}

/* Main layout: scheduler + True Finals games sidebar */
.app-main {
  display: flex;
  flex-wrap: nowrap;
  gap: 24px;
  align-items: flex-start;
  margin-top: 10px;
}

.app-main__scheduler-wrap {
  flex: 1 1 60%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.app-main__scheduler {
  min-width: 0;
  overflow-x: auto;
  overflow-y: auto;
}

.scheduler-new-slot {
  flex-shrink: 0;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}

/* Match standard 2-minute grid card: 2 rows × 32px = 64px, cage min-width 140px */
.scheduler-new-slot__card {
  cursor: grab;
  width: 132px;
  min-height: 60px;
  height: 60px;
}

.scheduler-new-slot__card:active {
  cursor: grabbing;
}

/* True Finals games sidebar - same Y and height as the scheduler grid */
.app-tf-games {
  flex: 0 0 280px;
  /* Opaque: the grid beside it carries min-width 720px, so at narrow widths it
     overflows its own column and ends up behind this panel. */
  background: var(--portal-c-surface-opaque);
  border: 1px solid var(--portal-c-edge-control);
  border-radius: var(--bs-border-radius-lg);
  padding: 16px;
  margin-top: 20px;
  height: 70vh;
  overflow-y: auto;
}

.app-tf-games__title {
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  font-size: var(--portal-text-base);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--portal-c-text);
  margin: 0 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--portal-c-edge-control-subtle);
}

.app-tf-games__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Green card color (saved for alternate use): bg var(--fs-linked-bg), border var(--fs-linked-border) */
.app-tf-games__card {
  padding: 8px 10px;
  border-radius: var(--bs-border-radius-sm);
  font-size: var(--portal-text-md);
  line-height: 1.3;
  background: var(--fs-sched-bg);
  color: var(--fs-sched-ink);
  border: 1px solid var(--fs-sched-border);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 2px;
  cursor: grab;
}
.app-tf-games__card:active {
  cursor: grabbing;
}

.app-tf-games__card-name {
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  font-size: var(--portal-text-md);
  color: var(--fs-sched-ink);
}

.app-tf-games__card-matchup {
  font-size: var(--portal-text-sm);
  opacity: 0.95;
}

/* ===== Menu bar ===== */
.menu-bar {
  display: flex;
  align-items: center;
  gap: 0;
  margin-bottom: 6px;
  padding: 0;
  border-bottom: 1px solid var(--portal-c-edge-control-subtle);
  /* Opaque, not the translucent chrome wash: this strip sits directly above a
     dense grid of coloured fight blocks, and its dropdowns open over them. It
     was also solid (#e9ecef) on light theme before the token pass. */
  background: var(--portal-c-surface-opaque);
}

.menu-bar__item {
  position: relative;
}

/* Webhook pause/play toggle */
.menu-bar__webhook-toggle {
  margin-left: auto;
  display: flex;
  align-items: center;
  padding-right: 8px;
}
.menu-bar__webhook-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  background: transparent;
  border: none;
  border-radius: var(--bs-border-radius-sm);
  color: var(--fs-accent-text);
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.menu-bar__webhook-btn:hover { background: var(--portal-c-edge-control-subtle); }
.menu-bar__webhook-btn--paused { color: var(--portal-c-badge-warning-color); }
.menu-bar__webhook-btn--paused:hover { background: var(--portal-c-badge-warning-bg); }
.menu-bar__webhook-btn:disabled { opacity: 0.5; cursor: default; }
/* The order-warning icon toggles via the [hidden] attribute; without this it
   inherits .menu-bar__webhook-btn's display:flex and the attribute is ignored. */
.menu-bar__order-warning-btn { color: var(--portal-c-badge-warning-color); }
.menu-bar__order-warning-btn[hidden] { display: none; }

.menu-bar__trigger {
  display: block;
  padding: 10px 16px;
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  font-size: var(--portal-text-md);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--portal-c-text);
  background: transparent;
  border: none;
  cursor: pointer;
  line-height: 1.2;
}

.menu-bar__trigger:hover:not([aria-expanded="true"]) {
  color: var(--portal-c-text);
  background: var(--portal-c-chrome-hover);
}

/* Open menu: the trigger matches its dropdown so they read as one surface.
   The OVERLAY tier, one step above the bar's own surface tier — with the bar
   now opaque too, sharing a tone would leave an open menu indistinguishable
   from the strip it sits in. */
.menu-bar__trigger[aria-expanded="true"] {
  color: var(--portal-c-text);
  background: var(--portal-c-overlay-opaque);
}

.menu-bar__dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 220px;
  margin: 0;
  padding: 6px 0;
  list-style: none;
  background: var(--portal-c-overlay-opaque);
  border: 1px solid var(--portal-c-edge-control);
  border-radius: var(--bs-border-radius);
  /* The menu elevation, not the flat card shadow — this floats over a grid of
     coloured blocks and needs to read as lifted off it. */
  box-shadow: var(--portal-c-elev-menu);
  z-index: 100;
  display: none;
}

.menu-bar__item.is-open .menu-bar__dropdown {
  display: block;
}

.menu-bar__dropdown li[role="separator"] {
  height: 1px;
  margin: 6px 8px;
  background: var(--portal-c-edge-control);
  list-style: none;
}

.menu-bar__cmd {
  display: block;
  width: 100%;
  padding: 8px 16px;
  font-family: var(--portal-font-body);
  font-size: var(--portal-text-sm);
  text-align: left;
  color: var(--portal-c-text);
  background: transparent;
  border: none;
  cursor: pointer;
  text-decoration: none;
  line-height: 1.3;
}

.menu-bar__cmd:hover:not(:disabled) {
  color: var(--portal-c-on-accent);
  background: var(--portal-c-accent);
}

.menu-bar__cmd:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.menu-bar__cmd--danger {
  color: var(--fs-magenta);
}

.menu-bar__cmd--danger:hover:not(:disabled) {
  background: var(--fs-magenta);
  color: var(--portal-c-on-accent);
}

.menu-bar__status {
  padding: 6px 0 10px;
  border-bottom: 1px solid var(--portal-c-edge-control-subtle);
  position: relative;
}

.menu-bar__status--dismissable {
  cursor: pointer;
  padding-right: 28px;
}

.menu-bar__status-dismiss {
  position: absolute;
  top: 6px;
  right: 4px;
  background: none;
  border: none;
  color: var(--portal-c-text-muted);
  font-size: var(--portal-text-lg);
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}

.menu-bar__status-dismiss:hover {
  color: var(--portal-c-text);
}

.app-toolbar__file-input {
  position: absolute;
  width: 0.1px;
  height: 0.1px;
  opacity: 0;
  overflow: hidden;
  z-index: -1;
}

.app-toolbar__btn {
  display: inline-block;
  padding: 10px 18px;
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  font-size: var(--portal-text-md);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border: none;
  border-radius: var(--bs-border-radius);
  cursor: pointer;
  text-decoration: none;
  line-height: 1.2;
}

.app-toolbar__btn--primary {
  color: var(--portal-c-on-accent);
  background: var(--portal-c-accent);
}

.app-toolbar__btn--primary:hover {
  background: var(--portal-c-accent);
}

.app-toolbar__btn--clear {
  color: var(--portal-c-on-accent);
  background: var(--fs-magenta);
}

.app-toolbar__btn--clear:hover {
  background: var(--fs-magenta);
}

.app-toolbar__error {
  margin: 0;
  color: var(--portal-c-badge-danger-color);
  font-size: var(--portal-text-md);
}

.app-toolbar__ok {
  margin: 0;
  color: var(--fs-accent-text);
  font-size: var(--portal-text-md);
}

/* True Finals loaded list (top right, in header) */
.app-tf-loaded {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 0;
  flex-shrink: 0;
}

.app-tf-loaded__label {
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  font-size: var(--portal-text-xs);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--portal-c-text-muted);
  flex-shrink: 0;
}

.app-tf-loaded__list {
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: left;
}

.app-tf-loaded__item {
  font-size: var(--portal-text-sm);
  color: var(--portal-c-text);
  padding: 2px 0;
}

.app-tf-loaded__id {
  font-size: var(--portal-text-2xs);
  color: var(--portal-c-text-muted);
  margin-left: 4px;
}

.app-tf-loaded__error {
  color: var(--portal-c-badge-danger-color);
}

/* ===== Modal (scoped so it beats Bootstrap's bare .modal) =====

   Toggled by the .modal--open CLASS, not the `hidden` attribute. `hidden`
   pairs with display:none, and display is not animatable — an element that is
   display:none one frame and laid out the next has nothing to transition
   between, which is why these nine modals were the one surface in the suite
   that could not take the overlay motion spec.

   visibility does the hiding instead: it keeps the element out of the
   accessibility tree and out of tab order the way display:none did, but it IS
   transitionable, so it can be delayed until the fade has finished. That delay
   is the whole trick — without it the panel vanishes instantly on close and the
   exit animation is never seen. */
.fight-scheduler-app .modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--portal-dur-2) var(--portal-ease-out),
    visibility 0s linear var(--portal-dur-2);
}

.fight-scheduler-app .modal.modal--open {
  opacity: 1;
  visibility: visible;
  /* Visible immediately on the way IN; only the exit waits for the fade. */
  transition:
    opacity var(--portal-dur-2) var(--portal-ease-out),
    visibility 0s;
}

.modal__backdrop {
  position: absolute;
  inset: 0;
  background: var(--portal-c-scrim);
}

.modal__panel {
  position: relative;
  background: var(--portal-c-surface-opaque);
  border: 1px solid var(--portal-c-edge-control);
  border-radius: var(--bs-border-radius-lg);
  padding: 24px;
  max-width: 400px;
  width: 100%;
  box-shadow: var(--portal-c-elev-overlay);
  /* Overlay motion spec: enter on dur-3 with the emphasized ease, exit on the
     quicker dur-2 — an overlay should arrive with some weight and leave without
     making the operator wait for it. */
  transform: translateY(10px) scale(0.98);
  transition: transform var(--portal-dur-2) var(--portal-ease-out);
}

.modal--open .modal__panel {
  transform: none;
  transition: transform var(--portal-dur-3) var(--portal-ease-emphasized);
}

/* Motion is decoration here; the toggle must still work without it. Zeroing the
   durations rather than dropping the transition keeps the visibility delay at 0
   too, so closing stays instant instead of hanging for the fade that no longer
   runs. */
@media (prefers-reduced-motion: reduce) {
  .fight-scheduler-app .modal,
  .fight-scheduler-app .modal.modal--open {
    transition-duration: 0s;
    transition-delay: 0s;
  }

  .modal__panel,
  .modal--open .modal__panel {
    transform: none;
    transition-duration: 0s;
  }
}

.modal__title {
  margin: 0 0 12px;
  font-size: var(--portal-text-xl);
  color: var(--portal-c-text);
}

.modal__text {
  margin: 0 0 12px;
  color: var(--portal-c-text-muted);
  font-size: var(--portal-title-card);
  line-height: 1.4;
}

.modal__date-input {
  display: block;
  width: 100%;
  margin-bottom: 20px;
  padding: 10px 12px;
  font-family: var(--portal-font-body);
  font-size: var(--portal-text-base);
  color: var(--portal-c-text);
  background: var(--portal-c-chrome-hover);
  border: 1px solid var(--portal-c-edge-control-subtle);
  border-radius: var(--bs-border-radius);
}

.modal__date-input:focus {
  outline: none;
  border-color: var(--portal-c-accent);
}

.modal__actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
}

.modal__actions--top {
  margin-top: 20px;
}

.tf-conflict-choices {
  margin: 16px 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.tf-conflict-choice {
  padding: 12px;
  background: var(--portal-c-chrome-hover);
  border-radius: var(--bs-border-radius);
  color: var(--portal-c-text-muted);
  font-size: var(--portal-title-card);
}

.modal__panel--wide {
  max-width: 480px;
}

/* Push to True Finals progress */
.push-tf-status {
  margin-bottom: 12px;
}
.push-tf-progress-wrap {
  height: 8px;
  background: var(--portal-c-edge-control);
  border-radius: var(--bs-border-radius-sm);
  overflow: hidden;
  margin-bottom: 12px;
}
.push-tf-progress-bar {
  height: 100%;
  width: 0%;
  background: var(--portal-c-accent);
  border-radius: var(--bs-border-radius-sm);
  transition: width 0.2s ease;
}
.push-tf-progress-bar--indeterminate {
  width: 40% !important;
  animation: push-tf-indeterminate 1.2s ease-in-out infinite;
}
@keyframes push-tf-indeterminate {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}
.push-tf-error {
  margin: 0;
}
.push-tf-error-detail {
  margin: 8px 0 0;
  padding: 10px;
  font-size: var(--portal-text-2xs);
  max-height: 200px;
  overflow: auto;
  background: var(--portal-c-input-bg);
  border-radius: var(--bs-border-radius-sm);
  white-space: pre-wrap;
  word-break: break-all;
}
.push-tf-actions {
  margin-top: 16px;
}

/* Push TF preview */
.push-tf-panel { max-width: 640px; }
.push-tf-preview__summary { color: var(--portal-c-text-muted); margin-bottom: 12px; font-size: var(--portal-title-card); }
.push-tf-preview__table-wrap { max-height: 400px; overflow-y: auto; margin-bottom: 16px; }
.push-tf-preview__table { width: 100%; border-collapse: collapse; font-size: var(--portal-text-md); }
.push-tf-preview__table th { text-align: left; padding: 6px 8px; color: var(--portal-c-text-muted); border-bottom: 1px solid var(--portal-c-edge-control); }
.push-tf-preview__table td { padding: 6px 8px; color: var(--portal-c-text); border-bottom: 1px solid var(--portal-c-edge-control-subtle); }
.push-tf-row--invalid td { color: var(--portal-c-badge-danger-color); }
.push-tf-status--ok { color: var(--fs-accent-text); }
.push-tf-status--warn { color: var(--portal-c-badge-danger-color); }
.push-tf-remove-btn { background: none; border: none; color: var(--portal-c-text-muted); cursor: pointer; font-size: var(--portal-text-lg); padding: 2px 6px; line-height: 1; }
.push-tf-remove-btn:hover { color: var(--portal-c-badge-danger-color); }

/* True Finals pages modal */
.tf-pages-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 16px 0;
}

.tf-pages-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.tf-pages-row input {
  flex: 1;
  padding: 8px 10px;
  font-size: var(--portal-text-md);
  font-family: var(--portal-font-body);
  color: var(--portal-c-text);
  background: var(--portal-c-chrome-hover);
  border: 1px solid var(--portal-c-edge-control-subtle);
  border-radius: var(--bs-border-radius-sm);
}

.tf-pages-row input::placeholder {
  color: var(--portal-c-text-muted);
  opacity: 0.8;
}

.tf-pages-remove {
  padding: 6px 12px;
  font-size: var(--portal-text-xs);
  font-family: var(--portal-font-body);
  color: var(--portal-c-text);
  background: transparent;
  border: 1px solid var(--portal-c-edge-control-subtle);
  border-radius: var(--bs-border-radius-sm);
  cursor: pointer;
  flex-shrink: 0;
}

.tf-pages-remove:hover {
  background: var(--portal-c-chrome-hover);
}

.tf-pages-add {
  padding: 8px 14px;
  font-size: var(--portal-text-sm);
  font-family: var(--portal-font-body); font-weight: var(--portal-weight-strong);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--fs-accent-text);
  background: transparent;
  border: 1px solid var(--portal-c-accent);
  border-radius: var(--bs-border-radius-sm);
  cursor: pointer;
}

.tf-pages-add:hover {
  background: var(--portal-c-accent-muted);
}

/* Set Prime Time modal */
.prime-time-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 16px 0;
  max-height: 60vh;
  overflow-y: auto;
}

.prime-time-row {
  display: grid;
  grid-template-columns: 120px 1fr 1fr;
  gap: 12px;
  align-items: center;
}

.prime-time-label {
  font-size: var(--portal-text-md);
  color: var(--portal-c-text);
}

.prime-time-input,
.prime-time-select {
  padding: 8px 10px;
  font-size: var(--portal-text-md);
  font-family: var(--portal-font-body);
  color: var(--portal-c-text);
  background: var(--portal-c-badge-info-bg);
  border: 1px solid var(--portal-c-edge-control);
  border-radius: var(--bs-border-radius-sm);
}

.prime-time-select {
  cursor: pointer;
}

#prime-time-modal .modal__panel--wide {
  max-width: 520px;
}

/* ===== Log page ===== */

/* ===== Trash can drop zone ===== */
.trash-zone {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--fs-magenta) 25%, transparent);
  border: 2px solid color-mix(in srgb, var(--fs-magenta) 50%, transparent);
  border-radius: var(--bs-border-radius-xl);
  transition: background 0.15s, border-color 0.15s, transform 0.15s;
}

.trash-zone:hover,
.trash-zone.drag-hover {
  background: color-mix(in srgb, var(--fs-magenta) 50%, transparent);
  border-color: var(--fs-magenta);
  transform: scale(1.1);
}

.trash-zone__icon {
  width: 28px;
  height: 28px;
  fill: var(--fs-magenta);
  opacity: 0.85;
  pointer-events: none;
}

/* Override Locations checkmark */
.menu-bar__cmd-check {
  display: inline-block;
  width: 18px;
  text-align: center;
  font-size: var(--portal-text-md);
}

/* Flamethrowers modal */
.flamethrowers-add-row {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}
.flamethrower-input {
  flex: 1;
  padding: 6px 8px;
  font-size: var(--portal-text-md);
  background: var(--portal-c-edge-control-subtle);
  border: 1px solid var(--portal-c-edge-control);
  border-radius: var(--bs-border-radius-sm);
  color: var(--portal-c-text);
}
.flamethrower-input::placeholder {
  color: var(--portal-c-text-muted);
}
.flamethrower-list {
  list-style: none;
  padding: 0;
  margin: 0 0 12px 0;
  max-height: 40vh;
  overflow-y: auto;
}
.flamethrower-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 8px;
  background: var(--portal-c-tab-hover);
  border-radius: var(--bs-border-radius-sm);
  font-size: var(--portal-text-md);
  color: var(--portal-c-text);
  margin-bottom: 4px;
}
.flamethrower-row--empty {
  color: var(--portal-c-text-muted);
  font-style: italic;
}
.flamethrower-remove-btn {
  font-size: var(--portal-text-xs);
  padding: 2px 8px;
}

/* Locations modal */
.location-list {
  margin-bottom: 12px;
}
.location-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--portal-c-edge-control-subtle);
}
.location-row:last-child {
  border-bottom: none;
}
.location-row__title {
  font-weight: bold;
  min-width: 80px;
  font-size: var(--portal-text-md);
  color: var(--portal-c-text);
}
.location-row__cb {
  font-size: var(--portal-text-md);
  color: var(--portal-c-text);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
}
.location-row__cb input[type="checkbox"] {
  cursor: pointer;
}
.location-row__divider {
  width: 1px;
  height: 20px;
  background: var(--portal-c-edge-control);
}


/* ── Public read-only view (view.twig) ───────────────────────────────────
   Three rules that used to live in an inline <style> on that page, where
   they carried the only hard-coded colours left in the template. They are
   here rather than there so the page has no inline styles to drift, and so
   they resolve through the same token layer as everything above.        */
.fight-scheduler-app .view-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.fight-scheduler-app .view-live-badge {
  font-size: var(--portal-text-xs);
  font-weight: var(--portal-weight-label);
  letter-spacing: var(--portal-label-tracking);
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: var(--bs-border-radius-sm);
  color: var(--portal-c-badge-live-color);
  background: var(--portal-c-badge-live-bg);
}
.fight-scheduler-app .view-empty {
  padding: 2rem;
  color: var(--portal-c-text-muted);
}

/* ── Narrow widths ───────────────────────────────────────────────────────
   .app-main is a nowrap flex row: the schedule column beside a fixed 280px
   True Finals panel. The grid carries min-width: 720px, so once the viewport
   drops below roughly 1100px of content width the two can no longer both fit —
   the grid overflows its own column and the panel ends up painted over the
   right-hand cages. Collapsing the Portal sidebar buys ~200px and is why it
   looks fine there and broken here.

   Wrapping is the fix rather than shrinking either side: the grid has a real
   minimum (six cages of 140px plus the time strip) and the panel is a drag
   source, so neither survives being squeezed. Below the breakpoint the panel
   moves under the schedule and goes full width, which also gives the drag a
   shorter travel on a laptop trackpad. */
@media (max-width: 1200px) {
  .app-main {
    flex-wrap: wrap;
  }

  /* Let the 720px grid scroll inside its own column instead of spilling out of
     it — the page itself must never scroll sideways. Header and body scroll
     together here, so the cage headings stay over their columns. */
  .app-main__scheduler-wrap {
    flex: 1 1 100%;
    overflow-x: auto;
  }

  .app-tf-games {
    flex: 1 1 100%;
    height: auto;
    max-height: 40vh;
    margin-top: 0;
  }

  /* 70vh of grid plus 40vh of panel does not fit on a laptop; the grid gives up
     the height because it scrolls internally and the panel does not. */
  .scheduler {
    max-height: 55vh;
  }
}

/* Tablet and below: the display type is costing more vertical space than the
   schedule it labels. */
@media (max-width: 768px) {
  .fight-scheduler-app h1 {
    font-size: 28px;
  }

  .sparky-logo {
    height: 28px;
    margin-right: 8px;
  }

  .app-header {
    flex-wrap: wrap;
    gap: 8px;
  }

  .app-header__right {
    align-items: flex-start;
  }

  .fight-scheduler-app h2 {
    font-size: var(--portal-text-xl);
  }
}
