/* 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;
  }
}

/* =============================================================================
   Completion celebration — the cleaned object's sticker drops from
   center-screen onto its calendar day cell / library card ("slap-down"),
   then the slot pops (.scruble-day-pop) with a spark burst
   (coin-unlock-spark). The sprite is laid out at its FINAL size at the
   start point; the keyframes hold it big (--slap-start-scale) for a
   beat, then accelerate it onto the target (--slap-tx/ty delta) landing
   at the slot's seeded tilt (--slap-rot). The element's animation runs
   linear — the per-segment timing functions below carry the feel.
   ============================================================================= */

@keyframes sticker-slap-down {
  0% {
    transform: translate(0, 0) scale(calc(var(--slap-start-scale, 3) * 0.6)) rotate(-6deg);
    opacity: 0;
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.4, 1);
  }
  14% {
    /* Popped in — hold big so the player registers what they earned. */
    transform: translate(0, 0) scale(var(--slap-start-scale, 3)) rotate(-2deg);
    opacity: 1;
    animation-timing-function: ease-in-out;
  }
  42% {
    /* A little breathe at the top, then the slap accelerates down. */
    transform: translate(0, 0) scale(calc(var(--slap-start-scale, 3) * 1.05)) rotate(1deg);
    opacity: 1;
    animation-timing-function: cubic-bezier(0.55, 0, 0.85, 0.6);
  }
  100% {
    transform: translate(var(--slap-tx, 0px), var(--slap-ty, 0px)) scale(1)
               rotate(var(--slap-rot, 0deg));
    opacity: 1;
  }
}

/* =============================================================================
   Auth / sign-in modal — themed to match the home/HUD chrome (Fredoka,
   brand orange #ff731a, chunky pill buttons with a 0 Npx 0 "candy" shadow
   that compresses on :active). Layout/dynamic bits stay inline in
   washer.auth-modal; these classes carry the focus/hover/press states
   that inline styles can't express.
   ============================================================================= */

.auth-input {
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  font-size: 15px;
  font-family: system-ui, sans-serif;
  color: #2a2a2a;
  background: #fff;
  border: 2px solid #ead9c9;          /* soft orange-tinted neutral */
  border-radius: 12px;
  outline: none;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

.auth-input::placeholder { color: #b3a596; }

.auth-input:focus {
  border-color: #ff731a;
  box-shadow: 0 0 0 3px rgba(255, 115, 26, 0.18);
}

.auth-btn-primary {
  width: 100%;
  padding: 13px 16px;
  font-family: 'Fredoka', system-ui, sans-serif;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.02em;
  color: #fff;
  background: #ff731a;
  border: none;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 5px 0 #cc5a14;        /* darker-orange chunk = candy depth */
  transition: transform 80ms ease, box-shadow 80ms ease, background 120ms ease;
  touch-action: manipulation;
}

.auth-btn-primary:hover:not(:disabled) { background: #ff8333; }

.auth-btn-primary:active:not(:disabled) {
  transform: translateY(4px);
  box-shadow: 0 1px 0 #cc5a14;
}

.auth-btn-primary:disabled {
  opacity: 0.5;
  cursor: default;
  box-shadow: 0 5px 0 rgba(0, 0, 0, 0.12);
}

.auth-btn-google {
  width: 100%;
  padding: 12px 16px;
  font-family: 'Fredoka', system-ui, sans-serif;
  font-weight: 600;
  font-size: 15px;
  color: #333;
  background: #fff;
  border: 2px solid #e6e6e6;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.08);
  transition: transform 80ms ease, box-shadow 80ms ease, border-color 120ms ease;
  touch-action: manipulation;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.auth-btn-google:hover { border-color: #d2d2d2; }

.auth-btn-google:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08);
}

/* Sign in with Apple: same shape as the Google button, but Apple's HIG
   wants the black fill + white logo/label. */
.auth-btn-apple {
  width: 100%;
  padding: 12px 16px;
  font-family: 'Fredoka', system-ui, sans-serif;
  font-weight: 600;
  font-size: 15px;
  color: #fff;
  background: #000;
  border: 2px solid #000;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  transition: transform 80ms ease, box-shadow 80ms ease, background 120ms ease;
  touch-action: manipulation;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.auth-btn-apple:hover { background: #1c1c1e; }

.auth-btn-apple:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
}

.auth-link {
  align-self: center;
  background: transparent;
  border: none;
  color: #ff731a;
  font-family: 'Fredoka', system-ui, sans-serif;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  padding: 4px 6px;
}

.auth-link:hover { color: #e85f0c; text-decoration: underline; }

.auth-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #f1efec;
  color: #9a9087;
  border: none;
  border-radius: 999px;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}

.auth-close:hover { background: #e7e3df; color: #6a625b; }

/* User-pill dropdown menu item (e.g. "Sign out") — brand orange Fredoka
   with a soft orange hover wash, matching the dropdown's orange border. */
.auth-menu-item {
  width: 100%;
  padding: 10px 14px;
  background: transparent;
  border: none;
  border-radius: 8px;
  text-align: left;
  cursor: pointer;
  font-family: 'Fredoka', system-ui, sans-serif;
  font-weight: 600;
  font-size: 14px;
  color: #ff731a;
  transition: background 120ms ease;
}

.auth-menu-item:hover { background: rgba(255, 115, 26, 0.10); }

.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); }
}

/* ---------------------------------------------------------------------
   Unsupported-browser screen (washer.compat). Replaces the contents of
   #loading-screen, so it inherits that element's fixed full-screen sky
   backdrop and centering — these rules only style the copy inside it.
   Kept in CSS rather than inline styles so it matches the rest of the
   boot chrome, and so it still applies if the screen is ever shown from
   somewhere other than compat/show-unsupported!. */
.compat-box {
  max-width: 34rem;
  margin: 0 auto;
  padding: 0 28px;
  text-align: center;
  font-family: 'Fredoka', system-ui, sans-serif;
  color: #4a4038;
}
.compat-title {
  margin: 0 0 18px;
  font-size: 28px;
  font-weight: 700;
  line-height: 1.2;
  color: #ff731a;
}
.compat-body {
  margin: 0 0 14px;
  font-size: 17px;
  line-height: 1.5;
}
.compat-copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin-top: 20px;
}
.compat-url {
  /* Long URLs must wrap rather than force the page sideways on a phone. */
  display: block;
  max-width: 100%;
  overflow-wrap: anywhere;
  padding: 10px 14px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.75);
  border: 2px solid rgba(255, 115, 26, 0.45);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px;
  /* Selectable even though the body sets user-select:none for gameplay. */
  -webkit-user-select: all;
  user-select: all;
}
.compat-copy-btn {
  padding: 12px 26px;
  border: 3px solid #fff;
  border-radius: 999px;
  background: #ff731a;
  color: #fff;
  font-family: inherit;
  font-weight: 700;
  font-size: 17px;
  cursor: pointer;
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.2);
  /* Comfortable thumb target — this is the one control on the screen. */
  min-height: 48px;
}

/* ---------------------------------------------------------------------
   First-play coach marks (washer.coachmarks). Pops in with a small
   overshoot so a bubble appearing mid-spray gets noticed, then holds
   still — it sits over live gameplay, where a looping animation would
   nag. Two variants because the keyframes have to carry the bubble's
   own centering translate: an animation with fill-mode `both` owns the
   transform outright, so a centered bubble animated by the plain
   variant would snap out of its centering and stay there. */
.scruble-coachmark {
  transform-origin: bottom left;
  animation: scruble-coachmark-in 320ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
}

.scruble-coachmark-center {
  transform-origin: bottom center;
  animation: scruble-coachmark-in-center 320ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
}

@keyframes scruble-coachmark-in {
  0%   { opacity: 0; transform: translateY(8px) scale(0.85); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes scruble-coachmark-in-center {
  0%   { opacity: 0; transform: translateX(-50%) translateY(8px) scale(0.85); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}
