/* ==========================================================================
   Tournament "VS" splash
   A pre-match title card: two diagonal halves charge in from opposite sides
   carrying each player, a gold VS slams into the middle, brief hold, then the
   halves part to reveal the board.

   Timing is driven entirely from here (JS only adds/removes classes and reads
   --vs-total), so the whole sequence can be retuned without touching the JS.
   ========================================================================== */

.vs-splash {
  --vs-in: 0.55s;      /* halves charge in */
  --vs-slam: 0.30s;    /* VS impact */
  --vs-hold: 1.35s;    /* readable hold */
  --vs-out: 0.55s;     /* halves part */
  --vs-red: #c0392b;
  --vs-blue: #3a7bd5;
  --vs-gold: #e6ce6a;

  position: fixed;
  inset: 0;
  z-index: 10050;
  overflow: hidden;
  cursor: pointer;
  /* The halves carry their own backdrop, so the layer itself stays clear
     until they arrive — no dark flash before the animation starts. */
  background: transparent;
  animation: vsSplashFade 0.35s ease forwards;
  animation-delay: calc(var(--vs-in) + var(--vs-slam) + var(--vs-hold));
}

/* --- the two charging halves --------------------------------------------- */

.vs-splash-side {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 62%;
  display: flex;
  align-items: center;
  overflow: hidden;
  will-change: transform;
}

.vs-splash-side-red {
  left: 0;
  justify-content: flex-start;
  padding-left: 6vw;
  /* Diagonal seam; the two halves meet just off-centre. */
  clip-path: polygon(0 0, 100% 0, calc(100% - 12vh) 100%, 0 100%);
  background:
    radial-gradient(ellipse at 30% 50%, rgba(255, 120, 100, 0.30) 0%, transparent 62%),
    linear-gradient(120deg, #4a1410 0%, #7d2418 45%, #2a0c08 100%);
  transform: translateX(-102%);
  animation: vsSlideInLeft var(--vs-in) cubic-bezier(0.18, 0.9, 0.25, 1.05) forwards,
             vsPartLeft var(--vs-out) cubic-bezier(0.6, 0, 0.9, 0.4) forwards;
  animation-delay: 0s, calc(var(--vs-in) + var(--vs-slam) + var(--vs-hold));
}

.vs-splash-side-blue {
  right: 0;
  justify-content: flex-end;
  padding-right: 6vw;
  clip-path: polygon(12vh 0, 100% 0, 100% 100%, 0 100%);
  background:
    radial-gradient(ellipse at 70% 50%, rgba(120, 180, 255, 0.30) 0%, transparent 62%),
    linear-gradient(120deg, #071a33 0%, #17456f 55%, #04101f 100%);
  transform: translateX(102%);
  animation: vsSlideInRight var(--vs-in) cubic-bezier(0.18, 0.9, 0.25, 1.05) forwards,
             vsPartRight var(--vs-out) cubic-bezier(0.6, 0, 0.9, 0.4) forwards;
  animation-delay: 0s, calc(var(--vs-in) + var(--vs-slam) + var(--vs-hold));
}

/* --- campaign level side ---------------------------------------------------
   A campaign opponent has no player colour, so its half carries the level's
   artwork instead of the red/blue gradient. The inline background-image
   replaces the gradient stack from the shorthand above; ::before lays a scrim
   over it so the name stays legible on any artwork. */
.vs-splash-side-art {
  background-repeat: no-repeat !important;
  background-size: cover !important;
  background-position: center !important;
}

.vs-splash-side-art::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.28) 0%, rgba(0, 0, 0, 0.74) 100%);
}

/* Speed streaks so the charge reads as motion, not just a slide. */
.vs-splash-side::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    100deg,
    rgba(255, 255, 255, 0.055) 0 2px,
    transparent 2px 26px
  );
  opacity: 0;
  animation: vsStreaks var(--vs-in) ease-out forwards;
}

/* --- player block --------------------------------------------------------- */

.vs-splash-player {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  /* Must not reach the centre: the VS caps at 8rem/11vw, so leave half of it
     plus breathing room. A long name wraps instead of sliding under the mark. */
  max-width: 34vw;
  opacity: 0;
  animation: vsPlayerRise 0.45s cubic-bezier(0.2, 0.9, 0.3, 1) forwards;
  animation-delay: calc(var(--vs-in) * 0.55);
}

/* Sized as a fixed BOX, not by width alone.
   Width-only sizing let each skin's aspect ratio decide its height, which is
   fine while every castle is roughly square (the classic ones land ~180x179)
   but not for the portrait composites: stacked_castle (320x575) came out
   180x323 and the mage tower similarly, shoving that side's name and chips
   ~90px below the opponent's and breaking the symmetry the whole split
   depends on. A square box with object-fit keeps every castle inside the same
   footprint; bottom alignment stands them all on one baseline. */
.vs-splash-castle {
  width: clamp(90px, 15vw, 190px);
  height: clamp(90px, 15vw, 190px);
  object-fit: contain;
  object-position: bottom;
  image-rendering: pixelated;
  filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.65));
}

.vs-splash-side-red .vs-splash-castle { transform: scaleX(1); }
/* Face the two castles toward each other across the seam. */
.vs-splash-side-blue .vs-splash-castle { transform: scaleX(-1); }

.vs-splash-name {
  font-family: "Cinzel", serif;
  font-weight: 700;
  font-size: clamp(1.1rem, 3vw, 2.2rem);
  letter-spacing: 1px;
  color: #fff;
  text-align: center;
  line-height: 1.15;
  max-width: 100%;
  overflow-wrap: anywhere;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.6), 0 0 18px rgba(0, 0, 0, 0.5);
}

.vs-splash-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
  font-size: clamp(0.62rem, 1.3vw, 0.8rem);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.82);
}

.vs-splash-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.34);
  border: 1px solid rgba(255, 255, 255, 0.28);
  white-space: nowrap;
}

.vs-splash-side-red .vs-splash-chip-side { border-color: rgba(255, 140, 120, 0.75); }
.vs-splash-side-blue .vs-splash-chip-side { border-color: rgba(130, 190, 255, 0.75); }

/* --- centre VS ------------------------------------------------------------ */

.vs-splash-vs {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 4;
  transform: translate(-50%, -50%) scale(3.4);
  opacity: 0;
  font-family: "Cinzel", serif;
  font-weight: 700;
  font-size: clamp(3rem, 11vw, 8rem);
  line-height: 1;
  letter-spacing: 2px;
  color: var(--vs-gold);
  text-shadow:
    0 0 14px rgba(230, 206, 106, 0.65),
    0 4px 0 #6b4e07,
    0 6px 16px rgba(0, 0, 0, 0.75);
  animation: vsSlam var(--vs-slam) cubic-bezier(0.2, 1.4, 0.35, 1) forwards,
             vsVsOut 0.3s ease forwards;
  animation-delay: var(--vs-in), calc(var(--vs-in) + var(--vs-slam) + var(--vs-hold));
}

/* Impact ring pushed out by the slam. */
.vs-splash-shock {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 3;
  width: 26vh;
  height: 26vh;
  margin: -13vh 0 0 -13vh;
  border-radius: 50%;
  border: 3px solid rgba(230, 206, 106, 0.85);
  opacity: 0;
  animation: vsShock 0.5s ease-out forwards;
  animation-delay: var(--vs-in);
}

.vs-splash-flash {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: #fff;
  opacity: 0;
  pointer-events: none;
  animation: vsFlash 0.32s ease-out forwards;
  animation-delay: var(--vs-in);
}

/* --- round label & skip hint --------------------------------------------- */

.vs-splash-round {
  position: absolute;
  top: 8vh;
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  padding: 6px 18px;
  border-radius: 2px;
  /* Sits over campaign artwork, which can be bright sky right where the label
     lands — a 55% scrim washed the gold text out. Near-opaque plate instead. */
  background: rgba(12, 8, 4, 0.86);
  border: 1px solid rgba(230, 206, 106, 0.75);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.55);
  font-family: "Cinzel", serif;
  font-weight: 700;
  font-size: clamp(0.75rem, 2vw, 1.05rem);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--vs-gold);
  white-space: nowrap;
  opacity: 0;
  animation: vsLabelIn 0.4s ease forwards;
  animation-delay: calc(var(--vs-in) + var(--vs-slam));
}

.vs-splash-skip {
  position: absolute;
  bottom: 6vh;
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  font-size: 0.72rem;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
  opacity: 0;
  animation: vsLabelIn 0.4s ease forwards;
  animation-delay: calc(var(--vs-in) + var(--vs-slam) + 0.5s);
}

/* --- keyframes ------------------------------------------------------------ */

@keyframes vsSlideInLeft {
  from { transform: translateX(-102%); }
  to   { transform: translateX(0); }
}

@keyframes vsSlideInRight {
  from { transform: translateX(102%); }
  to   { transform: translateX(0); }
}

@keyframes vsPartLeft {
  from { transform: translateX(0); }
  to   { transform: translateX(-104%); }
}

@keyframes vsPartRight {
  from { transform: translateX(0); }
  to   { transform: translateX(104%); }
}

@keyframes vsStreaks {
  0%   { opacity: 0.9; transform: translateX(-6%); }
  100% { opacity: 0;   transform: translateX(0); }
}

@keyframes vsPlayerRise {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes vsSlam {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(3.4) rotate(-12deg); }
  60%  { opacity: 1; transform: translate(-50%, -50%) scale(0.88) rotate(2deg); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1) rotate(0deg); }
}

@keyframes vsVsOut {
  from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  to   { opacity: 0; transform: translate(-50%, -50%) scale(1.5); }
}

@keyframes vsShock {
  0%   { opacity: 0.9; transform: scale(0.15); border-width: 6px; }
  100% { opacity: 0;   transform: scale(2.6);  border-width: 1px; }
}

@keyframes vsFlash {
  0%   { opacity: 0.75; }
  100% { opacity: 0; }
}

@keyframes vsLabelIn {
  from { opacity: 0; transform: translateX(-50%) translateY(-8px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

@keyframes vsSplashFade {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Opacity-only fade that leaves transform alone (reduced-motion path). */
@keyframes vsFadeInPlace {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Screen shake on impact, applied to the splash's own wrapper only. */
.vs-splash-shake {
  animation: vsShake 0.3s ease-out;
  animation-delay: var(--vs-in);
}

@keyframes vsShake {
  0%, 100% { transform: translate(0, 0); }
  20% { transform: translate(-7px, 4px); }
  40% { transform: translate(6px, -5px); }
  60% { transform: translate(-4px, -3px); }
  80% { transform: translate(3px, 3px); }
}

/* --- portrait: stack the split ---------------------------------------------
   Keyed on orientation rather than width so the layout always follows the
   screen's long axis: a portrait phone stacks (red on top, blue below) and a
   phone turned landscape gets the side-by-side split back, same as desktop. */

@keyframes vsSlideInTop {
  from { transform: translateY(-102%); }
  to   { transform: translateY(0); }
}

@keyframes vsSlideInBottom {
  from { transform: translateY(102%); }
  to   { transform: translateY(0); }
}

@keyframes vsPartTop {
  from { transform: translateY(0); }
  to   { transform: translateY(-104%); }
}

@keyframes vsPartBottom {
  from { transform: translateY(0); }
  to   { transform: translateY(104%); }
}

@keyframes vsStreaksV {
  0%   { opacity: 0.9; transform: translateY(-6%); }
  100% { opacity: 0;   transform: translateY(0); }
}

@media (orientation: portrait) {
  .vs-splash-side {
    width: 100%;
    height: 62%;
    top: auto;
    bottom: auto;
    left: 0;
    right: auto;
    justify-content: center;
  }

  .vs-splash-side-red {
    top: 0;
    align-items: flex-start;
    padding: 13vh 4vw 0;
    /* Diagonal seam runs across the screen instead of down it. */
    clip-path: polygon(0 0, 100% 0, 100% calc(100% - 11vw), 0 100%);
    animation-name: vsSlideInTop, vsPartTop;
    transform: translateY(-102%);
  }

  .vs-splash-side-blue {
    bottom: 0;
    align-items: flex-end;
    padding: 0 4vw 11vh;
    clip-path: polygon(0 11vw, 100% 0, 100% 100%, 0 100%);
    animation-name: vsSlideInBottom, vsPartBottom;
    transform: translateY(102%);
  }

  .vs-splash-side::after {
    animation-name: vsStreaksV;
    background: repeating-linear-gradient(
      170deg,
      rgba(255, 255, 255, 0.055) 0 2px,
      transparent 2px 26px
    );
  }

  /* Facing across a horizontal seam, so no mirroring. */
  .vs-splash-side-blue .vs-splash-castle { transform: none; }

  .vs-splash-player {
    max-width: 88vw;
    gap: 8px;
  }

  .vs-splash-castle { width: clamp(72px, 26vw, 150px); }
  .vs-splash-name { font-size: clamp(1.15rem, 6vw, 2rem); }
  .vs-splash-round { top: 4vh; }
  .vs-splash-skip { bottom: 3vh; }
  .vs-splash-vs { font-size: clamp(2.6rem, 17vw, 6rem); }
  .vs-splash-shock { width: 22vh; height: 22vh; margin: -11vh 0 0 -11vh; }
}

/* Short landscape (phone on its side): keep the vertical split but pull the
   chrome in so nothing collides with the seam. */
@media (orientation: landscape) and (max-height: 500px) {
  .vs-splash-castle { width: clamp(58px, 12vw, 96px); }
  .vs-splash-player { gap: 5px; }
  .vs-splash-round { top: 3vh; padding: 4px 12px; }
  .vs-splash-skip { bottom: 3vh; }
}

/* --- reduced motion -------------------------------------------------------
   No charging, no slam, no shake — the same information cross-fades in and
   holds, then fades out. Everything still reads; nothing flies. */
@media (prefers-reduced-motion: reduce) {
  .vs-splash,
  .vs-splash-side,
  .vs-splash-player,
  .vs-splash-vs,
  .vs-splash-round,
  .vs-splash-skip {
    animation-duration: 0.28s !important;
    animation-timing-function: ease !important;
  }

  /* Plain opacity only — vsLabelIn carries a translate that would shove a
     half off its own edge. */
  .vs-splash-side-red,
  .vs-splash-side-blue {
    transform: none !important;
    animation-name: vsFadeInPlace, vsSplashFade !important;
  }

  .vs-splash-side::after,
  .vs-splash-shock,
  .vs-splash-flash,
  .vs-splash-shake {
    animation: none !important;
    opacity: 0 !important;
  }

  .vs-splash-vs {
    transform: translate(-50%, -50%) scale(1);
    animation-name: vsLabelIn, vsVsOut !important;
  }
}

/* ── Night ──────────────────────────────────────────────────────────────────
   The splash is the brightest thing in the game — full-bleed level artwork and
   lit castles against a black overlay — which is a glare in a dark room. Dim
   the imagery, and only the imagery: the names, chips and the VS mark keep
   their contrast.

   Keyed on both signals like the scenic background: the in-game toggle
   (html[data-theme="dark"]) and the OS preference, with an explicit light
   override so a forced-light toggle still wins over a dark OS. The artwork side
   is dimmed with its own scrim rather than a filter, because a filter on the
   element would take the name and chips sitting on top of it down too. */
:root[data-theme="dark"] .vs-splash-castle {
  filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.65)) brightness(0.66) saturate(0.85);
}

:root[data-theme="dark"] .vs-splash-side-art::before {
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0.85) 100%);
}

@media (prefers-color-scheme: dark) {
  .vs-splash-castle {
    filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.65)) brightness(0.66) saturate(0.85);
  }
  .vs-splash-side-art::before {
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0.85) 100%);
  }
}

:root[data-theme="light"] .vs-splash-castle {
  filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.65));
}

:root[data-theme="light"] .vs-splash-side-art::before {
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.28) 0%, rgba(0, 0, 0, 0.74) 100%);
}
