/* Amphibian Games — site styles.
 *
 * One page, no framework, dark-only. Tokens are defined once on :root and every rule
 * reads them, so nothing has its only definition inside a media query. Layout is
 * fluid: no fixed widths, no min-width wider than a phone, and the page never scrolls
 * horizontally.
 */

/* --- Fonts ----------------------------------------------------------------
 *
 * PLACEHOLDER typefaces until the brand fonts are chosen: Fredoka (display) and
 * Nunito (body), both SIL OFL 1.1 — licence texts sit beside the files in
 * assets/fonts/. Self-hosted because the CSP allows no off-origin fonts (`font-src
 * 'self'`, see src/_headers). URLs are relative to dist/site.css, which sits beside
 * dist/assets/. Variable fonts, so one file covers every weight used.
 *
 * The families are named by ROLE ("Site Display" / "Site Body"), not by typeface, so
 * swapping brands touches only these two blocks — `src` and, for a non-variable
 * font, `font-weight` — and the `--font-*` tokens never change. A typeface name here
 * would have to be kept in step with the tokens by hand, and a mismatch falls back
 * to the system stack with nothing failing.
 */
@font-face {
  font-family: "Site Display";
  src: url("assets/fonts/fredoka-latin-wght.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Site Body";
  src: url("assets/fonts/nunito-latin-wght.woff2") format("woff2");
  font-weight: 200 1000;
  font-style: normal;
  font-display: swap;
}

/* --- Tokens ----------------------------------------------------------------
 *
 * PLACEHOLDER "dark pond" palette: deep pond green, pale ink, lily-pad lime and a
 * sunny pop for calls to action. Replace the values, not the names, when the brand
 * palette lands — every rule below reads tokens only.
 *
 * The site is dark-only, whatever the viewer's OS preference: one palette, no theme
 * switch, no light variant to keep in step.
 */
:root {
  /* Tells the UA to render its own surfaces (scrollbars, form controls, the default
     canvas) dark too, so nothing flashes light before the stylesheet paints. */
  color-scheme: dark;

  --bg: #0e1a14;
  --surface: #15241c;
  --text: #eef3e6;
  --text-muted: #a3b5a8;
  --accent: #7fdc8f;
  --accent-contrast: #0b1510;
  --lily: #6fbf3a;
  --lily-soft: #1d3322;
  --on-lily: #0b1510;
  --pop: #ffc24d;
  /* Text ON the --pop swatch. */
  --pop-text: #2a1d00;
  /* Black ink vanishes on the dark pond; a lifted green keeps the sticker edges. */
  --ink: #3f6b4e;
  --border: #27392f;
  --placeholder: #1b2d23;
  --ripple: #23392b;

  --font-display: "Site Display", ui-rounded, "SF Pro Rounded", system-ui, sans-serif;
  --font-body: "Site Body", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;

  --measure: 62ch;
  --gutter: clamp(1rem, 5vw, 3rem);
  --section-gap: clamp(3rem, 9vw, 6rem);
  --radius: 18px;
  /* The "sticker" look: a hard, unblurred offset shadow in ink colour. */
  --sticker: 5px 5px 0 var(--ink);
  --sticker-sm: 3px 3px 0 var(--ink);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding-block: 0 var(--section-gap);
  padding-inline: var(--gutter);
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
.wordmark,
.cta,
.eyebrow {
  font-family: var(--font-display);
}

/* One shared centred column for every top-level block. */
.site-header,
main,
.site-footer {
  max-width: 68rem;
  margin-inline: auto;
}

img,
video {
  max-width: 100%;
  height: auto;
}

a {
  color: var(--accent);
}

a:focus-visible,
.cta:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* --- Media slots ---------------------------------------------------------- */

.media {
  display: block;
  width: 100%;
  border-radius: var(--radius);
  object-fit: cover;
}

/* Aspect ratios live here, not in an inline style attribute. The CSP is
   `style-src 'self'` with no 'unsafe-inline', and browsers block inline style
   ATTRIBUTES under it — an inline ratio would collapse every media box on the
   deployed page while looking correct locally. render.js maps a slot's ratio to one
   of these classes and throws on an unregistered one, so the two files cannot drift. */
.media--1x1 {
  aspect-ratio: 1 / 1;
}

.media--16x9 {
  aspect-ratio: 16 / 9;
}

/* A placeholder is a real, correctly-proportioned box so the layout can be reviewed
   before any art exists. Dropping in art is a content.js edit, not a CSS one. The
   ripple rings are pure gradients — a data: URI or SVG background would need img-src
   beyond 'self'. */
.media--placeholder {
  display: grid;
  place-items: center;
  background:
    repeating-radial-gradient(circle at 30% 70%, transparent 0 22px, var(--ripple) 22px 24px),
    var(--placeholder);
  border: 2px solid var(--ink);
  color: var(--text-muted);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-align: center;
  padding: 1rem;
}

.media__label {
  padding: 0.25rem 0.75rem;
  border-radius: 999px;
  background: var(--surface);
}

/* Present for screen readers, absent on screen. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Header --------------------------------------------------------------- */

.site-header {
  /* Above the hero: its lily pads reach up into the header on narrow screens and,
     coming later in the document, would otherwise paint over the logo and wordmark. */
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding-block: 1.75rem;
}

.site-header .logo {
  width: 2.75rem;
  flex: none;
  border-radius: 50%;
  background: var(--lily);
  /* The full shorthand, not just a width: the ring must survive the swap to real
     art, when the placeholder class (and its border style) no longer applies. */
  border: 2px solid var(--ink);
}

/* The logo slot's own label would be illegible at 44px; the wordmark beside it
   already names the studio. */
.site-header .logo .media__label {
  display: none;
}

.wordmark {
  font-weight: 600;
  font-size: 1.375rem;
  letter-spacing: -0.01em;
}

/* The studio's promise, pushed to the far edge; wraps under the wordmark on phones. */
.tagline {
  margin: 0 0 0 auto;
  color: var(--text-muted);
  font-family: var(--font-display);
  font-weight: 600;
}

/* --- Hero ----------------------------------------------------------------- */

.hero {
  position: relative;
  isolation: isolate;
  padding-block: clamp(2rem, 6vw, 4rem) var(--section-gap);
}

/* Decorative lily pads behind the headline. Pure CSS: no asset, no markup.
   They may bleed into the body gutter but never past the page edge (offset is at
   most one --gutter): mobile browsers widen the layout viewport to fit overflowing
   content, and `overflow-x: clip` on <html> does not stop them — the root's overflow
   is handed to the viewport, where clip is not honoured. */
.hero::before,
.hero::after {
  content: "";
  position: absolute;
  z-index: -1;
  border-radius: 50%;
  pointer-events: none;
}

.hero::before {
  width: clamp(14rem, 40vw, 30rem);
  aspect-ratio: 1;
  top: -3rem;
  right: calc(-1 * var(--gutter));
  /* The conic wedge is the notch that makes a circle read as a lily pad. */
  background: conic-gradient(from 20deg, transparent 0 28deg, var(--lily-soft) 28deg 360deg);
}

.hero::after {
  width: clamp(5rem, 12vw, 9rem);
  aspect-ratio: 1;
  top: 0.5rem;
  left: calc(-1 * var(--gutter));
  background: conic-gradient(from 200deg, transparent 0 30deg, var(--lily-soft) 30deg 360deg);
}

.hero__headline {
  margin: 0;
  max-width: 16ch;
  font-size: clamp(2.5rem, 1.4rem + 5vw, 5rem);
  line-height: 1;
  letter-spacing: -0.02em;
  font-weight: 700;
}

.hero__media {
  margin-top: clamp(2rem, 5vw, 3.5rem);
  border: 3px solid var(--ink);
  box-shadow: var(--sticker);
}

.hero__media-wrap {
  position: relative;
}

/* --- Welcome (post-confirmation page) ------------------------------------- */

.welcome__body {
  max-width: var(--measure);
  margin-block: 1.25rem 1.75rem;
  color: var(--text-muted);
  font-size: 1.125rem;
}

.cta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

/* --- Sections ------------------------------------------------------------- */

/* A row of dots stands in for a wavy divider: pure gradient, no image fetch. */
section + section {
  padding-top: var(--section-gap);
  background:
    radial-gradient(circle, var(--lily) 0 4px, transparent 5px) top left / 22px 12px repeat-x;
}

.eyebrow {
  display: inline-block;
  margin: 0 0 0.75rem;
  padding: 0.2rem 0.8rem;
  border: 2px solid var(--ink);
  border-radius: 999px;
  background: var(--lily);
  color: var(--on-lily);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

h2 {
  margin: 0;
  font-size: clamp(2rem, 1.2rem + 3vw, 3.25rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.015em;
  max-width: 20ch;
}

/* Pitch and status: shared by the hero and the More games cards. */
.game__pitch {
  max-width: var(--measure);
  margin: 1rem 0 0;
  font-size: clamp(1.125rem, 1rem + 0.6vw, 1.5rem);
  font-weight: 600;
  line-height: 1.4;
}

.game__status {
  margin: 1.5rem 0 1.25rem;
  font-weight: 800;
}

/* --- CTA ------------------------------------------------------------------ */

.cta {
  display: inline-block;
  padding: 0.75rem 1.6rem;
  border: 2px solid var(--ink);
  border-radius: 999px;
  background: var(--pop);
  color: var(--pop-text);
  box-shadow: var(--sticker-sm);
  font-size: 1.0625rem;
  font-weight: 600;
  text-decoration: none;
  transition: transform 120ms ease, box-shadow 120ms ease;
}

/* Press-in on hover: the button travels onto its own shadow. */
a.cta:hover,
button.cta:enabled:hover {
  transform: translate(3px, 3px);
  box-shadow: 0 0 0 var(--ink);
}

/* A CTA with no URL yet is inert text, styled to read as "not yet" rather than
   "broken". */
.cta--pending {
  background: var(--surface);
  color: var(--text-muted);
  border-style: dashed;
  box-shadow: none;
  cursor: default;
}

/* --- Screenshot grid ------------------------------------------------------ */

.shots {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: 1.5rem;
  /* Bottom margin clears the tilted corners and shadows off the next divider. */
  margin-block: clamp(2rem, 4vw, 3rem) clamp(1.5rem, 3vw, 2.5rem);
}

/* Polaroids on a pinboard: a small alternating tilt that straightens on hover. */
.shot {
  box-shadow: var(--sticker-sm);
  rotate: -1.5deg;
  transition: rotate 150ms ease;
}

.shot:nth-child(even) {
  rotate: 1.5deg;
}

.shot:hover {
  rotate: 0deg;
}

/* --- More games ---------------------------------------------------------- */

.games {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
  gap: 2rem;
  margin-block: clamp(1.5rem, 3vw, 2.5rem);
}

.game-card__art {
  border: 3px solid var(--ink);
  box-shadow: var(--sticker-sm);
}

.game-card__name {
  margin: 1.25rem 0 0;
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1.1;
}

.game-card__pitch {
  margin: 0.5rem 0 0;
  font-weight: 600;
}

.game-card .game__status {
  margin: 0.75rem 0 1rem;
}

/* --- Signup --------------------------------------------------------------- */

.signup__body {
  max-width: var(--measure);
  color: var(--text-muted);
}

.signup__form {
  max-width: 34rem;
}

.signup__label {
  display: block;
  margin-bottom: 0.4rem;
  font-weight: 700;
}

.signup__row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.signup__input {
  flex: 1 1 14rem;
  min-width: 0;
  padding: 0.75rem 1rem;
  border: 2px solid var(--ink);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
}

.signup__input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* The CTA was built for links; as a button it needs the UA's font and cursor reset. */
button.cta {
  font-family: var(--font-display);
  cursor: pointer;
}

button.cta:disabled {
  cursor: progress;
  opacity: 0.7;
}

.signup__fine {
  max-width: var(--measure);
  margin: 0.75rem 0 0;
  color: var(--text-muted);
  font-size: 0.875rem;
}

.signup__status:empty {
  display: none;
}

/* Success replaces the form (signup.js hides it), so the message takes its place as
   a lily-pad panel. Errors keep the form and sit under it, dashed like the pending
   CTA — no new colour token. */
.signup__status {
  max-width: 34rem;
  margin: 1.25rem 0 0;
  padding: 0.9rem 1.2rem;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  background: var(--lily-soft);
  color: var(--text);
  font-weight: 700;
}

.signup__status--error {
  border-style: dashed;
  background: var(--surface);
}

/* --- Footer --------------------------------------------------------------- */

.site-footer {
  position: relative;
  margin-top: var(--section-gap);
  padding: 2rem clamp(1.25rem, 4vw, 2.5rem);
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-contrast);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
  font-size: 0.9375rem;
}

/* The one-line "who we are": its own row above the links. */
.studio-line {
  flex-basis: 100%;
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: 600;
}

.site-footer a {
  color: inherit;
  font-weight: 700;
}

.site-footer a:focus-visible {
  outline-color: currentColor;
}

.social {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}

.copyright {
  margin: 0;
  opacity: 0.85;
  flex-basis: 100%;
}

/* --- Frog hunt easter egg -------------------------------------------------
 *
 * See docs/decisions/site-frog-hunt.md. Each frog is a hidden <button> render.js
 * emits at its spot; frogs.js unhides the ones not yet found. Frame size is the
 * asset's native 48px doubled (D6): the Idle sheet is 8 frames, the Hop sheet 7,
 * both laid out left-to-right, so the sprite classes below only need a frame
 * count and a sheet width to define an animation.
 */

.frog {
  /* The one source of truth for this frog's size: the box, the sprite's
     background-size, AND the idle/hop keyframes below all derive from it, so a
     spot that displays at a different size (the --at-logo/-wishlist/-signup
     group) can never drift out of sync with how far the sprite travels — that
     drift is exactly what made those frogs go invisible for part of every idle
     loop (the shared keyframes hardcoded the *other* group's frame width, so the
     smaller frogs animated background-position clean off the edge of their own,
     narrower sheet). */
  --frog-frame: 96px;

  position: absolute;
  width: var(--frog-frame);
  height: var(--frog-frame);
  padding: 0;
  border: 0;
  background-color: transparent;
  background-repeat: no-repeat;
  image-rendering: pixelated;
  cursor: pointer;
  /* 8 Idle frames. */
  background-size: calc(var(--frog-frame) * 8) var(--frog-frame);
  animation: frog-idle 1.2s steps(8) infinite;
}

.frog:disabled {
  cursor: default;
}

.frog:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 8px;
}

@keyframes frog-idle {
  from {
    background-position-x: 0;
  }
  to {
    background-position-x: calc(var(--frog-frame) * -8);
  }
}

@keyframes frog-hop-sprite {
  from {
    background-position-x: 0;
  }
  to {
    background-position-x: calc(var(--frog-frame) * -7);
  }
}

@keyframes frog-hop-away {
  from {
    transform: translateX(0);
    opacity: 1;
  }
  to {
    transform: translateX(40px);
    opacity: 0;
  }
}

/* One rule per variant registered in render.js's FROG_VARIANTS: the Idle sheet.
   Sizing comes from the shared --frog-frame math above, not repeated here. */
.frog--GreenBrown {
  background-image: url("assets/frogs/ToxicFrogGreenBrown_Idle.png");
}

.frog--BlueBlue {
  background-image: url("assets/frogs/ToxicFrogBlueBlue_Idle.png");
}

.frog--PurpleWhite {
  background-image: url("assets/frogs/ToxicFrogPurpleWhite_Idle.png");
}

.frog--GreenBlue {
  background-image: url("assets/frogs/ToxicFrogGreenBlue_Idle.png");
}

.frog--BlueBrown {
  background-image: url("assets/frogs/ToxicFrogBlueBrown_Idle.png");
}

/* The Hop sheet swap. Higher specificity than the idle rules above (two classes,
   not one) so it overrides the background-image reliably regardless of source
   order. */
.frog--hopping.frog--GreenBrown {
  background-image: url("assets/frogs/ToxicFrogGreenBrown_Hop.png");
}

.frog--hopping.frog--BlueBlue {
  background-image: url("assets/frogs/ToxicFrogBlueBlue_Hop.png");
}

.frog--hopping.frog--PurpleWhite {
  background-image: url("assets/frogs/ToxicFrogPurpleWhite_Hop.png");
}

.frog--hopping.frog--GreenBlue {
  background-image: url("assets/frogs/ToxicFrogGreenBlue_Hop.png");
}

.frog--hopping.frog--BlueBrown {
  background-image: url("assets/frogs/ToxicFrogBlueBrown_Hop.png");
}

.frog--hopping {
  /* 7 Hop frames. */
  background-size: calc(var(--frog-frame) * 7) var(--frog-frame);
  animation:
    frog-hop-sprite 0.35s steps(7) 2,
    frog-hop-away 0.9s ease-out forwards;
}

/* Reduced motion (JS branches to this instead of frog--hopping): no sprite step,
   no slide — just a fade, and the global reduced-motion rule below flattens even
   that to effectively instant. */
.frog--fading {
  transition: opacity 300ms ease;
  opacity: 0;
}

/* Per-spot placement. .frog--at-hero and .frog--at-footer are true overlays (the
   hero media wrapper and .site-footer are each `position: relative` for theirs);
   every other spot is an ordinary flex child placed by markup order in render.js,
   so it needs `position: static` — the base .frog rule is `position: absolute`,
   which with no inset set would otherwise collapse onto its neighbour (the CTA /
   subscribe button) instead of sitting beside it. */
.frog--at-hero {
  right: 0.5rem;
  bottom: 0.5rem;
}

.frog--at-footer {
  right: clamp(1.25rem, 4vw, 2.5rem);
  bottom: 1rem;
}

.frog--at-logo,
.frog--at-wishlist,
.frog--at-signup {
  --frog-frame: 3rem;
  position: static;
}

/* --- Frog badge + hint list ------------------------------------------------ */

.frog-badge {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 2;
}

.frog-badge__toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.5rem 0.9rem;
  border: 2px solid var(--ink);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--sticker-sm);
  font-family: var(--font-display);
  font-weight: 700;
  cursor: pointer;
}

.frog-badge__panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  width: min(20rem, 80vw);
  padding: 1.25rem;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--sticker);
}

.frog-badge__heading {
  margin: 0 0 0.75rem;
  font-size: 1.125rem;
}

.frog-badge__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.6rem;
}

.frog-hint {
  padding-block: 0.4rem;
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 0.9375rem;
}

.frog-hint--found {
  color: var(--accent);
  font-weight: 700;
}

.frog-badge__reset {
  display: inline-block;
  margin-top: 0.75rem;
  font-size: 0.8125rem;
}

.frog-badge__credit {
  margin: 0.5rem 0 0;
  font-size: 0.75rem;
  color: var(--text-muted);
}

/* --- Toasts ----------------------------------------------------------------- */

.frog-toast-region {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 3;
  display: grid;
  gap: 0.5rem;
  /* The frog badge sits at the same corner; toasts stack above it without a fixed
     pixel guess by pushing down from further out. */
  padding-top: 3.5rem;
  pointer-events: none;
}

.frog-toast {
  max-width: 18rem;
  padding: 0.9rem 1.1rem;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  background: var(--lily-soft);
  color: var(--text);
  box-shadow: var(--sticker-sm);
  animation: frog-toast-in 200ms ease;
  pointer-events: auto;
}

@keyframes frog-toast-in {
  from {
    transform: translateY(-8px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.frog-toast__eyebrow {
  margin: 0 0 0.25rem;
  color: var(--accent);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.frog-toast__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
}

a.frog-toast__title {
  color: var(--accent);
}

.frog-toast__body {
  margin: 0.35rem 0 0;
  font-size: 0.875rem;
  color: var(--text-muted);
}

/* A video slot renders twice (see renderMedia): the autoplay loop, and a still copy
   with controls for viewers who prefer reduced motion. CSS cannot pause autoplay, so
   the preference is honoured by showing one and not the other. */
.media--still {
  display: none;
}

/* Reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .media--motion {
    display: none;
  }

  .media--still {
    display: block;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  /* The static tilt is decoration, not motion — but the hover straighten and the
     CTA press-in are, and they read as jitter to a vestibular-sensitive viewer. */
  .shot:hover {
    rotate: -1.5deg;
  }

  .shot:nth-child(even):hover {
    rotate: 1.5deg;
  }

  a.cta:hover,
  button.cta:enabled:hover {
    transform: none;
    box-shadow: var(--sticker-sm);
  }
}
