/* ====================================================================
   FLAMMIS
   --------------------------------------------------------------------
   Hand-written, no utility framework, no build step. What is here is
   what ships.

   The hero block is lifted from the approved reference (design/archive/flammis-hero.html).
   Its lede now uses the locked scale from FLAMMIS.md rather than the
   15px it shipped with, so one type scale governs the whole page.
   flammis-hero.html still carries the old value and is the one place
   the two can drift apart.
   ==================================================================== */

/* ── Fonts ──
   Self-hosted rather than loaded from Google, so no visitor's address is
   sent to a third party just to draw a letter, and there is one less
   connection before the headline paints. Both are SIL Open Font Licence
   (assets/fonts/OFL-*.txt). Latin and Latin Extended only, split the way
   Google splits them so a page in plain English loads just the first.
   Inter is a variable font: one file covers 400 to 600. */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/anton-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/anton-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('../fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('../fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* The ramp's two ends. Everything dark on the page is a point between
     them, which is what makes the site read as one colour rather than as
     black with something stuck on it. #000000 is an end, not a fill --
     see html/body below.

     --scorch is dark enough (relative luminance 0.0076) that it can be
     used at FULL strength anywhere, including behind muted text, where
     the saturated red it replaces had to be held under about a third
     opacity. That is why the stops below are opaque rather than washes:
     a wash of a near-black reads as dirt, not as a grade. */
  --ink:        #000000;
  --scorch:     #2B0B04;

  --ash:        #120502;   /* base surface: ~42% along the ramp */
  --ash-raised: #1E0803;   /* lifted surface: ~70% */
  --ember:      #C9491E;
  --molten:     #FF7A1A;
  --gold:       #E8A23D;
  --bone:       #EDE3D3;
  --bone-dim:   #8E8271;
  --rule:       rgba(237,227,211,0.11);

  --gut: clamp(20px, 4vw, 56px);

  /* vertical rhythm for the sections below the hero */
  --section-y: clamp(72px, 11vh, 136px);
  --measure:   68ch;   /* body copy stays under 80 characters */
}

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

/* Flat. A gradient over the whole document moved about 4 red values per
   screen -- invisible, and its only effect was making each plain section
   a slightly different colour from the last. Ink appears where something
   puts it there (the top of the hero), not as the page's ground. */
html { background: var(--ink); }
body { background: var(--ash); }

/* Dither.

   Eight-bit colour cannot draw a smooth ramp this dark: #000 to #2B0B04
   is 43 steps of red, so a gradient across a few hundred pixels lands a
   visible band every ~19px. No amount of tuning the stops fixes that --
   the steps are the display's, not the gradient's. A grain over the top
   breaks the eye's edge detection and the banding goes. The sections are
   flat now, so this only has real work to do in the hero and under the
   waitlist fire, the two places that still carry a gradient.

   Baked once by the browser from an inline SVG (no request, no
   per-frame work) and tiled. pointer-events: none so it never eats a
   click. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/></filter><rect width='180' height='180' filter='url(%23n)'/></svg>");
  background-size: 180px 180px;
}

body {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 17px;
  line-height: 1.62;
  color: var(--bone);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }

/* The forms and panels quests.js shows and hides set display themselves
   (flex, grid), which beats the browser's own [hidden] rule. */
[hidden] { display: none !important; }

:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  border-radius: 2px;
}

.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;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 50;
  padding: 12px 18px;
  background: var(--bone);
  color: var(--ash);
  font-size: 14px;
  font-weight: 500;
}
.skip-link:focus { left: 12px; top: 12px; }

/* ── Message badges ──────────────────────────────────
   Every result message on the site -- waitlist and code, usernames,
   a failed claim, the review page's notices -- is the same badge, so
   success and failure look the same wherever they turn up.

   The tone is carried by an icon as well as colour (a tick, an
   exclamation mark, a turning ring), so it does not rest on telling
   gold from orange. Each badge plates itself: some of them sit in the
   waitlist flame's path, and the tint goes over a near-opaque ground
   rather than letting the fire through the text.

   Contrast with the brightest flame behind the plate: gold on the ok
   badge 7.2:1, bone on the err badge 12.1:1, bone-dim on the busy
   badge 4.9:1.

   Markup: any element with .badge and data-tone="ok|err|busy". The
   status lines below opt in through their own selectors, because
   quests.js sets data-tone on them as it writes the text. */
.badge,
.form-status[data-tone],
.handle-status[data-tone],
.quest-error {
  display: inline-flex;
  align-items: flex-start;
  gap: 8px;
  max-width: 46ch;
  padding: 7px 14px 7px 10px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: rgba(10,3,1,0.93);
  font-size: 13.5px;
  line-height: 1.45;
  text-align: left;
  color: var(--bone-dim);
}
.badge::before,
.form-status[data-tone]::before,
.handle-status[data-tone]::before,
.quest-error::before {
  content: '';
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 1px;                /* centre on the first line */
  background-color: currentColor;
  -webkit-mask: var(--badge-icon) center / contain no-repeat;
          mask: var(--badge-icon) center / contain no-repeat;
}

[data-tone="ok"]:is(.badge, .form-status, .handle-status) {
  color: var(--gold);
  border-color: rgba(232,162,61,0.42);
  background: linear-gradient(rgba(232,162,61,0.10), rgba(232,162,61,0.10)), rgba(10,3,1,0.93);
  --badge-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle cx='8' cy='8' r='7' fill='none' stroke='black' stroke-width='1.6'/><path d='M4.8 8.3l2.1 2.1 4.3-4.6' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}

/* Text stays bone: molten would pass (5.9:1 at worst), but bone reads
   as the message and the orange icon and edge carry the alarm. */
[data-tone="err"]:is(.badge, .form-status, .handle-status),
.quest-error {
  color: var(--bone);
  border-color: rgba(255,122,26,0.5);
  background: linear-gradient(rgba(255,122,26,0.12), rgba(255,122,26,0.12)), rgba(10,3,1,0.93);
  --badge-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle cx='8' cy='8' r='7' fill='none' stroke='black' stroke-width='1.6'/><path d='M8 4.3v4.6' stroke='black' stroke-width='1.8' stroke-linecap='round'/><circle cx='8' cy='11.5' r='1.05' fill='black'/></svg>");
}
[data-tone="err"]:is(.badge, .form-status, .handle-status)::before,
.quest-error::before { background-color: var(--molten); }

[data-tone="busy"]:is(.badge, .form-status, .handle-status) {
  --badge-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle cx='8' cy='8' r='6.2' fill='none' stroke='black' stroke-opacity='.25' stroke-width='1.8'/><path d='M8 1.8a6.2 6.2 0 0 1 6.2 6.2' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/></svg>");
}
[data-tone="busy"]:is(.badge, .form-status, .handle-status)::before {
  animation: badge-spin .8s linear infinite;
}
@keyframes badge-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  [data-tone="busy"]:is(.badge, .form-status, .handle-status)::before { animation: none; }
}

/* ── Display type ────────────────────────────────────────
   Anton ships one weight. Hierarchy is size and space only;
   font-weight is pinned to 400 everywhere it is used because
   the UA sheet makes headings bold and a synthetic embolden
   on a single-weight face renders badly.
   Anton never appears below 20px -- anything smaller is Inter. */

h1, h2, h3 {
  font-family: 'Anton', Impact, sans-serif;
  font-weight: 400;
  text-transform: uppercase;
}

h2 {
  font-size: clamp(32px, 5vw, 56px);
  line-height: 0.98;
  letter-spacing: 0.006em;
  /* Anton is loud; the space around it does the work a lighter
     weight would do elsewhere */
  margin-bottom: clamp(20px, 3vh, 34px);
  text-wrap: balance;
}

h3 {
  font-size: 22px;
  line-height: 1.1;
  letter-spacing: 0.01em;
}

.lede {
  font-size: 19px;
  line-height: 1.55;
  color: var(--bone-dim);
  max-width: var(--measure);
  text-wrap: pretty;
}

.meta {
  font-size: 12px;
  line-height: 1.6;
  color: var(--bone-dim);
}

/* ====================================================================
   HERO  -- lifted from the approved reference, values unchanged
   ==================================================================== */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  isolation: isolate;
}

/* One gradient, top to bottom: ink under the masthead, scorch at the
   floor. Nothing centred on the character -- the mascot brings its own
   fire, and a glow drawn around it only competed with it.

   The floor is the warmest point and the footline sits on it. Bone-dim
   on full #2B0B04 reads 4.85:1, so the ticker passes on the gradient
   alone; the ash plate it needed was for the orange spill, now gone. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  /* Black holds through the top half, so the hero reads as black with
     heat rising at the floor rather than a warm wash from the top down. */
  background: linear-gradient(180deg, var(--ink) 0%, var(--ink) 55%, var(--scorch) 100%);
}

/* The video fills the stage column and nothing else. It used to be
   pinned to the hero itself, which put its top under the masthead: by the
   final pose the flames reach the top of frame, and the logo and nav sat
   on bright fire. Inside the stage it starts below the masthead and stops
   above the copy at both layouts, so no text ever sits on the mascot. */
.hero-media {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;

  /* Flay is shot on black, so black is the background: screen
     drops it out and leaves only the character and the fire, with no
     frame, mask or edge anywhere. Fire is light, and screen is how
     light adds -- the same reason the waitlist flame uses it.

     On this element, not the <video> inside it: a blend mode blends
     with the backdrop of its own stacking context, and .hero is that
     context, with its gradients right behind. On the child it would
     only see this box, which is empty.

     The file feathers its own edges to black (assets/video/README.md),
     because the source crops the flames at the top of frame and screen
     would otherwise show that crop as a straight line through the fire. */
  mix-blend-mode: screen;
}

/* contain, not cover: the clip is portrait and the flames reach the top
   of frame, so cover would cut them off. The letterbox it leaves is
   black, and black is exactly what screen removes. */
.hero-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* No file, no codec, or autoplay refused: .still behind this is a CSS
   gradient, so the hero is never blank. */
body.hero-static .hero-media { display: none; }

/* shown when WebGL is unavailable or the device is below the floor */
.still {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: none;
  /* Tuned to sit BEHIND something; with nothing in front of it those
     opacities read as two orange blobs rather than as heat. Softened and
     spread so it works as what it now is -- the hero when the video is
     missing, refused or still arriving. */
  background:
    radial-gradient(26% 20% at 27% 74%, rgba(255,122,26,0.30) 0%, transparent 74%),
    radial-gradient(46% 38% at 27% 60%, rgba(201,73,30,0.22) 0%, transparent 76%),
    radial-gradient(72% 56% at 27% 64%, rgba(43,11,4,0.95) 0%, transparent 80%);
}
body.hero-static .still { display: block; }

/* ── Masthead ──────────────────────────────────────────── */

.masthead {
  position: relative;
  display: flex;
  align-items: center;
  gap: clamp(20px, 3vw, 44px);
  padding: 22px var(--gut);
}

.wordmark {
  font-family: 'Anton', Impact, sans-serif;
  font-size: 20px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  gap: 9px;
}

/* The brand mark, from the supplied logo. Bone like the wordmark beside
   it rather than pure white -- the page has no pure white anywhere, and
   the two should read as one lockup. Sized to the cap height of the
   20px Anton next to it so the bars line up with the letters. */
.wordmark .mark {
  width: 19px;
  height: 18px;
  flex: none;
  fill: currentColor;
}

.primary-nav {
  display: flex;
  gap: 26px;
  font-size: 14px;
  color: var(--bone-dim);
}
.primary-nav a { transition: color .2s ease; }
.primary-nav a:hover { color: var(--bone); }

.masthead .spacer { flex: 1; }

/* deliberately quiet: the hero CTA is the loud one, and two
   bone-filled pills in one viewport fight each other */
.masthead-cta {
  font-size: 14px;
  color: var(--bone-dim);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 3px;
  transition: color .2s ease, border-color .2s ease;
}
.masthead-cta:hover { color: var(--bone); border-color: var(--bone-dim); }

/* ── Split ─────────────────────────────────────────────── */

.split {
  position: relative;
  flex: 1;
  display: grid;
  grid-template-columns: 57% 43%;
  align-items: center;
}

/* The mascot's box. Stretched to the full height of the split so the
   video gets all of it, and positioned so the video can be measured
   against it -- but no z-index, so this is not a stacking context and the
   video's blend still reaches the hero gradient behind it. */
.stage-col {
  position: relative;
  align-self: stretch;
  min-height: 46vh;
}

.copy-col {
  position: relative;
  /* The copy starts on the column edge itself. The rule is set a little
     way INTO it below, so the text overhangs the line rather than
     standing clear of it -- see the note on .copy-col h1::before. */
  padding: 0 var(--gut) 0 0;
}

/* The reference runs its rule down the column edge, but the reading block
   interrupts it: a segment above the headline, a segment below the CTA,
   and nothing behind the text itself.

   The segments sit 14px INSIDE the text's left edge, not outside it.
   That is the difference between a line the copy interrupts and a line
   the copy merely sits beside: on this axis the rule reads as entering
   the block from above and leaving it below. The headline overhangs it
   by those 14px, exactly as the reference's does.

   Anchored to the headline and the actions rather than drawn as one
   gradient down .split, because the gap has to be the copy's real height.
   Expressed as a share of .split it would drift every time the headline
   took another line or the viewport changed height, and the text would
   start crossing its own rule. */
.copy-col h1,
.copy-col .actions { position: relative; }

.copy-col h1::before,
.copy-col .actions::after {
  content: '';
  position: absolute;
  left: 14px;
  width: 1px;
  /* Short, like the reference: long enough to read as a division, short
     enough that the upper segment stops below the masthead instead of
     running up behind it. The far end fades so a clip at the hero edge
     never reads as the line being cut off. */
  height: clamp(120px, 18vh, 200px);
  pointer-events: none;
}
.copy-col h1::before {
  bottom: calc(100% + clamp(30px, 4vh, 52px));
  background: linear-gradient(to top, var(--rule) 0%, var(--rule) 48%, transparent 100%);
}
.copy-col .actions::after {
  top: calc(100% + clamp(26px, 3.4vh, 44px));
  background: linear-gradient(to bottom, var(--rule) 0%, var(--rule) 48%, transparent 100%);
}

h1 {
  font-size: clamp(52px, 9vw, 104px);
  line-height: 0.94;
  letter-spacing: 0.004em;
  margin-bottom: 28px;
  text-wrap: balance;
}

/* Inherits the locked lede scale (19px/1.55) from .lede above; only the
   measure and spacing are hero-specific now. */
.hero .lede {
  max-width: 36ch;
  margin-bottom: 32px;
  hanging-punctuation: first;   /* Safari; harmless elsewhere */
}

.actions { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }

.start {
  display: inline-block;
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ash);
  background: var(--bone);
  border: 0;
  border-radius: 999px;
  padding: 15px 28px;
  cursor: pointer;
  transition: transform .18s ease, box-shadow .28s ease, background .18s ease;
}
/* one of only two glowing things on the page (the other is the
   flame). Gold, not molten -- molten is reserved for lava. */
.start:hover {
  background: var(--gold);
  transform: translateY(-1px);
  box-shadow: 0 0 0 1px rgba(232,162,61,0.45), 0 10px 34px rgba(232,162,61,0.24);
}
.start[disabled] { opacity: .55; cursor: default; transform: none; box-shadow: none; }

.secondary {
  font-size: 14.5px;
  color: var(--bone-dim);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 3px;
  transition: color .2s ease, border-color .2s ease;
}
.secondary:hover { color: var(--bone); border-color: var(--bone-dim); }

/* ── Footline ──────────────────────────────────────────── */

.footline {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding: 0 var(--gut) 30px;
}

.ticker {
  font-size: 13.5px;
  color: var(--bone-dim);
  min-height: 1.3em;
}
.ticker .caret {
  display: inline-block;
  width: 1px;
  height: 1em;
  background: var(--gold);
  vertical-align: -0.15em;
  margin-left: 2px;
  animation: blink 1.05s steps(1, end) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

.scroll-cue {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--bone-dim);
}
.scroll-cue svg { animation: nudge 2.1s ease-in-out infinite; }
@keyframes nudge {
  0%, 100% { transform: translateY(0); opacity: .55; }
  50%      { transform: translateY(5px); opacity: 1; }
}

/* ====================================================================
   SECTIONS
   --------------------------------------------------------------------
   Each one is structured to fit its content rather than dropped into a
   shared card shell. Alternation is a flat background change, not a
   border or a shadow.
   ==================================================================== */

/* One system: flat surfaces that alternate, and heat only where
   something is actually hot.

   Every section used to warm toward its own floor as well. Run together
   with the alternation, that made every section end at the same warm
   value while the plain ones started near black -- so three seams were
   hard tonal cuts and two were invisible, for no reason the content
   explained. It also spent the heat motif on the glossary and the
   community links, which left less of it for the sphere and the fire.

   Now every boundary is the same step, --ash against --ash-raised, with
   the hairline on top. The gradients that remain -- the hero's bloom and
   the waitlist's -- each sit under a real heat source. */
.section {
  padding: var(--section-y) var(--gut);
  border-top: 1px solid var(--rule);
}

/* Stays OPAQUE. mix-blend-mode has nothing to blend against inside an
   isolated group with a see-through backdrop, and the fire's black
   ground would come back as a box. */
.section--raised { background: var(--ash-raised); }
.section p { max-width: var(--measure); }

/* ── Wave edge ──
   Waitlist, quest board, how-it-works and community open with a wave
   in place of the hairline: the section's own colour curving up into
   the one above, with a second wave in ember behind it, out of phase,
   so a sliver of heat shows along the seam. --ash and --ash-raised are
   one small step apart, and the ember layer is what makes the curve
   readable at all.

   One wave across the full width rather than a repeating tile, so it
   reads as a single sweep at any size. Alternate sections mirror it,
   so the page does not stack the same curve four times.

   Height is held by width and by viewport height, because the wave
   stands in the bottom padding of the section above (11vh, at least
   72px): at most 64px, and never more than 8vh. */
.section--wave {
  position: relative;
  border-top: 0;
  --wave-h: clamp(36px, min(5vw, 8vh), 64px);
  --wave-fill: var(--ash);
}
.section--wave.section--raised { --wave-fill: var(--ash-raised); }

/* The wave paths run past the bottom of their box, so the bottom row
   of the mask is fully opaque rather than anti-aliased into a line. */
.section--wave::before,
.section--wave::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  /* a pixel of overlap, so no seam shows between wave and section */
  bottom: calc(100% - 1px);
  height: var(--wave-h);
  pointer-events: none;
}
.section--wave::before {
  /* ends a few pixels above the front wave's base: where both boxes
     stopped on the same fractional pixel, the ember showed through the
     front's anti-aliased bottom row as a thin line */
  bottom: calc(100% + 3px);
  background: rgba(201,73,30,0.45);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 44' preserveAspectRatio='none'><path d='M0 18C70 0 120 38 182 20S282 2 320 24L320 60L0 60Z'/></svg>") 0 0 / 100% 100% no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 44' preserveAspectRatio='none'><path d='M0 18C70 0 120 38 182 20S282 2 320 24L320 60L0 60Z'/></svg>") 0 0 / 100% 100% no-repeat;
}
.section--wave::after {
  background: var(--wave-fill);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 44' preserveAspectRatio='none'><path d='M0 30C60 8 110 8 160 24S262 42 320 18L320 60L0 60Z'/></svg>") 0 0 / 100% 100% no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 44' preserveAspectRatio='none'><path d='M0 30C60 8 110 8 160 24S262 42 320 18L320 60L0 60Z'/></svg>") 0 0 / 100% 100% no-repeat;
}
.section--wave-flip::before,
.section--wave-flip::after { transform: scaleX(-1); }

/* ── What Flammis is ─────────────────────────────────── */

/* Four parts, read in order: the folklore, its number, what the
   campaign rewards, and the system it rewards it through. The model is
   the section's one memorable element -- everything else stays quiet
   enough to let it be. */
.about-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
  grid-template-areas:
    "intro   stat"
    "rewards model";
  column-gap: clamp(36px, 6vw, 96px);
  row-gap: clamp(44px, 6vw, 80px);
  align-items: start;
  max-width: 1240px;
  margin: 0 auto;
}

.about-intro   { grid-area: intro; }
.about-stat    { grid-area: stat; align-self: end; }
.about-model   { grid-area: model; }

/* The lists are 290px against a 750px model, which left a dead
   column beside the diagram. Held in view instead, so what the campaign
   rewards stays beside the system that rewards it while the diagram is
   read. Desktop only -- stacked, there is nothing to sit beside. */
.about-rewards {
  grid-area: rewards;
  position: sticky;
  top: clamp(96px, 14vh, 140px);
}

/* ── The number ──
   The bestiary claim, pulled out of the lede and set as a figure. It is
   the copy's own number, not a statistic added to dress the section. */
.about-stat { max-width: none; }
.about-stat-num {
  display: block;
  font-family: 'Anton', Impact, sans-serif;
  font-weight: 400;
  font-size: clamp(112px, 13vw, 176px);
  line-height: 0.82;
  letter-spacing: 0.005em;
}
.about-stat-cap {
  position: relative;
  display: block;
  max-width: 24ch;
  margin-top: 22px;
  padding-top: 16px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--bone-dim);
}
.about-stat-cap::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 40px;
  height: 2px;
  background: var(--ember);
}

/* ── What it rewards ──
   Two lists split by the same hairline the hero uses. The marks carry
   the meaning as well as the colour does -- a hollow ring and a struck
   line against a filled dot -- so the split survives without colour. */
.about-bridge { margin-bottom: 30px; }
/* the name, straight after the line it comes from */
.about-bridge + .about-kindlers { margin-top: -18px; }
.about-kindlers strong { color: var(--gold); font-weight: 600; }

.rewards-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.rewards-col { padding-right: clamp(18px, 2.4vw, 32px); }
.rewards-col + .rewards-col {
  padding-right: 0;
  padding-left: clamp(18px, 2.4vw, 32px);
  border-left: 1px solid var(--rule);
}

.rewards-col h3 { margin-bottom: 18px; }
.rewards-col[data-kind="no"] h3 { color: var(--bone-dim); }

.rewards-col ul { list-style: none; }
.rewards-col li {
  position: relative;
  padding-left: 22px;
  font-size: 16px;
  line-height: 1.45;
}
.rewards-col li + li { margin-top: 12px; }
.rewards-col li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.42em;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  box-sizing: border-box;
}

.rewards-col[data-kind="no"] li {
  color: var(--bone-dim);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(142,130,113,0.6);
}
.rewards-col[data-kind="no"] li::before { border: 1.5px solid var(--bone-dim); }

.rewards-col[data-kind="yes"] li { color: var(--bone); }
.rewards-col[data-kind="yes"] li::before { background: var(--ember); }

/* ── The model ──
   The glossary, drawn as the system it describes. Each row is still a
   term and its definition; the definition just carries a picture of
   itself, and the label between rows says how the terms connect. */
.model-row {
  padding: 24px 0 26px;
  border-top: 1px solid var(--rule);
}

/* how this row relates to the one above it */
.model-row[data-link]::before {
  content: attr(data-link);
  display: block;
  width: fit-content;
  margin-bottom: 16px;
  padding: 1px 0 1px 12px;
  border-left: 2px solid var(--ember);
  font-size: 13px;
  line-height: 1.5;
  color: var(--bone-dim);
}

.model-row dt {
  font-family: 'Anton', Impact, sans-serif;
  font-weight: 400;
  font-size: 22px;
  line-height: 1.1;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  margin-bottom: 18px;
}
.model-row dd p {
  max-width: 52ch;
  margin-top: 18px;
  color: var(--bone-dim);
}

/* Campaign: a track of four phases, the last one dashed because it is
   the next campaign, not this one. */
.model-cycle {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  list-style: none;
  overflow: hidden;           /* clips the marker's rail, never the labels */
}
.model-cycle li {
  position: relative;
  padding: 20px 8px 0 0;
  font-size: 14px;
  line-height: 1.35;
  color: var(--bone-dim);
}
.model-cycle li::before {     /* the phase */
  content: '';
  position: absolute;
  top: 4px;
  left: 0;
  right: 3px;
  height: 2px;
  background: rgba(237,227,211,0.24);
}
.model-cycle li:last-child::before {
  background: repeating-linear-gradient(90deg,
    rgba(237,227,211,0.24) 0 6px, transparent 6px 11px);
}
.model-cycle li::after {      /* where the phase begins */
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 2px;
  height: 10px;
  background: rgba(237,227,211,0.42);
}

/* The one moving thing in the section: an ember dot running a
   campaign's course, then the next one opening. The dot is drawn into
   a rail as wide as the track and the rail is translated, so it is a
   transform-only animation the compositor runs without touching layout.
   Not a glow -- a dot. */
.model-cycle::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 10px;
  height: 10px;
  background: radial-gradient(circle at 5px 5px, var(--ember) 0 4px, transparent 4.6px) no-repeat;
  animation: cycle-run 9s linear infinite;
  pointer-events: none;
}
@keyframes cycle-run {
  0%   { transform: translateX(0);    opacity: 0; }
  4%   { opacity: 1; }
  92%  { opacity: 1; }
  100% { transform: translateX(100%); opacity: 0; }
}

/* Quest: the kinds, as the pills the board itself uses. */
.model-kinds {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
}
.model-kinds li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  font-size: 14px;
  line-height: 1.3;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: rgba(237,227,211,0.03);
}
.model-kinds li::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ember);
}

/* Cinder: what happens to a claim. Credited is the only filled node;
   the last is dashed, connector and all, because it cannot happen yet
   -- the honest part of the diagram. States differ by fill and line
   style as well as colour. */
.model-states {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  list-style: none;
}
.model-states li {
  position: relative;
  padding: 24px 10px 0 0;
  font-size: 14px;
  line-height: 1.35;
}
.model-states li span { display: block; color: var(--bone-dim); }

.model-states li::before {    /* the node */
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  width: 12px;
  height: 12px;
  box-sizing: border-box;
  border: 1.5px solid var(--bone-dim);
  border-radius: 50%;
  background: var(--ash);
}
.model-states li::after {     /* the way to the next node */
  content: '';
  position: absolute;
  top: 5px;
  left: 12px;
  right: 0;
  height: 2px;
  background: rgba(237,227,211,0.24);
}
.model-states li:last-child::after { display: none; }

.model-states [data-state="claimed"]  { color: var(--bone-dim); }
.model-states [data-state="reviewed"]::before { border-color: var(--bone); }
.model-states [data-state="credited"]::before {
  background: var(--ember);
  border-color: var(--ember);
}
.model-states [data-state="credited"]::after {
  background: repeating-linear-gradient(90deg,
    rgba(237,227,211,0.24) 0 5px, transparent 5px 10px);
}
.model-states [data-state="later"] { color: var(--bone-dim); }
.model-states [data-state="later"]::before { border-style: dashed; }

@media (prefers-reduced-motion: reduce) {
  /* parked in "Runs" rather than removed, so the track still reads */
  .model-cycle::after { animation: none; opacity: 1; transform: translateX(37.5%); }
}

/* ── Waitlist ────────────────────────────────────────── */

/* Art goes behind the form, so the section is the positioning context
   and the content is lifted above it. */
.section--waitlist {
  position: relative;
  isolation: isolate;
  /* A bloom rising from the same floor the fire rises from, so the
     flame sits in heat rather than on a flat surface. Centred and gone
     by the corners: the section meets the next one on the same plain
     --ash edge as the sections around it, and only the strip
     under the fire runs warm -- where the fire itself is anyway.

     Layered into the section's own background, which keeps the base
     opaque and the screen blend intact. */
  background:
    radial-gradient(54% 64% at 50% 100%, rgba(43,11,4,1) 0%, rgba(43,11,4,0.44) 42%, transparent 74%),
    var(--ash);
}

/* Mount point for the art that goes behind the form. Kept deliberately:
   whatever sits here must stay clear of the status line and the note
   below, because bone-dim on ash-raised is only 4.75:1 to begin with and
   measured under the bar with even a 10%-opacity wash behind it. */
.waitlist-stage { position: relative; }

.waitlist-fire {
  position: absolute;
  left: 50%;
  /* rises from the floor of the section */
  bottom: 0;
  transform: translateX(-50%);
  /* Tall enough to carry from the section floor up to the input. Sized by
     HEIGHT with width following, so the 4:5 asset cannot overshoot the way
     width-driven sizing did. */
  height: clamp(300px, 62%, 460px);
  width: auto;
  z-index: 0;
  pointer-events: none;
  /* Black ground falls out, flame stays. This only works because no
     ancestor between here and .section--waitlist creates a stacking
     context -- mix-blend-mode blends with the backdrop inside the nearest
     one, and an ancestor with no background gives it nothing to blend
     against. That is why .waitlist-inner carries no z-index. */
  mix-blend-mode: screen;
  opacity: 0.92;
  /* Strong at the base and thinning as it climbs, which is how a flame
     actually behaves -- and what keeps the tips faint by the time they
     reach the form. Gone entirely before the lede. */
  -webkit-mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,0.85) 30%, rgba(0,0,0,0.42) 62%, transparent 88%);
          mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,0.85) 30%, rgba(0,0,0,0.42) 62%, transparent 88%);
}
.waitlist-fire img { display: block; height: 100%; width: auto; }

.waitlist-inner {
  position: relative;
  /* deliberately no z-index: it would make a stacking context and stop
     the fire's screen blend reaching the section background */
  max-width: 620px;
  margin: 0 auto;
  text-align: center;
}
.waitlist-inner .lede { margin: 0 auto; }
.waitlist-inner p { max-width: 52ch; margin-left: auto; margin-right: auto; }

.waitlist-form {
  position: relative;
  z-index: 1;                /* above whatever sits in the stage */
  display: flex;
  gap: 12px;
  margin: clamp(46px, 6vh, 62px) 0 14px;
}

.waitlist-form input {
  flex: 1;
  min-width: 0;
  font-family: inherit;
  font-size: 16px;          /* 16px stops iOS zooming the field on focus */
  color: var(--bone);
  /* Its own ground rather than transparent, so the field stays readable
     with the flame behind it. Matched to the status and note plates at
     0.93: once the fire rose from the section floor it got brighter here,
     and 0.90 measured 4.55:1 -- passing, but with nothing spare. */
  background: rgba(10,3,1,0.93);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 15px 22px;
  transition: border-color .2s ease;
}
.waitlist-form input::placeholder { color: var(--bone-dim); }
.waitlist-form input:hover { border-color: var(--bone-dim); }
.waitlist-form input:focus-visible { border-color: var(--gold); }

.waitlist-form .start { flex: none; }

/* Both of these stand in the flame's path, so they carry their own ground
   the way the input does -- the fire passes behind them rather than
   through them. bone-dim on ash-raised is 4.75:1 and has effectively no
   luminance headroom, so there is no "faint enough" setting that would
   have let fire sit behind this text.

   The status only becomes a badge (see Message badges) once it has
   something to say: data-tone is set at the same moment its text is. */
.form-status {
  min-height: 1.6em;
  font-size: 14px;
  margin-bottom: 10px;
}

#waitlist-note {
  display: inline-block;
  max-width: 46ch;
  padding: 8px 18px;
  border-radius: 14px;
  /* 0.90 measured 4.55:1 against a worst-case flame -- passing, but with
     nothing spare. 0.93 measures 4.74:1. */
  background: rgba(10,3,1,0.93);
}
/* Which providers are accepted, right under the field it is about. */
.field-hint {
  display: inline-block;
  margin: -4px auto 14px;
  padding: 3px 12px;
  border-radius: 10px;
  background: rgba(10,3,1,0.93);
  font-size: 13px;
  color: var(--bone-dim);
}

/* ── The code step ──
   Replaces the email form once a code is sent. Same field and button,
   so it reads as the same form moving on a step. */
.code-step { position: relative; z-index: 1; }
.code-sent {
  display: inline-block;
  margin: clamp(46px, 6vh, 62px) auto 0;
  padding: 7px 18px;
  border-radius: 14px;
  background: rgba(10,3,1,0.93);   /* the flame passes behind it */
  font-size: 15px;
}
/* Moves to its own line before it ever splits; only an address wider
   than the whole line breaks, and then anywhere. */
.code-sent strong {
  display: inline-block;
  max-width: 100%;
  font-weight: 600;
  color: var(--bone);
  overflow-wrap: anywhere;
}
.code-form { margin-top: 16px; }
.code-form input {
  text-align: center;
  letter-spacing: 0.4em;
  font-variant-numeric: tabular-nums;
}
.code-form input::placeholder { letter-spacing: 0.4em; }

.code-links {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 4px 10px;
  margin: 0 auto 14px;
  padding: 2px 14px;
  border-radius: 14px;
  background: rgba(10,3,1,0.93);
  font-size: 14px;
  color: var(--bone-dim);
}
.text-btn {
  min-height: 44px;                /* touch target, though it reads as a link */
  padding: 0 4px;
  font: inherit;
  color: var(--bone);
  background: none;
  border: 0;
  text-decoration: underline;
  text-decoration-color: rgba(237,227,211,0.35);
  text-underline-offset: 3px;
  cursor: pointer;
}
.text-btn:hover { color: var(--gold); text-decoration-color: currentColor; }
.text-btn[disabled] { opacity: .5; cursor: default; }

.signed-in {
  position: relative;
  z-index: 1;
  display: inline-block;
  margin: clamp(46px, 6vh, 62px) auto 14px;
  padding: 10px 20px;
  border-radius: 14px;
  background: rgba(10,3,1,0.93);
  font-size: 15px;
}
.signed-in p { margin: 0 auto; }
.signed-in a { color: var(--gold); text-decoration: underline; text-underline-offset: 3px; }

/* Sign out sits quietly under the line it belongs to; .text-btn gives
   it the 44px touch target. */
.signout-form { margin-top: 2px; }
.signout-form .text-btn { font-size: 14px; color: var(--bone-dim); }
.signout-form .text-btn:hover { color: var(--bone); }
.campaign-note p { margin: 0; }
.campaign-note .signout-form { margin-top: 4px; margin-left: -4px; }



/* ── Quest board ─────────────────────────────────────── */

.quests-head { max-width: 1240px; margin: 0 auto; }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ── Board ────────────────────────────────────
   Cards sit on --ash, darker than the --ash-raised section, which is what
   separates them. No shadows: the brief rules out the shadowed-card look,
   and a value step does the same job without one. */

.board {
  max-width: 1240px;
  margin: clamp(28px, 4vh, 44px) auto 0;
  display: grid;
  gap: clamp(14px, 1.8vw, 22px);
}

/* ── Balance card ── */

/* Smoked glass. The base stays --ash rather than a lighter translucent
   panel: the lighter version measured 4.42:1 on the 12px bone-dim labels,
   under the bar, and smoked glass is tinted dark anyway. What sells the
   material is the sheen and the lit edge, not the tint.

   Both are inset or flat -- no blur radius, nothing emissive -- so this
   spends nothing from the two-glow budget. */
.balance-card {
  position: relative;
  /* visible, so the mascot can rise out past the top edge; the sheen
     below takes the card's radius instead of relying on a clip */
  overflow: visible;
  padding: clamp(22px, 3vw, 32px);
  border-radius: 18px;
  background: var(--ash);
  border: 1px solid rgba(237,227,211,0.07);
  /* the pane's lit top and left edges, the way light catches real glass */
  box-shadow: inset 0 1px 0 rgba(237,227,211,0.16),
              inset 1px 0 0 rgba(237,227,211,0.06);
}

/* One diagonal sheen, entering from the TOP-RIGHT. It started top-left,
   which is exactly where the label sits, and measured 4.13:1 there. The
   card's right side carries no glyphs, so the light has somewhere to fall.
   Peak is 0.06 rather than 0.10 as well, which holds 4.55:1 even measured
   at its brightest point rather than relying on where the text happens to
   be. Pseudo-element, so the markup carries no decoration. */
.balance-card::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(218deg,
    rgba(237,227,211,0.060) 0%,
    rgba(237,227,211,0.015) 30%,
    transparent 58%);
}

/* content clears the sheen */
.balance-card > * { position: relative; z-index: 1; }

/* Flay, the yellow flame, climbing out of the card at its far right. The image
   is cut at the waist, and that flat cut sits exactly on the card's top
   border -- 1px above the padding box, so the border and the lit inner
   edge both run unbroken beneath it and read as the edge it is coming up
   through. It sits entirely outside the card, so it covers none of the
   card's text.

   Wide, the paragraph above stops well short of the card's right end,
   so there is room. Stacked, the paragraph runs the full width, and the
   card moves down to make the room instead (see the narrow block). */
.balance-card > .card-mascot {
  position: absolute;
  right: clamp(18px, 3vw, 40px);
  bottom: calc(100% + 1px);
  z-index: 2;
  width: 150px;
  height: auto;
  pointer-events: none;
}

.balance-label { font-size: 12px; line-height: 1.6; color: var(--bone-dim); }

.balance-figure {
  font-family: 'Anton', Impact, sans-serif;
  font-weight: 400;
  font-size: clamp(44px, 7vw, 68px);
  line-height: 1;
  letter-spacing: 0.01em;
  margin: 4px 0 18px;
}

.balance-card .rail {
  position: relative;
  height: 2px;
  background: var(--rule);
  overflow: hidden;
}
.balance-card .rail-fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--ember);
  transition: width .45s ease;
}

.balance-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px clamp(22px, 4vw, 52px);
  margin-top: 16px;
}
.balance-meta dt { font-size: 12px; line-height: 1.6; color: var(--bone-dim); }
.balance-meta dd {
  font-family: 'Anton', Impact, sans-serif;
  font-weight: 400;
  font-size: 22px;          /* Anton floor is 20 */
  line-height: 1.1;
  letter-spacing: 0.01em;
}

.campaign-note { margin-top: 16px; max-width: 52ch; }
.campaign-note a { color: var(--bone); border-bottom: 1px solid var(--rule); }
.campaign-note a:hover { border-color: var(--bone-dim); }
/* Signed out the card is the campaign's pitch; signed in it is the
   member's own figures. Both are in the markup, the attribute picks. */
.balance-card[data-registered="0"] .card-member { display: none; }
.balance-card[data-registered="1"] .card-anon   { display: none; }

.balance-unit {
  font-size: 0.42em;
  letter-spacing: 0.02em;
  color: var(--gold);
  vertical-align: 0.5em;
}
.card-anon-sub { max-width: 40ch; margin: -6px 0 18px; font-size: 15px; color: var(--bone-dim); }

/* ── Groups ──
   A count beside each heading, after the reference. It encodes real
   information -- how much is left against how much is done -- which is
   the only reason the device is allowed. */

.quest-group {
  padding: clamp(16px, 2vw, 22px) clamp(14px, 2vw, 22px) clamp(8px, 1.4vw, 14px);
  border-radius: 18px;
  background: var(--ash);
}

.group-head {
  display: flex;
  align-items: center;
  gap: 10px;
  /* the claimed group's head is a summary, so it is a tap target too */
  min-height: 44px;
  padding: 0 clamp(4px, 1vw, 10px);
  font-size: 14px;
  font-weight: 500;
  color: var(--bone-dim);
}

.group-count {
  min-width: 22px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(237,227,211,0.08);
  font-size: 12px;
  font-weight: 500;
  color: var(--bone);
  text-align: center;
}

/* native collapse on the claimed group -- no script involved */
details.quest-group > summary { list-style: none; cursor: pointer; }
details.quest-group > summary::-webkit-details-marker { display: none; }
details.quest-group > summary::after {
  content: '';
  width: 7px;
  height: 7px;
  margin-left: auto;
  border-right: 1.5px solid var(--bone-dim);
  border-bottom: 1.5px solid var(--bone-dim);
  transform: rotate(45deg) translateY(-2px);
  transition: transform .2s ease;
}
details.quest-group[open] > summary::after { transform: rotate(-135deg) translateY(1px); }
details.quest-group > summary:hover::after { border-color: var(--bone); }

/* the open group swaps its list for a line once nothing is left */
.group-empty {
  display: none;
  padding: 6px clamp(4px, 1vw, 10px) 14px;
  color: var(--bone-dim);
  font-size: 15px;
}
.quest-group[data-empty="1"] .quest-list { display: none; }
.quest-group[data-empty="1"] .group-empty { display: block; }

/* ── Rows ── */

.quest-list { list-style: none; }

.quest-row {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) auto auto;
  grid-template-areas: "badge body pill action";
  align-items: center;
  gap: 0 clamp(12px, 1.6vw, 20px);
  padding: 14px clamp(4px, 1vw, 10px);
  border-radius: 12px;
  transition: background .2s ease;
}
.quest-badge  { grid-area: badge; }
.quest-body   { grid-area: body; }
.quest-pill   { grid-area: pill; }
/* right-aligned, so an error note under the button keeps the button
   at the row's edge */
.quest-action { grid-area: action; text-align: right; }
.quest-row:hover { background: rgba(237,227,211,0.03); }

/* ── Badge ──
   Circular mark, after the reference. It carries the platform the quest
   actually happens on, so it identifies rather than decorates. */
.quest-badge {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(237,227,211,0.05);
  color: var(--bone-dim);
  transition: color .2s ease, background .2s ease;
}
.quest-badge svg { width: 19px; height: 19px; fill: currentColor; display: block; }
.quest-row:hover .quest-badge { color: var(--bone); background: rgba(237,227,211,0.08); }
.quest-row[data-state="verified"] .quest-badge { color: var(--ember); }
.quest-row[data-state="pending"]  .quest-badge { color: var(--gold); }

.quest-title {
  font-family: 'Inter', system-ui, sans-serif;
  font-weight: 600;
  font-size: 16px;
  line-height: 1.3;
  letter-spacing: 0;
  text-transform: none;
  margin-bottom: 2px;
}

/* A title that goes where the quest is done. Underlined only on hover:
   the arrow already says it leaves the page. */
.quest-link { transition: color .2s ease; }
.quest-link:hover { color: var(--gold); text-decoration: underline; text-underline-offset: 3px; }
.quest-link-arrow { margin-left: 5px; font-weight: 500; color: var(--gold); }
.quest-detail { font-size: 13.5px; line-height: 1.45; color: var(--bone-dim); max-width: 52ch; }
.quest-badge-auto { margin-top: 4px; color: var(--gold); }

/* ── Reward pill ──
   Outlined while open, filled once credited, and the credited one carries
   a "+" so the difference survives a greyscale render. */
.quest-pill {
  min-width: 54px;
  padding: 6px 13px;
  border-radius: 999px;
  border: 1px solid var(--rule);
  background: transparent;
  font-size: 14px;
  font-weight: 600;
  color: var(--bone);
  text-align: center;
  white-space: nowrap;
}
.quest-row[data-state="pending"] .quest-pill {
  border-color: rgba(232,162,61,0.45);
  color: var(--gold);
}
/* Tinted rather than solid ember. Bone on full ember measures 3.71:1 --
   under the bar, and the brief warns about text on ember for this exact
   reason. Solid gold would pass at 8.83:1 but gold is never a fill. The
   tint keeps the filled look against the outlined open pill, holds the
   border at full strength, and measures 11.3:1. */
.quest-row[data-state="verified"] .quest-pill {
  border-color: var(--ember);
  background: rgba(201,73,30,0.28);
  color: var(--bone);
}
.quest-row[data-state="rejected"] .quest-pill { opacity: .5; }

.quest-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px floor: padding alone left this at 35px, under the touch-target
     guideline on the device the brief actually targets */
  min-height: 44px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--ash);
  background: var(--bone);
  border: 0;
  border-radius: 999px;
  padding: 9px 20px;
  cursor: pointer;
  white-space: nowrap;
  transition: background .2s ease;
}
.quest-btn:hover { background: var(--gold); }
.quest-btn[disabled] { opacity: .5; cursor: default; background: var(--bone); }

.quest-state { font-size: 13.5px; font-weight: 500; white-space: nowrap; }
.quest-state--verified { color: var(--ember); }
.quest-state--pending  { color: var(--gold); }
.quest-state--rejected { color: var(--bone-dim); }

/* why a claim did not go through: an error badge under its button,
   held to the row's right edge */
.quest-error {
  display: flex;
  width: fit-content;
  max-width: 26ch;
  margin: 8px 0 0 auto;
  padding: 6px 11px 6px 8px;
  font-size: 12px;
}
.quest-error::before { width: 14px; height: 14px; }

/* ── Handles quest ──
   The one row that takes input. Its form spans the full row under the
   task, so the three fields get real width instead of being squeezed
   into the body column. */

.quest-row--handles { align-items: start; }

/* ── Referral quest ──
   The member's link under the row, full width like the handles form. */
.referral-panel {
  grid-column: 1 / -1;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
}
.referral-label { display: block; margin-bottom: 5px; font-size: 12px; line-height: 1.6; color: var(--bone-dim); }
.referral-row { display: flex; gap: 10px; }
.referral-row input {
  flex: 1;
  min-width: 0;
  font-family: inherit;
  font-size: 16px;
  color: var(--bone);
  background: rgba(237,227,211,0.04);
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 11px 14px;
}
.referral-row input:focus-visible { border-color: var(--gold); outline: none; }
.referral-row .quest-btn { flex: none; }
.referral-stats { margin-top: 8px; font-size: 13.5px; color: var(--bone-dim); }
.quest-row--handles .quest-badge,
.quest-row--handles .quest-pill,
.quest-row--handles .quest-action { margin-top: 2px; }

.handle-form {
  grid-column: 1 / -1;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
}

.handle-fields {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.handle-field { display: flex; flex-direction: column; gap: 5px; }
.handle-field label { font-size: 12px; line-height: 1.6; color: var(--bone-dim); }

.handle-field input {
  width: 100%;
  min-width: 0;
  font-family: inherit;
  font-size: 16px;          /* 16px stops iOS zooming the field on focus */
  color: var(--bone);
  background: rgba(237,227,211,0.04);
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 11px 14px;
  transition: border-color .2s ease;
}
.handle-field input::placeholder { color: var(--bone-dim); }
.handle-field input:hover { border-color: var(--bone-dim); }
.handle-field input:focus-visible { border-color: var(--gold); }

.handle-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 14px;
}
.handle-status { font-size: 13.5px; min-height: 1.4em; }

.quest-empty {
  max-width: 1240px;
  margin: 22px auto 0;
  color: var(--bone-dim);
  font-size: 17px;
}

/* ── How it works ────────────────────────────────────
   Numbered badge notched into the top-left of a tall art
   panel, after the ScaleFast reference.

   Panels are the mascots, full bleed, served at two widths
   through srcset. See assets/img/README.md for how they are
   framed and checked for legibility.

   TO SWAP AN IMAGE: replace the two files for that step and
   keep the names and the 4:5 framing (face in the upper
   third). Nothing in the CSS needs to change. */

.how-head { max-width: 1240px; margin: 0 auto clamp(34px, 5vh, 60px); }

.steps {
  list-style: none;
  max-width: 1240px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(20px, 2.4vw, 34px);
}

/* nothing left to push to the bottom now that the meta row is gone */
.step { display: block; }

/* ── The art panel ── */

/* The figure does NOT clip, so the badge can hang outside the panel's
   corner the way the reference has it. The panel clips, so the art and
   its rounded corners stay contained. */
.step-figure {
  position: relative;
  margin-bottom: 20px;
}

.step-panel {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: 16px;
  overflow: hidden;
  /* the images are shot on black, so the ground before one loads is too */
  background-color: var(--ink);
}

/* The mascot fills the panel edge to edge, the way the photographs
   used to, and the mock UI sits over the lower half. The images are
   framed for that: 4:5, the face in the upper half (eyes at ~35%), the
   body and the dark floor below, where the UI goes. */
.step-img {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 100%;
}

/* Graduated scrim, dark where the UI sits and clear over the face,
   so the mock UI reads against the mascot's bright body without a
   veil dulling the whole picture. */
.step-panel::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top,
    rgba(0,0,0,0.92) 0%,
    rgba(0,0,0,0.88) 42%,
    rgba(0,0,0,0.64) 56%,
    rgba(0,0,0,0) 68%);
}


/* ── Badge ──
   The ring is the section background, which reads as the panel being
   punched out around it -- the notch from the reference, without a
   mask or a shape file. */
.step-badge {
  position: absolute;
  /* hangs off the corner rather than sitting inside it */
  top: -14px;
  left: -14px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 6px solid var(--ash-raised);
  background: var(--ash);
  color: var(--bone);
  font-family: 'Anton', Impact, sans-serif;
  font-weight: 400;
  font-size: 20px;          /* Anton floor */
  line-height: 1;
  letter-spacing: 0.01em;
}

/* ── Mock UI inside the panels ── */

/* The UI holds the lower part of the panel, over the scrim, and leaves
   the mascot's face clear above it. */
.art {
  position: absolute;
  inset: auto 0 0 0;
  min-height: 44%;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  padding: 18px 18px 22px;
  text-align: center;
}

.art--field { gap: 12px; }
.art-field {
  width: 100%;
  max-width: 210px;
  padding: 11px 16px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-size: 13px;
  color: var(--bone-dim);
  background: rgba(10,3,1,0.42);
}
.art-btn {
  padding: 11px 20px;
  border-radius: 999px;
  background: var(--bone);
  color: var(--ash);
  font-size: 13px;
  font-weight: 600;
}

/* Step 1's panel is a link to the real form (see the markup), so its
   drawn field and button respond the way the real ones do. */
.step-link { display: block; border-radius: 16px; }
.step-link:focus-visible { border-radius: 16px; }
.step-link .art-field,
.step-link .art-btn { transition: background .2s ease, border-color .2s ease; }
.step-link:hover .art-field { border-color: var(--bone-dim); }
.step-link:hover .art-btn   { background: var(--gold); }

.art--pills {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-content: flex-end;
  /* Tighter than the other panels: five pills have to wrap into three
     rows under the mascot's face; at 6px/11px they took four at 1280
     and the top row sat on Flay's mouth.
     Type stays at 12px, the smallest the scale allows. */
  gap: 5px;
  padding: 12px 10px 18px;
}
.art-pill {
  padding: 5px 9px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: rgba(10,3,1,0.42);
  font-size: 12px;
  color: var(--bone);
}

.art--balance { gap: 7px; }
.art-num {
  font-family: 'Anton', Impact, sans-serif;
  font-weight: 400;
  font-size: 46px;
  line-height: 1;
  letter-spacing: 0.01em;
  color: var(--bone);
}
.art-label { font-size: 12px; line-height: 1.6; color: var(--bone-dim); }
.art-rail {
  position: relative;
  width: 100%;
  max-width: 150px;
  height: 2px;
  margin: 5px 0;
  background: var(--rule);
  overflow: hidden;
}
.art-rail-fill { position: absolute; inset: 0 auto 0 0; width: 100%; background: var(--ember); }

.art-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 14px 18px;
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: rgba(10,3,1,0.58);
}
.art-card-label { font-size: 12px; color: var(--bone-dim); }
.art-card-num {
  font-family: 'Anton', Impact, sans-serif;
  font-weight: 400;
  font-size: 34px;
  line-height: 1;
  letter-spacing: 0.01em;
}
/* one line, so the card stays low and clear of Flay's face */
.art-card-note { font-size: 11px; line-height: 1.5; color: var(--bone-dim); }

/* ── Text under the panel ── */

.step h3 { margin-bottom: 8px; }
.step > p { color: var(--bone-dim); font-size: 15px; line-height: 1.55; }


/* ── Community ───────────────────────────────────────
   Centred: three marks in a row, and the heading and lede
   centre with them so the block reads as one composition
   rather than a left-aligned heading over centred icons. */

.community-inner { max-width: 1240px; margin: 0 auto; text-align: center; }
.community-inner .lede { max-width: 54ch; margin-left: auto; margin-right: auto; }

/* Andra, the salamander, waving under the links: the page signing off, small enough to
   be a goodbye rather than a headline. No shadow and no glow -- the
   page's glow budget is spent on the hero flame and the CTA. The image
   carries its own transparency. */
.community-mascot {
  display: block;
  width: clamp(104px, 10vw, 136px);
  height: auto;
  margin: clamp(30px, 4.5vh, 48px) auto 0;
}
/* ── Mascot bios ──
   Each mascot is introduced once, beside where it appears: Flay by the
   quest card it climbs out of, Andra under the picture in Community. */
.mascot-bio {
  max-width: 52ch;
  font-size: 15px;
  line-height: 1.6;
  color: var(--bone-dim);
}
.mascot-bio strong { color: var(--bone); font-weight: 600; }

/* Andra's sits centred under the picture, like the section around it */
.mascot-bio--andra { margin: 14px auto 0; }

/* Flay's sits above the card on the left, bottom-aligned to it, while
   Flay rises out of the card's top-right corner. 58ch keeps it well
   clear of the mascot down to the narrowest desktop width. */
.mascot-bio--flay { max-width: 58ch; align-self: end; }

.community-links {
  list-style: none;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: clamp(26px, 6vw, 76px);
  margin-top: clamp(34px, 5vh, 60px);
}

/* No ring, box or card around the marks -- a border here would encode
   nothing. The 56px square is the touch target; only the mark shows. */
.community-link {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  color: var(--bone-dim);
  border-radius: 50%;
  transition: color .2s ease;
}
.community-link:hover { color: var(--gold); }

.community-link svg {
  display: block;
  width: 28px;
  height: 28px;
  fill: currentColor;
}

/* ── Footer ──────────────────────────────────────────── */

.site-footer {
  /* the bottom is the logo's, so the padding under it is a sliver */
  padding: var(--section-y) var(--gut) clamp(10px, 1.6vw, 22px);
  border-top: 1px solid var(--rule);
}

.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(2, minmax(0, 1fr));
  gap: clamp(32px, 5vw, 72px);
  max-width: 1240px;
  margin: 0 auto;
}

.footer-brand .meta { max-width: 34ch; }

.footer-nav { display: flex; flex-direction: column; align-items: flex-start; gap: 9px; }
.footer-nav a { font-size: 14px; color: var(--bone-dim); transition: color .2s ease; }
.footer-nav a:hover { color: var(--bone); }

/* Inter, not Anton: Anton is not allowed below 20px and this is a
   14px label. Small quiet headings go to Inter by design. */
.footer-heading {
  font-family: 'Inter', system-ui, sans-serif;
  font-weight: 500;
  font-size: 14px;
  line-height: 1.6;
  letter-spacing: 0;
  text-transform: none;
  color: var(--bone);
  margin-bottom: 3px;
}

.footer-legal {
  max-width: 1240px;
  margin: clamp(44px, 7vh, 80px) auto 0;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
}
.footer-year { display: block; margin-top: 6px; }
.footer-privacy {
  display: inline-block;
  margin-top: 10px;
  color: var(--bone);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 3px;
}
.footer-privacy:hover { text-decoration-color: currentColor; }

/* the note under the waitlist form links to the privacy page */
#waitlist-note a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }

/* ── Legal pages (privacy) ──
   Plain reading: one column at a comfortable measure, Anton only for the
   headings, and the site's own type scale everywhere else. */
.legal {
  max-width: 76ch;
  margin: 0 auto;
  padding: clamp(28px, 6vh, 64px) var(--gut) var(--section-y);
}
.legal h1 { font-size: clamp(44px, 7vw, 76px); margin-bottom: 10px; }
.legal-updated { margin-bottom: 26px; }
.legal .lede { margin-bottom: 18px; }
.legal h2 {
  font-size: clamp(24px, 3vw, 30px);
  margin: clamp(36px, 5vh, 52px) 0 12px;
  scroll-margin-top: 20px;
}
.legal p, .legal li { color: var(--bone-dim); }
.legal p + p { margin-top: 12px; }
.legal ul { padding-left: 1.2em; }
.legal li + li { margin-top: 8px; }
.legal li::marker { color: var(--ember); }
.legal strong { color: var(--bone); font-weight: 600; }
.legal a { color: var(--bone); text-decoration: underline; text-decoration-color: var(--rule); text-underline-offset: 3px; }
.legal a:hover { text-decoration-color: currentColor; }
.legal code { font-size: 0.92em; color: var(--bone); }

/* Scrolls sideways on its own if a mid-size screen is still too narrow,
   rather than pushing the page. */
.legal-table { overflow-x: auto; margin-top: 4px; }
.legal-table:focus-visible { outline-offset: 2px; }
.legal table { width: 100%; min-width: 560px; border-collapse: collapse; font-size: 15px; line-height: 1.55; }
.legal th {
  text-align: left;
  font-size: 13px;
  font-weight: 500;
  color: var(--bone-dim);
  padding: 0 14px 10px 0;
  border-bottom: 1px solid var(--rule);
}
.legal td { vertical-align: top; padding: 14px 14px 14px 0; border-bottom: 1px solid var(--rule); color: var(--bone-dim); }
.legal td:first-child { color: var(--bone); font-weight: 500; width: 26%; }
.legal td:nth-child(2) { width: 24%; }

/* Phones: each row becomes a short card -- what, then when and why with
   their labels -- instead of a table scrolled sideways. */
@media (max-width: 620px) {
  .legal table { min-width: 0; }
  .legal thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .legal tr, .legal td { display: block; width: auto !important; }
  .legal tr { padding: 14px 0; border-bottom: 1px solid var(--rule); }
  .legal td { padding: 0; border: 0; }
  .legal td + td { margin-top: 6px; }
  .legal td:nth-child(2)::before { content: 'When: '; color: var(--bone); }
  .legal td:nth-child(3)::before { content: 'Why: '; color: var(--bone); }
}

/* The full logo across the whole footer, edge to edge inside the page
   gutter -- deliberately not held to the 1240px column the rest of the
   footer uses, so it is the width of the screen it is on.

   Bone at low strength, fading toward the bottom, so it reads as the
   floor the page stands on rather than a second headline: the footer's
   text stays the thing you read. No glow -- that budget is the hero
   flame and the CTA. */
.footer-logo {
  margin-top: clamp(40px, 7vh, 80px);
  color: rgba(237,227,211,0.16);
  pointer-events: none;
  user-select: none;
}
.footer-logo-art {
  display: block;
  width: 100%;
  height: auto;
  fill: currentColor;
  -webkit-mask-image: linear-gradient(to bottom, #000 20%, rgba(0,0,0,0.3) 100%);
          mask-image: linear-gradient(to bottom, #000 20%, rgba(0,0,0,0.3) 100%);
}

/* ====================================================================
   NARROW
   ==================================================================== */

@media (max-width: 900px) {
  .split {
    grid-template-columns: 1fr;
    align-items: stretch;
    padding-bottom: 8px;
  }
  .copy-col h1::before,
  .copy-col .actions::after { display: none; }

  /* Stacked, the stage is the band above the copy. Slightly taller than
     before because it now holds the whole video rather than letting it
     run up under the masthead. The ash fade that used to sit here hid
     the old object's drips before the text; the video can no longer
     reach the text, and the fade was flattening the hero gradient's
     floor on phones. */
  .stage-col { min-height: 48svh; }

  /* the paragraph above runs the full width here, so the card drops to
     give the mascot its own space rather than sitting on the text */
  .balance-card { margin-top: 84px; }
  .balance-card > .card-mascot { width: 100px; }
  /* Flay's bio drops under the card here: there is no room beside the
     mascot on a narrow screen. */
  .mascot-bio--flay { order: 1; }
  .board > .quest-group { order: 2; }
  /* the side links used to sit between the mascot and the headline; with
     them gone, this is what keeps Flay's feet off the headline */
  .copy-col { padding: clamp(18px, 3.2vh, 30px) var(--gut) 0; }
  .primary-nav { display: none; }

  .about-grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "intro" "stat" "rewards" "model";
  }
  .about-stat { align-self: start; }
  .about-rewards { position: static; }
  .hero .lede { max-width: 40ch; }

  .footline { padding-bottom: 22px; }
  .scroll-cue { display: none; }

  .about-grid { grid-template-columns: 1fr; }

  /* four art panels will not hold their shape on a tablet */
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* three columns of figures do not survive a phone; stack them and let
     the progress rail run full width where it is actually readable */
  .campaign {
    grid-template-columns: 1fr;
    gap: 22px;
    align-items: start;
  }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .footer-brand { grid-column: 1 / -1; }
}

/* Short phones. At 360x640 the stage's 48svh pushed the waitlist button
   to y=702, past the fold, so the one action the hero exists for needed a
   scroll to find. The mascot gives up some height here instead; taller
   phones keep the full band. */
@media (max-width: 900px) and (max-height: 700px) {
  .stage-col { min-height: 35svh; }
}

@media (max-width: 620px) {
  /* two lists side by side stop being a comparison at this width and
     start being two cramped columns */
  .rewards-grid { grid-template-columns: minmax(0, 1fr); row-gap: 26px; }
  .rewards-col { padding-right: 0; }
  .rewards-col + .rewards-col {
    padding-left: 0;
    padding-top: 26px;
    border-left: 0;
    border-top: 1px solid var(--rule);
  }

  .waitlist-form { flex-direction: column; }
  .waitlist-form .start { width: 100%; }

  /* Pill and action drop under the task, but stay on ONE row together:
     the pill sits against the text column and the button against the
     right edge, so the reward and the thing that earns it read as a pair
     rather than as two stacked blocks. */
  .quest-row {
    grid-template-columns: 44px minmax(0, 1fr) auto;
    grid-template-areas:
      "badge body body"
      ".     pill action";
    row-gap: 12px;
  }
  .quest-badge  { align-self: start; }
  .quest-pill   { justify-self: start; }
  .quest-action { justify-self: end; }

  /* three handle fields will not hold their shape on a phone */
  .handle-fields { grid-template-columns: 1fr; }

  /* A row you swipe through instead of a column four panels tall (that
     was 2,800px on a phone). Each step is 82% wide so the next one peeks
     in and says there is more; it snaps step by step, runs to the screen
     edges, and keeps room above for the badges that hang off each
     corner. The panels stay 4:5: anything wider crops the mascot's head. */
  .steps {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 82%;
    gap: 16px;
    margin: 0 calc(-1 * var(--gut));
    padding: 16px var(--gut) 6px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gut);
    scrollbar-width: none;
  }
  .steps::-webkit-scrollbar { display: none; }
  .steps:focus-visible { outline-offset: -2px; }
  .step { scroll-snap-align: start; }

  .community-links a { flex-direction: column; gap: 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-cue svg, .ticker .caret { animation: none; }
  .start, .quest-btn, .waitlist-form input { transition: none; }
}
