/* Meld Mail · Coming soon. Always dark, monochrome.
   Two halves of the Meld mark drift together and melt into one, a single
   light crosses the fused mark, then the title settles. About 4.6 s to the
   resting frame, then only a slow breath, a drifting light pool and grain. */
@font-face {
  font-family: 'Geist';
  src: url('/fonts/Geist-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Geist Mono';
  src: url('/fonts/GeistMono-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: #0a0a0b;
  --ink: #f4f4f5;
  --ink-soft: #8d8d93;
  --label: #e4e4e7;
  --hair: rgba(244, 244, 245, 0.3);
  --field: rgba(244, 244, 245, 0.035);
  --field-focus: rgba(244, 244, 245, 0.055);
  --field-line: rgba(244, 244, 245, 0.14);
  --field-line-hover: rgba(244, 244, 245, 0.24);
  --field-line-focus: rgba(244, 244, 245, 0.56);
  --placeholder: #939399; /* at least 4.5:1 on the field, grain and focus included */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --mx: 50vw;
  --my: 40vh;
  color-scheme: dark;
}

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

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
}

body {
  overflow: hidden;
  font-family: 'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* ---- stage --------------------------------------------------------------- */
.stage {
  position: relative;
  z-index: 2;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px 16px calc(24px + 4vh);
  text-align: center;
}

.mark {
  height: clamp(96px, 17vh, 210px);
  aspect-ratio: 278.56 / 261.3;
  transform-origin: 50% 55%;
}
.mark svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.word {
  position: relative;
  margin: clamp(30px, 5.2vh, 48px) 0 0;
  font-weight: 700;
  font-size: clamp(44px, 3.4vw + 16px, 84px);
  line-height: 1;
  letter-spacing: -0.038em;
  text-transform: lowercase;
  white-space: nowrap;
  color: var(--ink);
}
.word .w2 { color: var(--ink-soft); }
.word-sharp { display: block; }
/* A pre-blurred twin: the title racks into focus by crossfading the two
   (opacity and transform only, so the compositor does all the work). */
.word-soft {
  position: absolute;
  inset: 0;
  display: block;
  filter: blur(12px);
  opacity: 0;
  pointer-events: none;
}

/* COMING SOON sits straight under the name, between two hairlines. */
.soon {
  margin: clamp(22px, 3.8vh, 36px) 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.05em;
  font-family: 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
  font-weight: 500;
  font-size: clamp(13px, 0.35vw + 10.5px, 17px);
  line-height: 1;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--label);
}
.soon-t {
  display: block;
  margin-right: -0.34em; /* keep tracked caps optically centred */
}
.rule {
  display: block;
  width: clamp(22px, 2.6vw, 44px);
  height: 1px;
  background: var(--hair);
}
.r1 { transform-origin: 100% 50%; }
.r2 { transform-origin: 0 50%; }

.line {
  margin: clamp(18px, 3.1vh, 30px) 0 0;
  font-size: clamp(14px, 0.35vw + 11px, 19px);
  line-height: 1.5;
  letter-spacing: -0.005em;
  color: var(--ink-soft);
}

/* ---- waitlist -------------------------------------------------------------- */
/* The form hangs under the last line instead of joining the centred stack, so
   the intro frames stay exactly where they were. Short screens put it back in
   the flow (see the max-height queries at the end). */
.tail {
  position: relative;
  align-self: stretch;
}

.join {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  width: min(440px, 100%);
  margin: clamp(28px, 4.6vh, 44px) auto 0;
  text-align: center;
}
.no-js .join { display: none; }

.sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Row and confirmation share one cell, so the swap never moves anything. */
.join-slot { display: grid; grid-template-columns: minmax(0, 1fr); }
.join-slot > * { grid-area: 1 / 1; }

.join-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px;
  border: 1px solid var(--field-line);
  border-radius: 14px;
  background: var(--field);
  transition: border-color 0.2s ease, background-color 0.2s ease, opacity 0.35s ease, transform 0.45s var(--ease-out), visibility 0s linear 0s;
}
.join-row:hover { border-color: var(--field-line-hover); }
.join-row:focus-within {
  border-color: var(--field-line-focus);
  background: var(--field-focus);
}
.join-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 42px;
  margin: 0;
  padding: 0 8px 0 12px;
  border: 0;
  border-radius: 9px;
  outline: none;
  background: transparent;
  color: var(--ink);
  caret-color: var(--ink);
  font: 400 16px/1.2 'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  letter-spacing: -0.01em;
  -webkit-appearance: none;
  appearance: none;
}
.join-input::placeholder { color: var(--placeholder); opacity: 1; }
.join-input:-webkit-autofill,
.join-input:-webkit-autofill:hover,
.join-input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  caret-color: var(--ink);
  transition: background-color 600000s 0s;
}
.join-row:has(.join-input[aria-invalid="true"]) { border-color: var(--field-line-focus); }

.join-btn {
  flex: none;
  display: grid;
  height: 42px;
  margin: 0;
  padding: 0 17px;
  border: 0;
  border-radius: 9px;
  background: var(--ink);
  color: var(--bg);
  font: 500 14px/1 'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  letter-spacing: -0.005em;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.2s ease, transform 0.16s var(--ease-out);
}
.join-btn > span {
  grid-area: 1 / 1;
  align-self: center;
  transition: opacity 0.2s ease;
}
.join-btn-busy { opacity: 0; }
.join-btn:hover { background: #ffffff; }
.join-btn:active { transform: scale(0.97); }
.join-btn:focus-visible {
  outline: 2px solid transparent; /* forced-colors mode keeps a visible ring */
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 3.5px var(--ink);
}
/* With the button focused, its ring is the indicator; the row stays quiet. */
.join-row:has(.join-btn:focus-visible) {
  border-color: var(--field-line-hover);
  background: var(--field);
}
.is-sending .join-btn { cursor: progress; background: var(--label); }
.is-sending .join-btn:active { transform: none; }
.is-sending .join-btn-idle { opacity: 0; }
.is-sending .join-btn-busy { opacity: 1; }
.is-sending .join-input { color: var(--ink-soft); }

.join-done {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
  outline: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
  transition: opacity 0.5s ease 0.12s, transform 0.7s var(--ease-out) 0.12s, visibility 0s linear 0.62s;
}
.join-tick {
  flex: none;
  width: 20px;
  height: 20px;
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.join-tick circle { stroke: var(--hair); }
.is-done .join-row {
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.3s ease, transform 0.45s var(--ease-out), visibility 0s linear 0.3s;
}
.is-done .join-done {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 0.5s ease 0.12s, transform 0.7s var(--ease-out) 0.12s, visibility 0s linear 0s;
}

/* The honeypot: off screen, out of the tab order, hidden from assistive tech. */
.join-hp {
  position: absolute;
  left: -10000px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Note and message share one cell too; a message covers the note. */
.join-foot {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin-top: 14px;
}
.join-foot > * { grid-area: 1 / 1; }
.join-note,
.join-msg {
  margin: 0;
  font-size: 13px;
  line-height: 1.55;
  letter-spacing: -0.003em;
  text-wrap: balance;
}
.join-note {
  color: var(--ink-soft);
  transition: opacity 0.25s ease, visibility 0s linear 0s;
}
.join-note a {
  margin-left: 0.15em;
  color: var(--label);
  text-decoration: underline;
  text-decoration-color: var(--hair);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  border-radius: 3px;
  transition: text-decoration-color 0.2s ease, color 0.2s ease;
}
.join-note a:hover { color: var(--ink); text-decoration-color: var(--ink); }
.join-note a:focus-visible { outline: 1.5px solid var(--ink); outline-offset: 2px; }
.join-msg {
  color: var(--label);
  font-weight: 500;
}
.join-msg:empty { visibility: hidden; }
.has-msg .join-note {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0s linear 0.2s;
}
/* While sending, the message only speaks to screen readers. */
.is-sending .join-msg {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- atmosphere ---------------------------------------------------------- */
.melt {
  position: fixed;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.light {
  position: fixed;
  z-index: 0;
  left: var(--mx);
  top: var(--my);
  width: 1400px;
  height: 1400px;
  margin: -700px 0 0 -700px;
  pointer-events: none;
  background: radial-gradient(closest-side,
    rgba(255, 255, 255, 0.070) 0%,
    rgba(255, 255, 255, 0.038) 30%,
    rgba(255, 255, 255, 0.012) 62%,
    rgba(255, 255, 255, 0) 100%);
}

.grain {
  position: fixed;
  z-index: 3;
  left: -160px;
  top: -160px;
  pointer-events: none;
  opacity: 0.06;
}

.vignette {
  position: fixed;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background: radial-gradient(ellipse 120% 95% at 50% 45%, rgba(0, 0, 0, 0) 55%, rgba(0, 0, 0, 0.55) 100%);
}

/* ---- choreography -------------------------------------------------------- */
/* Before the script starts the timeline, everything waits in the dark. */
.js:not(.still) .mark svg,
.js:not(.still) .word-sharp,
.js:not(.still) .soon-t,
.js:not(.still) .rule,
.js:not(.still) .line,
.js:not(.still) .light { opacity: 0; }
.js:not(.still) .join { opacity: 0; visibility: hidden; }

/* The canvas melt hands over to the crisp SVG mark at 2.7 s. */
.go .mark svg { animation: fade 0.25s linear 2.7s both; }
.go .mark { animation: breathe 7s ease-in-out 4.6s infinite; }
/* No WebGL2: the mark simply arrives. */
.go.nogl .mark svg { animation: arrive 2.2s var(--ease-out) 0.3s both; }

.go .word-soft  { animation: defocus 1.1s linear 1.75s both; }
.go .word-sharp { animation: focus 1.4s var(--ease-out) 1.8s both; }
.go .rule       { animation: rule 1.2s var(--ease-out) 2.3s both; }
.go .soon-t     { animation: rise 1.1s var(--ease-out) 2.35s both; }
.go .line       { animation: rise 1.4s var(--ease-out) 3.1s both; }
/* The waitlist waits until everything above has come to rest (about 4.9 s). */
.go .join       { animation: join-in 1.3s var(--ease-out) 4.9s both; }

.go .light { animation: fade 2.4s ease-out 1.8s both, drift 26s ease-in-out 4.6s infinite alternate; }
.go .grain { animation: grain 0.9s steps(1) infinite; }

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

@keyframes arrive {
  from { opacity: 0; transform: scale(0.94); }
  to { opacity: 1; transform: none; }
}

@keyframes breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.014); }
}

@keyframes focus {
  from { opacity: 0; transform: translateY(10px) scale(1.02); }
  35% { opacity: 0.4; }
  to { opacity: 1; transform: none; }
}

@keyframes defocus {
  from { opacity: 0; transform: translateY(12px) scale(1.03); }
  30% { opacity: 0.85; }
  to { opacity: 0; transform: none; }
}

@keyframes rule {
  from { opacity: 0; transform: scaleX(0); }
  to { opacity: 1; transform: none; }
}

@keyframes rise {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

@keyframes join-in {
  from { opacity: 0; visibility: hidden; transform: translateY(8px); }
  to { opacity: 1; visibility: visible; transform: none; }
}

@keyframes drift {
  from { transform: translate3d(-2.5%, -1.5%, 0) scale(1); }
  to { transform: translate3d(2.5%, 2%, 0) scale(1.08); }
}

@keyframes grain {
  0% { transform: translate3d(0, 0, 0); }
  12.5% { transform: translate3d(-37px, 61px, 0); }
  25% { transform: translate3d(83px, -22px, 0); }
  37.5% { transform: translate3d(-104px, -79px, 0); }
  50% { transform: translate3d(19px, 112px, 0); }
  62.5% { transform: translate3d(128px, 47px, 0); }
  75% { transform: translate3d(-66px, 9px, 0); }
  87.5% { transform: translate3d(51px, -121px, 0); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .mark svg, .word-sharp, .soon-t, .rule, .line, .light { opacity: 1 !important; }
  .join { opacity: 1 !important; visibility: visible !important; }
  .word-soft { display: none; }
  .melt { display: none; }
}

/* Short landscape phones: tighten the stack. */
@media (max-height: 520px) {
  .stage { padding-bottom: 24px; }
  .mark { height: 76px; }
  .word { margin-top: 18px; font-size: 38px; }
  .soon { margin-top: 16px; }
  .line { margin-top: 10px; }
}

/* Not enough room under the stack: the form joins the flow and the page may scroll. */
@media (max-height: 620px) {
  body { overflow-y: auto; }
  .join { position: static; margin-top: 22px; }
}
@media (max-height: 520px) {
  .join { margin-top: 16px; }
  .join-foot { margin-top: 10px; }
}
/* Very narrow phones: field above button. */
@media (max-width: 359px) {
  .join-row { flex-direction: column; align-items: stretch; }
  .join-input { text-align: center; padding: 0 8px; }
}
