/* ===========================================================================
   Homepage background scene.
                                                                    
   Linked only from index.html. Every rule here is scoped under .gd-scene or
   body.page-home, so nothing in this file can reach another page -- the other
   pages are free to do something completely different.

   The layout is a Geometry Dash level turned on its side: background scrolling
   horizontally behind everything, a ground strip down each edge, and triple
   spikes riding along on the ground. The left side travels up, the right side
   travels down.

   Every source image is greyscale or a black silhouette, which is what makes
   the colour cycle work: the art supplies the shape, `background-color` plus
   `multiply` supplies the colour, and one hue-rotate animation moves the whole
   palette through RGB.
   =========================================================================== */

.gd-scene {
  /* One tile drives everything: strip width, spike size, scroll distance and,
     because the spike cycle is measured in tiles, the scroll speeds too. Change
     this and the whole scene rescales in proportion. */
  --gd-tile: 104px;
  --gd-cycle: calc(var(--gd-tile) * 18);
  --gd-grnd-tile: 390px;
  --gd-grnd-cycle: calc(var(--gd-grnd-tile) * 18);

  /* Ground moves one tile per this long. The spike cycle is 18 tiles, so its
     duration is 18x this, which keeps spikes glued to the ground they sit on. */
  --gd-step: 7.125s;
  --gd-spike-duration: 34.2s;

  --gd-bg-tint: #6b4ff0;
  --gd-ground-tint: #160e42;
  --gd-hue-duration: 16s;

  /* Breathing room around each thumbnail in the homepage grid. */
  --gd-thumb-gap: clamp(10px, 1.5vw, 26px);

  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background: #12101f;
}

/* The page's own radial gradients would sit behind the scene and never show. */
body.page-home {
  background-image: none;
  background-color: #12101f;
}

/* --- background -------------------------------------------------------------

   Sized `auto 100%`: the tile is square, so it renders exactly one viewport
   tall and the built-in dark-to-light gradient runs top to bottom once, the way
   it does in game. That also makes the tile exactly 100vh wide, which is the
   distance the scroll has to travel to loop seamlessly.                        */

.gd-bg {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: calc(-100vh - 4px);
  background-color: var(--gd-bg-tint);
  background-image: url("../../media/homepagebg.png");
  background-repeat: repeat-x;
  background-size: auto 100%;
  background-blend-mode: multiply;
  animation:
    gd-bg-scroll 26s linear infinite,
    gd-hue var(--gd-hue-duration) linear infinite;
}

@keyframes gd-bg-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-100vh); }
}

/* Rotating the hue of the composited result cycles the whole palette without
   needing a keyframe per colour. */
@keyframes gd-hue {
  from { filter: hue-rotate(0deg); }
  to   { filter: hue-rotate(360deg); }
}

/* --- the two sides ----------------------------------------------------------

   Each side is two tiles wide: the outer tile is ground, the inner one is the
   lane the spikes rise through. Spikes need that room because they stand on the
   ground and point inwards.                                                    */

.gd-side {
  position: absolute;
  top: 0;
  bottom: 0;
  width: calc(var(--gd-tile) * 2);
  overflow: hidden;
}

.gd-side--left  { left: 0; }
.gd-side--right { right: 0; }

/* --- ground -----------------------------------------------------------------

   Overhangs the viewport by two tiles top and bottom so the translate never
   exposes an edge. Moving exactly one tile lands on an identical pattern, which
   is what makes the loop invisible.                                            */

.gd-ground {
  position: absolute;
  top: calc(var(--gd-grnd-tile) * -2);
  bottom: calc(var(--gd-grnd-tile) * -2);
  width: var(--gd-tile);
  background-color: var(--gd-ground-tint);
  background-image: url("../../media/homepageground.png");
  background-repeat: repeat;
  background-size: var(--gd-grnd-tile) var(--gd-grnd-tile);
  background-blend-mode: multiply;
  animation: gd-hue var(--gd-hue-duration) linear infinite;
}

.gd-side--left  .gd-ground {  left: 0; }
.gd-side--right .gd-ground { right: 0; }

/* Two animations on one element: the scroll, and the shared colour cycle. */
.gd-side--left .gd-ground {
  animation:
    gd-ground-up var(--gd-step) linear infinite,
    gd-hue var(--gd-hue-duration) linear infinite;
}

.gd-side--right .gd-ground {
  animation:
    gd-ground-down var(--gd-step) linear infinite,
    gd-hue var(--gd-hue-duration) linear infinite;
}

@keyframes gd-ground-up {
  from { transform: translateY(0); }
  to   { transform: translateY(calc(var(--gd-grnd-tile) * -1)); }
}

@keyframes gd-ground-down {
  from { transform: translateY(calc(var(--gd-grnd-tile) * -1)); }
  to   { transform: translateY(0); }
}

/* --- ground line ------------------------------------------------------------

   The source is a horizontal 1776x6 white strip that fades out at both ends, so
   it is stretched once rather than tiled -- tiling would turn those fades into a
   dashed line. Rotating a 100vh-long strip by a quarter turn stands it up along
   the ground's inner edge, brightest at eye level and falling away top and
   bottom, the way the horizon line does in game.                               */

.gd-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 8px;
}

.gd-line::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100vh;
  height: 6px;
  margin: -3px 0 0 -50vh;
  transform: rotate(90deg);
  background-image: url("../../media/homepagegroundline.png");
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

.gd-side--left  .gd-line { left:  calc(var(--gd-tile) - 4px); }
.gd-side--right .gd-line { right: calc(var(--gd-tile) - 4px); }

/* --- spikes -----------------------------------------------------------------

   The lane just inside the ground. Two identical tracks stacked and translated
   by exactly one track height, so the pattern repeats without a seam.          */

.gd-spikes {
  position: absolute;
  top: 0;
  bottom: 0;
  width: var(--gd-tile);
  overflow: hidden;
}

.gd-side--left  .gd-spikes { left:  var(--gd-tile); }
.gd-side--right .gd-spikes { right: var(--gd-tile); }

.gd-spikes-inner {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: calc(var(--gd-cycle) * 2);
}

.gd-side--left .gd-spikes-inner {
  animation: gd-spikes-up var(--gd-spike-duration) linear infinite;
}

.gd-side--right .gd-spikes-inner {
  animation: gd-spikes-down var(--gd-spike-duration) linear infinite;
}

@keyframes gd-spikes-up {
  from { transform: translateY(0); }
  to   { transform: translateY(calc(var(--gd-cycle) * -1)); }
}

@keyframes gd-spikes-down {
  from { transform: translateY(calc(var(--gd-cycle) * -1)); }
  to   { transform: translateY(0); }
}

.gd-spike-track {
  position: absolute;
  left: 0;
  right: 0;
  height: var(--gd-cycle);
}

.gd-spike-track--1 { top: 0; }
.gd-spike-track--2 { top: var(--gd-cycle); }

/* Three spikes in a row: a triple spike. */
.gd-spike-group {
  position: absolute;
  left: 0;
  right: 0;
  height: calc(var(--gd-tile) * 3);
  display: flex;
  flex-direction: column;
}

/* Spaced unevenly so the loop reads as "every so often" rather than a rhythm.
   The right side uses different offsets so the two sides never line up. */
.gd-spike-group--1 { top: calc(var(--gd-tile) * 1); }
.gd-spike-group--2 { top: calc(var(--gd-tile) * 7.5); }
.gd-spike-group--3 { top: calc(var(--gd-tile) * 13); }

.gd-side--right .gd-spike-group--1 { top: calc(var(--gd-tile) * 3); }
.gd-side--right .gd-spike-group--2 { top: calc(var(--gd-tile) * 9); }
.gd-side--right .gd-spike-group--3 { top: calc(var(--gd-tile) * 14.5); }

/* The art points up; each side turns it a quarter turn to point inwards, so the
   spikes stand on the ground rather than lying along it. */
.gd-spike {
  flex: 1;
  background-image: url("../../media/homepagespike.png");
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

.gd-side--left  .gd-spike { transform: rotate(90deg); }
.gd-side--right .gd-spike { transform: rotate(-90deg); }

/* --- legibility -------------------------------------------------------------

   The scene is bright and always moving, so the middle of the screen gets a
   scrim. It is heaviest where the text sits and lightest at the edges, which
   keeps the ground and spikes vivid while the copy stays readable.             */

.gd-veil {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 58% 78% at 50% 40%,
      rgba(12, 13, 22, 0.82) 0%,
      rgba(12, 13, 22, 0.55) 58%,
      rgba(12, 13, 22, 0.08) 100%);
}

/* --- responsive -------------------------------------------------------------

   Only the tile changes. Everything else is expressed in tiles, so the strips,
   spikes, scroll distances and speeds all rescale from these two lines.        */

@media (max-width: 900px) {
  .gd-scene { --gd-tile: 74px; }
}

@media (max-width: 600px) {
  .gd-scene { --gd-tile: 52px; }
  /* The sides would crowd the text at this width, so the ground keeps its
     footing but the spike lane is dropped. */
  .gd-spikes { display: none; }
}

/* The site-wide reduced-motion rule already stops every animation; this keeps
   the result looking deliberate rather than half-scrolled. */
@media (prefers-reduced-motion: reduce) {
  .gd-veil {
    background:
      radial-gradient(ellipse 65% 85% at 50% 42%,
        rgba(12, 13, 22, 0.9) 0%,
        rgba(12, 13, 22, 0.72) 55%,
        rgba(12, 13, 22, 0.5) 100%);
  }
}

/* --- level artwork over the backdrop ------------------------------------------

   A grid of level thumbnails between the scrolling background and the ground.
   Gaps rather than a solid sheet: the Geometry Dash backdrop is meant to stay
   visible through it, which is the whole reason this sits in the middle of the
   scene rather than on top of it.                                              */

.gd-thumbs {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

/* Sized entirely from the two variables assets/js/pagebg.js sets on .gd-thumbs.
   The width is stated explicitly rather than left to the content, because the
   animation translates by a percentage of it and that has to be exact. */
.thumb-grid-inner {
  display: grid;
  grid-template-columns: repeat(var(--gd-cols, 8), var(--gd-cell, 240px));
  gap: 0;
  width: calc(var(--gd-cell, 240px) * var(--gd-cols, 8));
  animation: gd-thumbs-drift 70s linear infinite;
}

.gd-thumbs .thumb-tile {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;

  /* Spacing comes from a transparent border, not from grid `gap`.
     With a gap the row is n*cell + (n-1)*gap wide, so half of it is not a whole
     number of cells and the diagonal loop would creep out of alignment. A
     border lives inside the cell, which keeps the arithmetic exact. Clipping to
     the padding box keeps the rounded corners. */
  border: var(--gd-thumb-gap, 16px) solid transparent;
  background-clip: padding-box;

  background-size: cover;
  background-position: center;
  border-radius: 20px;
  opacity: 0.5;
  transform: skewX(25deg);

  /* The shared .thumb-tile rule in style.css fades every edge, which is what
     the other pages want. Here the tiles are meant to read as crisp artwork
     sitting on the backdrop, so the fade is switched off. */
  -webkit-mask-image: none;
  mask-image: none;
}

/* Left and up together. Both percentages are of the grid's own size, and the
   grid is exactly two blocks wide and two tall, so this travels precisely one
   block on each axis -- the frame at the end is identical to the one at the
   start. */
@keyframes gd-thumbs-drift {
  from { transform: translate(0, 0); }
  to   { transform: translate(-50%, -50%); }
}
