/* Single source of truth for the home / loading sky gradient. Referenced
   by #loading-screen below, the <body> background (index.html), the home
   overlay (washer.home/component), and the first-play fade-scrim tint
   (washer.hud/fade-scrim) — all via var(--scruble-sky) so they never
   drift. */
:root {
  --scruble-sky: linear-gradient(180deg, #8fd3f0 0%, #cfeffb 52%, #ffffff 100%);
}

#loading-screen {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 200;
  /* Same sky gradient as the home overlay so the boot loading screen
     flows seamlessly into the home screen rather than flashing black. */
  background: var(--scruble-sky);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
}

.loading-text {
  font-family: 'Fredoka', system-ui, sans-serif;
  font-weight: 700;
  font-size: 24px;
  /* Brand orange — readable on the light gradient (the old #c0c0c0 was
     tuned for the black background). */
  color: #ff731a;
  letter-spacing: 0.1em;
}

/* Matches the in-HUD transition bar (.scruble-loading-bar /
   --determinate below) so the boot loading bar and the
   Downloading/Processing bar read as the same component. The boot bar
   is always determinate — gk writes #loading-bar-fill's width directly. */
.loading-bar-container {
  position: relative;
  width: 240px;
  height: 10px;
  background: rgba(0, 0, 0, 0.45);
  border: 3px solid #fff;
  border-radius: 999px;
  overflow: hidden;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
}

.loading-bar-fill {
  width: 0%;
  height: 100%;
  background: linear-gradient(90deg, #ffb066 0%, #ff731a 50%, #ffb066 100%);
  border-radius: 999px;
  transition: width 120ms ease-out;
}

/* =============================================================================
   Crosshair (spray reticle)
   ============================================================================= */

.crosshair {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.crosshair-dot {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.8);
  background: transparent;
  top: -5px;
  left: -5px;
}

/* =============================================================================
   Progress-bar bubbles — emitted at the leading edge as score advances.
   Bubble base position is `left: <x>%; top: 50%` with a `translate(-50%, ...)`
   so X/Y are centred on the spawn point. The keyframes drift the bubble up
   and fade it out.
   ============================================================================= */

@keyframes scruble-bubble {
  0% {
    transform: translate(-50%, 0) scale(0.3);
    opacity: 0;
  }
  15% {
    transform: translate(-50%, -4px) scale(1);
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -42px) scale(0.45);
    opacity: 0;
  }
}

/* =============================================================================
   Coin-collect flight — sprites burst from screen-center, scatter outward,
   then fly into the top-right balance pill. CSS variables (set inline by
   washer.coins/coin-sprite) carry the per-sprite scatter and target deltas.
   ============================================================================= */

/* =============================================================================
   Stickerbook — today's day cell pulses a soft brand-orange ring so the
   player spots where they are in the album grid at a glance.
   ============================================================================= */

.scruble-day-today {
  animation: scruble-day-today-pulse 1.6s ease-in-out infinite;
}

@keyframes scruble-day-today-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(255, 115, 26, 0.55),
                0 0 8px 2px rgba(255, 115, 26, 0.35);
    transform: scale(1);
  }
  50% {
    box-shadow: 0 0 0 10px rgba(255, 115, 26, 0),
                0 0 16px 5px rgba(255, 115, 26, 0.75);
    transform: scale(1.06);
  }
}

/* =============================================================================
   "<object> clean!" banner — pops in once the win animation lands in :won.
   Bouncy scale + slight rise so it lands with a little personality.
   ============================================================================= */

.scruble-clean-banner {
  animation: scruble-clean-banner 700ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  transform-origin: center;
}

@keyframes scruble-clean-banner {
  0% {
    transform: translateY(-12px) scale(0.4);
    opacity: 0;
  }
  60% {
    transform: translateY(2px) scale(1.08);
    opacity: 1;
  }
  100% {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
}

/* =============================================================================
   Transition loading overlay — centered indeterminate bar + stage
   label shown over the fade-scrim while a lazy model swap is in
   flight. Mounted from `washer.hud/loading-bar`.
   ============================================================================= */

.scruble-loading-overlay {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  pointer-events: none;
  z-index: 9994;
}

.scruble-loading-bar {
  position: relative;
  width: 240px;
  height: 10px;
  background: rgba(0, 0, 0, 0.45);
  border: 3px solid #fff;
  border-radius: 999px;
  overflow: hidden;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
}

.scruble-loading-bar-fill {
  position: absolute;
  top: 0;
  left: 0;
  width: 35%;
  height: 100%;
  background: linear-gradient(90deg, #ffb066 0%, #ff731a 50%, #ffb066 100%);
  border-radius: 999px;
  animation: scruble-loading-slide 1.3s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}

/* Applied while we have a real Content-Length-derived download
   ratio — disables the slide animation, anchors the fill to the
   left, and smooths the per-event width updates. Inline `style`
   on the fill element drives the actual width. */
.scruble-loading-bar-fill--determinate {
  animation: none;
  transform: translateX(0);
  transition: width 120ms ease-out;
}

.scruble-loading-text {
  font-family: 'Fredoka', system-ui, sans-serif;
  font-weight: 700;
  font-size: 20px;
  letter-spacing: 0.04em;
  color: #fff;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.7);
  min-height: 24px;
  text-align: center;
}

@keyframes scruble-loading-slide {
  0% {
    transform: translateX(-110%);
  }
  100% {
    transform: translateX(310%);
  }
}

/* Purchase-confirmation processing spinner (washer.store-return). */
@keyframes scruble-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes coin-fly {
  0% {
    transform: translate(0, 0) scale(0.5);
    opacity: 0;
  }
  10% {
    transform: translate(0, 0) scale(1);
    opacity: 1;
  }
  35% {
    transform: translate(var(--coin-sx, 0px), var(--coin-sy, 0px)) scale(1.15) rotate(20deg);
    opacity: 1;
  }
  90% {
    opacity: 1;
  }
  100% {
    transform: translate(var(--coin-tx, 0px), var(--coin-ty, 0px)) scale(0.55) rotate(-30deg);
    opacity: 0;
  }
}

/* =============================================================================
   Stickerbook unlock — coins fly FROM the wallet coin pill TO the day cell
   that was just unlocked, then the cell pops. The CSS vars carry the
   per-sprite target deltas (sprite is positioned at the pill in inline
   style; the translate is a delta to the cell). The midpoint lifts above
   the straight line so each coin arcs rather than slides.
   ============================================================================= */

@keyframes coin-spend-fly {
  0% {
    transform: translate(0, 0) scale(0.4) rotate(0deg);
    opacity: 0;
  }
  12% {
    transform: translate(0, 0) scale(1.05) rotate(0deg);
    opacity: 1;
  }
  55% {
    transform: translate(
      calc(var(--coin-tx, 0px) * 0.55 + var(--coin-curve, 0px)),
      calc(var(--coin-ty, 0px) * 0.55 + var(--coin-lift, -70px))
    ) scale(1.1) rotate(calc(var(--coin-spin, 360deg) * 0.5));
    opacity: 1;
  }
  100% {
    transform: translate(var(--coin-tx, 0px), var(--coin-ty, 0px))
               scale(0.55) rotate(var(--coin-spin, 360deg));
    opacity: 0;
  }
}

/* One-shot impact ring where each coin lands on the day cell. Base
   opacity on the element is 0 so nothing shows during the per-coin
   animation-delay; `forwards` keeps it hidden after it finishes. */
@keyframes coin-land-ring {
  0% {
    transform: scale(0.25);
    opacity: 0.9;
  }
  100% {
    transform: scale(1.5);
    opacity: 0;
  }
}

/* Radial spark for the unlock moment (last coin lands, silhouette
   crossfades gray → blue). Per-spark direction comes in via CSS vars. */
@keyframes coin-unlock-spark {
  0% {
    transform: translate(-50%, -50%) scale(0.2);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  100% {
    transform: translate(
      calc(-50% + var(--spark-x, 0px)),
      calc(-50% + var(--spark-y, 0px))
    ) scale(1);
    opacity: 0;
  }
}

.scruble-day-pop {
  animation: scruble-day-pop 360ms ease-out;
}

/* Scale pop with a small rotation wiggle — reads as the sticker being
   slapped onto the page rather than just inflating. */
@keyframes scruble-day-pop {
  0%   { transform: scale(1) rotate(0deg); }
  35%  { transform: scale(1.18) rotate(-3deg); }
  70%  { transform: scale(0.96) rotate(2deg); }
  100% { transform: scale(1) rotate(0deg); }
}
