/* ==========================================================================
   join our community

   One screen, one job, no scroll. Three questions asked one at a time, with
   nothing boxing them in — the page is a stage, not a form in a card.

   The signature is the pool at the foot of the screen. It rises one notch per
   answer, so progress is measured in goo rather than in a bar, and the moment
   you pick a favourite it turns that character's colour and takes the rest of
   the page with it. That retint runs off `:has()` alone; js/community.js only
   moves the step, validates, submits and welcomes.
   ========================================================================== */

/* Registered as real colours so they cross-fade when the pick changes; plain
   custom properties would jump. Same reason as css/crew.css.

   The neutral default is the cream the nav's surface is made of, so before a
   pick the top and bottom edges of the screen are the same frosted glass and
   neither has a colour of its own. Three others were tried and every one read
   as a choice the page had already made: a warm oat that spread across the foot
   of the screen as sand, the yellow group's grey-blue, which was simply blue at
   this size, and a plain grey, which was a fourth neutral the site does not
   otherwise own. Matching the nav is not a neutral chosen over the others — it
   is the page having no colour at all until you give it one. */
@property --pick-a {
  syntax: "<color>";
  inherits: true;
  initial-value: #fffefb;
}

@property --pick-ink {
  syntax: "<color>";
  inherits: true;
  initial-value: #50341e;
}

/* The nav casts its shadow in the same ink; with no group picked, so does
   everything here. */
@property --pick-tint {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(80, 52, 30, 0.3);
}

/* ==========================================================================
   Page shell
   ========================================================================== */

/* `svh` rather than `vh`: on mobile `vh` is the tallest the viewport ever gets,
   so a page sized to it scrolls by exactly the height of the browser chrome —
   the one scrollbar this page is not allowed to have. */
.join-page.is-stepped {
  height: 100svh;
  overflow: hidden;
}

.joinpage {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  /* The blot behind a picked character and the pool both bleed past the edges.
     `clip` trims them without making this a scroll container. */
  overflow: clip;
  background: var(--cream);

  --ease-out-back: cubic-bezier(0.22, 1.32, 0.36, 1);

  /* How high the goo stands. One value per step, read by `.pool` below — the
     only thing that changes between steps besides which question is up. */
  --pool-h: 14svh;
}

.joinpage[data-step="email"]   { --pool-h: 18svh; }
.joinpage[data-step="pick"]    { --pool-h: 17svh; }
.joinpage[data-step="done"]    { --pool-h: 27svh; }

/* Quieter than the home page's: the coloured things here are the pool and the
   goo you pick, and three washes competing turn all of them to mud. Fixed, so
   it holds still under the page. */
.join-page .hero__wash {
  position: fixed;
  inset: -14%;
  /* Lower than the home page's, higher than it first was. Most of this screen
     is the space between one question and the goo, and at 0.2 that space read
     as bare cream rather than as atmosphere. */
  opacity: 0.3;
}

/* ---- The retint ---------------------------------------------------------
   Keyed off the value that gets submitted rather than an id or a class, so the
   colour a person sees and the answer we record cannot drift apart.

   `:has()` rather than crew.html's `:checked ~ *`: the radios belong inside the
   question they answer, not parked at the top of the document as siblings of
   every section. */
.joinpage:has(.pick__input[value="unigoo"]:checked) {
  --pick-a: #ebd0fb;
  --pick-ink: #50341e;
  --pick-tint: rgba(219, 176, 250, 0.55);
}

.joinpage:has(.pick__input[value="dragoo"]:checked) {
  --pick-a: #95d46c;
  --pick-ink: #597f2f;
  --pick-tint: rgba(149, 212, 108, 0.6);
}

.joinpage:has(.pick__input[value="honeygoo"]:checked) {
  --pick-a: #ffce57;
  --pick-ink: #50341e;
  --pick-tint: rgba(255, 206, 87, 0.65);
}

/* Declared on the shell so every descendant inherits the tween for free. */
.joinpage {
  transition:
    --pick-a 0.6s ease,
    --pick-ink 0.6s ease,
    --pick-tint 0.6s ease;
}

/* ==========================================================================
   The pool — the page's signature, and its progress bar

   A band of goo across the foot of the screen whose top edge rises one notch
   per answer. The crest is a border-radius animation rather than an SVG path:
   it is the same device as every other blot on the site, it costs nothing to
   run, and it stays smooth at any width.
   ========================================================================== */

.pool {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* Never shallower than the thing standing in it. `--foot-h` is the measured
     height of the foot, set by js/community.js; the step values below are what
     the pool rises to, and this floor is what stops the fine print from
     hanging over the crest when the foot wraps to three rows on a phone. */
  height: max(var(--pool-h), calc(var(--foot-h, 0px) + 0.85rem));
  z-index: 0;
  pointer-events: none;
  /* Slower than the retint on purpose: the goo is thick, and a level that
     snapped would read as a progress bar rather than as a liquid. */
  transition: height 1.1s var(--ease-smooth);
}

/* Two crests, not one. The body is the far surface of the pool; `::after` is
   the near lip, deeper in colour, shorter, and churning out of phase with it.
   One dome alone reads as a rounded rectangle no matter how hard it wobbles —
   it is the second edge crossing the first that makes it read as liquid.

   Both are wider than the viewport so a crest's shoulders never show as a
   corner, and both sway — ambient motion on this site travels, it does not
   pulse in place. */
/* The nav's surface, poured. Same fill, same blur, same saturate, same hairline
   edge and the same shadow thrown off it — the only differences are that this
   one ramps from thin at the crest to the nav's own 0.88 at the floor, the way
   a body of liquid reads from the side, and that its edge is a churning curve
   rather than a straight line.

   The hairline is what makes it visible at all: cream on cream, the fill alone
   would be a rumour. It is the nav's `border-bottom` turned upwards, and it
   rides the animating border-radius, so the crest carries it along whatever
   shape it is holding. The white rim above it is the wet part. */
.pool__body {
  position: absolute;
  left: -12%;
  right: -12%;
  bottom: 0;
  top: 0;
  background:
    radial-gradient(
      130% 80% at 34% 4%,
      color-mix(in srgb, var(--pick-a) 30%, transparent) 0%,
      transparent 62%
    ),
    linear-gradient(
      180deg,
      color-mix(in srgb, var(--pick-a) 62%, transparent) 0%,
      color-mix(in srgb, var(--pick-a) 78%, transparent) 52%,
      color-mix(in srgb, var(--pick-a) 88%, transparent) 100%
    );
  border-radius: 46% 54% 0 0 / 46% 40% 0 0;
  backdrop-filter: blur(14px) saturate(1.15);
  -webkit-backdrop-filter: blur(14px) saturate(1.15);
  box-shadow:
    inset 0 1px 0 0 rgba(80, 52, 30, 0.09),
    inset 0 2.5px 0 0 rgba(255, 255, 255, 0.65),
    0 -18px 34px -26px rgba(80, 52, 30, 0.42),
    0 -2px 6px -4px rgba(80, 52, 30, 0.14);
  animation:
    poolCrest 13s ease-in-out infinite,
    poolSway 21s ease-in-out infinite;
}

/* The near lip, and the reason it has no edge of its own.

   It used to be drawn like the crest above it — a hard rim with a hairline
   along it. Across this much width its arc rises only about fifty pixels, so
   what is geometrically a curve reads as a ruled line, and the hairline made it
   a visible one cutting the pool in half. The shape is still here and still
   churning out of phase with the crest, but it is painted as a density that
   fades up from nothing rather than as a surface with a lip: the dome's own
   edge lands where the fill is already transparent, so there is nothing there
   to see a line in. What is left is the pool getting deeper towards the floor,
   in a shape that moves.

   No backdrop-filter either: it sits inside the body, so it would be blurring
   an already-blurred backdrop for nothing. */
.pool__body::after {
  content: "";
  position: absolute;
  left: -8%;
  right: -8%;
  bottom: 0;
  top: 26%;
  background: linear-gradient(
    180deg,
    transparent 0%,
    color-mix(in srgb, var(--pick-a) 16%, transparent) 46%,
    color-mix(in srgb, var(--pick-a) 34%, transparent) 100%
  );
  border-radius: 58% 42% 0 0 / 52% 44% 0 0;
  animation:
    poolLip 17s ease-in-out infinite,
    poolSway 26s ease-in-out infinite reverse;
}

/* The crest slides from shoulder to shoulder — the horizontal halves swap,
   which walks the high point of the dome across the screen. */
@keyframes poolCrest {
  0%, 100% { border-radius: 46% 54% 0 0 / 46% 40% 0 0; }
  33%      { border-radius: 66% 34% 0 0 / 38% 52% 0 0; }
  66%      { border-radius: 31% 69% 0 0 / 54% 34% 0 0; }
}

/* Deliberately not the same three positions as the crest, and on a period that
   does not divide into it, so the two edges never settle into a pattern. */
@keyframes poolLip {
  0%, 100% { border-radius: 58% 42% 0 0 / 52% 44% 0 0; }
  40%      { border-radius: 34% 66% 0 0 / 42% 56% 0 0; }
  70%      { border-radius: 70% 30% 0 0 / 58% 38% 0 0; }
}

@keyframes poolSway {
  0%, 100% { transform: translate3d(-1.8%, 0, 0); }
  50%      { transform: translate3d(1.8%, 0, 0); }
}

/* Goo that splashed up and has not come down. Anchored to the pool's top edge
   rather than its floor, so they ride the surface at every level, and lighter
   than the body so they read in front of it. Each takes a different amount of
   time to cross, so they never line up twice. */
.pool__drop {
  position: absolute;
  top: 0;
  /* Deepened toward the group's own ink rather than tinted a colour of their
     own, so a drop still belongs to the goo you picked but stops disappearing
     into it. At the pool's own strength the two matched exactly and the drops
     read as smudges on the surface. */
  --drop-mix: 26%;
  --drop-a: color-mix(in srgb, var(--pick-ink) var(--drop-mix), var(--pick-a));
  /* Flat, not shaded. A radial highlight turns a blob into a sphere, and a
     sphere floating over a pool is a pearl — which is the one thing on this
     page that is not made of goo. Translucent like the pool, with the same rim
     light, so a drop is a piece of the surface that has come loose rather than
     an object dropped onto it. */
  background: color-mix(in srgb, var(--drop-a) 80%, transparent);
  border-radius: 68% 32% 45% 55% / 52% 62% 38% 48%;
  backdrop-filter: blur(10px) saturate(1.15);
  -webkit-backdrop-filter: blur(10px) saturate(1.15);
  box-shadow:
    inset 0 1px 0 0 color-mix(in srgb, var(--pick-ink) 12%, transparent),
    inset 0 2px 0 0 rgba(255, 255, 255, 0.55),
    0 3px 8px -5px color-mix(in srgb, var(--pick-ink) 36%, transparent);
}

.pool__drop--a {
  left: 12%;
  width: clamp(22px, 2.6vw, 38px);
  aspect-ratio: 1;
  animation: dropCross 26s ease-in-out infinite, blotChurn 9s ease-in-out infinite;
}

.pool__drop--b {
  left: 56%;
  width: clamp(13px, 1.7vw, 24px);
  aspect-ratio: 1;
  animation: dropCross 34s ease-in-out infinite -9s, blotChurn 7s ease-in-out infinite;
}

.pool__drop--c {
  left: 79%;
  width: clamp(17px, 2.1vw, 30px);
  aspect-ratio: 1;
  animation: dropCross 30s ease-in-out infinite -18s, blotChurn 11s ease-in-out infinite;
}

/* Drifts sideways while rising off the surface and sinking back into it. */
@keyframes dropCross {
  0%, 100% { transform: translate3d(-34px, 14px, 0) scale(0.9); }
  25%      { transform: translate3d(22px, -26px, 0) scale(1.06); }
  50%      { transform: translate3d(70px, 6px, 0) scale(0.96); }
  75%      { transform: translate3d(20px, -34px, 0) scale(1.1); }
}

/* ==========================================================================
   The stage

   Flex column: the questions take the room that is left, the foot keeps its
   own. Nothing here is boxed — the question sits on the page.
   ========================================================================== */

.stage {
  position: relative;
  z-index: 2;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  /* The top pad clears the injected nav AND the head pinned inside it, so what
     is centred below is only ever the question. The bottom pad keeps the
     centring band off the pool's crest. */
  padding:
    calc(var(--nav-h, 84px) + 1.5vh + 3.2rem)
    var(--space-3)
    clamp(1.2rem, 4vh, 2.6rem);
}

/* Eyebrow and progress as one cluster, pinned to the top of the stage rather
   than stacked above the questions. Two reasons, both about the shared cell
   below: in flow it gets shoved up the screen by the tallest question's height
   and stranded a long way from the title it belongs to, and it would slide
   every time the question under it changed size. Pinned, it is the one thing on
   the page that never moves. */
.stage__head {
  position: absolute;
  top: calc(var(--nav-h, 84px) + 1.5vh);
  left: 50%;
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  animation: riseIn 0.8s var(--ease-smooth) backwards;
}

.stage__eyebrow {
  font-family: var(--font-display);
  font-weight: 500;
  text-transform: lowercase;
  letter-spacing: 0.16em;
  font-size: 0.8rem;
  color: var(--pick-ink);
  opacity: 0.75;
  margin: 0;
}

/* ==========================================================================
   The questions

   All three stack in one grid cell so the page never changes height between
   steps — the only thing that moves when you answer is the goo.

   Without the script every question is visible and the page is a plain, tall,
   working form. `.is-stepped`, which js/community.js adds, is what turns it
   into one question at a time.
   ========================================================================== */

.ask {
  display: grid;
  width: 100%;
  min-height: 0;
}

/* `[hidden]` is a UA rule of the lowest possible weight, so the `display` above
   would otherwise keep the whole form on screen underneath the welcome. */
.ask[hidden] {
  display: none;
}

.q {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* All three questions share one grid cell, so the cell is as tall as the
     tallest — the pick. Centring in it splits the spare height of a short
     question above and below rather than leaving half a screen of empty cream
     between the input and the goo. The title does shift between steps as a
     result; it shifts under a half-second blur-fade, which is a step changing
     rather than a page jumping. */
  justify-content: center;
  text-align: center;
  gap: clamp(0.5rem, 1.4vh, 0.9rem);
  min-width: 0;
}

/* Only once the script has taken over: it is what stacks the three questions
   into one cell and hides all but one. With the script missing none of this
   applies and the three simply follow each other down a taller page. */
.is-stepped .q {
  grid-area: 1 / 1;
  visibility: hidden;
  opacity: 0;
  transform: translateY(14px);
  filter: blur(6px);
  pointer-events: none;
  transition:
    opacity 0.4s var(--ease-smooth),
    transform 0.5s var(--ease-smooth),
    filter 0.4s var(--ease-smooth),
    visibility 0s linear 0.5s;
}

.is-stepped .q.is-current {
  visibility: visible;
  opacity: 1;
  transform: none;
  filter: blur(0);
  pointer-events: auto;
  transition:
    opacity 0.5s var(--ease-smooth) 0.12s,
    transform 0.6s var(--ease-smooth) 0.12s,
    filter 0.5s var(--ease-smooth) 0.12s,
    visibility 0s;
}

.q__ask {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: lowercase;
  letter-spacing: -0.03em;
  font-size: clamp(1.9rem, 5.2vw, 3.5rem);
  line-height: 1.02;
  color: #131313;
  margin: 0;
  text-wrap: balance;
}

.q__note {
  max-width: 46ch;
  margin: 0;
  font-size: clamp(0.92rem, 0.6vw + 0.72rem, 1.02rem);
  line-height: 1.75;
  color: var(--ink);
  opacity: 0.88;
  text-wrap: pretty;
}

.q__note strong {
  font-weight: 700;
  opacity: 1;
}

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

/* ==========================================================================
   The pill

   The one input on screen, floating on the page rather than sitting in a card:
   a cream capsule lifted off the cream ground by a warm, colour-tinted shadow
   and a hairline ring. The leading mark is a live drop of goo rather than a
   stock envelope — every other marker on this site is a blot, and this is the
   smallest one.

   The arrow only appears once the answer is usable, so the control that moves
   you on is also the page's way of saying the answer is good.
   ========================================================================== */

.pill {
  position: relative;
  display: flex;
  align-items: center;
  width: min(100%, 440px);
  margin: clamp(0.3rem, 1vh, 0.7rem) 0 0;
  padding: 0.42rem 0.42rem 0.42rem 1.15rem;
  border-radius: var(--radius-pill);
  background: var(--cream);
  box-shadow:
    inset 0 0 0 1.5px color-mix(in srgb, var(--ink) 16%, transparent),
    0 2px 4px -2px rgba(80, 52, 30, 0.14),
    0 18px 34px -22px var(--pick-tint);
  transition:
    box-shadow 0.45s var(--ease-smooth),
    transform 0.45s var(--ease-smooth);
}

.pill:hover {
  box-shadow:
    inset 0 0 0 1.5px color-mix(in srgb, var(--ink) 28%, transparent),
    0 2px 4px -2px rgba(80, 52, 30, 0.14),
    0 22px 40px -22px var(--pick-tint);
}

/* The ring is on the pill, not the input: the input has no edges of its own
   here, so a focus ring around it would float inside the capsule. */
.pill:focus-within {
  transform: translateY(-2px);
  box-shadow:
    inset 0 0 0 2px var(--pick-ink),
    0 2px 4px -2px rgba(80, 52, 30, 0.14),
    0 26px 46px -24px var(--pick-tint);
}

.pill__drop {
  flex: none;
  width: 0.72rem;
  height: 0.72rem;
  margin-right: 0.7rem;
  background: var(--pick-ink);
  border-radius: 68% 32% 45% 55% / 52% 62% 38% 48%;
  opacity: 0.55;
  animation: blotChurn 6s ease-in-out infinite;
  transition: opacity 0.4s var(--ease-smooth), background 0.6s ease;
}

.pill:focus-within .pill__drop {
  opacity: 1;
}

.pill__input {
  flex: 1;
  min-width: 0;
  padding: 0.5em 0;
  border: none;
  background: none;
  font-family: var(--font-body);
  font-size: clamp(0.95rem, 0.5vw + 0.82rem, 1.05rem);
  line-height: 1.6;
  color: var(--ink);
}

.pill__input::placeholder {
  color: color-mix(in srgb, var(--ink) 45%, transparent);
}

.pill__input:focus {
  outline: none;
}

/* The pill carries the focus ring for the whole control, so a second one on
   the input inside it would be a ring within a ring. */
.pill__input:focus-visible {
  outline: none;
}

/* ---- The arrow ---------------------------------------------------------
   Collapsed to nothing until the answer is usable, then it grows into place.
   `.is-ready` is set by js/community.js on every keystroke. */
.pill__go {
  flex: none;
  display: flex;
  width: 0;
  overflow: hidden;
  transition: width 0.4s var(--ease-out-back);
}

.pill.is-ready .pill__go {
  width: 2.6rem;
}

.go {
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  flex: none;
  border-radius: 50%;
  background: var(--ink);
  color: var(--cream);
  box-shadow: 0 8px 16px -8px rgba(80, 52, 30, 0.6);
  transition:
    transform 0.35s var(--ease-smooth),
    background 0.35s var(--ease-smooth);
}

.go svg {
  width: 1.1rem;
  height: 1.1rem;
  transition: transform 0.35s var(--ease-smooth);
}

.go:hover,
.go:focus-visible {
  background: var(--green-accent2);
}

.go:hover svg,
.go:focus-visible svg {
  transform: translateX(3px);
}

.go:active {
  transform: scale(0.94);
}

/* ---- Errors -------------------------------------------------------------
   Coral is the only warm alarm in the palette; nothing here leans on it alone,
   because the sentence says what is wrong in words. */
.pill:has(.pill__input[aria-invalid="true"]) {
  box-shadow:
    inset 0 0 0 2px var(--pink-accent2),
    0 2px 4px -2px rgba(80, 52, 30, 0.14),
    0 18px 34px -22px var(--pick-tint);
}

.q__error {
  display: none;
  max-width: 44ch;
  font-size: 0.85rem;
  line-height: 1.55;
  color: var(--ink);
}

.q__error.is-shown {
  display: block;
}

/* One drop of coral goo in front of the sentence — the site's marker for
   "this one", at the size the rest of the page uses for a drop. */
.q__error.is-shown::before {
  content: "";
  display: inline-block;
  width: 0.6em;
  height: 0.6em;
  margin-right: 0.55em;
  vertical-align: baseline;
  background: var(--pink-accent2);
  border-radius: 38% 62% 64% 36% / 58% 41% 59% 42%;
}

/* ---- Go back ------------------------------------------------------------ */

.back {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.4em 0.9em;
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.88rem;
  text-transform: lowercase;
  color: var(--ink);
  opacity: 0.7;
  transition:
    opacity 0.35s var(--ease-smooth),
    transform 0.35s var(--ease-smooth),
    color 0.35s var(--ease-smooth);
}

.back svg {
  width: 1em;
  height: 1em;
  transition: transform 0.35s var(--ease-smooth);
}

.back:hover,
.back:focus-visible {
  opacity: 1;
  color: var(--pick-ink);
}

.back:hover svg,
.back:focus-visible svg {
  transform: translateX(-3px);
}

.back:active {
  transform: translateY(1px);
}

/* ==========================================================================
   The picker — the page's one piece of theatre

   Three characters standing at rest until you choose. The one you pick lands in
   a blot of its own goo and starts to bob; the other two step back, and the
   pool at the foot of the page turns the same colour.
   ========================================================================== */

.picker {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(0.3rem, 1.4vw, 0.9rem);
  width: min(100%, 480px);
  border: none;
  margin: clamp(0.2rem, 0.8vh, 0.6rem) 0 0;
  padding: 0;
  min-width: 0;
}

.pick {
  --own-a: #d9cebd;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.3rem 0.2rem 0.4rem;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: opacity 0.45s var(--ease-smooth);
}

/* The blot is the character's own colour, always — it is that character's goo,
   not the page's, so it does not follow the retint. */
.pick:has(.pick__input[value="unigoo"])   { --own-a: #ebd0fb; }
.pick:has(.pick__input[value="dragoo"])   { --own-a: #95d46c; }
.pick:has(.pick__input[value="honeygoo"]) { --own-a: #ffce57; }

/* The radio covers the whole slot so the picture is the control, and stays a
   real radio underneath — arrow keys walk the group, forms submit the value. */
.pick__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.pick:has(.pick__input:focus-visible) {
  outline: 3px solid var(--green-accent2);
  outline-offset: 2px;
}

.pick__stage {
  position: relative;
  display: block;
  width: 100%;
  max-width: 150px;
  margin: 0 auto;
  aspect-ratio: 1;
}

/* All three source crops share one 1097×1100 canvas, so three of them across
   line up on their own — no per-character sizing. */
.pick__char {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: bottom center;
  transform-origin: 50% 92%;
  filter: drop-shadow(0 14px 18px rgba(80, 52, 30, 0.16));
  transition: transform 0.5s var(--ease-out-back);
}

/* Anchored to the foot of the slot: goo the character is standing in, not a
   halo around it. */
.pick__blot {
  position: absolute;
  left: 50%;
  bottom: 4%;
  width: 80%;
  height: 50%;
  translate: -50% 0;
  /* Filled to the edge on purpose: the churn is a border-radius animation, and
     a gradient that fades before the edge would hide the very silhouette that
     is doing the moving.

     One hue, not two. The group's second colour was tried in the middle of the
     puddle and read as a stain under the character rather than as goo — worst
     under honeygoo, whose second colour is a cool grey-blue. The sheen is the
     same hue lightened, so the puddle looks wet instead of two-tone. */
  background: radial-gradient(
    ellipse at 42% 66%,
    color-mix(in srgb, var(--own-a) 72%, white) 0%,
    var(--own-a) 52%
  );
  border-radius: 68% 32% 45% 55% / 52% 62% 38% 48%;
  opacity: 0;
  transform: scale(0.5);
  transform-origin: 50% 85%;
  transition:
    transform 0.55s var(--ease-out-back),
    opacity 0.35s var(--ease-smooth);
}

.pick__name {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(0.75rem, 1.6vw, 0.95rem);
  letter-spacing: -0.01em;
  text-transform: lowercase;
  color: var(--ink);
  transition: font-weight 0.2s linear;
}

/* ---- Hover: a character leans in, nothing lands yet ---------------------- */

.pick:hover .pick__char {
  transform: translateY(-5px) scale(1.03);
}

.pick:active .pick__char {
  transform: translateY(-1px) scale(1.01);
}

/* ---- Picked -------------------------------------------------------------- */

.pick:has(.pick__input:checked) .pick__blot {
  opacity: 1;
  transform: scale(1);
  animation: blotChurn 8s ease-in-out infinite;
}

.pick:has(.pick__input:checked) .pick__char {
  transform: translateY(-6px) scale(1.07);
  animation: pickBob 6s ease-in-out 0.5s infinite;
}

.pick:has(.pick__input:checked) .pick__name {
  font-weight: 600;
  color: var(--pick-ink);
}

/* The two not chosen step back — enough to send the eye to the pick, not enough
   to read as unavailable. */
.picker:has(.pick__input:checked) .pick:not(:has(.pick__input:checked)) {
  opacity: 0.5;
}

.picker:has(.pick__input:checked) .pick:not(:has(.pick__input:checked)):hover {
  opacity: 0.85;
}

@keyframes blotChurn {
  0%, 100% { border-radius: 68% 32% 45% 55% / 52% 62% 38% 48%; }
  25%      { border-radius: 40% 60% 66% 34% / 61% 42% 58% 39%; }
  50%      { border-radius: 57% 43% 33% 67% / 68% 36% 64% 32%; }
  75%      { border-radius: 34% 66% 59% 41% / 43% 61% 39% 57%; }
}

/* Bobbing on top of the picked transform, so the two do not overwrite each
   other: the keyframes restate the lift the checked rule sets. */
@keyframes pickBob {
  0%, 100% { transform: translateY(-6px) scale(1.07) rotate(-1.2deg); }
  50%      { transform: translateY(-13px) scale(1.07) rotate(1.2deg); }
}

/* The whole row wobbles once when you try to submit without an answer — the
   question shakes its head. */
.picker.is-nudged {
  animation: pickNudge 0.5s var(--ease-smooth);
}

@keyframes pickNudge {
  0%, 100% { transform: translateX(0); }
  22%      { transform: translateX(-7px); }
  55%      { transform: translateX(6px); }
  80%      { transform: translateX(-3px); }
}

/* ==========================================================================
   Consent

   A real checkbox under a drop of goo. Unticked by default, and the only way
   past the submit — see community.html for why it sits on this step.
   ========================================================================== */

.consent {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  max-width: 46ch;
  margin: clamp(0.3rem, 1vh, 0.7rem) 0 0;
  padding: 0.35rem 0.2rem;
  text-align: left;
  cursor: pointer;
}

.consent__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
}

.consent__box {
  flex: none;
  width: 1.2rem;
  height: 1.2rem;
  margin-top: 0.14rem;
  /* Enough of a corner to not be a circle: an empty round box beside a line of
     text reads as an unselected radio, and this is a decision with two states,
     not one of three options. */
  border-radius: 42% 58% 46% 54% / 52% 44% 56% 48%;
  background: var(--cream);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--ink) 32%, transparent);
  transition:
    background 0.4s var(--ease-smooth),
    box-shadow 0.4s var(--ease-smooth),
    transform 0.4s var(--ease-out-back);
}

.consent:hover .consent__box {
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--ink) 55%, transparent);
}

/* Ticked, the box fills with the goo you chose and starts to churn — the same
   confirmation the pool gives, at the size of a tick. */
.consent:has(.consent__input:checked) .consent__box {
  background: var(--pick-ink);
  box-shadow: inset 0 0 0 1.5px var(--pick-ink);
  transform: scale(1.08);
  animation: blotChurn 7s ease-in-out infinite;
}

.consent:has(.consent__input:focus-visible) .consent__box {
  outline: 3px solid var(--green-accent2);
  outline-offset: 3px;
}

.consent:has(.consent__input[aria-invalid="true"]) .consent__box {
  box-shadow: inset 0 0 0 2px var(--pink-accent2);
}

.consent__text {
  font-size: 0.83rem;
  line-height: 1.65;
  color: var(--ink);
  opacity: 0.9;
}

.consent__text strong {
  font-weight: 700;
}

.q__error--consent,
.q__error--pick {
  text-align: center;
}

/* ==========================================================================
   Submit
   ========================================================================== */

.trap {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.submit {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-top: clamp(0.2rem, 0.8vh, 0.5rem);
}

/* Ink-filled, like every other primary action on the site. It deliberately does
   not take the group colour: no group has a member dark enough to carry cream
   text, and a button that changed colour under the pointer on the way to being
   pressed would read as a second answer. */
.submit__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  padding: 0.9em 1.9em;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--cream);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(0.95rem, 1.1vw, 1.05rem);
  line-height: 1.3;
  box-shadow: 0 14px 28px -14px rgba(80, 52, 30, 0.55);
  transition:
    transform 0.35s var(--ease-smooth),
    background 0.35s var(--ease-smooth),
    box-shadow 0.35s var(--ease-smooth);
}

.submit__cta svg {
  width: 1.05em;
  height: 1.05em;
  transition: transform 0.35s var(--ease-smooth);
}

.submit__cta:hover,
.submit__cta:focus-visible {
  transform: translateY(-2px);
  background: var(--green-accent2);
  box-shadow: 0 18px 32px -14px rgba(89, 127, 47, 0.55);
}

.submit__cta:hover svg,
.submit__cta:focus-visible svg {
  transform: translateX(4px);
}

.submit__cta:active {
  transform: translateY(0);
}

.submit__cta[disabled] {
  cursor: progress;
  opacity: 0.7;
  transform: none;
}

/* Only ever holds a sentence about a submit that did not go through. */
.submit__fail {
  display: none;
  margin: 0;
  max-width: 40ch;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--ink);
}

.submit__fail.is-shown {
  display: block;
}

/* ==========================================================================
   The trail

   Three drops that fill as the pool rises. The pool alone says "further along";
   these say how much further there is — the one thing a stepped form has to
   answer and a rising level cannot.
   ========================================================================== */

.trail {
  display: flex;
  gap: 0.45rem;
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
}

.trail__drop {
  width: 0.44rem;
  height: 0.44rem;
  border-radius: 68% 32% 45% 55% / 52% 62% 38% 48%;
  background: color-mix(in srgb, var(--ink) 20%, transparent);
  transition:
    background 0.5s var(--ease-smooth),
    transform 0.5s var(--ease-out-back);
}

/* Every step up to and including the one you are on. Written as three explicit
   rules rather than a loop because there are three, and a nth-child sum here
   would be harder to read than the list it replaces. */
.joinpage[data-step="name"]  .trail__drop[data-for="name"],
.joinpage[data-step="email"] .trail__drop:is([data-for="name"], [data-for="email"]),
.joinpage[data-step="pick"]  .trail__drop,
.joinpage[data-step="done"]  .trail__drop {
  background: var(--pick-ink);
  transform: scale(1.25);
}

.joinpage[data-step="done"] .trail {
  display: none;
}

/* ==========================================================================
   The welcome

   Takes the questions' place in the same spot and the same colour, so the page
   answers rather than reloads.
   ========================================================================== */

.welcome {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  animation: riseIn 0.7s var(--ease-out-back) backwards;
}

/* `[hidden]` is a UA rule of the lowest possible weight, so any `display` set
   here would silently keep a hidden block on screen. */
.welcome[hidden] {
  display: none;
}

.welcome__stage {
  position: relative;
  width: clamp(120px, 18vh, 190px);
  aspect-ratio: 1;
  margin-bottom: clamp(0.3rem, 1.2vh, 0.8rem);
}

.welcome__blot {
  position: absolute;
  left: 50%;
  bottom: 4%;
  width: 82%;
  height: 48%;
  translate: -50% 0;
  background: radial-gradient(
    ellipse at 42% 66%,
    color-mix(in srgb, var(--pick-a) 72%, white) 0%,
    var(--pick-a) 52%
  );
  border-radius: 68% 32% 45% 55% / 52% 62% 38% 48%;
  animation: blotChurn 8s ease-in-out infinite;
}

/* All three stand here; the pick decides which one is visible. */
.welcome__char {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: bottom center;
  filter: drop-shadow(0 16px 22px rgba(80, 52, 30, 0.18));
  display: none;
  animation: pickBob 6s ease-in-out infinite;
}

.joinpage:has(.pick__input[value="unigoo"]:checked)   .welcome__char[data-char="unigoo"],
.joinpage:has(.pick__input[value="dragoo"]:checked)   .welcome__char[data-char="dragoo"],
.joinpage:has(.pick__input[value="honeygoo"]:checked) .welcome__char[data-char="honeygoo"] {
  display: block;
}

.welcome__title {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: lowercase;
  letter-spacing: -0.03em;
  font-size: clamp(1.7rem, 4.4vw, 2.8rem);
  line-height: 1.05;
  color: #131313;
  margin: 0;
  text-wrap: balance;
}

/* The title is lowercase like every other title on the site; the name inside it
   is not the site's to restyle. */
.welcome__title [data-slot="name"] {
  text-transform: none;
}

.welcome__title:focus-visible {
  outline: 3px solid var(--pick-ink);
  outline-offset: 6px;
  border-radius: var(--radius-sm);
}

/* The character you picked is the one speaking. */
.welcome__line {
  display: none;
  margin: 0.6rem 0 0;
  max-width: 48ch;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(0.98rem, 1.4vw, 1.12rem);
  line-height: 1.6;
  color: var(--pick-ink);
}

.joinpage:has(.pick__input[value="unigoo"]:checked)   .welcome__line[data-for="unigoo"],
.joinpage:has(.pick__input[value="dragoo"]:checked)   .welcome__line[data-for="dragoo"],
.joinpage:has(.pick__input[value="honeygoo"]:checked) .welcome__line[data-for="honeygoo"] {
  display: block;
}

.welcome__mail {
  margin: clamp(0.5rem, 1.4vh, 0.9rem) 0 0;
  max-width: 44ch;
  font-size: 0.92rem;
  line-height: 1.7;
  color: var(--ink);
}

.welcome__addr {
  font-weight: 700;
  overflow-wrap: anywhere;
}

.welcome__back {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  margin-top: clamp(0.8rem, 2vh, 1.4rem);
  padding: 0.8em 1.7em;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--cream);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.96rem;
  text-decoration: none;
  text-transform: lowercase;
  box-shadow: 0 14px 28px -14px rgba(80, 52, 30, 0.55);
  transition:
    transform 0.35s var(--ease-smooth),
    background 0.35s var(--ease-smooth),
    box-shadow 0.35s var(--ease-smooth);
}

.welcome__back svg {
  width: 1.05em;
  height: 1.05em;
  transition: transform 0.35s var(--ease-smooth);
}

.welcome__back:hover,
.welcome__back:focus-visible {
  transform: translateY(-2px);
  background: var(--green-accent2);
  box-shadow: 0 18px 32px -14px rgba(89, 127, 47, 0.55);
}

.welcome__back:hover svg,
.welcome__back:focus-visible svg {
  transform: translateX(4px);
}

.welcome__back:active {
  transform: translateY(0);
}

/* ==========================================================================
   The foot

   Sits in the goo rather than on a bar of its own — the pool is always deeper
   than this strip, and ink on any of the four pool colours clears AA at this
   size. Where else to find us on one side, what happens to your data on the
   other.
   ========================================================================== */

/* Three columns, the outer two pinned to the same width, so the notice is
   centred on the page rather than in whatever the icons left over. It was hard
   right before, which read as two things pushed apart rather than one line of
   fine print sitting under the page — and the page above it is centred all the
   way down.

   The side tracks are a measurement, not a guess: `--foot-side` is the wider of
   the two things standing in them, which is now the contact block rather than
   the icon row. `1fr` was tried and does not work here — a flexible track floors
   at the width of what is in it, so the two columns come out different widths
   and push the centre off by the difference. Fixed and equal, the middle column
   is the page's middle, and it gives up its width evenly as the window
   narrows: the notice takes another line long before it runs into an icon. */
.foot {
  --foot-side: 16.2rem;

  position: relative;
  z-index: 2;
  flex: none;
  display: grid;
  grid-template-columns: var(--foot-side) minmax(0, 1fr) var(--foot-side);
  align-items: center;
  gap: clamp(0.5rem, 1.5vw, 1.4rem);
  width: 100%;
  max-width: 1320px;
  margin: 0 auto;
  /* Even top and bottom: the strip is the goo's, and the row sits in the
     middle of it rather than resting on the bottom edge. */
  padding: clamp(0.9rem, 2vh, 1.3rem) var(--space-3);
}

/* Everything else about these icons is in style.css, shared with the site
   foot. Only the hover chip differs: this strip is coloured goo, so the drop
   that appears under the icon is cream rather than an ink tint. */
.social__link:hover,
.social__link:focus-visible {
  background: color-mix(in srgb, var(--cream) 60%, transparent);
}

/* Now that both side tracks carry something, the middle one is narrow enough
   for three lines. `balance` rather than `pretty`: pretty only protects the
   last line, which left two full-width lines over a four-word orphan. Balanced,
   the three come out even and the block reads as fine print that has been set
   rather than squeezed. */
.notice {
  margin: 0;
  font-size: 0.72rem;
  line-height: 1.7;
  color: var(--ink);
  opacity: 0.8;
  text-align: center;
  text-wrap: balance;
}

.notice a {
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}

.notice a:hover,
.notice a:focus-visible {
  opacity: 1;
  color: var(--pick-ink);
}

/* Where to reach a person, opposite the icons. The label is set in the display
   face so it reads as the page speaking and the two addresses read as data —
   the same split the pill and its placeholder use. They stack because the email
   is the widest string in the foot: side by side it would set the width of both
   outer tracks and leave the notice a slot it cannot wrap into. */
/* Shared with the site foot; see style.css. Here it is the right-hand track of
   the strip, and `--pick-ink` — the colour of whichever goo is chosen above —
   is what its links warm to on hover. */

/* ==========================================================================
   Narrow and short screens

   The page has one screen to work with and no scroll to fall back on, so both
   axes get their own budget rather than one shared breakpoint.
   ========================================================================== */

@media (max-width: 760px) {
  /* No room for side columns: everything centres in one. Stacked, the two ways
     of reaching us belong together and the fine print goes last, which is the
     order the DOM cannot have — the notice sits between them there so that the
     three-column desktop foot reads left to right. */
  .foot {
    grid-template-columns: minmax(0, 1fr);
    justify-content: center;
    justify-items: center;
    gap: 0.4rem;
    padding: clamp(0.7rem, 1.6vh, 1rem) var(--space-3);
  }

  .social {
    justify-self: center;
    gap: 0.35rem;
    order: 1;
  }

  .social__link {
    width: 2.4rem;
    height: 2.4rem;
  }

  /* Five lines of fine print is the tallest thing in the foot on a phone, and
     the foot has to stand in the goo. Set a size down with the leading pulled
     in to match, it gives back the depth the contact line takes. */
  .notice {
    order: 3;
    font-size: 0.62rem;
    line-height: 1.6;
    max-width: 56ch;
    text-wrap: pretty;
  }

  /* Stacked, there is no narrow track to protect, so the label and both
     addresses run along one line and the foot keeps a row rather than gaining
     two. It wraps of its own accord on the smallest screens. */
  .reach {
    order: 2;
    justify-self: center;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.2rem 0.5rem;
  }

  .reach__lines {
    grid-auto-flow: column;
    justify-items: center;
    gap: 0.6rem;
  }

  .reach__label {
    font-size: 0.8rem;
  }

  .reach__link {
    font-size: 0.66rem;
  }

  /* Higher than the desktop numbers would suggest, not lower: stacked, the foot
     is three rows tall, and the goo has to be deep enough for it to be standing
     in it at the very first step. */
  .joinpage { --pool-h: 18svh; }
  .joinpage[data-step="email"] { --pool-h: 21svh; }
  .joinpage[data-step="pick"]  { --pool-h: 24svh; }
  .joinpage[data-step="done"]  { --pool-h: 30svh; }
}

/* Short and wide — a laptop with the browser chrome open, mostly. The pool and
   the picture sizes give up their room first; the type does not.

   `min-width` matters as much as the height here: most phones are also under
   720px tall, and without it they were taking this shallow pool on top of the
   stacked three-row foot above — which is how the foot ended up standing
   outside the goo rather than in it. */
@media (min-width: 761px) and (max-height: 720px) {
  .joinpage { --pool-h: 10svh; }
  .joinpage[data-step="email"] { --pool-h: 13svh; }
  .joinpage[data-step="pick"]  { --pool-h: 16svh; }
  .joinpage[data-step="done"]  { --pool-h: 22svh; }

  .pick__stage { max-width: 108px; }
  .q__ask { font-size: clamp(1.6rem, 4vw, 2.4rem); }
}

/* Below this the page cannot hold three characters, a consent line and a button
   on one screen without shrinking the type past readable — so it is allowed to
   scroll, which is the one honest way out. */
@media (max-height: 560px) {
  .join-page,
  .joinpage {
    height: auto;
    min-height: 100svh;
    overflow: visible;
  }

  .joinpage { overflow-x: clip; }
  .pool { position: fixed; }
}

/* ==========================================================================
   Without the script

   js/community.js is what turns this into one question at a time; it is not
   what makes it work. With it missing the three questions follow each other
   down the page, the browser validates them and submits them itself, and the
   only thing that has to give is the promise of no scroll — a page that clips
   two thirds of its own form to keep that promise is keeping the wrong one.
   ========================================================================== */

.join-page:not(.is-stepped) .stage {
  padding-top: clamp(1.5rem, 5vh, 3rem);
  padding-bottom: clamp(2rem, 6vh, 4rem);
  gap: clamp(2rem, 6vh, 4rem);
}

/* Back into flow: pinned, it would sit on top of the first question. */
.join-page:not(.is-stepped) .stage__head {
  position: static;
  translate: none;
  margin-bottom: clamp(1rem, 3vh, 2rem);
}

/* Three questions down a page are three questions, not one that changed. */
.join-page:not(.is-stepped) .ask {
  display: grid;
  gap: clamp(2.5rem, 8vh, 5rem);
}

/* The pool belongs to the viewport, not to the foot of a document that is now
   taller than one. */
.join-page:not(.is-stepped) .pool {
  position: fixed;
}

/* Nothing is stepping, so nothing has got anywhere. */
.join-page:not(.is-stepped) .trail {
  display: none;
}

.join-page:not(.is-stepped) .back {
  display: none;
}

/* ==========================================================================
   Reduced motion

   The page keeps its states and loses its loops: nothing here bobs, churns,
   sways or drifts, and the steps swap without the blur.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .pool__body,
  .pool__drop,
  .pill__drop,
  .welcome__blot,
  .picker.is-nudged,
  .consent:has(.consent__input:checked) .consent__box,
  .pick:has(.pick__input:checked) .pick__blot,
  .pick:has(.pick__input:checked) .pick__char,
  .joinpage .welcome__char,
  .stage__eyebrow,
  .welcome {
    animation: none;
  }

  .is-stepped .q,
  .is-stepped .q.is-current {
    filter: none;
    transform: none;
  }
}
