/* =========================================================================
   Fonts
   The Figma source specifies "KMR Waldenburg SemiCondensed" for the display
   heading — proprietary. "KMR Apparat" from Accomplish's brand library is
   the closest related family we have rights to ship; DM Sans is the free
   public-fonts fallback below it.
   ========================================================================= */
@font-face {
  font-family: "KMR Waldenburg";
  src: url("assets/fonts/KMRWaldenburg-SemiCondensed.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "KMR Apparat";
  src: url("assets/fonts/kmr-apparat-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* =========================================================================
   Design tokens
   Two themes from Figma:
     - Cream (node 2333:24760): bg #353535, fg #fffae5
     - Moss  (node 2333:24747): bg #43534f, fg #b9ff8f ("Marker")
   Themes set --color-bg / --color-fg; the rest of the system references those.
   ========================================================================= */
:root,
[data-theme="cream"] {
  --color-bg: #353535;
  --color-fg: #fffae5;
  --color-ink: #171717;
  --color-switcher-bg: rgba(255, 250, 229, 0.06);
  --color-switcher-bg-hover: rgba(255, 250, 229, 0.12);
}

[data-theme="moss"] {
  --color-bg: #43534f;
  --color-fg: #b9ff8f;
  --color-ink: #171717;
  --color-switcher-bg: rgba(185, 255, 143, 0.06);
  --color-switcher-bg-hover: rgba(185, 255, 143, 0.14);
}

:root {
  /* Typography — families */
  --font-display: "KMR Waldenburg", "KMR Apparat", "DM Sans", "Inter",
    "Helvetica Neue", Arial, sans-serif;
  --font-body: "DM Sans", "Inter", "Helvetica Neue", Arial, sans-serif;

  /* Typography — sizes
     Display is fluid: 48px floor → 9vw fluid → 129.352px ceiling (Figma).
     Caption/button stay fixed so UI chrome doesn't shrink unreadable. */
  --fs-display: clamp(48px, 9vw, 129.352px);
  --fs-button: 14px;
  --fs-caption: 13px;
  --fs-body-sm: 14px;

  /* Typography — tracking. Scaled with the same ratio as fs-display so
     the optical letter-spacing stays consistent across sizes. */
  --tracking-display: -0.02em;
  --tracking-button: -0.28px;

  /* Radii */
  --radius-button: 8px;

  /* Spacing */
  --pad-x-button: 22px;
  --pad-y-button: 14px;

  /* Reference frame (Figma is 1920x1080) */
  --frame-w: 1920;
  --frame-h: 1080;

  /* Fluid positions — values match Figma at 1920×1080 and shrink with
     the viewport. Both axes have hard floors so the layout never
     collapses below readable margins. */
  --content-left: clamp(20px, 4.3vw, 83px);
  --content-right: clamp(20px, 4.3vw, 83px);
  --logo-top: clamp(28px, 7.7vh, 83.26px);
  --logo-w: clamp(170px, 12vw, 232.508px);
  --logo-h: auto;                /* width-driven so aspect ratio holds */
  --hero-title-top: clamp(96px, 17.8vh, 192px);
  --hero-title-w: clamp(280px, 38.6vw, 741px);
  --hero-cta-top: clamp(220px, 30.5vh, 330px);

  /* Cursor reveal disc radius — Figma is 200px (= 400px clip).
     Floor at 120px so the disc remains usable on phones. */
  /* 30% smaller than the original Figma 200px / 15vw / 120px floor. */
  --reveal-radius: clamp(84px, 10.5vw, 140px);

  /* Theme transition — applied to everything that uses theme tokens */
  --theme-transition: background-color 200ms ease, color 200ms ease,
    border-color 200ms ease, fill 200ms ease;
}

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

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  /* No scrolling — the design always fits the viewport. */
  overflow: hidden;
  overscroll-behavior: none;
}

body {
  background: var(--color-bg);
  color: var(--color-fg);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  transition: var(--theme-transition);
}

img,
svg {
  display: block;
  max-width: none;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
select {
  font: inherit;
  color: inherit;
}

/* =========================================================================
   Page frame
   Fills the viewport exactly. Content positions are fluid (clamp + vw/vh)
   so the design reflows on smaller screens. `isolation: isolate` contains
   `mix-blend-mode` on the text so the inversion only blends with the
   backdrop layer beneath, never with whatever's outside the app.
   ========================================================================= */
.page {
  position: relative;
  width: 100vw;
  height: 100vh;
  /* Modern mobile-safe viewport unit — falls back to vh on older Safari. */
  height: 100dvh;
  background: var(--color-bg);
  overflow: hidden;
  transition: var(--theme-transition);
  isolation: isolate;
}

/* =========================================================================
   Back layer (MacBook mockup) — Figma node 2351:23104
   Cursor-driven mask, sourced from CSS vars set by cursor-fx.js:
     --cursor-x, --cursor-y  : smoothed cursor position (0..100%)
   ========================================================================= */
:root {
  --cursor-x: 50%;
  --cursor-y: 50%;
}

/* Backdrop and the foreground content are siblings without explicit
   z-index — DOM order handles stacking. This is deliberate so the text's
   `mix-blend-mode: difference` can blend against the backdrop pixels;
   nested stacking contexts would isolate it and break the inversion. */
.page__backdrop {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

.page__backdrop-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* `cover` fills the viewport in both dimensions and preserves the
     image's natural aspect ratio — overflow on the longer axis gets
     cropped rather than stretched. */
  object-fit: cover;
  object-position: center;
  filter: saturate(0.92);
  /* Hard-edged circular reveal that follows the cursor. Radius is fluid
     (clamp 120–200px) so the disc stays proportionate on small screens. */
  --cursor-reveal: radial-gradient(
    circle var(--reveal-radius) at var(--cursor-x) var(--cursor-y),
    #000 100%,
    transparent 100%
  );
  mask-image: var(--cursor-reveal);
  -webkit-mask-image: var(--cursor-reveal);
}

@media (prefers-reduced-motion: reduce) {
  .page__backdrop-image {
    transition: none;
  }
}

/* On touch/mobile devices the cursor-following disc has no cursor to
   follow, so the backdrop just reads as a frozen dark image. Hide it
   entirely and let the flat theme background carry the page. */
@media (hover: none), (max-width: 640px) {
  .page__backdrop {
    display: none;
  }
}

/* =========================================================================
   Logo — top-left, inline SVG painted with currentColor so theming flows
   ========================================================================= */
.page__header {
  position: absolute;
  top: var(--logo-top);
  left: var(--content-left);
}

.logo {
  display: inline-flex;
  color: var(--color-fg);
  transition: var(--theme-transition);
}

.logo__svg {
  width: var(--logo-w);
  height: var(--logo-h);
  /* Preserve aspect ratio when --logo-h is `auto`. */
}

/* The logo and hero text invert when the cursor disc passes over them.
   `mix-blend-mode: difference` blends the painted glyphs against the
   pixels behind: against the dark page bg the text reads as cream-ish
   (slightly dimmed), against the revealed mockup it flips to dark — so
   the text stays legible no matter what the disc is exposing. */
.logo,
.hero__title,
.email-field__label,
.email-input,
.btn {
  mix-blend-mode: difference;
}

/* =========================================================================
   Hero — anchored top-left at (--content-left, --hero-title-top).
   Title and CTA row sit beneath as flex children. The vertical gap is
   computed so the CTA top lands at --hero-cta-top regardless of fluid
   font-size, matching Figma's y=330 anchor on the 1080px reference.
   ========================================================================= */
.hero {
  position: absolute;
  top: var(--hero-title-top);
  left: var(--content-left);
  right: var(--content-right);     /* allows the hero to breathe on mobile */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Tight, font-size-proportional gap. Figma's 1920×1080 reference puts
     ~8.6px between title bottom and CTA top (≈6.7% of title font size).
     Binding the gap to --fs-display keeps that ratio as the title shrinks
     instead of opening up a huge dead zone — the old formula computed the
     gap from absolute y-anchors that didn't shrink in lock-step with the
     font, so the CTA drifted away when the title got smaller. The clamp
     keeps the gap visually present (≥6px) and bounded (≤16px) at extremes. */
  gap: clamp(6px, calc(var(--fs-display) * 0.067), 16px);
}

.hero__title {
  margin: 0 0 0 -6px;
  width: var(--hero-title-w);
  max-width: 100%;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-display);
  line-height: 1;
  letter-spacing: var(--tracking-display);
  color: var(--color-fg);
  text-align: center;
  white-space: nowrap;
  transition: var(--theme-transition);
}

/* =========================================================================
   Button
   ========================================================================= */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 38px;
  padding: var(--pad-y-button) var(--pad-x-button);
  border-radius: var(--radius-button);
  border: 0;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--fs-button);
  line-height: 1;
  letter-spacing: var(--tracking-button);
  font-variation-settings: "opsz" 14;
  white-space: nowrap;
  cursor: pointer;
  transition: var(--theme-transition), transform 120ms ease;
}

.btn--accent {
  background: var(--color-fg);
  color: var(--color-ink);
}

.btn--accent:hover {
  transform: translateY(-1px);
}

.btn--accent:active {
  transform: translateY(0);
}

.btn:disabled {
  cursor: default;
}

/* Spinner / check: absolutely centered so the button keeps the text's
   width and never resizes between idle, loading, and done states. */
.btn__spinner,
.btn__check {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  opacity: 0;
  pointer-events: none;
}

.btn__spinner {
  animation: btn-spin 0.9s linear infinite;
}

.btn.is-loading .btn__text,
.btn.is-done .btn__text {
  visibility: hidden;
}

.btn.is-loading .btn__spinner {
  opacity: 1;
}

.btn.is-done .btn__check {
  opacity: 1;
}

@keyframes btn-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .btn__spinner {
    animation-duration: 2.5s;
  }
}

/* =========================================================================
   Signup state cards (success / error)
   Replace the form in-place when submit resolves. Sized to roughly match
   the form's footprint so layout doesn't jump.
   ========================================================================= */
.hero__signup {
  display: contents;
}

.signup-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  color: var(--color-fg);
  font-family: var(--font-body);
  transition: var(--theme-transition);
}

.signup-state[hidden] {
  display: none;
}

.signup-state__title {
  margin: 0;
  font-size: 18px;
  font-weight: 500;
  letter-spacing: var(--tracking-button);
  line-height: 1.2;
}

.signup-state__retry {
  margin-top: 6px;
  padding: 8px 14px;
  height: 32px;
  border-radius: var(--radius-button);
  border: 1px solid var(--color-fg);
  background: transparent;
  color: var(--color-fg);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--fs-button);
  letter-spacing: var(--tracking-button);
  cursor: pointer;
  transition: var(--theme-transition), opacity 120ms ease;
}

.signup-state__retry:hover {
  opacity: 0.75;
}

.signup-state__retry:focus-visible {
  outline: 2px solid var(--color-fg);
  outline-offset: 2px;
}

/* =========================================================================
   Hero CTA row (Figma node 2368:24008)
   Row of two flex items: the email-field column (label stacked above the
   input) and the "Get Early Access" button. `align-items: flex-end` keeps
   the button's baseline level with the input's baseline.
   ========================================================================= */
.hero__cta {
  position: relative;          /* anchors the absolutely-positioned error */
  display: flex;
  align-items: flex-end;
  gap: 10px;
}

/* The class rule above wins over the UA [hidden] rule on specificity ties,
   so re-assert hidden for the form's swap-out state. */
.hero__cta[hidden] {
  display: none;
}

.email-field {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

.email-field__label {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-caption);
  line-height: normal;
  color: var(--color-fg);
  transition: var(--theme-transition);
}

.email-input {
  /* Figma node 2333:24968 — outline field, transparent fill, cream text. */
  width: 288px;
  height: 38px;
  /* Vertical padding 0 (browser centers the text natively); horizontal 10
     matches Figma's px-[10px]. Box-sizing: border-box from the reset keeps
     the rendered height at exactly 38px including the 1px border. */
  padding: 0 10px;
  border: 1px solid var(--color-fg);
  border-radius: var(--radius-button);
  background: transparent;
  color: var(--color-fg);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--fs-button);
  line-height: normal;
  letter-spacing: var(--tracking-button);
  font-variation-settings: "opsz" 14;
  outline: none;
  transition: var(--theme-transition);
}

.email-input::placeholder {
  /* Figma's 30% alpha gets washed out under `mix-blend-mode: difference`
     (the alpha attenuates the blend, so a 30% cream over a bright bg
     barely budges). Lift to 60% so the placeholder stays readable when
     the disc reveals the cream-toned mockup behind the field. */
  color: var(--color-fg);
  opacity: 0.6;
}

.email-input:focus-visible {
  outline: 2px solid var(--color-fg);
  outline-offset: 2px;
}

/* Inline validation error: absolutely positioned below the field so it
   appears without pushing the form layout (the button used to drift down
   when the error pushed the email-field column taller). Themed with the
   same tokens as the label so it reads as part of the field. */
.email-field__error {
  position: absolute;
  top: 100%;
  left: 0;
  margin: 10px 0 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-caption);
  line-height: normal;
  color: #ff9d9d;
  letter-spacing: var(--tracking-button);
  white-space: nowrap;
  transition: var(--theme-transition);
}

.email-field__error[hidden] {
  display: none;
}

/* =========================================================================
   Theme switcher
   Top-right control to switch between Cream and Moss schemes. Themed in
   the same token system so it adapts when its own selection changes.
   ========================================================================= */
.theme-switcher {
  position: absolute;
  top: 32px;
  right: 32px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  z-index: 10;
}

.theme-switcher__label {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: -0.24px;
  color: var(--color-fg);
  opacity: 0.6;
  text-transform: uppercase;
  transition: var(--theme-transition);
}

.theme-switcher__control {
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 38px;
  padding: 0 36px 0 16px;
  border-radius: var(--radius-button);
  border: 1px solid var(--color-fg);
  background: var(--color-switcher-bg);
  color: var(--color-fg);
  transition: var(--theme-transition);
}

.theme-switcher__control:hover {
  background: var(--color-switcher-bg-hover);
}

.theme-switcher__select {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  outline: none;
  padding: 0;
  margin: 0;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: var(--tracking-button);
  color: var(--color-fg);
  cursor: pointer;
  /* Native select option list — color reset for readable dropdown */
}

.theme-switcher__select option {
  color: var(--color-ink);
  background: var(--color-fg);
}

.theme-switcher__chevron {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 10px;
  height: 7px;
  pointer-events: none;
  color: var(--color-fg);
}

.theme-switcher__select:focus-visible + .theme-switcher__chevron,
.theme-switcher__control:focus-within {
  outline: 2px solid var(--color-fg);
  outline-offset: 2px;
}

/* =========================================================================
   Mobile / narrow-viewport adjustments
   The CTA row would overflow on phones (288px input + 10px gap + button is
   wider than a typical mobile viewport), so it stacks. The input stretches
   to fill the hero width and the button hugs the start.
   ========================================================================= */
@media (max-width: 640px) {
  /* Keep the hero column left-anchored on mobile (matches desktop), but
     give the title some breathing room from the form below it — the
     font-size-proportional gap on desktop collapses to its 6px floor at
     small font sizes, which reads too tight on phones. */
  .hero {
    gap: clamp(20px, 4vh, 36px);
  }

  .hero__cta {
    flex-direction: column;
    align-items: flex-start;
    width: 100%;
    max-width: 360px;
  }

  .email-field,
  .email-input {
    width: 100%;
  }

  /* Switch the validation error from absolute (which would overlap the
     wrapped button beneath it on mobile) to in-flow. Because the error
     sits at the end of the form's child list, it lands naturally below
     the submit button. */
  .email-field__error {
    position: static;
    margin: -4px 0 0;
    white-space: normal;
    text-align: left;
  }

  /* Theme switcher chip is dense — drop the label on phones to save space. */
  .theme-switcher__label {
    display: none;
  }

  /* No cursor on touch devices, so the reveal disc would just sit idle.
     Drop the mask so the backdrop image shows in full. */
  .page__backdrop-image {
    mask-image: none;
    -webkit-mask-image: none;
  }
}
