/*
  1015 AM landing page. Hand-written, no build step.
  Tokens follow the reference prototype; light/dark follow prefers-color-scheme.
*/
:root {
  color-scheme: light dark;
  --bg: #edeff7;
  --surface: #f8f9fd;
  --fg: #11142b;
  --muted: #565b7a;
  --line: #ccd0e4;
  --field-line: #7a80a3; /* control borders need 3:1 (WCAG 1.4.11); --line is decorative only */
  --accent: #3640f5;
  --accent-ink: #ffffff;
  --danger: #b3261e;
  --display: "Unbounded", "Arial Black", system-ui, sans-serif;
  --body: "Instrument Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0c0e1f;
    --surface: #14172e;
    --fg: #eceefb;
    --muted: #9399bc;
    --line: #2a2f52;
    --field-line: #646b9f;
    --accent: #8a93ff;
    --accent-ink: #0c0e1f;
    --danger: #ff8a80;
  }
}

/*
  Word colours. While the hero sequence runs, script.js adds .is-tinted to <html> and sets --tint
  (a flat pastel) and --ink (its complement: the hue rotated 180 degrees and darkened until the
  contrast is at least 8:1, so it stays readable but still reads as a colour). The same tinted
  token set is used in light and dark mode. Everything derives from those two values: text is
  the ink, muted text and borders are ink mixed back toward the pastel, the button is an
  ink-coloured block with pastel text, and the focus ring is the ink.

  Registering the tokens as <color> lets the browser animate them, so every element that reads a
  token follows along with no per-element transitions. Browsers without @property just swap
  instantly.
*/
@property --bg { syntax: "<color>"; inherits: true; initial-value: #edeff7; }
@property --surface { syntax: "<color>"; inherits: true; initial-value: #f8f9fd; }
@property --fg { syntax: "<color>"; inherits: true; initial-value: #11142b; }
@property --muted { syntax: "<color>"; inherits: true; initial-value: #565b7a; }
@property --line { syntax: "<color>"; inherits: true; initial-value: #ccd0e4; }
@property --field-line { syntax: "<color>"; inherits: true; initial-value: #7a80a3; }
@property --accent { syntax: "<color>"; inherits: true; initial-value: #3640f5; }
@property --accent-ink { syntax: "<color>"; inherits: true; initial-value: #ffffff; }
@property --danger { syntax: "<color>"; inherits: true; initial-value: #b3261e; }

/*
  Light theme: pastel and ink are both light-background/dark-text, so every token can tween
  together (also pastel -> pastel).
*/
html,
html.is-tinted {
  transition:
    --bg 0.7s linear,
    --surface 0.7s linear,
    --fg 0.7s linear,
    --muted 0.7s linear,
    --line 0.7s linear,
    --field-line 0.7s linear,
    --accent 0.7s linear,
    --accent-ink 0.7s linear,
    --danger 0.7s linear;
}

/*
  Dark theme: going between the dark page and a pastel, text goes light -> dark. Fading ink and
  background together would pass through ~1:1 and the word would vanish, so only --bg tweens and
  the rest step when the background crosses mid-grey: 60% of the way in (0.42s of 0.7s) and 40%
  of the way out (0.28s), the best flip points for these inks (simulated per pastel, minimum
  3.0-3.5:1; large text needs 3:1), for about 100ms. Pastel -> pastel
  has no such crossing, so it tweens everything (script.js drops .is-entering after the first
  word).
*/
@media (prefers-color-scheme: dark) {
  /* leaving a pastel for the dark theme */
  html {
    transition:
      --bg 0.7s linear,
      --surface 0s 0.28s,
      --fg 0s 0.28s,
      --muted 0s 0.28s,
      --line 0s 0.28s,
      --field-line 0s 0.28s,
      --accent 0s 0.28s,
      --accent-ink 0s 0.28s,
      --danger 0s 0.28s;
  }

  /* pastel -> pastel */
  html.is-tinted {
    transition:
      --bg 0.7s linear,
      --surface 0.7s linear,
      --fg 0.7s linear,
      --muted 0.7s linear,
      --line 0.7s linear,
      --field-line 0.7s linear,
      --accent 0.7s linear,
      --accent-ink 0.7s linear,
      --danger 0.7s linear;
  }

  /* dark theme -> first pastel */
  html.is-tinted.is-entering {
    transition:
      --bg 0.7s linear,
      --surface 0s 0.42s,
      --fg 0s 0.42s,
      --muted 0s 0.42s,
      --line 0s 0.42s,
      --field-line 0s 0.42s,
      --accent 0s 0.42s,
      --accent-ink 0s 0.42s,
      --danger 0s 0.42s;
  }
}

html.is-tinted {
  color-scheme: light; /* native controls (scrollbar, autofill) match the light pastel */
  --bg: var(--tint);
  --surface: color-mix(in srgb, #ffffff 60%, var(--tint));
  --fg: var(--ink);
  --muted: color-mix(in srgb, var(--ink) 82%, var(--tint));
  --line: color-mix(in srgb, var(--ink) 20%, var(--tint));
  --field-line: color-mix(in srgb, var(--ink) 72%, var(--tint));
  --accent: var(--ink);
  --accent-ink: var(--tint);
  --danger: #9f1a12;
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.55;
  padding-inline: max(16px, 4vw);
}

/*
  The word is centered on both axes: grid rows 1fr / auto / 1fr keep it in the middle of the
  viewport whether or not the form is showing. The form fades in under it (row 3) and the
  footer sits at the bottom (row 4). The top padding equals the footer row (--foot-h plus the
  16px bottom gap), so the footer does not pull the word off-centre. Row 3 may grow only if the
  form really needs the room (phones with errors showing).
*/
.wrap {
  --foot-h: 48px;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto minmax(auto, 1fr) var(--foot-h);
  width: 100%;
  max-width: 1120px;
  min-height: 100dvh;
  margin-inline: auto;
  padding-block: calc(var(--foot-h) + 16px) 16px;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

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

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

/* The h1 is the size container: the word is sized from the row, not the viewport. */
.row {
  grid-row: 2;
  margin: 0;
  container-type: inline-size;
}

/* The whole row is the hover target, so it never shrinks when the word changes length. */
.stage {
  display: block;
  width: 100%;
  margin: 0;
  padding-block: clamp(12px, 3vw, 28px);
  padding-inline: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: center;
  cursor: pointer;
  overflow: hidden;
  overflow: clip; /* safety net: a stray wide glyph mid-scramble is clipped, never scrolls the page */
  -webkit-tap-highlight-color: transparent;
}

.word {
  display: block;
  font-family: var(--display);
  font-weight: 800;
  /* Fallback for browsers without container query units. */
  font-size: clamp(1.9rem, 9.2vw, 6rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  text-align: center;
  white-space: nowrap;
  min-height: 1.15em;
}

/*
  "COMING SOON" is the longest word: 9.51em wide in Unbounded 800, 9.29em with the
  letter-spacing above, so 9.6cqw leaves about 11% spare at every width. The spare room also
  absorbs the scramble frames (see GLYPHS in script.js): simulated overflow is 0.00%.
*/
@supports (font-size: 1cqw) {
  .word {
    font-size: min(8rem, 9.6cqw);
  }
}

/* Without JS the hero cannot animate, so it is not a control. */
.no-js .stage {
  cursor: default;
}

/* ---------- Reveal ---------- */

/*
  With JS the contact area starts hidden. script.js adds .is-shown (display), forces a
  reflow, then .is-open, so the opacity transition has a starting frame to fade from.
  Without JS (.no-js stays on <html>) none of these rules apply and the form is just there.
*/
.reveal {
  grid-row: 3;
  align-self: start;
  padding-top: clamp(16px, 3vw, 32px);
}

html:not(.no-js) .reveal {
  display: none;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.9s ease, transform 0.9s ease;
}

html:not(.no-js) .reveal.is-shown {
  display: block;
}

html:not(.no-js) .reveal.is-open {
  opacity: 1;
  transform: none;
}

/* ---------- Contact: one inline row, all lower case ---------- */

form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 12px;
}

h2 {
  flex: 0 0 auto;
  align-self: center;
  margin: 0 8px 0 0;
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(1rem, 2.4vw, 1.25rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.field {
  min-width: 0;
}

.field-name {
  flex: 1 1 9rem;
}

.field-email {
  flex: 1.3 1 12rem;
}

.field-message {
  flex: 2.5 1 16rem;
}

input,
textarea {
  display: block;
  width: 100%;
  margin: 0;
  padding: 10px 14px;
  font: 400 16px/1.4 var(--body); /* 16px keeps iOS from zooming on focus */
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--field-line);
  border-radius: 0;
}

input::placeholder,
textarea::placeholder {
  color: var(--muted);
  opacity: 1;
}

/* One line high until the person writes more; script.js grows it as they type. */
textarea {
  min-height: 44px;
  max-height: 50dvh;
  resize: none;
  overflow-y: auto;
}

[aria-invalid="true"] {
  border-color: var(--danger);
}

.error {
  margin: 6px 0 0;
  font-size: 14px;
  line-height: 1.4;
  color: var(--danger);
}

.btn {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 10px 24px;
  font: 500 15px/1.4 var(--mono);
  letter-spacing: 0.04em;
  color: var(--accent-ink);
  background: var(--accent);
  border: 0;
  border-radius: 0;
  cursor: pointer;
  transition: opacity 0.2s;
}

.btn:hover {
  opacity: 0.88;
}

.btn:disabled {
  opacity: 0.6;
  cursor: progress;
}

.status,
.sent {
  flex: 0 0 100%;
  text-align: center;
}

/* Honeypot: off-screen rather than display:none, so naive bots still fill it. */
.hp {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.notice {
  margin: 0;
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: 0;
}

.notice[data-tone="error"] {
  border-left-color: var(--danger);
}

/* No-JS success: /api/contact redirects to /#sent and :target reveals this. */
.sent {
  display: none;
  margin-bottom: 16px;
}

.sent:target {
  display: block;
}

/* Stay rendered (not display:none) so the live region announces reliably. */
.status:empty {
  margin: 0;
  padding: 0;
  border: 0;
}

/* Phones: one control per row. */
@media (max-width: 640px) {
  h2 {
    flex-basis: 100%;
    margin: 0;
    text-align: center;
  }

  .field-name,
  .field-email,
  .field-message,
  .btn {
    flex: 1 1 100%;
  }
}

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

.foot p {
  margin: 0;
}

.foot {
  grid-row: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-align: center;
  color: var(--muted);
}

@media (prefers-reduced-motion: reduce) {
  html,
  html.is-tinted {
    transition: none;
  }

  .btn {
    transition: none;
  }

  html:not(.no-js) .reveal {
    transition: none;
    transform: none;
  }
}
