/* The invitation (invite.html, at /i/<code>) and the sign-up page (signup.html, at /signup): the same paper
   and typewriter as the letter, with one expressive moment on arrival. The page is written in its finished
   state: everything that moves is added by signup.js and only for people who haven't asked for less
   motion, and only transform and opacity ever animate. No cookies, no third parties. */
@font-face {
  font-family: "Courier Prime";
  src: url("/fonts/courier-prime-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

:root {
  --paper: #f5f1e8;
  --ink: #2b2824;
  --faint: #6b645a; /* 5.18:1 on the paper: grey that is still readable (AA needs 4.5) */
  --red: #a3263a;   /* the spikelets' wine red, 6.4:1 with paper: the one accent */
  --lift: #fbf8f1;  /* a sheet lying on the paper: the ticket, a message */
  --shade: #ece4d2; /* behind a refusal */
}

* { box-sizing: border-box; }
html { background: var(--paper); color: var(--ink); -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: "Courier Prime", "Courier New", monospace;
  font-size: clamp(1rem, 0.9rem + 0.35vw, 1.15rem);
  line-height: 1.6;
}
main { width: min(100%, 34em); margin: 0 auto; padding: clamp(1rem, 3vh, 4.5rem) 1.5rem 2.5rem; }
p { margin: 0 0 1em; }
[hidden] { display: none !important; }
.note { color: var(--faint); }
.nb { white-space: nowrap; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* One file is the invitation and the other the sign-up page; the body says which (and the script, from the
   address, says it again: /signup?code=… is an invitation too). */
[data-page="invite"] .only-signup, [data-page="signup"] .only-invite { display: none; }

/* The head: the plant, and beside it the ticket (an invitation) or how far along you are (signing up). */
.head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1em; min-height: 6rem; margin-bottom: 0.9rem; }
.mark { display: block; flex: none; height: 6rem; width: auto; overflow: visible; margin-left: -0.9rem; }
.stepline { margin: 0; color: var(--faint); text-align: right; }
[data-page="invite"] .stepline { display: none; }

/* The ticket: the invite is good for one person, so it looks like something you hold. Its three holes are
   the three steps, punched as you go; a stamp lands on it at the end, or says why it can't be used. */
.ticket {
  display: none;
  position: relative;
  flex: 0 1 15.5em;
  min-width: 0;
  align-self: center;
  border: 1.5px solid var(--ink);
  background: var(--lift);
  font-size: 0.78rem;
  line-height: 1.35;
  transform: rotate(1.4deg);
}
[data-page="invite"] .ticket { display: flex; }
.ticket-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.15em; padding: 0.7em 0.9em; }
.t-k { letter-spacing: 0.18em; text-transform: uppercase; }
.t-v { color: var(--faint); white-space: nowrap; }
.t-no { margin-top: 0.3em; min-height: 1.35em; overflow-wrap: anywhere; }
.ticket-stub {
  flex: none; width: 2.5em;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.5em;
  border-left: 1.5px dashed var(--ink);
}
.ticket-stub i { display: block; width: 0.75em; height: 0.75em; border: 1.5px solid var(--ink); border-radius: 50%; }
.ticket-stub i.on { background: var(--ink); }
/* Two bites out of the edge where the stub tears off. */
.ticket-stub::before, .ticket-stub::after {
  content: ""; position: absolute; right: calc(2.5em - 0.45em); width: 0.9em; height: 0.9em;
  border: 1.5px solid var(--ink); border-radius: 50%; background: var(--paper);
}
.ticket-stub::before { top: calc(-0.45em - 1.5px); clip-path: inset(50% -2px -2px -2px); }
.ticket-stub::after { bottom: calc(-0.45em - 1.5px); clip-path: inset(-2px -2px 50% -2px); }
/* A ticket that can't be used: greyed, its number struck, but still to be read (5:1 on the sheet). */
.ticket.void { border-color: var(--faint); }
.ticket.void .ticket-main, .ticket.void .t-k { color: var(--faint); }
.ticket.void #ticket-code { text-decoration: line-through; }
.ticket.void .ticket-stub { border-left-color: var(--faint); }
.ticket.void .ticket-stub i, .ticket.void .ticket-stub::before, .ticket.void .ticket-stub::after { border-color: var(--faint); }
.stamp {
  position: absolute; right: 1.2em; bottom: -0.75em;
  padding: 0.05em 0.5em 0;
  border: 2px solid currentColor; border-radius: 3px;
  color: var(--red);
  font-size: 1.3em; letter-spacing: 0.16em; text-transform: uppercase; white-space: nowrap;
  transform: rotate(-8deg);
  opacity: 0.9;
  pointer-events: none;
}
.stamp.grey { color: var(--faint); opacity: 1; } /* at full strength: 5.2:1 on the paper, 5.5:1 on the ticket */

/* The line that greets you. Two lines of fixed height, neither ever wraps (the size is set so 24 letters
   fit the column), so a name arriving late changes nothing around it. */
h1 {
  margin: 0 0 0.85rem;
  font-weight: 400;
  font-size: clamp(1.1rem, calc((100vw - 3rem) / 14.5), 1.9rem);
  line-height: 1.28;
}
h1 .l { display: block; height: 1.28em; white-space: nowrap; }
.t { position: relative; display: inline-block; }
/* A typed line is whole underneath; a strip of paper slides off it a letter at a time (the face is
   monospaced, so one step is one letter), with the block of the type head on its edge. */
.cover { position: absolute; inset: -0.05em 0 -0.3em 0; background: var(--paper); }
.cover::before { content: ""; position: absolute; left: 0; top: 0.2em; bottom: 0.42em; width: 0.5em; background: var(--ink); opacity: 0; }
.cover.on::before { opacity: 1; }
.u { position: relative; }
.u::after {
  content: "";
  position: absolute; left: -0.1em; right: 0.35em; bottom: -0.2em; height: 0.32em;
  background: var(--red);
  -webkit-mask: url("/underline.svg") no-repeat center / 100% 100%;
  mask: url("/underline.svg") no-repeat center / 100% 100%;
  transform-origin: 0 50%;
}

.intro { margin-bottom: 0.6rem; line-height: 1.5; }

/* What people hand me, as the messages they would write. All of them at rest; with motion allowed, one
   message being typed and cleared in a box that keeps its size. */
.asks { list-style: none; margin: 0 0 1.2rem; padding: 0; display: flex; flex-direction: column; gap: 0.45em; }
.asks li {
  align-self: flex-end;
  max-width: 100%;
  border: 1.5px solid var(--ink);
  border-radius: 1.1em 1.1em 0.25em 1.1em;
  background: var(--lift);
  padding: 0.42em 0.9em 0.38em;
  line-height: 1.45;
  font-size: 0.95em;
}
.asks .bubble { display: none; }
.asks.cycling li:not(.bubble) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.asks.cycling .bubble { display: block; align-self: stretch; min-height: calc(2.9em + 0.8em + 3px); }
@media (max-width: 359px) { .asks.cycling .bubble { min-height: calc(4.35em + 0.8em + 3px); } }
/* On a phone the number field has to stay clear of the keyboard, so the page starts a little higher. */
@media (max-width: 480px) { main { padding-top: 1rem; } .head { margin-bottom: 0.5rem; } .asks { margin-bottom: 0.8rem; } }
.caret { display: inline-block; width: 0.5em; height: 1em; margin-left: 1px; background: var(--ink); vertical-align: -0.14em; }
.plain-asks { list-style: none; margin: 0 0 1.2em; padding: 0; }
.plain-asks li { margin-bottom: 0.35em; }

/* Links: the hand-drawn underline, as on the letter. */
a { position: relative; color: inherit; text-decoration: none; }
a::after {
  content: "";
  position: absolute; left: -0.1em; right: -0.15em; bottom: -0.3em; height: 0.42em;
  background: var(--ink);
  -webkit-mask: url("/underline.svg") no-repeat center / 100% 100%;
  mask: url("/underline.svg") no-repeat center / 100% 100%;
  opacity: 0.4;
}

/* Fill-in lines, as on a printed form run through the same typewriter. */
label { display: block; margin: 1.3em 0 0.25em; color: var(--faint); }
form > label:first-child { margin-top: 0; }
input[type="text"], input[type="tel"], input[type="email"] {
  display: block;
  width: 100%;
  border: 0;
  border-bottom: 1.5px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: max(1em, 16px); /* under 16px an iPhone zooms the page */
  padding: 0.1em 0.1em 0.25em;
  border-radius: 0;
}
/* The line under the field you're in thickens; the padding gives the pixel back so nothing moves. */
input:focus { outline: none; border-bottom-width: 2.5px; padding-bottom: calc(0.25em - 1px); }
input[aria-invalid="true"] { border-bottom-color: var(--red); }
/* Keyboard users need to see where they are. */
a:focus-visible, button:focus-visible, input[type="checkbox"]:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
[tabindex="-1"]:focus { outline: none; }
::placeholder { color: var(--faint); opacity: 1; }
.code { letter-spacing: 0.15em; }
/* The spaced-out code style is for codes, not the hint: spaced, the hint clips on a narrow phone. */
.code::placeholder { letter-spacing: normal; }

.boxes { margin-top: 1.6em; display: flex; flex-direction: column; gap: 0.8em; }
.boxes label { display: flex; gap: 0.8em; align-items: flex-start; margin: 0; color: var(--ink); cursor: pointer; }
input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  flex: none;
  width: 1.375rem; height: 1.375rem; margin: 0.1em 0 0;
  border: 1.5px solid var(--ink); border-radius: 0;
  background: transparent;
  cursor: pointer;
}
input[type="checkbox"]:checked { background: var(--ink); box-shadow: inset 0 0 0 3px var(--paper); }
.legal { margin: 0.9em 0 0; }
.legal a { margin-right: 1.6em; }

/* One filled button per step, the width of the column, saying what it does. */
.btn {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 3.25rem;
  margin: 1.4rem 0 0;
  padding: 0.5em 1em;
  border: 1.5px solid var(--ink); border-radius: 3px;
  background: var(--ink); color: var(--paper);
  font: inherit; text-align: center;
  cursor: pointer;
}
.btn::after { content: none; }
.btn:hover { background: #3f3a34; }
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn:disabled:hover { background: #5a544c; border-color: #5a544c; cursor: default; } /* paper on it: 6.6:1 */
.btn.red { background: var(--red); border-color: var(--red); }
.btn.red:hover { background: #8c2032; }
.btn.quiet { background: transparent; color: var(--ink); }
.btn.quiet:hover { background: var(--shade); }

/* A refusal: ink on a darker strip with a red rule, right above the button it stopped. */
.error { margin-top: 1.2em; padding: 0.5em 0.8em; border-left: 3px solid var(--red); background: var(--shade); color: var(--ink); }
.error.at-field { margin-top: 0.6em; }
/* Under the number: how to fill it in, and (from phone.js) the number as it will be sent. One line is
   always kept for it, so nothing below moves when its words change. */
.hint { margin: 0.35em 0 0; min-height: 1.6em; font-size: 0.9em; }
.lead { margin: 0 0 0.9em; font-weight: 400; font-size: 1.15em; line-height: 1.4; }
.lead.big { font-size: clamp(1.1rem, calc((100vw - 3rem) / 14.5), 1.9rem); }

.after { margin-top: 1.1rem; color: var(--faint); font-size: 0.92em; }
/* The three steps, as one line. */
.after ol { list-style: none; margin: 0; padding: 0; }
.after li { display: inline; }
.after li:not(:last-child)::after { content: " \2192 "; }
.after p { margin: 0; }
.ask { font: inherit; color: inherit; background: none; border: 0; margin: -0.85em 0; padding: 0.85em 0; cursor: pointer; text-decoration: underline; text-underline-offset: 0.2em; }
.ask:hover, .ask:focus-visible { color: var(--ink); }

/* The first screen asks one thing. On the plain sign-up and sign-in pages the line above the field has
   already said whose number; the label stays for screen readers. */
[data-page="signup"] label[for="phone"], [data-page="account"] label[for="phone"] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* One line is kept under the field: empty, then the number as it will be sent, or what is wrong with it.
   The error takes the hint's place, not a second line. */
.hint:has(+ .at-field:not([hidden])) { display: none; }
.hint + .error.at-field { margin-top: 0.35em; min-height: 1.6em; font-size: 0.9em; }

/* A QR of the wa.me link: scanned from a desktop screen with the phone that has WhatsApp. */
.qr { margin: 0.4em 0 1.2em; min-height: 11em; } /* the code's own height, kept before it is drawn */
.qr svg { display: block; width: 11em; height: 11em; }
/* Dark modules in ink, the background in paper. The library puts a <description> before the
   background rect, so "rect:first-child" never matched and the whole code went ink. */
.qr svg path { fill: var(--ink); }
.qr svg > rect { fill: var(--paper); }
.toggle { color: var(--faint); cursor: pointer; }
#step-wa .btn { margin: 0.4rem 0 1rem; }
#step-done .btn { margin: 0.2rem 0 1.6rem; }

/* The same four links close every page; each is a thumb's height (44px) and at least as wide. */
.fine { display: flex; flex-wrap: wrap; width: min(100%, 34em); margin: 0 auto; padding: 0 1.5rem 2rem; color: var(--faint); }
.fine a { display: inline-flex; align-items: center; min-height: 2.75rem; min-width: 2.75rem; padding-right: 1.7em; font-size: 0.9rem; }
.fine a:hover { color: var(--ink); }
.fine a::after { content: none; } /* as on the homepage: plain words, no brush stroke under a tall link */

/* The plant, for when it moves. signup.js names its strokes (leaf, stem, spk, sand) by their order in the
   drawing, so the drawing itself stays as tools/draw-brand.mjs writes it. Everything grows from the root. */
.mark .leaf, .mark .stem { transform-box: view-box; transform-origin: 50px 84px; }
.mark .spk { transform-box: fill-box; transform-origin: 50% 50%; }
.mark .sand { transform-box: fill-box; transform-origin: 0 50%; }

@media (prefers-reduced-motion: no-preference) {
  /* Arrival, once: the sand line is swept in, the tuft and the stems come up, the spikelets open one by one. */
  .arriving .mark path { opacity: 0; }
  .arriving .mark .sand { animation: sweep 0.32s ease-out var(--d, 0ms) both; }
  .arriving .mark .leaf, .arriving .mark .stem { animation: grow 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) var(--d, 0ms) both; }
  .arriving .mark .spk { animation: open 0.3s cubic-bezier(0.3, 1.5, 0.5, 1) var(--d, 0ms) both; }
  /* Waiting for the hello: the stems lean a little, slowly, so the page is seen to be alive. */
  .waiting .mark .stem, .waiting .mark .spk {
    transform-box: view-box; transform-origin: 50px 84px;
    animation: lean 4.6s ease-in-out var(--s, 0ms) infinite alternate;
  }
  /* There you are: every spikelet swells once, stem by stem. */
  .flowering .mark .spk { animation: swell 0.7s ease-in-out var(--d, 0ms) 1 both; }
  /* The underline is inked in once the greeting has been typed. */
  .arriving h1:not(.inked) .u::after { transform: scaleX(0); }
  .arriving h1.inked .u::after { animation: sweep 0.4s ease-out both; }
  .caret { animation: blink 1.06s steps(1, end) infinite; }
  .paused .caret { animation: none; }
}
@keyframes sweep { from { transform: scaleX(0); opacity: 1; } to { transform: none; opacity: 1; } }
@keyframes grow { from { transform: scale(0.05); opacity: 0; } 30% { opacity: 1; } to { transform: none; opacity: 1; } }
@keyframes open { from { transform: scale(0); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes lean { from { transform: rotate(-0.9deg); } to { transform: rotate(0.9deg); } }
@keyframes swell { 50% { transform: scale(1.9); } }
@keyframes blink { 50% { opacity: 0; } }
