/* ============================================================
   The waiting-list site's only stylesheet.

   WHY THE COLOURS ARE HEX LITERALS HERE AND NOWHERE ELSE
   CLAUDE.md's rule — "no hex literals in a screen" — is about
   client/src, where theme.ts exists and can be imported. This site
   is deliberately outside that build: no Metro, no bundler, no
   TypeScript, so there is nothing to import from. It is the same
   compromise client/public/index.html already makes and documents,
   and the same obligation comes with it:

     EVERY VALUE BELOW IS A COPY OF A TOKEN IN client/src/theme.ts.
     If the palette moves there, it has to be moved here by hand, or
     the waiting list and the product stop being the same brand.

   The mapping, so a future edit does not have to guess:

     #0b0b0f  color.bg            (room.base)
     #12141c  color.bgSubtle      (room.sunken)
     #16181f  color.surface       (room.surface)
     #2a2e3d  color.border        (room.border)
     #3a3f52  color.borderStrong  (room.borderStrong)
     #e5e7eb  color.text          (neutral[200], via room.ink)
     #9ca3af  color.textMuted     (neutral[400], via room.inkMuted)
     #7d8593  color.textSubtle
     #6b7280  color.textFaint     (neutral[500])
     #e11d48  color.primary       (rose[500])
     #fb7185  color.primaryInk    (rose[400])
     #c81e4a  rose[600]           — the pressed fill
     #ffffff  color.onPrimary     (neutral[0])
     #34d399  color.success
     #fda4af  rose[300]           — error ink on the dark ground

   Georgia is font.serif and the system stack is font.sans, both
   verbatim from theme.ts.
   ============================================================ */

:root {
  color-scheme: dark;

  --bg: #0b0b0f;
  --bg-subtle: #12141c;
  --surface: #16181f;
  --border: #2a2e3d;
  --border-strong: #3a3f52;
  --text: #e5e7eb;
  --text-muted: #9ca3af;
  --text-subtle: #7d8593;
  --text-faint: #6b7280;
  --primary: #e11d48;
  --primary-ink: #fb7185;
  --primary-pressed: #c81e4a;
  --on-primary: #ffffff;
  --success: #34d399;
  --danger-ink: #fda4af;

  --serif: Georgia, 'Times New Roman', serif;
  --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* space.md / lg / xl / 2xl / 3xl off theme.ts's 4-8 rhythm. */
  --s-sm: 8px;
  --s-md: 12px;
  --s-lg: 16px;
  --s-xl: 24px;
  --s-2xl: 32px;
  --s-3xl: 48px;

  --radius: 14px;
}

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

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* The page scrolls. The app sets `overflow: hidden` on body because
   every scroller in it is a ScrollView; this is an ordinary document
   and must not inherit that assumption. */
body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

a {
  color: var(--primary-ink);
  text-decoration-color: rgba(251, 113, 133, 0.4);
  text-underline-offset: 2px;
}

a:hover {
  text-decoration-color: currentColor;
}

/* Visible focus on every interactive thing. The app gets this from
   the a11y helpers in ui/a11y.ts; a plain document gets it here, and
   removing it would be the single most common accessibility
   regression on a form. */
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 2px solid var(--primary-ink);
  outline-offset: 2px;
}

.wrap {
  width: 100%;
  /* Narrower than it was (680). With the card's chrome gone there is
     no box holding the form together, so the COLUMN has to do it —
     a lone input stretched to 680px stops reading as a form. `.doc`
     overrides this back up for the legal pages, which are prose and
     want the wider measure. */
  max-width: 560px;
  margin: 0 auto;
  padding: var(--s-3xl) var(--s-xl);
  flex: 1 0 auto;
}

/* ---------- the wordmark ---------- */

/* An <img>, sized by WIDTH with height:auto, so whatever aspect ratio
   the PNG turns out to have is preserved rather than squashed. The
   cap is deliberately modest — a plain page's logo should not be the
   loudest thing on it.

   If the file is absent the browser renders the alt text ("Spark")
   in the flow instead, which is why this block sets no fixed height:
   a height here would leave a tall empty box around a short line of
   fallback text. */
.wordmark {
  display: block;
  margin: 0 auto var(--s-xl);
  width: auto;
  height: auto;
  max-width: 180px;
  /* Retina: a 180px slot wants ~360-540px of pixels. See README. */
}

@media (min-width: 720px) {
  .wordmark {
    max-width: 220px;
  }
}

/* ---------- type ---------- */

/* Plain by request: the sans stack rather than Georgia, normal-ish
   weight, no letter-spacing trick, no brand colour on a single word.
   The serif headline is still the app's voice — this page just isn't
   using it.

   Still bounded on both axes. Sizing off width alone is what lets a
   headline overrun a short window. */
h1 {
  font-family: var(--sans);
  font-weight: 600;
  font-size: clamp(24px, 4.5vw, 30px);
  line-height: 1.25;
  margin: 0 0 var(--s-md);
  text-align: center;
  color: var(--text);
}

.lede {
  color: var(--text-muted);
  text-align: center;
  margin: 0 auto var(--s-2xl);
  max-width: 46ch;
}

/* ---------- the rotating tagline ---------- */

/* min-height is the load-bearing property here. The lines are
   different lengths, and on a narrow screen a longer one wraps to two
   rows while a shorter one does not — so without a floor the entire
   page below would jump up and down once every few seconds as the
   text cycled. Two lines' worth of room, reserved permanently.

   Monospace, because the effect being imitated is a terminal, and a
   proportional font gives it away: in a proportional face the caret
   visibly jitters left and right as it passes over narrow and wide
   letters. */
.tagline {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
  line-height: 1.5;
  color: var(--primary-ink);
  text-align: center;
  margin: 0 auto var(--s-xl);
  min-height: 3em;
  max-width: 42ch;
  /* The typed string can contain runs of spaces mid-erase; without
     this the browser collapses them and the text shivers. */
  white-space: pre-wrap;
}

/* An old-school block caret rather than a thin bar — it is the DOS
   cursor, which is the look being asked for. */
.caret {
  display: inline-block;
  width: 0.6em;
  height: 1.05em;
  margin-left: 1px;
  vertical-align: text-bottom;
  background: var(--primary-ink);
  animation: caret-blink 1.06s steps(1, end) infinite;
}

@keyframes caret-blink {
  0%,
  49% {
    opacity: 1;
  }
  50%,
  100% {
    opacity: 0;
  }
}

/* ---------- the progress bar ---------- */

.progress {
  margin: 0 auto var(--s-2xl);
  max-width: 420px;

  /* ---------- THE ONE PLACE THE NUMBER LIVES ----------
     Unitless, so it can be used three different ways below: as a
     width (× 100%), as the end of the fill animation, and as the
     offset of the leading-edge glow. It was three hardcoded `75%`
     values before the bar was animated, and the animation would have
     made it four — at which point a change to the progress means
     finding all four or the glow detaches from the fill.

     The HTML still carries "75%" twice, in the visible label and the
     aria-label, because CSS cannot write text content. If this moves,
     those two move with it. */
  --p: 0.75;
}

.progress-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--text-subtle);
  margin-bottom: 6px;
}

.progress-pct {
  color: var(--primary-ink);
  font-weight: 700;
}

/* Square corners, visible border, inset padding: the bar is drawn as
   a container with a fill inside it rather than as a rounded modern
   meter. */
.progress-track {
  height: 16px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-strong);
  padding: 2px;
  /* Anchors the leading-edge glow below. */
  position: relative;
}

/* The bright edge at the head of the fill, breathing.

   Positioned rather than parented to .progress-fill on purpose: the
   fill is masked into discrete blocks, and a mask clips everything
   the element paints — including a glow or a box-shadow. Anything
   meant to bleed past the blocks has to live outside the masked
   element.

   The offset re-derives the fill's right edge: 2px of track padding,
   plus the fraction of the remaining content width. */
.progress-track::after {
  content: '';
  position: absolute;
  top: 2px;
  bottom: 2px;
  left: calc(2px + (100% - 4px) * var(--p));
  width: 2px;
  margin-left: -2px;
  background: var(--primary-ink);
  box-shadow: 0 0 7px 1px var(--primary);
  animation: edge-breathe 1.9s ease-in-out infinite;
}

@keyframes edge-breathe {
  0%,
  100% {
    opacity: 0.45;
  }
  50% {
    opacity: 1;
  }
}

/* The segments are a repeating gradient rather than 20 elements.
   Same blocky result, one node instead of twenty, and the count
   reflows by itself at any width — hand-placed cells would have to be
   re-counted every time the bar's max-width changed.

   The gaps are transparent so the track shows through them, which is
   what makes it read as discrete blocks rather than a striped bar. */
/* The blocks are now a MASK rather than the background.

   Inverting it is what lets the bar shimmer. As a background the
   blocks were opaque paint, and anything drawn over them would also
   cover the transparent gaps — a highlight sweeping the full width
   rather than lighting each block in turn. As a mask, the blocks
   define where the element is visible at all, so the shimmer
   underneath them is revealed block by block and the gaps stay empty.

   -webkit- prefix included: Safari has only shipped masks prefixed,
   and without it the whole bar renders as one solid rectangle rather
   than failing visibly. That is the dangerous kind of breakage —
   still looks like a progress bar, just the wrong one. */
.progress-fill {
  height: 100%;
  width: calc(var(--p) * 100%);

  background-color: var(--primary);
  /* The travelling highlight. no-repeat + a fixed size means one band
     crossing, not a striped pattern. */
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.5) 50%,
    transparent 100%
  );
  background-size: 64px 100%;
  background-repeat: no-repeat;

  -webkit-mask-image: repeating-linear-gradient(
    90deg,
    #000 0 9px,
    transparent 9px 12px
  );
  mask-image: repeating-linear-gradient(90deg, #000 0 9px, transparent 9px 12px);

  /* Fills on load, then shimmers forever — the shimmer waits for the
     fill to finish so the highlight is not chasing a moving edge.
     The blanket prefers-reduced-motion rule at the foot of this file
     switches both off, and because the declared width is already the
     final value the bar simply appears filled and still. */
  animation:
    progress-fill-in 900ms ease-out 1,
    progress-shimmer 2.6s linear 900ms infinite;
}

@keyframes progress-fill-in {
  from {
    width: 0;
  }
  to {
    width: calc(var(--p) * 100%);
  }
}

@keyframes progress-shimmer {
  from {
    background-position-x: -64px;
  }
  to {
    background-position-x: calc(100% + 64px);
  }
}

/* ---------- the status LED ---------- */

.progress-title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--success);
  box-shadow: 0 0 6px var(--success);
  animation: live-dot-pulse 2.1s ease-in-out infinite;
}

@keyframes live-dot-pulse {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 1;
  }
}

.stages {
  display: flex;
  justify-content: space-between;
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--text-faint);
}

.stages li {
  color: var(--text-faint);
  margin: 0;
}

.stages .done {
  color: var(--text-subtle);
}

/* The only stage that is not grey. It is the one the page is asking
   you to care about. */
.stages .next {
  color: var(--primary-ink);
  font-weight: 700;
}

/* ---------- accessibility helper ---------- */

/* Visible to a screen reader, invisible on screen. NOT `display:none`
   or `visibility:hidden` — both of those remove the element from the
   accessibility tree as well, which would defeat the entire purpose.
   The clip-rect technique is the one that hides it visually while
   leaving it announced. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

h2 {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 19px;
  line-height: 1.3;
  margin: var(--s-2xl) 0 var(--s-md);
  color: var(--text);
}

h3 {
  font-size: 16px;
  font-weight: 600;
  margin: var(--s-xl) 0 var(--s-sm);
  color: var(--text);
}

p,
li {
  color: var(--text-muted);
}

/* ---------- the form's container ---------- */

/* Flat by request. It was a raised surface — background, border,
   radius, padding — which on a page containing exactly one thing was
   a box drawn around the whole page.

   Removing the chrome has a side effect worth keeping: the consent
   block below is now the ONLY bordered element on the page, so the
   one thing that legally must not be skimmed past is also the one
   thing the eye stops at. That was true before only by contrast
   against a second border.

   Kept as a class rather than deleted so the markup does not have to
   change, and so there is somewhere to put the width cap. */
.card {
  background: none;
  border: none;
  padding: 0;
}

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

.field {
  margin-bottom: var(--s-lg);
}

.field label {
  display: block;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 6px;
}

.field input {
  width: 100%;
  padding: 13px var(--s-md);
  font-family: inherit;
  font-size: 16px; /* 16px or iOS Safari zooms the page on focus. */
  color: var(--text);
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.field input::placeholder {
  color: var(--text-faint);
}

.field input:focus {
  border-color: var(--border-strong);
}

.field input[aria-invalid='true'] {
  border-color: var(--danger-ink);
}

.hint {
  font-size: 13px;
  color: var(--text-subtle);
  margin: 6px 0 0;
}

/* ---------- consent ----------

   Given its own box rather than being one more line in the form.
   Quebec's Law 25 requires consent to be "manifest" and given
   separately from any other agreement, and CASL requires it to be a
   positive act. A checkbox that looks like the rest of the form is
   not manifest; a bordered block that the eye has to stop at is. */

.consent {
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: var(--s-md);
  margin-bottom: var(--s-lg);
}

.consent-row {
  display: flex;
  gap: var(--s-md);
  align-items: flex-start;
}

.consent input[type='checkbox'] {
  flex: 0 0 auto;
  /* 18px, not smaller. The block was asked to shrink, and everything
     in it did except this: a checkbox below ~18px is hard to hit on a
     phone, and this is the one control on the page that must never be
     ticked by accident OR missed by someone who meant to tick it. The
     label is a click target too (the `for` attribute), which is what
     actually makes the row comfortable. */
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  accent-color: var(--primary);
}

.consent label {
  font-size: 13px;
  color: var(--text-muted);
  line-height: 1.45;
  cursor: pointer;
}

/* ---------- the expander ---------- */

.consent-more {
  margin-top: 8px;
  /* Indented to clear the checkbox, so the summary lines up with the
     label above it rather than with the box. */
  padding-left: calc(18px + var(--s-md));
}

.consent-more summary {
  font-size: 12px;
  color: var(--text-subtle);
  cursor: pointer;
  /* The default disclosure triangle is replaced below; this removes
     it in WebKit, where `list-style` alone does not. */
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* A generous hit area without adding visible bulk — the summary is
     small type and would otherwise be a 14px-tall tap target. */
  padding: 4px 0;
}

.consent-more summary::-webkit-details-marker {
  display: none;
}

/* Own triangle, so it can rotate. ▸ closed, ▾ open. */
.consent-more summary::before {
  content: '▸';
  display: inline-block;
  font-size: 10px;
  color: var(--text-faint);
  transition: transform 120ms ease-out;
}

.consent-more[open] summary::before {
  transform: rotate(90deg);
}

.consent-more summary:hover {
  color: var(--text-muted);
}

.consent-detail {
  font-size: 12.5px;
  color: var(--text-subtle);
  line-height: 1.5;
  padding: 4px 0 2px;
}

.consent-detail p {
  font-size: inherit;
  color: inherit;
  margin: 0 0 6px;
}

.consent-detail ul {
  margin: 0;
  padding-left: 16px;
}

.consent-detail li {
  font-size: inherit;
  color: inherit;
  margin-bottom: 3px;
}

/* ---------- the button ---------- */

button.primary {
  display: block;
  width: 100%;
  padding: 15px var(--s-lg);
  font-family: inherit;
  font-size: 16px;
  font-weight: 600;
  color: var(--on-primary);
  background: var(--primary);
  border: none;
  border-radius: 10px;
  cursor: pointer;
  /* touch.min in theme.ts — a 44px target is the floor, not a
     suggestion. */
  min-height: 48px;
}

button.primary:hover:not(:disabled) {
  background: var(--primary-pressed);
}

button.primary:disabled {
  opacity: 0.55;
  cursor: default;
}

/* ---------- status ---------- */

.status {
  margin-top: var(--s-lg);
  font-size: 14px;
  min-height: 1.5em;
}

.status.error {
  color: var(--danger-ink);
}

.status.ok {
  color: var(--success);
}

/* Replaces the whole form once someone is on the list, rather than
   sitting under a form they might fill in again.

   Selected by ID, not by a `.done` class, and that is deliberate:
   the progress bar's completed stages are also "done", and an
   unscoped `.done` rule reached them too — putting 16px of vertical
   padding on every stage label and centring them inside their flex
   cells. Two unrelated things legitimately called "done" is a name
   collision waiting to happen twice; an ID matches exactly one
   element and cannot collide at all. */
#done {
  text-align: center;
  padding: var(--s-lg) 0;
}

#done h2 {
  margin-top: 0;
  color: var(--text);
}

.legal-note {
  font-size: 13px;
  color: var(--text-subtle);
  text-align: center;
  margin: var(--s-lg) 0 0;
}

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

footer {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  padding: var(--s-xl);
  text-align: center;
  font-size: 13px;
  color: var(--text-subtle);
}

footer a {
  margin: 0 var(--s-sm);
  white-space: nowrap;
}

/* ---------- legal documents ---------- */

.doc {
  max-width: 760px;
}

.doc h1 {
  text-align: left;
  font-size: clamp(28px, 5vw, 38px);
}

.doc .updated {
  color: var(--text-faint);
  font-size: 14px;
  margin: 0 0 var(--s-2xl);
}

.doc ul,
.doc ol {
  padding-left: var(--s-xl);
}

.doc li {
  margin-bottom: 6px;
}

/* `.todo` and `.banner` lived here — the red dashed blanks and the
   "Draft — not yet published" bar on the legal pages. Both are gone
   now that those pages are published, and the rules went with them
   rather than sitting unused: dead CSS is a standing invitation to
   re-add the markup without re-reading why it existed.

   If blanks are ever needed again, the point of them was that an
   unfilled legal document must be impossible to publish BY ACCIDENT
   — loud, ugly, and visible on the page itself rather than a note in
   a file nobody opens. Rebuild that property, not just the styling. */

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}
