/* ============================================================
   [PRODUCT] — landing page component styles

   Direction: light, warm, roomy. Cradle-scale vertical rhythm.
   The accent is the only chromatic colour (it means "act here").
   Patina is reserved exclusively for data/output marks.
   Everything else is warm greyscale.

   CONTRACT: no colour, duration or easing literal lives in this
   file. Every such value resolves from a token in tokens.css or
   from color-mix() over those tokens. The single exception is
   the 80ms reveal stagger, which the build contract specifies
   verbatim.
   ============================================================ */

/* ------------------------------------------------------------
   base
   ------------------------------------------------------------ */

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

html {
  /* tokens.css is custom-properties-only, so the property that
     drives native scrollbars, autofill and select popups is
     applied here. */
  color-scheme: var(--scheme);
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: 5.5rem;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.55;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

@media (min-width: 48rem) {
  body {
    font-size: 1.0625rem;
  }

}

/* the Geist wght axis is clamped 400-600; nothing above exists */
h1,
h2,
h3 {
  margin: 0;
  text-wrap: balance;
  font-weight: 600;
  letter-spacing: -0.03em;
}

p {
  margin: 0;
}

figure {
  margin: 0;
}

img,
svg {
  max-width: 100%;
}

a {
  color: var(--accent);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

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

::selection {
  background: var(--accent);
  color: var(--on-accent);
}

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

/* ------------------------------------------------------------
   skip link
   ------------------------------------------------------------ */

/* Visually hidden, still announced. Used for the email label, which is
   redundant sighted (the placeholder and button say it) but required for
   screen readers — a placeholder is not an accessible name. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 60;
  padding: 0.75rem 1rem;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  font-size: 0.875rem;
  text-decoration: none;
}

.skip:focus {
  left: 1rem;
  top: 1rem;
}

/* ------------------------------------------------------------
   layout shell
   ------------------------------------------------------------ */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: 1.25rem;
}

@media (min-width: 48rem) {
  .shell {
    padding-inline: 2rem;
  }

}

/* ------------------------------------------------------------
   shared primitives
   ------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3.25rem;
  padding: 0 1.75rem;
  position: relative;
  overflow: hidden;
  background: var(--sheen);
  background-size: 220% 100%;
  background-position: 32% 0;
  box-shadow: var(--btn-lip), var(--btn-cast);
  color: var(--on-sheen);
  border: 0;
  border-radius: var(--radius);
  transition: background-position var(--dur) var(--ease);
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.005em;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}

.btn:hover {
  background: var(--sheen-hover);
  background-size: 220% 100%;
  background-position: 68% 0;
  color: var(--on-sheen);
}

/* the highlight that travels across the metal on hover */
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    rgba(255, 255, 255, 0) 40%,
    rgba(255, 255, 255, 0.13) 50%,
    rgba(255, 255, 255, 0) 60%
  );
  transform: translateX(-120%);
  transition: transform var(--dur-l) var(--ease);
  pointer-events: none;
}

.btn:hover::after,
.btn:focus-visible::after {
  transform: translateX(120%);
}

@media (prefers-reduced-motion: reduce) {
  .btn,
  .btn::after {
    transition: none;
  }
  .btn::after {
    display: none;
  }

}

.btn:active {
  transform: translateY(1px);
}

.btn-sm {
  min-height: 2.375rem;
  padding: 0 0.75rem;
  font-size: 0.75rem;
  border-radius: var(--radius-sm);
}

@media (min-width: 48rem) {
  .btn-sm {
    padding: 0 0.9375rem;
    font-size: 0.8125rem;
  }

}

.text-link {
  color: var(--accent);
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
}

.text-link:hover {
  color: var(--accent-hover);
}

/* ------------------------------------------------------------
   masthead
   ------------------------------------------------------------ */

.masthead {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--dark-ground);
  border-bottom: 0;
}

/* The masthead overlays the dark hero, so it carries the dark palette and only
   grows a backdrop once it has scrolled off the hero. */
.masthead {
  --ground: var(--dark-ground);
  --surface: var(--dark-surface);
  --ink: var(--dark-ink);
  --ink-2: var(--dark-ink-2);
  --ink-3: var(--dark-ink-3);
  --line: var(--dark-line);
  --accent: var(--dark-accent);
  --on-accent: var(--dark-on-accent);
  --focus: var(--dark-focus);
  color: var(--ink);
}

.masthead .shell {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: 4rem;
}

.wordmark {
  font-family: var(--mono);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
}

.wordmark:hover {
  color: var(--accent);
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}

@media (min-width: 48rem) {
  .nav-links {
    gap: 1.75rem;
  }

}

.nav-link {
  padding: 0.375rem 0;
  border-radius: var(--radius-sm);
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--ink-2);
  text-decoration: none;
  white-space: nowrap;
}

@media (min-width: 48rem) {
  .nav-link {
    padding-inline: 0.5rem;
    font-size: 0.875rem;
  }

}

.nav-link:hover {
  color: var(--accent);
  background: var(--accent-wash);
}

/* ------------------------------------------------------------
   hero — centred copy block. The product frame that used to sit
   beneath it is removed until there is a real capture for it.
   ------------------------------------------------------------ */

.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;

  /* the one dark band at the top of the page */
  --ground: var(--dark-ground);
  --surface: var(--dark-surface);
  --sunk: var(--dark-sunk);
  --ink: var(--dark-ink);
  --ink-2: var(--dark-ink-2);
  --ink-3: var(--dark-ink-3);
  --line: var(--dark-line);
  --line-strong: var(--dark-line-strong);
  --accent: var(--dark-accent);
  --accent-hover: var(--dark-accent-hover);
  --accent-wash: var(--dark-accent-wash);
  --on-accent: var(--dark-on-accent);
  --patina: var(--dark-patina);
  --focus: var(--dark-focus);

  background: var(--ground);
  color: var(--ink);
  padding-top: 4rem; /* clears the overlaid masthead */
}

/* Without the product frame the hero had no bottom edge of its own and the
   headline sat on the band boundary. It now carries its own height. */
.hero-bare {
  display: flex;
  align-items: center;
  min-height: min(82vh, 46rem);
  padding-bottom: clamp(3rem, 7vw, 6rem);
}

/* Ambient light behind everything in the hero. This is the
   baseline hero lighting AND the shader's fallback, so its
   background is never touched — only its opacity steps back once
   the shader is confirmed live (see .hero-metal below). */
.hero-glow {
  position: absolute;
  inset: -10% -5% auto;
  height: 130%;
  z-index: -1;
  background: var(--glow-key), var(--glow-fill);
  pointer-events: none;
  transition: opacity var(--dur-l) var(--ease);
}

/* ------------------------------------------------------------
   hero-metal — the raymarched liquid-metal backdrop

   .hero sets `isolation: isolate`, so both negative-z layers are
   contained by the hero's own stacking context and cannot escape
   behind the page. Within it the paint order is: .hero background
   -> canvas (-2) -> .hero-glow (-1) -> in-flow content. The glow
   composites OVER the metal, which is why it is softened rather
   than removed when the shader is live.

   Progressive enhancement, strictly one-way: the canvas is
   opacity 0 and empty until hero-metal.js reports a successful
   first frame by setting data-metal="on". Every rule that changes
   the hero's appearance is scoped to that attribute, so with the
   script missing, broken, or WebGL unavailable the hero renders
   pixel-identically to the pre-shader design. The CSS glow is the
   fallback and is never removed.

   Legibility is one number: the opacity the canvas settles at.
   Nothing masks it, nothing is painted over it, and nothing about
   the attenuation knows where the copy is.

   That is deliberate, and it replaces two earlier attempts. The
   first was a horizontal ramp that assumed the form sat right of
   centre and left the headline column dark; the form is centred
   now, so the assumption is gone. The second followed the copy
   properly — a plateau over the headline and description columns,
   feathered in both axes — and it measured well and looked wrong.
   Any attenuation keyed to the copy has a boundary, and a boundary
   crossing an organic churning form reads as a straight edge laid
   over it. A uniform veil has no boundary anywhere, so there is
   nothing to see except the metal getting quieter. It also cannot
   go stale: it makes no assumption about the form's size, position
   or phase, so the shader can be retuned without invalidating it.

   The cost is that the veil is set by the worst pixel anywhere
   under any copy. That used to be the 11px eyebrow, which capped it
   at 0.08. The eyebrow is gone, so the binding element is now the
   17px subhead, and the ceiling has roughly doubled. Measured
   against a pure-white canvas — brighter than the shader can emit,
   so these are floors rather than estimates — with the glow at 0.62
   on top:

     veil 0.08   headline 12.42:1   subhead 6.00:1
     veil 0.14   headline 10.63:1   subhead 5.13:1
     veil 0.20   headline  8.97:1   subhead 4.33:1  <- fails

   0.08 is kept here because it is what shipped and what was signed
   off on, not because it is the limit any more. Anything up to
   about 0.16 is available if the metal should read as a subject
   rather than as atmosphere.
   ------------------------------------------------------------ */

.hero-metal {
  /* Below 60rem the shader does not run at all — the CSS glow
     carries the hero on phones. Re-enabled in the query below. */
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-l) var(--ease);
}

/* Every rule that reacts to data-metal lives inside this query.
   The attribute is set by the script, which cannot see the
   breakpoint, so it lands on phones too — and below 60rem the
   canvas is display:none, which means the painted glow is the
   only light the hero has. Softening it there would dim the
   mobile hero for a shader that is not running. */
@media (min-width: 60rem) {
  .hero-metal {
    display: block;
  }

  /* Set by hero-metal.js after its first successful frame.
     Nothing else in the page may key off this. The value is the
     whole legibility mechanism — see the note above. */
  .hero-metal[data-metal="on"] {
    opacity: 0.08;
  }

  /* The painted glow steps back rather than doubling up with the
     shader's own warm key — but it steps back, it does not step
     out: this corner is the visible source the metal's magenta
     reflections answer to, and it is the part of the hero the brief
     is most explicit about keeping. 0.62 is as bright as it can be.
     The eyebrow sits inside the glow's core, and against a
     pure-white canvas at veil 0.08 it measures 4.69:1 at 960px with
     the glow at 0.62, 4.41:1 at 0.75 and 3.88:1 at 1.0 — so the
     ceiling is around 0.66 and this sits just under it. Sibling
     combinator, not :has(), because the canvas is guaranteed to be
     .hero-glow's immediately preceding sibling, so this works
     everywhere the fade does. */
  .hero-metal[data-metal="on"] ~ .hero-glow {
    opacity: 0.62;
  }

}

/* The shader renders one still frame under reduced motion; fading
   it in would reintroduce the motion that was just removed. */
@media (prefers-reduced-motion: reduce) {
  .hero-metal,
  .hero-glow {
    transition: none;
  }

}


/* Headline left, description right — the description is a narrow column with a
   rule above it, not a centred subtitle. */
.hero-grid {
  display: grid;
  gap: 1.5rem;
  margin-top: 1.25rem;
}

@media (min-width: 60rem) {
  .hero-grid {
    grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
    gap: 3.5rem;
    /* bottom-aligned: the description ends level with "Experiments." rather
       than floating at the top of the headline block */
    align-items: end;
    margin-top: 1.75rem;
  }

}

#headline {
  max-width: 21ch;
  font-size: clamp(2.75rem, 1.35rem + 6.6vw, 5.5rem);
  line-height: 0.98;
  letter-spacing: -0.045em;
  /* deliberately NOT text-wrap: balance — balancing this line pushes it to
     three lines; the two-line break is the whole point of the size. */
}

#subhead {
  max-width: 34ch;
  color: var(--ink-2);
  font-size: 1rem;
  line-height: 1.6;
}

@media (min-width: 60rem) {
  #subhead {
    padding-top: 0.75rem;
    border-top: 1px solid var(--line-strong);
    font-size: 1.0625rem;
  }

}


/* --ink-3 on --sunk is 4.27:1, so this must qualify as WCAG
   large text: 19px at weight 600 clears the 18.66px/bold bar. */

/* ------------------------------------------------------------
   section bands
   ------------------------------------------------------------ */

section.band {
  border-top: 1px solid var(--line);
}

section.band > .shell {
  padding-block: 4.5rem;
}

@media (min-width: 48rem) {
  section.band > .shell {
    padding-block: 7rem;
  }

}

@media (min-width: 75rem) {
  section.band > .shell {
    padding-block: 8.5rem;
  }

}

/* ------------------------------------------------------------
   #access — the closing call to action.

   One centred column, one field. This is the last thing on the page
   and the only form on it, so it is sized like a statement rather than
   like a panel: the heading carries, the input sits directly under it.
   ------------------------------------------------------------ */

.band-cta {
  background: var(--sunk);
  border-top: 1px solid var(--line);
}

.callout {
  max-width: 44rem;
  margin-inline: auto;
  text-align: center;
}

.callout h2 {
  font-size: clamp(2rem, 1.3rem + 2.8vw, 3.25rem);
  line-height: 1.04;
  letter-spacing: -0.038em;
  text-wrap: balance;
}

.callout-sub {
  margin: 1rem auto 0;
  max-width: 34rem;
  color: var(--ink-2);
  font-size: 1.0625rem;
  line-height: 1.6;
  text-wrap: pretty;
}

.callout form {
  margin-top: 2.25rem;
}

/* Field and button on one line above 34rem, stacked below. The button is
   not shrink-wrapped to the text so the pair reads as a single control. */
.signup {
  display: grid;
  gap: 0.75rem;
  max-width: 30rem;
  margin-inline: auto;
}

@media (min-width: 34rem) {
  .signup {
    grid-template-columns: 1fr auto;
  }

}

.signup input[type="email"] {
  width: 100%;
  min-height: 3.25rem;
  padding: 0.75rem 1rem;
  font-family: var(--sans);
  /* 16px minimum prevents iOS zoom-on-focus */
  font-size: 16px;
  color: var(--ink);
  background: var(--surface);
  /* --line-strong clears 3:1 against the panel — WCAG 1.4.11 */
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
}

.signup input::placeholder {
  color: var(--ink-3);
}

.signup input[type="email"]:hover {
  border-color: var(--accent);
}

.signup input[type="email"]:focus-visible {
  border-color: var(--accent);
  background: var(--accent-wash);
}

.signup .btn {
  min-height: 3.25rem;
  white-space: nowrap;
}

/* The one remaining qualifier. Centred under the field, deliberately
   quieter than it — it is an option, not a second decision. */
.check {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.625rem;
  margin: 1.25rem 0 0;
}

.check input {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  accent-color: var(--accent);
}

.check label {
  font-family: var(--sans);
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--ink-2);
}

/* Netlify honeypot — never shown, never focusable */
.hp {
  display: none;
}

/* ------------------------------------------------------------
   footer
   ------------------------------------------------------------ */

footer.site {
  border-top: 1px solid var(--line);
}

footer.site .shell {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  padding-block: 2rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.05em;
  line-height: 1.9;
  color: var(--ink-3);
}

/* ------------------------------------------------------------
   motion

   Only opacity and transform animate on reveal. The rule is
   scoped to .js so the page is fully visible when JS does not
   run — reveal.js sets that class on <html> on load.
   ------------------------------------------------------------ */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(var(--rise));
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: calc(var(--i, 0) * 80ms);
}

.js [data-reveal].in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }

  /* interactive state changes only — nothing here moves */
  .btn,
  .nav-link,
  .text-link,
  .wordmark,
  .signup input[type="email"] {
    transition: background-color var(--dur-s) var(--ease),
      border-color var(--dur-s) var(--ease), color var(--dur-s) var(--ease);
  }

}

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .btn:active {
    transform: none;
  }

}
