/* ============================================================
   Terratuga - landing page styles.
   Every value here comes from tokens.css. No raw hex, no
   off-scale spacing, no blur shadow on anything interactive.
   ============================================================ */

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

/* A `display` declaration beats the UA stylesheet's [hidden] rule,
   so the flex containers below would stay visible when hidden. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* --- Type scale ------------------------------------------------ */

.t-display-2xl,
.t-display-xl,
.t-display,
.t-title {
  font-family: var(--font-display);
  margin: 0;
}

/* Hand-tuned tracking: large rounded Baloo 2 at default tracking
   looks untouched. -0.02em at the two largest sizes. */
.t-display-2xl {
  font-size: var(--fs-display-2xl);
  line-height: var(--lh-display-2xl);
  font-weight: 800;
  letter-spacing: -0.02em;
}
.t-display-xl {
  font-size: var(--fs-display-xl);
  line-height: var(--lh-display-xl);
  font-weight: 800;
  letter-spacing: -0.02em;
}
.t-display {
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.t-title {
  font-size: var(--fs-title);
  line-height: var(--lh-title);
  font-weight: 700;
}
.t-headline { font-size: var(--fs-headline); line-height: var(--lh-headline); font-weight: 700; }
.t-body     { font-size: var(--fs-body);     line-height: var(--lh-body);     font-weight: 400; }
.t-callout  { font-size: var(--fs-callout);  line-height: var(--lh-callout);  font-weight: 600; }
.t-caption  { font-size: var(--fs-caption);  line-height: var(--lh-caption);  font-weight: 600; }
.t-overline {
  font-size: var(--fs-overline);
  line-height: var(--lh-overline);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.muted { color: var(--ink-muted); }

/* --- Page frame ------------------------------------------------ */

/* The sky, not the flat canvas. This is the single biggest reason the page
   reads as the app rather than as a page about the app: the start screen
   opens on the same ramp. Stops and the rule about what may sit on them are
   specified on `sky` in src/theme/tokens.ts, not decided here. */
body {
  background: linear-gradient(
    to bottom,
    var(--sky-top) 0%,
    var(--sky-mid) 55%,
    var(--sky-bottom) 100%
  );
  background-attachment: fixed;
  min-height: 100vh;
}

/* Fills the viewport behind everything, and is never interactive. */
/* Capped, not full-bleed. The viewBox is the app's 390x844 phone frame, so
   stretching it across a desktop viewport scales every cloud by about 3x and
   they stop reading as sky and start reading as blobs. Capping the width
   keeps them the size they are in the app; the sky either side is just sky. */
.sky-decor {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(100%, 760px);
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

.page {
  position: relative;
  z-index: 1;
  max-width: 34rem;
  margin: 0 auto;
  padding: var(--space-xl) var(--space-lg) var(--space-2xl);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* --- Outlined display type ------------------------------------- */

/* A white glyph carrying its own dark halo. The one place white type is
   legal in this system: it is measured against the halo (16.28:1), not
   against whatever is behind it, so it stays readable over any part of the
   sky — including the deep blue at the bottom where nothing else can go.

   Twelve offsets, not one: a single offset is a drop shadow, not an outline.
   Eight around a square ring plus the four cardinals again at 1.25x, so a
   diagonal join cannot leave a nick. Same construction as the app's
   OutlineTitle, which stacks twelve copies because -webkit-text-stroke is
   web-only; here text-shadow does it in one declaration. */
.outlined {
  color: var(--outline-glyph);
  --halo: 0.075em;
  text-shadow:
    calc(-1 * var(--halo)) calc(-1 * var(--halo)) 0 var(--outline-halo),
    0 calc(-1 * var(--halo)) 0 var(--outline-halo),
    var(--halo) calc(-1 * var(--halo)) 0 var(--outline-halo),
    calc(-1 * var(--halo)) 0 0 var(--outline-halo),
    var(--halo) 0 0 var(--outline-halo),
    calc(-1 * var(--halo)) var(--halo) 0 var(--outline-halo),
    0 var(--halo) 0 var(--outline-halo),
    var(--halo) var(--halo) 0 var(--outline-halo),
    0 calc(-1.25 * var(--halo)) 0 var(--outline-halo),
    0 calc(1.25 * var(--halo)) 0 var(--outline-halo),
    calc(-1.25 * var(--halo)) 0 0 var(--outline-halo),
    calc(1.25 * var(--halo)) 0 0 var(--outline-halo);
}

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

/* One centred column, in the app's order: wordmark, Tuga, title, one line,
   the thing you act on. */
.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center; /* a short page sits in the middle, not at the top */
  text-align: center;
  flex: 1;
  gap: var(--space-md);
}

.wordmark {
  font-family: var(--font-display);
  font-size: var(--fs-title);
  line-height: var(--lh-title);
  font-weight: 800;
  letter-spacing: -0.01em;
  margin: 0;
}

.headline {
  max-width: 16ch;
  margin: 0 auto;
}

/* `ink`, never `ink-muted` — see the note in index.html. */
.lede {
  color: var(--ink);
  margin: 0 0 var(--space-md);
}

/* --- Tuga ------------------------------------------------------ */

.tuga-wrap {
  /* The one thing on a symmetrical page keeping it from reading as a slide
     template. Static, not a loop. */
  transform: rotate(-2.5deg);
  /* The sprite box reserves only ~3% padding under the feet (SPRITE.body in
     tuga.generated.ts), so the clearance has to come from here rather than
     from the asset. */
  margin: var(--space-sm) 0 var(--space-xl);
}

.tuga {
  position: relative;
  width: clamp(240px, 56vw, 340px);
  height: auto;
  display: block;
}

/* Enters scaling from 0.9 with a slight upward drift. */
.tuga { animation: tuga-enter var(--duration-slow) var(--ease-spring) both; }
@keyframes tuga-enter {
  from { opacity: 0; transform: scale(0.9) translateY(var(--space-md)); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

/* celebrate: scale 1 -> 1.12 -> 1. Fires once, on a real event. With drawn
   sprites the pop IS the gesture — the limbs cannot move — so it carries the
   whole celebration rather than supporting a flipper raise. */
.tuga[data-state="celebrate"] { animation: tuga-pop var(--duration-celebrate) var(--ease-out) both; }
@keyframes tuga-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.12); }
  100% { transform: scale(1); }
}

/* Expressions are whole drawings, cross-faded. Stacked in one box so the
   incoming frame is already decoded and the swap cannot flash, and so the
   figure keeps its size whichever state is showing.

   The drawings are registered against the globe on the shell at build time,
   so the body stays put across the fade and only the face moves. */
.tuga-frame {
  display: block;
  width: 100%;
  height: auto;
  transition: opacity var(--duration-quick) var(--ease-out);
}
.tuga-celebrate {
  position: absolute;
  inset: 0;
  opacity: 0;
}
.tuga[data-state="celebrate"] .tuga-idle { opacity: 0; }
.tuga[data-state="celebrate"] .tuga-celebrate { opacity: 1; }

/* --- Waitlist card -------------------------------------------- */

/* Card elevation: 2pt border plus a 4pt solid bottom edge. No blur. */
.card {
  background: var(--surface);
  border: var(--edge-hairline) solid var(--border);
  border-bottom: var(--edge-card) solid var(--border-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-xl);
  max-width: 460px;
  /* The column centres the block; the block itself stays left-aligned.
     A centred label above a left-aligned input looks like a mistake, and
     the form is the one thing on this page that is used rather than read. */
  margin-inline: auto;
  width: 100%;
  text-align: left;
}

.card-heading { margin: 0 0 var(--space-lg); }

.form-row { display: flex; flex-direction: column; gap: var(--space-sm); }
/* The field and its label are one group; the button is a separate
   beat, so it gets more air than the gap gives it. */
.form-submit { margin-top: var(--space-sm); }

.field-label { display: block; color: var(--ink-muted); }

/* Off-screen but still in the DOM and still focusable-by-script, which
   is what makes the bot trap work. Not display:none. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* TextField: 52pt tall, border.strong 2pt. A control boundary needs
   3:1 - decorative `border` is 1.35:1 and would be an a11y bug. */
.field {
  width: 100%;
  height: 52px;
  padding: 0 var(--space-lg);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  font-weight: 400;
  border: var(--edge-hairline) solid var(--border-strong);
  border-radius: var(--radius-md);
  transition: border-color var(--duration-quick) var(--ease-out);
}
.field::placeholder { color: var(--ink-muted); opacity: 1; }
.field:focus { border-color: var(--shell-dark); outline: none; }
.field:focus-visible { outline: var(--edge-hairline) solid var(--shell-dark); outline-offset: var(--space-xs); }
.field[aria-invalid="true"] { border-color: var(--error-ink); }
.field:disabled { background: var(--surface-sunken); color: var(--ink-faint); }

/* FormError: never colour alone - glyph plus words, both required. */
.form-error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  color: var(--error-ink);
  margin: 0;
}
.form-error[hidden] { display: none; }
.form-error .glyph { flex: none; margin-top: 1px; }

/* --- Button ---------------------------------------------------- */

/* Depth is a solid 4pt bottom edge in the fill's dark variant.
   On press the edge collapses and the content translates down 4px.
   Height is fixed and box-sizing is border-box, so collapsing the
   edge cannot change the layout box and nothing reflows. */
.btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 56px;
  padding: 0 var(--space-lg);
  border: 0;
  border-bottom: var(--edge-chunky) solid transparent;
  border-radius: var(--radius-lg);
  font-family: var(--font-ui);
  font-size: var(--fs-headline);
  line-height: var(--lh-headline);
  font-weight: 700;
  cursor: pointer;
  transition: transform var(--duration-instant) var(--ease-out),
              border-bottom-width var(--duration-instant) var(--ease-out);
}
.btn:focus-visible { outline: var(--edge-hairline) solid var(--ink); outline-offset: var(--space-xs); }

.btn-primary { background: var(--coral); color: var(--on-coral); border-bottom-color: var(--coral-dark); }
.btn-primary:active:not(:disabled) { transform: translateY(var(--edge-chunky)); border-bottom-width: 0; }

.btn-block { width: 100%; }

/* Disabled is a real fill change, never lowered opacity: a 50%
   coral button on sand is unreadable and still looks tappable. */
.btn:disabled {
  background: var(--surface-sunken);
  color: var(--ink-faint);
  border-bottom-width: 0;
  cursor: default;
}

/* Loading: the label swaps for a spinner in the label colour. The
   button is already full-width, so the row cannot jump. */
.spinner {
  width: 20px;
  height: 20px;
  border: 3px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--radius-pill);
  animation: spin 800ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.form-note { color: var(--ink-muted); margin: var(--space-md) 0 0; }

/* --- Success state -------------------------------------------- */

.success-state { display: flex; flex-direction: column; gap: var(--space-sm); }
.success-state p { margin: 0; }
/* Correct state carries a check glyph AND the words. Never the colour
   on its own — a green line is not a result to a screen reader or to
   roughly 1 in 12 men. */
.success-head { display: flex; align-items: center; gap: var(--space-sm); color: var(--success-ink); }

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

/* `ink`, not `ink-muted`, and no hairline rule. The footer sits at the foot
   of the sky where the ramp is a real saturated blue: `ink-muted` measures
   2.24:1 there and `border` is invisible. On a page this short the footer
   does not need a rule to separate it from anything anyway. */
.footer {
  margin-top: var(--space-2xl);
  color: var(--ink);
  text-align: center;
}
/* Only ever visible before the form has a destination. On a surface rather
   than straight on the sky, because both of its colours — `ink-muted` text
   and a `border-strong` dash — fail against the lower half of the ramp. */
.config-warning {
  margin: var(--space-lg) auto 0;
  text-align: left;
  padding: var(--space-md) var(--space-lg);
  background: var(--surface);
  border: var(--edge-hairline) dashed var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--ink-muted);
  max-width: 460px;
  width: 100%;
}
.config-warning[hidden] { display: none; }

/* --- Reduce motion --------------------------------------------
   Nothing is removed. Position and scale become opacity cross-fades;
   every state change still announces itself, just without moving. */
@media (prefers-reduced-motion: reduce) {
  .tuga,
  .tuga[data-state="celebrate"] {
    animation: tuga-fade var(--duration-quick) var(--ease-out) both;
  }
  @keyframes tuga-fade { from { opacity: 0; } to { opacity: 1; } }
  /* The expression still changes under reduce-motion — it is information,
     not decoration — it just arrives without the scale pop. */
  .btn-primary:active:not(:disabled) { transform: none; border-bottom-width: 0; }
  .spinner { animation-duration: 2400ms; }
}

/* --- Narrow screens ------------------------------------------- */

/* The layout is one centred column at every width, so this no longer has
   to rearrange anything — it just tightens the vertical rhythm where the
   fold is short. */
@media (max-width: 860px) {
  .page { padding-top: var(--space-lg); }
}

/* The headline steps down a size before it can wrap into four lines
   on a small phone. Sized against the longest string, not "Peru". */
@media (max-width: 420px) {
  .t-display-2xl {
    font-size: var(--fs-display);
    line-height: var(--lh-display);
    letter-spacing: -0.01em;
  }
}
