/* Colours. The see-through ones are mixed from a base colour, so changing it changes them too. */
:root {
  --color_page: #132C3E;         /* around the square on non-square screens */
  --color_scene: #009081;        /* the stage's background */
  --color_ui: #e8f4f1;           /* sound controls and progress bars */
  --color_sun: #fbe08a;          /* the sun's yellow: the music loop bar */
  --color_debug-target: #ff0000; /* D: the collider to tap now */
  --color_glow: #ffffff;         /* D: the other colliders; H: the hint glow */

  --color_dock-bg: color-mix(in srgb, var(--color_page) 75%, transparent);
  --color_bar-track: color-mix(in srgb, var(--color_ui) 25%, transparent);
  --color_debug-collider: color-mix(in srgb, var(--color_glow) 25%, transparent);
  --color_debug-collider-target: color-mix(in srgb, var(--color_debug-target) 40%, transparent);
  --color_hint: color-mix(in srgb, var(--color_glow) 50%, transparent);
}

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

html, body {
  height: 100%;
  background: var(--color_page); /* fills the space around the square on non-square screens */
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
}

/* Square stage: as large as possible while fitting the viewport */
.stage {
  position: relative;
  width: min(100vw, 100vh);
  width: min(100vw, 100dvh);
  aspect-ratio: 1 / 1;
  background: var(--color_scene);
  overflow: hidden; /* the shifted character must not spill outside the square */
}

/* All layers occupy exactly the same box */
.stage img,
.collider,
.sun,
.layer-particles,
.riser,
.levitator,
.layer-slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}

/* Images not loaded yet (data-src only) would otherwise draw Chrome's empty-image outline */
.stage img:not([src]) { visibility: hidden; }

.layer-background { z-index: 0; }
.layer-floor,
.layer-particles { z-index: 1; } /* particles come after the floor in the HTML, so they sit on top of it */

/* The character starts shifted down so it sits in the floor grass, then slowly rises
   to its original position (script.js adds .is-risen, and starts the levitation once it arrives).
   Percentages are relative to the layer's own (square) size. */
.riser {
  z-index: 2;
  transform: translateY(25%);
  transition: transform 3s ease-in-out;
}
.riser.is-risen { transform: none; }

/* The aura behind the character, fading in as it starts to rise. Zooms (the hint, see script.js)
   around the aura's own centre (its visible part spans x 803–1375, y 580–1632 of 2160). */
.layer-particular-aura {
  opacity: 0;
  transform-origin: calc(1089/2160*100%) calc(1106/2160*100%);
  transition: opacity 1.2s ease;
}
.layer-particular-aura.is-visible { opacity: 1; }

/* Sliding layers (plants, mushrooms): each starts pushed out past its edge (--offset-x/--offset-y,
   far enough to be fully off-stage) and slides in when script.js adds .is-visible once the image is ready.
   Plant images are shown several times, each copy clipped to the part that holds a single plant;
   the clip moves with the image, so each copy always shows just its own plant. */
.layer-slide {
  z-index: 3;
  transform: translate(var(--offset-x, 0), var(--offset-y, 0));
  transition: transform 1.4s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* Side plants (plants_sides.png): come in horizontally, the left one as the page loads and the
   right one on the first tap */
.plant-left  { clip-path: inset(0 50% 0 0); --offset-x: -20%; }
.plant-right { clip-path: inset(0 0 0 50%); --offset-x:  20%; }

/* Side leaves (plants_leaves_sides.png): come in horizontally like the side plants, as the
   ukulele's ending phrase finishes (see startBirds in script.js), the right one a moment after the left */
.leaves-left  { clip-path: inset(0 50% 0 0); --offset-x: -20%; }
.leaves-right { clip-path: inset(0 0 0 50%); --offset-x:  20%; }
.leaves-right.is-visible { transition-delay: 0.2s; }

/* Each side plant grows from the middle of its edge, so its idle wobble (script.js) tilts it
   around that point */
.plant-left { transform-origin: 0 50%; }
.plant-right { transform-origin: 100% 50%; }
/* Lit versions (plants_sides_light.png, plants_corners_light.png, shrooms_light.png): sit on top
   of their layer and fade in on a later tap */
.layer-light {
  opacity: 0;
  transition: opacity 0.8s ease;
}
.layer-light.is-visible { opacity: 1; }

/* Corner plants (plants_corner.png): come in diagonally on a later tap */
.plant-top-left     { clip-path: inset(0 50% 50% 0); --offset-x: -40%; --offset-y: -40%; }
.plant-top-right    { clip-path: inset(0 0 50% 50%); --offset-x:  40%; --offset-y: -40%; transition-delay: 0.1s; }
.plant-bottom-left  { clip-path: inset(50% 50% 0 0); --offset-x: -40%; --offset-y:  40%; transition-delay: 0.2s; }
.plant-bottom-right { clip-path: inset(50% 0 0 50%); --offset-x:  40%; --offset-y:  40%; transition-delay: 0.3s; }

/* Mushrooms (shrooms.png): slide up from below on a later tap. Same level as the character but
   later in the HTML, so they sit in front of it. */
.layer-shrooms { z-index: 2; --offset-y: 20%; }

.layer-slide.is-visible { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .layer-slide,
  .riser { transition: none; }
}

/* --lift scales the levitation height (1 = full float, 0 = resting).
   Registering it lets it animate, so the character settles/rises smoothly. */
@property --lift {
  syntax: '<number>';
  inherits: false;
  initial-value: 1;
}

/* The character and its aura levitate together as one group */
.levitator {
  will-change: transform;
  transition: --lift 1.2s ease-in-out;
}

/* Ukulele off → the character comes to rest */
.ukulele-off .levitator { --lift: 0; }

/* Sun sits on top of the whole scene, visible from the start, slowly turning. Its image is cut off
   at the top edge, and its centre is a little below that (1081, 50 in the 2160 px sprite), so
   turning it as drawn would bring the cut into view. Instead the disc is made whole: a second copy,
   turned half way round the centre, fills in the missing cap above the cut. Turned rather than
   flipped, so its dots don't mirror the ones across the join, and faded in over a band past the
   cut, so the join has no edge. The whole disc turns around its centre, which the hint's zoom
   (script.js) uses too. */
.sun {
  z-index: 4;
  transform-origin: calc(1081 / 2160 * 100%) calc(50 / 2160 * 100%);
  animation: sun-turn 480s linear infinite;
}
/* The copy's own y maps to 100 - y once turned, so in its coordinates the cap above the cut
   (y < 0) is y > 100, and the 30 px fade just below the cut is y 70–100. */
.layer-sun-copy {
  transform: rotate(0.5turn);
  transform-origin: calc(1081 / 2160 * 100%) calc(50 / 2160 * 100%);
  -webkit-mask-image: linear-gradient(transparent calc(70 / 2160 * 100%), #000 calc(100 / 2160 * 100%));
  mask-image: linear-gradient(transparent calc(70 / 2160 * 100%), #000 calc(100 / 2160 * 100%));
}

@keyframes sun-turn {
  to { rotate: 1turn; }
}

@media (prefers-reduced-motion: reduce) {
  .sun { animation: none; }
}

/* The sun's ray (the aura strand, hanging from the sun): fades in when picked up. script.js builds
   it from slices of aura.webp (top, repeated twists, tail), sizes and places it, and turns it on the
   point where it hangs to reach the pointer. Same level as the sun but earlier in the HTML, so it
   comes out from behind it. */
.layer-ray {
  position: absolute;
  z-index: 4;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.layer-ray.is-visible { opacity: 1; }
/* Two strands on top of each other, cross-faded by script.js as the ray grows a twist */
.ray-strand {
  position: absolute;
  inset: 0 0 auto;
  display: flex;
  flex-direction: column;
}
.ray-strand > div {
  flex: none;
  background: url(assets/sprites/aura.webp) no-repeat;
}

/* The spiral above the head (aura.webp): fades in on a later tap, floating with the character */
.layer-aura {
  opacity: 0;
  transition: opacity 1.2s ease;
}
.layer-aura.is-visible { opacity: 1; }

/* The character starts levitating once it has risen (script.js adds .is-playing) */
.is-playing .levitator {
  animation: levitate 4s ease-in-out infinite;
}

/* Percentages are relative to the layer's own size, so the float scales with the screen */
@keyframes levitate {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(calc(var(--lift) * -1.5%)); }
}

/* The particles slowly turn around the centre while the character levitates, always starting from
   their original position. When stepping back stops the levitation, script.js turns them back
   to it (see sink()). */
.is-playing .layer-particles { animation: spin 180s linear infinite; }

@keyframes spin {
  to { transform: rotate(1turn); }
}

/* Each cluster of particles also floats up and down, very slowly, in opposite directions: the
   same cycle, half a cycle apart, so one rises while the other sinks. The image splits cleanly at x = 1095 (of 2160): an empty gap between the
   clusters. The float moves the separate `translate` property, so it adds to the spin above. */
.particles-left {
  clip-path: inset(0 calc(100% - 1095 / 2160 * 100%) 0 0);
  animation: particles-float 12s ease-in-out infinite;
}
.particles-right {
  clip-path: inset(0 0 0 calc(1095 / 2160 * 100%));
  animation: particles-float 12s ease-in-out -6s infinite; /* half a cycle ahead */
}

@keyframes particles-float {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -1%; }
}

@media (prefers-reduced-motion: reduce) {
  .is-playing .levitator,
  .is-playing .layer-particles,
  .particles-left,
  .particles-right { animation: none; }
}

/* Only the colliders take taps (see index.html): everything else on the stage lets them through */
.stage,
.stage * { pointer-events: none; }
.collider use {
  pointer-events: fill; /* the shape's inside, though it's painted transparent */
  fill: transparent;
}
.collider-floor { z-index: 1; }
.collider-sun { z-index: 4; }

/* The object that moves the scene on (script.js marks its collider .is-target) gets the hand cursor */
.collider.is-target use { cursor: pointer; }

/* D (or ?debug in the URL) shows the colliders: red for the current target, grey for the rest */
.debug .collider use { fill: var(--color_debug-collider); }
.debug .collider.is-target use { fill: var(--color_debug-collider-target); }

/* H: the current target glows twice in its own shape */
.collider.is-hinted use { animation: hint 0.7s ease-in-out 2; }
@keyframes hint {
  50% { fill: var(--color_hint); }
}

body {
  touch-action: none; /* no scrolling or zooming, so a finger can drag the sun's ray around */
  -webkit-tap-highlight-color: transparent;
}

/* Bottom dock, floating over the bottom of the screen: the loop progress bar stacked on the
   sound controls. As a grid column it's as wide as the controls, so the bar matches them. */
.dock {
  position: fixed;
  left: 50%;
  bottom: max(16px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 10;
  display: grid;
  gap: 8px;
  /* Hidden until the music starts (script.js adds .is-visible); visibility also takes the hidden
     controls out of reach of taps and Tab, once they've faded out. */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.8s ease, visibility 0s 0.8s;
}
.dock.is-visible {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.8s ease;
}

/* When there's room beside the square (desktop), the dock moves into the space to its right,
   centred in it, so it doesn't cover the scene. script.js adds .dock-side only when the dock fits.
   That space runs from the square's right edge to the screen's: with S the square's width
   (min(100vw, 100dvh)), its centre is 100vw - (100vw - S) / 4 = 75vw + S / 4. */
.dock-side .dock {
  left: calc(75vw + min(100vw, 100dvh) / 4);
}

.controls {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 16px 6px 8px;
  border-radius: 999px;
  background: var(--color_dock-bg); /* the page colour, see-through */
  color: var(--color_ui);
  cursor: default;
}

.mute {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: inherit;
  cursor: pointer;
}
.mute svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.mute .speaker { fill: currentColor; }
.mute .icon-off,
.mute[aria-pressed="true"] .icon-on { display: none; }
.mute[aria-pressed="true"] .icon-off { display: inline; }

.volume {
  width: 120px;
  accent-color: var(--color_scene);
  cursor: pointer;
}
.mute[aria-pressed="true"] + .volume { opacity: 0.5; }

.mute:focus-visible,
.volume:focus-visible {
  outline: 2px solid var(--color_ui);
  outline-offset: 2px;
}

/* Level progress bar along the top, as wide as the square stage (minus a gutter) */
.progress {
  position: fixed;
  top: max(12px, env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  width: calc(min(100vw, 100dvh) - 32px);
  display: grid;
  gap: 6px;
  pointer-events: none; /* taps go straight through to the scene */
}

.bar {
  height: 4px;
  border-radius: 999px;
  background: var(--color_bar-track); /* light and see-through: shows on the scene and on the dark sides */
  overflow: hidden;
}
.bar-fill {
  display: block;
  height: 100%;
  transform: scaleX(0);
  transform-origin: left;
}

/* Level: slides to each new step */
.bar-level .bar-fill {
  background: var(--color_ui);
  transition: transform 0.6s ease;
}

/* Loop: a playhead, updated every frame by script.js; lives in the bottom dock */
.bar-loop { pointer-events: none; }
.bar-loop .bar-fill { background: var(--color_sun); }
