/* Noonish splash — noonish.com
 *
 * Figma 2863:36552. The design frame is drawn on white; the dark palette is
 * its counterpart, tuned by feel rather than exported.
 *
 * v2 is SHORTER than v1 on purpose. v1 sat white text on a photograph, so it
 * needed a scrim solved per text zone against the photo's brightest pixels, an
 * LQIP, and a parallax with headroom to travel into. On a flat field contrast
 * is simply a palette choice, and all of that apparatus is gone rather than
 * disabled - v1 is on main and on the server if it is ever wanted back.
 *
 * Everything you'd want to tune by feel is a custom property. Colours live in
 * the two palette blocks below; sizes and timings live in :root.
 */

/* --- palettes ----------------------------------------------------------
 * Chosen by the inline boot script in index.html before first paint: a stored
 * choice wins, otherwise the local clock (light 06:00-17:59). These are
 * starting points - dial them here, nothing else reads a colour literal. */

:root[data-theme="light"] {
  --page: #ffffff;                   /* the Figma frame is drawn on white */
  --ink: #111111;
  --ink-muted: rgba(0,0,0,.62);

  --chip-bg: rgba(0,0,0,.06);        /* waitlist button, close button */
  --chip-bg-hover: rgba(0,0,0,.085); /* lightened by eye 2026-08-11 */

  /* Figma's PURPLE module (2863:36602) at its own values. Over white those
   * composite to card (197,181,218) and field (226,218,236) - exactly the
   * design render. The ONE thing not copied is the ink: the design sets white
   * type, which measures 1.91:1 on that card. Dark ink gets 8.69:1 and is the
   * only way this module is readable over a light page.
   *
   * Note the field is LIGHTER than the card, not darker - it is a frosted white
   * panel sitting ON the violet. Getting that backwards is what made an earlier
   * pass read as a sunken input instead of a floating one.
   *
   * Alpha went .6 -> .48 by Scott's eye (2026-08-11). Note the two modes move
   * OPPOSITE ways as it drops - the card pales toward white here and darkens
   * toward the page in dark mode - but both read as more of the page coming
   * through, and contrast IMPROVES in both directions, so this one is safe to
   * keep dialling. */
  --card-bg: rgba(158,132,194,.42);
  --card-ink: #241a30;
  /* NOT Figma's 60px. That radius was authored over a photograph, where 60px
   * still leaves tonal structure to see. Over this page - whose only content
   * behind the card is a thin wordmark - it smears everything to a flat wash,
   * so the card read as opaque paint however low the alpha went. That is why
   * "make it more translucent" could not be answered by alpha at all.
   *
   * ~24px is the working range: enough to destroy legible letterforms behind
   * the copy, little enough that the card visibly has something behind it.
   * Dropping toward 12px brings the wordmark back as a readable ghost and
   * starts eating contrast where it crosses the text. */
  --card-blur: 1.33em;
  --card-sheen: .30;                 /* the glass highlight; see .signup */
  --field-bg: rgba(255,255,255,.5);
  --field-ink: #241a30;
  --field-sheen: .38;
  /* Purple kin of Figma's #987fa5, darkened until it clears 3:1 on the
   * composited field. Not pushed to 4.5: a placeholder that reads as strongly
   * as real input makes people think the field is already filled. */
  --field-placeholder: #6b5c80;

  /* Black in BOTH modes (Scott, 2026-08-11) - the one element that does not
   * flip with the theme, so the primary action reads identically either way.
   * Hover goes LIGHTER, because black has nowhere darker to go. */
  --submit-bg: #111111;
  --submit-ink: #ffffff;
  --submit-bg-hover: #2e2e2e;
  --submit-bg-active: #3d3d3d;

  /* v1's tones were pale pastels built to sit on a dark photo; on white they
   * would be unreadable, so each mode carries its own pair. */
  --tone-error: #a3301c;
  --tone-ok: #2f6b1e;
}

:root[data-theme="dark"] {
  --page: #0e0f0c;                   /* near-black, de-olived from v1's photo tint */
  --ink: #f2f3ee;
  --ink-muted: rgba(255,255,255,.78);

  --chip-bg: rgba(255,255,255,.12);
  --chip-bg-hover: rgba(255,255,255,.17);   /* lightened to match light mode */

  /* Figma's "email collect (purple)" module (2863:36602), value for value.
   * This mode is the one the design was drawn for, so it is copied, not
   * adapted - EXCEPT the submit button, which Scott took to black in both
   * modes, so Figma's white-fill/olive-label and its no-fill hover no longer
   * apply here. */
  --card-bg: rgba(158,132,194,.42);
  --card-ink: #ffffff;   /* Figma draws pure white, not the page's off-white */
  /* NOT Figma's 60px. That radius was authored over a photograph, where 60px
   * still leaves tonal structure to see. Over this page - whose only content
   * behind the card is a thin wordmark - it smears everything to a flat wash,
   * so the card read as opaque paint however low the alpha went. That is why
   * "make it more translucent" could not be answered by alpha at all.
   *
   * ~24px is the working range: enough to destroy legible letterforms behind
   * the copy, little enough that the card visibly has something behind it.
   * Dropping toward 12px brings the wordmark back as a readable ghost and
   * starts eating contrast where it crosses the text. */
  --card-blur: 1.33em;
  /* Subtler than light mode, and capped: the sheen lightens the card under
   * WHITE type, so it eats contrast. At .16 the peak measured 4.58:1 - over the
   * floor but with nothing spare. .12 keeps it visible at 4.83:1. Raising this
   * is the one change here that can quietly fail WCAG. */
  --card-sheen: .12;
  --field-bg: rgba(255,255,255,.4);
  --field-sheen: .20;
  /* The field is the one place this module inverts: 40% white over the violet
   * card composites to (162,153,175), which is LIGHTER than the card. Inheriting
   * --card-ink (white) put typed text at 2.72:1 - invisible. Figma never states
   * a typed-input colour, only the placeholder, so this is filling a gap rather
   * than overriding a decision. */
  --field-ink: #241a30;
  /* Figma's own #987fa5 measures 1.30:1 on that composite. Deepened along the
   * same hue until it clears 3:1. */
  --field-placeholder: #5b4470;

  /* Black in BOTH modes (Scott, 2026-08-11) - the one element that does not
   * flip with the theme, so the primary action reads identically either way.
   * Hover goes LIGHTER, because black has nowhere darker to go. */
  --submit-bg: #111111;
  --submit-ink: #ffffff;
  --submit-bg-hover: #2e2e2e;
  --submit-bg-active: #3d3d3d;

  --tone-error: #ffc9bf;
  --tone-ok: #d7f2c3;
}

:root {
  /* --- motion ---------------------------------------------------------
   * The page arrives in two beats: the logo alone, then everything else a
   * --chrome-delay behind it, so the mark lands before the furniture. */
  --fade-in: .7s;
  --chrome-delay: .75s;
  --mode-fade: .3s;                /* light <-> dark cross-fade */
  --signup-nudge: 2.84vh;          /* where the design draws the module; 0 to centre it */

  /* --- type ----------------------------------------------------------- */
  /* The email module's Figma text is explicitly Helvetica, which confirms the
   * splash's `Arial` readings were missing-font fallbacks rather than a choice. */
  --brand-font: Helvetica, "Helvetica Neue", Arial, sans-serif;
  --fs-tagline: clamp(17px, 1.474vw, 33px);   /* 28.3 at 1920; it moved to the bottom row */
  --fs-button: clamp(15px, 1.161vw, 25px);
  --fs-footer: clamp(12.5px, .984vw, 21px);
  --pitch-gap: clamp(10px, .984vw, 22px);     /* tagline -> button, 18.9 at 1920 */

  /* --- layout ---------------------------------------------------------
   * --logo-w sizes the Lottie CANVAS, not the ink. v1's wordmark file was
   * nearly all ink (512x125, ink 97.5% of the width); the cloud file is
   * 375x256 with the wordmark occupying roughly the middle 70% and clouds
   * filling the rest, so the canvas has to be about 1.4x wider to land the
   * same ink size on screen. Those numbers then went up 30% by Scott's eye
   * (2026-08-11) - dial the whole mark from this one line. */
  --logo-w: clamp(278px, 19.5vw, 451px);
  --hero-gap: clamp(9px, .833vw, 19px);
  --hero-nudge: 2vh;               /* design sits the block just below centre */
  --pad-x: clamp(20px, 4.375vw, 88px);
  --pad-b: clamp(22px, 4.84vh, 62px);
  --pad-t: clamp(18px, 3.2vh, 44px);
}

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

html, body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--page);
  color: var(--ink);
  font-family: var(--brand-font);
  line-height: 1.28;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;

  /* Toggling modes cross-fades rather than cutting. */
  transition: background-color var(--mode-fade) ease, color var(--mode-fade) ease;
}

/* The page arrives in two beats rather than all at once: the logo, then the
 * chrome. Done in CSS, not JS, so a script failure can't strand anything at
 * zero opacity - `both` holds the end state either way. */
@keyframes page-in { from { opacity: 0 } to { opacity: 1 } }

.stage { animation: page-in var(--fade-in) ease both; }

.mode,
.footer { animation: page-in var(--fade-in) ease var(--chrome-delay) both; }

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

/* --- mode toggle -------------------------------------------------------
 * Sits on the same gutter as the footer. It shows the mode it will GIVE you,
 * not the one you're in: a sun in dark mode, a moon in light. The logo can't
 * do this job - it already opens the signup. */

.mode {
  position: fixed;
  z-index: 3;
  top: var(--pad-t);
  right: var(--pad-x);
  display: grid;
  place-items: center;
  width: 2.25em;
  height: 2.25em;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink);
  font-size: var(--fs-footer);
  cursor: pointer;
  transition: opacity .22s ease;
}

/* Fades DOWN on hover, matching the footer links. */
.mode:hover { opacity: .62; }

.mode svg {
  display: block;
  width: 1.5em;
  height: 1.5em;
}

/* Each glyph is hidden in the mode it would take you back to. */
:root[data-theme="light"] .mode__sun,
:root[data-theme="dark"] .mode__moon { display: none; }

/* --- hero --------------------------------------------------------------- */

.stage {
  position: fixed;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  padding: 0 var(--pad-x);
}

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--hero-gap);
  transform: translateY(var(--hero-nudge));
}

.logo {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  border-radius: 4px;
}

.logo__anim {
  display: block;
  width: var(--logo-w);
  aspect-ratio: 375 / 256;          /* the cloud Lottie's own canvas */
}

.logo__anim svg { display: block; }

/* Shown only until the animation mounts, and if it never does. */
.logo__fallback {
  display: block;
  font-size: calc(var(--logo-w) * .19);
  letter-spacing: -.01em;
}
.logo.has-anim .logo__fallback { display: none; }

/* Tagline and button share the centre cell of the footer grid, so the pair is
 * centred on the viewport while the copyright and legal links hold the edges. */
.footer__pitch {
  justify-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--pitch-gap);
}

.tagline {
  margin: 0;
  font-size: var(--fs-tagline);
  text-align: center;
  white-space: nowrap;
}

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

.footer {
  position: fixed;
  z-index: 2;
  left: 0; right: 0; bottom: 0;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  /* `center`, not `end`: the tagline+button pair is taller than the two text
   * columns, and aligning to the bottom edge left it visually low against them.
   * The Figma draws all three groups centred on one another. */
  align-items: center;
  gap: 16px;
  padding: 0 var(--pad-x) var(--pad-b);
  font-size: var(--fs-footer);
}

.footer__copy {
  margin: 0;
  justify-self: start;
}

.footer__legal {
  justify-self: end;
  display: flex;
  gap: 1.5em;                        /* 28px at the design's 18.9px */
}

.footer__legal a,
.footer__copy a {
  color: inherit;
  text-decoration: none;
  /* The Figma draws these on one line each. Left to wrap, the side columns
   * break to two lines at mid widths and throw the row's centring out. */
  white-space: nowrap;
  transition: opacity .22s ease;
}
/* Fades DOWN on hover rather than sitting dim and brightening. Inherited from
 * v1, where the footer's contrast had no headroom to give away; kept because
 * it reads as the same family of control as the mode toggle. */
.footer__legal a:hover,
.footer__copy a:hover { opacity: .62; }

/* --- the waitlist button ---------------------------------------------
 * Padding, gap and radius are expressed in em so the whole control scales
 * with its own type. At the design's 22.3px that reproduces 219 x 45 r7.4. */

.joinbtn {
  display: inline-flex;
  align-items: center;
  gap: .457em;
  padding: .332em .749em .377em .834em;
  border: 0;
  border-radius: .332em;
  background: var(--chip-bg);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-button);
  line-height: 1.28;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color .18s ease, transform .18s ease;
}

.joinbtn:hover { background: var(--chip-bg-hover); }
.joinbtn:active { transform: scale(.98); }

.joinbtn__apple {
  height: .924em;
  width: auto;
  /* the glyph's optical centre sits above its box centre */
  margin-top: -.08em;
  flex: none;
}

/* One ring for both modes. v1 needed a white ring plus a dark halo to survive
 * a photograph underneath; over a flat field the ink colour is already the
 * highest-contrast thing on the page, so the halo is gone with the photo. */
:where(.logo, .joinbtn, .mode, .footer__legal a, .signup__submit, .signup__close):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* --- signup overlay ---------------------------------------------------
 * Figma 2860:36461. A frosted card: title, one line of copy, a field, and a
 * full-width Submit.
 *
 * Everything inside is sized in `em` off --signup-unit, so the entire module
 * scales from one number. The design draws it at 16; we run larger because at
 * the drawn size it read small on screen.
 *
 * The page behind stays untouched - no dim, no blur - because the design's
 * backdrop rectangle carries no fill. The blur belongs to the card itself,
 * which blurs and fades both on the way in and on the way out.
 *
 * The frosted background lives on the FORM, not the dialog. An element with a
 * `filter` (or any filtered ancestor) has its `backdrop-filter` suppressed, so
 * the two cannot sit on the same box. Keeping them on separate elements - and
 * resting at `filter: none` rather than `blur(0)` - means the frost is only
 * absent mid-transition, when the card is blurred and half-transparent anyway.
 *
 * <dialog> + showModal() still gives the focus trap, Esc, inertness and focus
 * restoration natively.
 */

.signup {
  --signup-unit: 18px;        /* the design draws 16 - dial the whole card here */
  --signup-blur: 9px;
  --signup-speed: .26s;

  font-size: var(--signup-unit);
  width: min(92vw, 23em);     /* 23em reproduces the design's 368 at its own scale */
  max-height: none;
  padding: 0;
  border: 0;
  border-radius: .625em;
  /* Two layers, because backdrop-filter alone can't produce the design's glass.
   * The page behind is a FLAT field, so there is nothing for a backdrop blur to
   * blur - it only bites where the logo happens to sit under the card. The
   * glassy read in the Figma render is a soft highlight falling across the
   * panel, so it is drawn: a diagonal sheen over the violet base. */
  background-color: var(--card-bg);
  background-image: linear-gradient(148deg,
    rgba(255,255,255,var(--card-sheen)) 0%,
    rgba(255,255,255,calc(var(--card-sheen) * .28)) 42%,
    rgba(255,255,255,0) 72%);
  -webkit-backdrop-filter: blur(var(--card-blur));
  backdrop-filter: blur(var(--card-blur));
  color: var(--card-ink);
  margin: auto;
  transform: translateY(var(--signup-nudge));
  overflow: visible;

  opacity: 0;
  /* allow-discrete on display/overlay is what lets a <dialog> animate OUT as
   * well as in; without it the exit is instant however long the transition. */
  transition:
    opacity var(--signup-speed) ease,
    overlay var(--signup-speed) allow-discrete,
    display var(--signup-speed) allow-discrete;
}

.signup[open] { opacity: 1; }

@starting-style {
  .signup[open] { opacity: 0; }
}

/* The blur lives on the CONTENT, never on the card.
 *
 * Putting `filter` on the dialog suppressed its own backdrop-filter for the
 * length of the transition, so the frost snapped on the instant the filter hit
 * `none` - the pop. A descendant's filter doesn't affect an ancestor, so
 * blurring the form leaves the card's frost untouched throughout. */
.signup__form {
  filter: blur(var(--signup-blur));
  transition: filter var(--signup-speed) ease;
}
.signup[open] .signup__form { filter: none; }

@starting-style {
  .signup[open] .signup__form { filter: blur(var(--signup-blur)); }
}

/* No dim, no blur behind the card. Deliberate - see the block comment. */
.signup::backdrop { background: transparent; }

.signup__form {
  position: relative;
  padding: 2.5em 1.625em 1.5em;
  outline: none;          /* it only ever holds focus to keep a phone keyboard down */
}

.signup__title {
  margin: 0 0 .125em;
  font-size: 1em;
  font-weight: 400;
  line-height: 1.28;
}

.signup__blurb {
  margin: 0 0 1.75em;
  font-size: .859em;      /* 13.75 at the design's 16 */
  line-height: 1.28;
}

.signup__input {
  display: block;
  width: 100%;
  height: 2.5em;
  padding: 0 .875em;
  border: 0;
  border-radius: .375em;
  /* A frosted panel sitting ON the card, so it reads LIGHTER than the card -
   * that is the relationship in the design, and it is what makes the field look
   * like glass laid on the module rather than a hole cut into it. */
  background-color: var(--field-bg);
  background-image: linear-gradient(105deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,var(--field-sheen)) 46%,
    rgba(255,255,255,calc(var(--field-sheen) * .35)) 100%);
  -webkit-backdrop-filter: blur(.78em);
  backdrop-filter: blur(.78em);
  color: var(--field-ink);
  font: inherit;
  font-size: 1em;         /* >= 16px, below which iOS Safari zooms on focus */
}
.signup__input::placeholder { color: var(--field-placeholder); }
.signup__input:focus-visible {
  outline: 2px solid var(--field-ink);
  outline-offset: 2px;
}

/* Full-width, directly under the field, as drawn. */
.signup__submit {
  display: block;
  width: 100%;
  height: 2.5em;
  margin-top: .625em;
  border: 0;
  border-radius: .375em;
  background: var(--submit-bg);
  color: var(--submit-ink);
  font: inherit;
  font-size: 1em;
  cursor: pointer;
  transition: background-color .18s ease;
}
.signup__submit:hover { background: var(--submit-bg-hover); }
.signup__submit:active { background: var(--submit-bg-active); }
.signup__submit[disabled] { opacity: .55; cursor: default; }

/* Off-screen rather than display:none - hidden fields are skipped by some
 * bots, but positioned ones still get filled. */
.signup__pot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.signup__status {
  margin: .75em 0 0;
  font-size: .82em;
  line-height: 1.28;
  color: var(--ink-muted);
}
/* Keeps the resting card exactly the height the design draws. */
.signup__status:empty { display: none; }
.signup__status[data-tone="error"] { color: var(--tone-error); }
.signup__status[data-tone="ok"] { color: var(--tone-ok); }

/* Close is collapsed until focus reaches it: the design draws none, but a
 * keyboard or screen-reader user needs one. Mouse users never see it. */
.signup__actions {
  display: flex;
  gap: 8px;
  max-height: 0;
  margin-top: 0;
  overflow: hidden;
  transition: max-height .18s ease, margin-top .18s ease;
}
.signup__actions:focus-within { max-height: 64px; margin-top: .875em; }

.signup__close {
  flex: none;
  padding: 8px 16px;
  border: 0;
  border-radius: 6px;
  background: var(--chip-bg);
  color: var(--card-ink);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}


/* --- narrow screens ---------------------------------------------------- */

@media (max-width: 720px) {
  /* Two rows: the pitch on top, then copyright + terms + privacy sharing one
   * line at the very bottom - Figma's hidden "Centered Footer" (2863:36561).
   * `auto auto` with the pair centred keeps the bottom row a single unit
   * rather than three stacked lines. */
  .footer {
    grid-template-columns: auto auto;
    justify-content: center;
    align-items: center;
    row-gap: 18px;
    column-gap: 20px;
    text-align: center;
  }
  /* Side by side doesn't fit a phone; the tagline and button stack. */
  .footer__pitch {
    grid-column: 1 / -1;
    grid-row: 1;
    flex-direction: column;
    gap: 14px;
  }
  .footer__copy  { grid-column: 1; grid-row: 2; justify-self: end; }
  .footer__legal { grid-column: 2; grid-row: 2; justify-self: start; gap: 20px; }
  .joinbtn { font-size: 17px; }
  :root { --fs-footer: 13px; --fs-tagline: 21px; --logo-w: 308px; }
}

/* Very narrow: three names on one line stop fitting, so let that row wrap as a
 * unit instead of letting any single label break mid-phrase. */
@media (max-width: 400px) {
  .footer { column-gap: 14px; }
  :root { --fs-footer: 12px; --logo-w: 268px; }
}

/* Short-and-wide (landscape phones): stop the hero colliding with the footer. */
@media (max-height: 520px) {
  :root { --hero-nudge: 0vh; --pad-b: 14px; --pad-t: 12px; }
  .hero { transform: translateY(-6%); }
}

/* --- reduced motion ----------------------------------------------------
 * Matches numb.llc: the logo shows its settled frame rather than blowing in,
 * and the mode change cuts instead of cross-fading. splash.js reads the same
 * query. */

@media (prefers-reduced-motion: reduce) {
  body { animation: none; transition: none; }
  /* Snap in and out; the base translateY that positions the card stays. */
  .signup, .signup__form { transition: none; filter: none; }
  .joinbtn, .signup__submit, .signup__actions, .mode,
  .footer__legal a, .footer__copy a { transition: none; }
}
