/* blue — heyblue.com
   Brand tokens come from the Blue design charter. Do not introduce a second hue. */

/* ---------- fonts ---------- */

@font-face {
  font-family: "Playfair Display";
  src: url("assets/fonts/PlayfairDisplay-var.woff2") format("woff2-variations");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Playfair Display";
  src: url("assets/fonts/PlayfairDisplay-Italic-var.woff2") format("woff2-variations");
  font-weight: 400 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("assets/fonts/Inter-var.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ---------- tokens ---------- */

:root {
  --true-blue: #0b2edb;
  --deep-focus: #000521;
  --clear-sky: #e6f2ff;

  /* True Blue is the brand colour, but it is too dark to read on the night
     field, so anything used as ink or an outline goes through --accent. */
  --accent: var(--true-blue);

  /* Light field. Paper, not screen white. */
  --field: #f7f8fc;
  --field-lift: #ffffff;
  --ink: #000521;
  --ink-soft: rgba(0, 5, 33, 0.62);
  --ink-faint: rgba(0, 5, 33, 0.40);
  --line: rgba(0, 5, 33, 0.14);
  --line-strong: rgba(0, 5, 33, 0.26);
  --grain: 0.045;

  --serif: "Playfair Display", "Iowan Old Style", Georgia, serif;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Two speeds only: ambient is slow, interface is 150 to 400ms. */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-sheet: cubic-bezier(0.32, 0.72, 0, 1);
  --t-ui: 260ms;
}

@media (prefers-color-scheme: dark) {
  :root {
    --accent: #7f9dfb;
    --field: #000521;
    --field-lift: #050b2c;
    --ink: #eef3ff;
    --ink-soft: rgba(238, 243, 255, 0.66);
    --ink-faint: rgba(238, 243, 255, 0.42);
    --line: rgba(238, 243, 255, 0.18);
    --line-strong: rgba(238, 243, 255, 0.34);
    --grain: 0.03;
  }
}

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

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

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

body {
  background: var(--field);
  color: var(--ink);
  font-family: var(--sans);
  font-optical-sizing: auto;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100svh;
  overflow-x: hidden;
}

/* Grain. Makes the flat field read as pigment rather than a lit screen. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9;
  pointer-events: none;
  opacity: var(--grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

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

.skip {
  position: absolute; left: 50%; top: 0;
  transform: translate(-50%, -140%);
  z-index: 50;
  background: var(--true-blue); color: #fff;
  padding: 10px 18px; border-radius: 0 0 10px 10px;
  font-size: 0.85rem; text-decoration: none;
}
.skip:focus-visible { transform: translate(-50%, 0); }

:where(a, button, input):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

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

.page {
  position: relative;
  z-index: 1;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  padding: clamp(24px, 4vh, 40px) 24px calc(clamp(20px, 3vh, 32px) + env(safe-area-inset-bottom));
  text-align: center;
}

.page__centre {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(26px, 6vh, 56px);
}

/* ---------- the mark ----------
   The real logo files, untouched. The only motion is a slow breath. */

.lockup {
  --mark: clamp(104px, 15vh, 140px);
  display: grid;
  justify-items: center;
  gap: calc(var(--mark) * 0.14);
}

/* The mark. The disc is a fixed image; everything inside it is alive.
   Four loops run at lengths that share no common multiple worth speaking of
   (9, 14, 23, 31 seconds), so the motion never visibly repeats. */

.mark {
  position: relative;
  width: var(--mark);
  height: var(--mark);
  isolation: isolate;
  animation: bob 23s ease-in-out infinite;
}

/* The light the mark sits in. */
.mark::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 210%;
  height: 210%;
  margin: -105% 0 0 -105%;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(11, 46, 219, 0.17) 0%,
    rgba(11, 46, 219, 0.06) 40%,
    rgba(11, 46, 219, 0) 68%);
  animation: glow 31s ease-in-out infinite;
  pointer-events: none;
}

.mark__disc {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
}

/* Layer 1: where in the disc the creature is. Kept well clear of the rim.
   The disc's radius is 50% of the mark, the blob's is 27.5%, so 22.5% is free.
   Drifting 5.5% of the mark leaves 17% for the body to deform into. */
.mark__drift {
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 50%;
  width: 57%;
  height: 57%;
  margin: -28.5% 0 0 -28.5%;
  animation: wander 14s ease-in-out infinite;
}

/* Layer 2: the body. It changes shape and turns over. */
.mark__blob {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 46% 54% 57% 43% / 52% 44% 56% 48%;
  animation: morph 9s ease-in-out infinite, turn 31s linear infinite;
}

/* Layer 3: the lighting, turning back the other way at the same rate so the
   light stays where it is while the body rolls underneath it. The scale keeps
   the layer covering the body at every angle: a square turned about its centre
   always contains its own inscribed circle, and 1.18 leaves room for the
   bulges the morph adds. Light positions are pre-divided by that scale. */
.mark__light {
  position: absolute;
  inset: 0;
  animation: turn-back 31s linear infinite;
  /* The light sits ON the lower left rim, the way it does in the artwork, and
     the body falls away from it into the same blues the original sphere uses. */
  background:
    radial-gradient(circle at 19% 81%,
      #c2ddff 0%,
      #9dc0ff 4%,
      #6d92fd 10%,
      #4c70fc 18%,
      #3a5afb 27%,
      #3452fa 37%,
      #2f4df9 52%,
      #2c49f7 74%,
      #2942f2 100%);
}

/* A still highlight on the glass over the whole mark, so the creature reads as
   something seen through a surface rather than sitting on top of one. */
.mark__gloss {
  position: absolute;
  z-index: 3;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  mix-blend-mode: screen;
  background: radial-gradient(circle at 30% 24%,
    rgba(206, 226, 255, 0.22) 0%,
    rgba(206, 226, 255, 0.07) 24%,
    rgba(206, 226, 255, 0) 52%);
}

.lockup__word {
  display: block;
  width: calc(var(--mark) * 0.8);
  height: auto;
  color: var(--ink);
}

@keyframes bob {
  0%, 100% { transform: translateY(-4px) scale(1); }
  50%      { transform: translateY(4px) scale(1.015); }
}
@keyframes glow {
  0%, 100% { opacity: 0.7; transform: scale(0.96); }
  50%      { opacity: 1;   transform: scale(1.06); }
}

/* Where it goes. Small circuit, never near the rim. */
@keyframes wander {
  0%, 100% { transform: translate(-4%, 3%)  scale(1); }
  25%      { transform: translate(3%, -4%)  scale(1.03); }
  50%      { transform: translate(5%, 4%)   scale(0.97); }
  75%      { transform: translate(-3%, -2%) scale(1.02); }
}

/* What shape it is. Five resting shapes, each a plausible soft body. */
@keyframes morph {
  0%, 100% { border-radius: 42% 58% 61% 39% / 55% 41% 59% 45%; }
  20%      { border-radius: 63% 37% 40% 60% / 42% 62% 38% 58%; }
  40%      { border-radius: 38% 62% 52% 48% / 61% 45% 55% 39%; }
  60%      { border-radius: 57% 43% 38% 62% / 45% 39% 61% 55%; }
  80%      { border-radius: 48% 52% 63% 37% / 38% 57% 43% 62%; }
}

@keyframes turn      { to { transform: rotate(360deg); } }
@keyframes turn-back {
  from { transform: rotate(0deg)    scale(1.18); }
  to   { transform: rotate(-360deg) scale(1.18); }
}

/* ---------- copy ---------- */

.say {
  width: 100%;
  max-width: 34rem;
  display: grid;
  align-content: start;
}

.headline {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(1.9rem, 5.4vw, 3rem);
  line-height: 1.08;
  letter-spacing: -0.018em;
  text-wrap: balance;
}
.headline__lede {
  font-style: italic;
  color: var(--accent);
}

.sub {
  margin: 16px auto 0;
  max-width: 30rem;
  font-size: 1.02rem;
  line-height: 1.62;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* ---------- form ---------- */

.join { margin-top: clamp(22px, 4vh, 34px); }

.join__row {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 27rem;
  margin: 0 auto;
  padding: 6px 6px 6px 8px;
  background: var(--field-lift);
  border: 1px solid var(--line);
  border-radius: 999px;
  transition: border-color var(--t-ui) var(--ease-sheet),
              box-shadow var(--t-ui) var(--ease-sheet);
}
.join__row:focus-within {
  border-color: var(--line-strong);
  box-shadow: 0 0 0 4px rgba(11, 46, 219, 0.08);
}

.join__input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 11px 12px;
  border: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.98rem;
  text-align: left;
}
.join__input::placeholder { color: var(--ink-faint); }
.join__input:focus { outline: none; }
.join__input:focus-visible { outline: none; }

.join__btn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  flex: 0 0 auto;
  padding: 11px 21px;
  border: 0;
  border-radius: 999px;
  background: var(--true-blue);
  color: #fff;
  font: inherit;
  font-size: 0.92rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  white-space: nowrap;
  cursor: pointer;
  transition: opacity var(--t-ui) linear;
}

/* A soft body that rises inside the button when you reach for it, then goes
   where the pointer goes. --dx and --dy are set in main.js; with no pointer, as
   on a keyboard, they stay at zero and the body sits in the middle. */
.join__btn::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 50%;
  width: 150%;
  aspect-ratio: 1;
  margin: -75% 0 0 -75%;
  border-radius: 46% 54% 57% 43% / 52% 44% 56% 48%;
  /* Feathered twice over: the fill thins out to nothing well before the shape's
     own edge, and a blur takes the last hard line off what is left. */
  background: radial-gradient(circle at 36% 30%,
    #7b96ff 0%,
    #5273f6 34%,
    rgba(51, 82, 238, 0.82) 56%,
    rgba(38, 65, 228, 0.4) 76%,
    rgba(32, 58, 224, 0) 92%);
  filter: blur(9px);
  /* translate carries the pointer, transform carries the reveal. Keeping them
     on separate properties avoids mixing a percentage with a custom property
     inside one calc, which is where this quietly stops working. */
  translate: var(--dx, 0px) var(--dy, 0px);
  transform: scale(0.2);
  opacity: 0;
  transition: translate 300ms var(--ease-out-expo),
              transform 300ms var(--ease-out-expo),
              opacity 300ms var(--ease-out-expo);
}

.join__btn:hover::before,
.join__btn:focus-visible::before {
  transform: scale(1);
  opacity: 1;
  animation: morph 7s ease-in-out infinite;
}

.join__btn-label { position: relative; z-index: 1; }

.join__btn[disabled] { opacity: 0.55; cursor: default; }
.join__btn[disabled]::before { transform: scale(0.2); opacity: 0; animation: none; }

.trap {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

.fineprint {
  margin-top: 14px;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--ink-faint);
}

.status {
  margin-top: 10px;
  min-height: 1.2em;
  font-size: 0.82rem;
  color: var(--accent);
}
.status:empty { margin-top: 0; }

/* ---------- after signup ---------- */

.say__after[hidden] { display: none; }
.say__after {
  animation: rise 700ms var(--ease-out-expo) both;
}
@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

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

.foot {
  flex: 0 0 auto;
  display: grid;
  gap: 5px;
  justify-items: center;
  padding-top: clamp(24px, 4vh, 40px);
}
.foot__mail {
  font-size: 0.82rem;
  color: var(--ink-soft);
  text-decoration: none;
  transition: color var(--t-ui) linear;
}
.foot__mail:hover { color: var(--accent); }
.foot__line {
  font-size: 0.72rem;
  letter-spacing: 0.01em;
  color: var(--ink-faint);
}

/* ---------- small screens ---------- */

@media (max-width: 460px) {
  .join__row {
    flex-wrap: wrap;
    border-radius: 22px;
    padding: 8px;
  }
  .join__input { flex-basis: 100%; text-align: center; padding: 10px 8px; }
  .join__btn { flex-basis: 100%; padding: 13px 20px; }
  .sub { font-size: 0.96rem; }
}

@media (max-height: 680px) {
  .lockup { --mark: clamp(84px, 13vh, 112px); }
}

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

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition-duration: 0.01ms !important;
  }
  /* The button's light stays in the middle rather than chasing the pointer. */
  .join__btn::before { translate: none !important; }
}

/* Touch and pens have no hovering pointer to follow, so the light stays put. */
@media not all and (hover: hover) and (pointer: fine) {
  .join__btn::before { translate: none; }
}
