/* =========================================================
   rezumo — landing page styles

   AURORA — one field
   Four hues, dispersed: violet leading, blue supporting, azure answering it
   across the lower page, and one warm trace kept in reserve for the CTA, the
   site mock and the close. They are painted as soft ellipses in one continuous
   field, once, across the whole document, so the light never meets an edge.
   They used to converge on the pill in the headline as four conic beams —
   four hard edges meeting at a point read as a shape sitting behind the
   headline rather than as light, and the pages whose hero has no pill had no
   geometry to justify it. Pure white is spent only on cards, so every surface
   floats off the field rather than sitting in it.

   1. Tokens
   2. Reset, base, light layers
   3. Layout helpers
   4. Buttons
   5. Nav
   6. Hero
   7. Stats
   9. What it is
   11. Final CTA
   12. Footer
   13. Waitlist dialog
   14. Responsive
   15. Reduced motion
   ========================================================= */

/* ---------------- 1. Tokens ---------------- */
:root {
  /* violet leads and blue supports. They are one step apart on the wheel —
     254deg against 230deg — so every gradient between them stays a single
     colour of light, and blue is still everywhere, which is what keeps the
     blue logo mark from reading as an orphan. */
  --violet: #5B34D6;
  --violet-deep: #3A1BA8;
  --blue: #2B4EFF;
  --blue-dark: #1E3AE0;
  --blue-deep: #1730BC;
  /* the two hues the pair is answered by. No hue twice in a row across the
     field — that is what keeps the set from reading as a smear. Recorded here
     as the direction's palette; the field and washes need alpha, so most uses
     are the rgba() form of these same values. Warm is the only hue on the page
     that is not cool, which is the whole of its job: it is scarce on purpose
     and it is what stops the rest reading as one wash. */
  --warm: #FF7A50;
  --azure: #3E9BFF;
  --live: #14A06B;
  /* the only red on the site. It is spent on one thing — telling a student
     their form is not through yet — so it is a deep rose rather than a
     warning red, and it still reads against white at 5.4:1. */
  --alert: #C0334D;
  --ink: #0B1230;
  --body: #444B66;

  /* the ground. Not white: white is now spent only on cards and buttons,
     so every surface reads as a page lifted off the canvas. */
  --canvas: #F5F7FE;
  --white: #FFFFFF;

  /* hairlines and washes are alpha rather than fixed greys, so one value
     holds on the canvas, inside a white card and out in the light alike */
  --border: rgba(11, 18, 48, 0.11);
  --tint: rgba(43, 78, 255, 0.07);

  /* paper is replaced, not restyled: the lines are dead at the token level
     too, so anything reaching for them comes up empty */
  --grid: transparent;
  --grid-on-band: transparent;

  --display: "Bricolage Grotesque", system-ui, -apple-system, sans-serif;
  --sans: "Hanken Grotesk", system-ui, -apple-system, sans-serif;
  /* structural voice: labels, chips, states and the URL — anything that is
     a record rather than a sentence */
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* the measured vertical centre of the pill inside the hero. The panel of
     light the headline sits on and the column under the buttons are both
     placed from it — re-measure after any copy change to the h1. */
  --fan-hero: 257px;
  /* the centre of the white button inside the CTA band */
  --cta-y: 270px;

  /* how much colour the top of the page carries. A phone has no room for
     field beside the copy, so the wash does more of the work there. */
  --wash: 0.07;

  --nav-h: 72px;
  --r-pill: 999px;
  --r-card: 22px;
  --r-band: 32px;
}

/* ---------------- 2. Reset, base, light layers ---------------- */
*, *::before, *::after { box-sizing: border-box; }

body, h1, h2, h3, h4, p, figure, blockquote, ul { margin: 0; }
ul { padding: 0; list-style: none; }
button { font: inherit; color: inherit; }
a { color: inherit; }
svg { max-width: 100%; }

html { scroll-behavior: smooth; }

/* the flat canvas goes on the body itself so nothing anywhere can fall
   through to white; .fx carries the wash on top of it. `isolation`, not just
   `relative`: it gives the page its own stacking context, so .fx sinks behind
   the content but never behind the canvas. */
/* the page fills the window even when it is short. The light layers are sized
   to the body box, so a page with less than a screen of content used to end the
   light partway down and leave bare canvas under the footer. `.fx` and
   `.fx-top` are absolute, so the column below is header / main / footer. */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  position: relative;
  isolation: isolate;
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.65;
  color: var(--body);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
}

/* grow, never shrink, and measure from its own content: `flex: 1` would zero
   the basis and hand the long pages' height to the flex algorithm instead */
main { flex: 1 0 auto; }

/* ---- Light layers ----
   two layers the size of the document, one behind the page and one in front,
   so the light is free of any section's clipping */
.fx,
.fx-top {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* clipped to itself: the top field is scaled about its centre, and at the top
   of its breath it would otherwise push the scrollable area a pixel wider than
   the viewport. The layer already is the document's own box. */
.fx {
  z-index: -1;
  overflow: clip;
}
.fx-top { z-index: 5; }

/* ---- The light field ----
   Every section used to paint its own glow. Two glows that each begin and end
   at their own box meet at mismatched alpha, and that meeting is a hard line
   straight across the page — one at each end of the stats, one at the footer.
   So there is one field now and it is painted once, here, on the layer that
   already is the document.

   Every vertical radius below is a length, not a percentage. A percentage is
   read against this layer's own height, which is the height of the document —
   so the same rule that was a soft 1000px wash down the home page collapsed
   into a 200px blotch on Pricing, and the short pages came out as three dense
   spots with a hole between them. Lengths hold their size on a page of any
   length: on a long one the blobs sit apart and tile the run, on a short one
   they overlap into a single even wash. Only the placement stays in percent,
   which is what spreads them over whatever height the page turns out to be.

   Eight of them, alternating side to side and never the same hue twice in a
   row: violet leads, blue supports, azure answers across the lower page, and
   warm crosses twice because it is the only hue here that is not cool and it
   is what keeps the rest from reading as one wash. */
.fx {
  background:
    /* the top of the page: violet over a step of blue */
    linear-gradient(180deg, rgba(91, 52, 214, var(--wash)) 0px, rgba(91, 52, 214, 0) 780px),
    linear-gradient(180deg, rgba(43, 78, 255, 0.035) 0px, rgba(43, 78, 255, 0) 940px),
    /* violet, upper left */
    radial-gradient(ellipse 55% 420px at 2% 12%, rgba(91, 52, 214, 0.20), transparent 72%),
    /* blue, upper right */
    radial-gradient(ellipse 52% 400px at 98% 22%, rgba(43, 78, 255, 0.18), transparent 72%),
    /* warm through the middle — scarce, and the only warm note up here */
    radial-gradient(ellipse 44% 340px at 60% 36%, rgba(255, 122, 80, 0.15), transparent 70%),
    /* violet again, mid left, the widest of the eight */
    radial-gradient(ellipse 58% 450px at 0% 50%, rgba(91, 52, 214, 0.22), transparent 72%),
    /* azure answering it on the right */
    radial-gradient(ellipse 54% 420px at 100% 62%, rgba(62, 155, 255, 0.22), transparent 72%),
    /* blue low and inboard: this one exists to fill the middle, which is
       where the old set left a bare patch on every short page */
    radial-gradient(ellipse 50% 390px at 36% 76%, rgba(43, 78, 255, 0.16), transparent 72%),
    /* violet, lower right */
    radial-gradient(ellipse 52% 400px at 90% 88%, rgba(91, 52, 214, 0.20), transparent 72%),
    /* the close: warm leaving at the bottom left */
    radial-gradient(ellipse 46% 340px at 10% 99%, rgba(255, 122, 80, 0.15), transparent 74%),
    /* the last deepening under the footer, also a length: as a percentage it
       was 900px of falloff on the home page and 200px on Pricing */
    linear-gradient(0deg, rgba(91, 52, 214, 0.13) 0px, rgba(91, 52, 214, 0) 420px),
    var(--canvas);
}

/* ---- The top field ----
   These four hues used to arrive as conic wedges converging on the pill. Four
   hard-edged beams meeting at a point read as a shape sitting behind the
   headline rather than as light, and on the pages whose hero has no pill to
   converge on there was no geometry to justify it at all.
   Same four hues, same left-to-right order — warm, violet, blue, azure — but
   dispersed as soft ellipses across the top of the page. This is how the field
   under it is already painted, which is why the page reads as light everywhere
   past the first screen and only ever looked constructed up here. It lives on
   .fx and not on .hero because the hero clips, and light that stops dead at a
   section edge stops being light. */
.fx::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1180px;
  pointer-events: none;
  background:
    /* warm high and left — the only hue here that is not cool, kept scarce */
    radial-gradient(ellipse 50% 400px at 14% 14%, rgba(255, 122, 80, 0.30), transparent 70%),
    /* violet leads: the widest of the six, down the left */
    radial-gradient(ellipse 56% 460px at 0% 42%, rgba(91, 52, 214, 0.42), transparent 72%),
    /* blue supports it from the opposite corner */
    radial-gradient(ellipse 52% 420px at 88% 16%, rgba(43, 78, 255, 0.36), transparent 72%),
    /* azure answers the blue, lower and further right */
    radial-gradient(ellipse 54% 440px at 100% 58%, rgba(62, 155, 255, 0.38), transparent 72%),
    /* two through the middle, held a step back: the point is dispersal, and
       colour only around the edges is just a vignette. Wide enough to reach
       the four above them — small ones here read as spots, not as light. */
    radial-gradient(ellipse 44% 380px at 60% 76%, rgba(91, 52, 214, 0.25), transparent 72%),
    radial-gradient(ellipse 42% 360px at 26% 88%, rgba(43, 78, 255, 0.22), transparent 72%);
  /* gone by its own bottom edge. This layer and the field beneath it are two
     separate paints, and any alpha still left where this one stops is a hard
     line straight across the page — the seam the field was built to avoid. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 58%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 58%, transparent 100%);
  /* scaled about its own middle: with nothing converging any more there is no
     point to hold still, and the drift only has to stay off the copy */
  transform-origin: 50% 40%;
  animation: aurora-breathe 34s ease-in-out infinite alternate;
}

@keyframes aurora-breathe {
  from { transform: scale(1); opacity: 0.86; }
  to { transform: scale(1.06); opacity: 1; }
}

/* ---- Grain ----
   above everything, type included. Two jobs: it gives the mesh tooth, which
   is most of the difference between light and a gradient, and it breaks the
   banding a conic gradient shows across a 900px run. */
.fx-top {
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23a)'/%3E%3C/svg%3E");
}

h1, h2 { font-family: var(--display); color: var(--ink); }
h3, h4 { font-family: var(--sans); color: var(--ink); }

h1 {
  font-size: clamp(2.75rem, 6.5vw, 4.75rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
}

h2 {
  font-size: clamp(1.9rem, 3.5vw, 2.6rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

/* electric blue at 11px cannot hold 4.5:1 once the ground is tinted; the deep
   indigo already in the set can, and reads as the same family a step back */
.eyebrow {
  font-family: var(--mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 700;
  color: var(--blue-deep);
  margin-bottom: 0.75rem;
}

a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 3px;
}

/* ---------------- 3. Layout helpers ---------------- */
.container {
  width: 100%;
  max-width: 1280px;
  margin-inline: auto;
  padding-inline: 20px;
}

.section { padding-block: 110px; }

section[id] { scroll-margin-top: calc(var(--nav-h) + 16px); }

/* ---------------- 4. Buttons ---------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.8rem 1.6rem;
  /* the join buttons are <button>s now, and a button's default face is the
     browser's grey. Named here rather than on each variant so the class means
     the same thing on whichever element it lands. */
  background-color: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  /* a button's label is the name of a control, not a sentence to quote. Left
     selectable, a drag across one paints a square block of highlight inside a
     pill, and the shape comes apart. */
  -webkit-user-select: none;
  user-select: none;
  /* and on a phone, the grey box a tap draws is that same square */
  -webkit-tap-highlight-color: transparent;
  transition: background-color .18s ease, background-position .3s ease, color .18s ease, border-color .18s ease, transform .18s ease;
}

/* These are real buttons now, so they take focus — and the dialog hands focus
   back to whichever one opened it, which means the ring is seen often enough
   to have to be part of the design rather than the browser's guess at one. An
   outline (not a shadow) so it follows the pill, and offset so it reads as a
   ring around the key rather than a second border on it. */
/* A key's ring is drawn, not outlined. An outline is centred on the border
   box, and a key's visible shape is that box plus the 3px lip hanging under
   it — so no single outline-offset can sit evenly: the gap comes out `offset`
   above and `offset - 3` below, and widening the offset only opens the top
   further while the bottom stays pinned on the lip. A ring of its own can be
   given that 3px back at the bottom and so sits square on what is seen. */
.btn { position: relative; }

.btn:focus-visible { outline: none; }

.btn:focus-visible::after {
  content: "";
  position: absolute;
  inset: -5px;
  border: 2px solid var(--violet);
  /* its own box is taller than the key's, and 999px clamps to half of
     whatever it is given, so the ring is a stadium around a stadium */
  border-radius: var(--r-pill);
  pointer-events: none;
}

/* the lip is on the primary key only, so only it is owed the extra room */
.btn-primary:focus-visible::after { inset: -5px -5px -8px; }

/* violet on violet is nothing at all: on the CTA band the ring is the light */
.btn-white:focus-visible::after { border-color: var(--white); }

/* hard offset edge, no blur — the button reads as a physical key that
   presses down. Used only here, on the primary action. The key now also has
   to lift off a coloured ground, so a bloom goes under it, and the face
   carries the same violet-into-blue run as the pill: violet holds the first
   third and blue only arrives at the far edge, so the key reads violet and
   the blue is the travel. A gradient cannot cross-fade, so it is laid down at
   twice the width and slid on hover — the light crosses the key instead of
   the colour flicking over, and the slide is what brings the blue up. */
.btn-primary {
  background-image: linear-gradient(100deg, var(--violet) 0%, var(--violet) 30%, var(--blue) 100%);
  background-size: 200% 100%;
  background-position: 0% 0%;
  color: var(--white);
  box-shadow: 0 3px 0 var(--violet-deep), 0 14px 30px -10px rgba(91, 52, 214, 0.50);
}
.btn-primary:hover {
  background-position: 100% 0%;
  transform: translateY(-1px);
  box-shadow: 0 4px 0 var(--violet-deep), 0 18px 34px -10px rgba(91, 52, 214, 0.55);
}
.btn-primary:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 var(--violet-deep), 0 8px 18px -8px rgba(91, 52, 214, 0.45);
}

/* frosted, not solid: the outline button sits out in the light, so it is one
   of the few things that lets some of it through */
.btn-secondary {
  background: rgba(255, 255, 255, 0.86);
  border-color: rgba(11, 18, 48, 0.14);
  color: var(--ink);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.btn-secondary:hover {
  border-color: var(--violet);
  color: var(--violet);
  transform: translateY(-1px);
}

/* a black shadow reads as dirt on a coloured band; this one is tinted to it */
.btn-white {
  background: var(--white);
  color: var(--violet);
  box-shadow: 0 10px 26px rgba(58, 27, 168, 0.32);
}
.btn-white:hover { transform: translateY(-1px); }

.btn-sm {
  padding: 0.6rem 1.15rem;
  font-size: 0.875rem;
}

/* ---------------- 5. Nav ---------------- */
/* the bar has no edge of its own. A 1px rule across the full width of a page
   made of light is the one thing that reads as a bug, so the frost and the
   filter both live on a masked layer that dissolves before it reaches the
   bar's bottom: the wash and the top of the fan carry straight through and
   out the other side, and nothing steps. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
}

.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* thin, and saturated rather than whitened, so what reads through the bar
     is the hero's own colour and not a grey of it */
  background: rgba(248, 250, 255, 0.60);
  -webkit-backdrop-filter: saturate(184%) blur(12px);
  backdrop-filter: saturate(184%) blur(12px);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 56%, rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 56%, rgba(0, 0, 0, 0) 100%);
}

/* the separation, in place of the rule. It starts inside the bar, where the
   frost is already dissolving, peaks on the bar's own bottom edge and is gone
   twenty pixels below it, so neither of its sides begins abruptly. A
   box-shadow cannot do this: it is clipped at the border box, so it always
   arrives at full strength exactly where the line used to be. */
.site-header::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 56%;
  height: 54px;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(11, 18, 48, 0) 0%,
    rgba(11, 18, 48, 0.05) 58%,
    rgba(11, 18, 48, 0) 100%);
}

/* three tracks rather than space-between: with equal 1fr flanks the centre
   column is centred on the bar itself, so the links stay put no matter how
   much the brand on one side and the two buttons on the other differ. */
.nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 20px;
  min-height: var(--nav-h);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  flex: none;
  text-decoration: none;
}
.brand .mark { display: block; flex: none; }
.brand .wordmark {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* the wrapper only exists so one button can collapse both clusters on a
   phone. Out of the way here, so the links and the buttons are grid items in
   their own right and can each take a track. */
.nav-menu { display: contents; }

.nav-links {
  display: flex;
  align-items: center;
  gap: 26px;
  justify-self: center;
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: 18px;
  justify-self: end;
}

.nav-links a,
.nav-login {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--body);
  text-decoration: none;
}
.nav-links a:hover,
.nav-login:hover { color: var(--ink); }

/* the current page. Every page already declares its own URL in front matter
   and base.html puts it on <body>, so the header stays one shared partial with
   no per-page markup: the link whose href matches the body's path is the one
   you are on. CSS cannot match one element's attribute against another's, so
   the three pairs are written out — there are only ever three. */
[data-path="/explore"] .nav-links a[href="/explore"],
[data-path="/pricing"] .nav-links a[href="/pricing"],
[data-path="/about"] .nav-links a[href="/about"] {
  color: var(--ink);
  font-weight: 600;
}

.nav-toggle {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 42px;
  height: 38px;
  flex: none;
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  width: 16px;
  height: 2px;
  border-radius: 2px;
  background: var(--ink);
}

/* ---------------- 6. Hero ---------------- */
/* a stacking context, so the two hero layers can be ordered under the copy:
   the lit panel, then the column of light over it, then the words */
/* no overflow clip: it existed to crop the graph paper, which is retired, and
   all it does now is cut the light panel's blurred tail off mid-alpha and
   leave an edge where the hero meets the section below. Light runs past the
   section boundary here for the same reason it does everywhere else. */
.hero {
  position: relative;
  z-index: 0;
  padding-top: 76px;
  text-align: center;
}

/* the graph paper's old home becomes the panel of light the headline sits on.
   Page-shaped rather than blob-shaped, and it is what guarantees the display
   type keeps its contrast where the beams are strongest. */
.hero::before {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(var(--fan-hero) - 140px);
  z-index: -2;
  width: min(760px, 88%);
  height: 390px;
  transform: translateX(-50%);
  pointer-events: none;
  /* reaches zero at its own bottom edge on purpose: the panel is wider than
     the headline and the hero clips, and a blurred edge still carrying alpha
     where it meets the clip leaves a hard line across the page. The long tail
     is what the sub-head, the buttons and the note stand on. */
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.90) 0%,
    rgba(255, 255, 255, 0.88) 34%,
    rgba(255, 255, 255, 0.74) 66%,
    rgba(255, 255, 255, 0.54) 88%,
    rgba(255, 255, 255, 0) 100%);
  border-radius: 44px;
  filter: blur(38px);
}

/* one soft column of light straight down through the buttons, pointing at the
   product below. It starts under the pill, so it lights the way out of the
   hero without washing the headline. */
.hero::after {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(var(--fan-hero) + 26px);
  z-index: -1;
  width: 300px;
  height: 260px;
  transform: translateX(-50%);
  pointer-events: none;
  /* fades in as well as out. Starting at full alpha drew a hard 300px-wide
     line straight across the page 26px under the pill — the horizontal mask
     feathers the sides but nothing was feathering the top. */
  background: linear-gradient(180deg,
    rgba(91, 52, 214, 0) 0%,
    rgba(91, 52, 214, 0.14) 30%,
    /* 72%, not lower: the note sits just past this stop and the light has to
       be gone by the time it gets there */
    rgba(91, 52, 214, 0) 72%);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 24%, #000 76%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 24%, #000 76%, transparent);
}

.hero-inner { position: relative; }

/* the hero carries no bottom padding — the section under it supplies the gap.
   On the pages that are still only a header, there is no section under it, so
   the footer would come straight up against the sub-head. It also takes the
   slack from the flex column, which keeps the footer at the bottom. */
.hero--solo { padding-bottom: 150px; }

/* frosted, not solid: the badge sits furthest out into the light */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0.4rem 0.95rem;
  background: rgba(255, 255, 255, 0.82);
  border: 1px solid rgba(11, 18, 48, 0.10);
  border-radius: var(--r-pill);
  font-size: 0.82rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--body);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 2px 12px -4px rgba(11, 18, 48, 0.10);
}
.badge-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--violet), var(--azure));
  flex: none;
}

.hero h1 {
  max-width: 17ch;
  margin: 1.5rem auto 0;
}
/* the address the whole page is about, set as an address. The claim is the
   display face; the line under it is the mono, which is the voice this file
   already gives every other record — the URL in the copy, the chips, the
   saved state. A sentence promises the thing, a record is the thing. */
.hero-url {
  display: block;
  margin-top: 0.3em;
  font-family: var(--mono);
  font-weight: 700;
  font-size: 0.7em;
  letter-spacing: -0.045em;
  /* an address that breaks across two lines stops being an address */
  white-space: nowrap;
  color: var(--ink);
}

/* where the four beams land. Violet enters at the left of the pill and only
   reaches blue at the far right — the same order as the fan above it, and the
   same weighting as the key below it.
   No tilt: the pill is a segment of an address now, and an address that is
   not on one baseline stops reading as somewhere you could actually go. */
.hero h1 .pill {
  display: inline-block;
  padding: 0 0.3em;
  border-radius: var(--r-pill);
  background: linear-gradient(100deg, var(--violet) 0%, var(--blue) 96%);
  color: var(--white);
  box-shadow: 0 10px 26px -8px rgba(72, 44, 224, 0.55);
}

.hero-sub {
  max-width: 54ch;
  margin: 1.4rem auto 0;
  font-size: 1.0625rem;
  line-height: 1.65;
}

.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
  margin-top: 2.1rem;
}

.hero-note {
  margin-top: 1.1rem;
  font-size: 0.875rem;
  color: var(--body);
}

/* ---------------- 7. Stats ---------------- */
/* no band, no rules. A tinted rectangle with a hairline at each end is two
   seams and a boundary, and the section does not need one: the field carries
   violet across this whole stretch of the page, and three white cards lifted
   off a coloured ground already say where the section is. */
.stats {
  padding-block: 72px;
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}

.stat {
  padding: 2rem 1.75rem;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  /* tight and navy-tinted: a wide black-ish blur is the template default */
  box-shadow: 0 1px 2px rgba(11, 18, 48, 0.05), 0 14px 30px -14px rgba(23, 48, 188, 0.20);
}

/* flat blue on a page that is already coloured disappears. The card takes the
   two leading hues on a diagonal — violet in first, blue only at the far
   corner — with the warm beam caught in the top corner where only the big
   number sits, which is the one place it can be bright without being loud. */
.stat--lit {
  background:
    radial-gradient(ellipse 320px 220px at 88% 6%, rgba(255, 122, 80, 0.26), transparent 64%),
    linear-gradient(148deg, var(--violet) 0%, var(--blue-dark) 100%);
  border: none;
  box-shadow: 0 2px 6px rgba(45, 22, 130, 0.30), 0 20px 42px -16px rgba(91, 52, 214, 0.44);
}

.stat-num {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2.75rem, 4.5vw, 3.5rem);
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: var(--ink);
}

.stat-label {
  margin-top: 0.6rem;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
}

.stat-desc {
  margin-top: 0.5rem;
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--body);
}

.stat--lit .stat-num,
.stat--lit .stat-label { color: var(--white); }
.stat--lit .stat-desc { color: rgba(255, 255, 255, 0.95); }

/* ---------------- 9. What it is ---------------- */
/* sits directly under the hero, so a little tighter on top. No background of
   its own: the fan runs out through the top of this section and the field
   takes it from there — blue beside the editor column, warm under the mock. */
.what {
  padding-top: 72px;
}

.what-grid {
  display: grid;
  grid-template-columns: 5fr 7fr;
  align-items: center;
  gap: 56px;
}

/* lets the columns shrink past the editor's nowrap min-content width,
   so the long entry titles ellipsize instead of widening the page */
.what-grid > * { min-width: 0; }

.what-lede {
  max-width: 44ch;
  margin-top: 1.1rem;
  font-size: 1.0625rem;
  color: var(--body);
}

.what-list {
  display: grid;
  gap: 0.7rem;
  margin-top: 1.6rem;
}

.what-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 0.95rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink);
}

/* violet, blue, azure down the list — the fan's left-to-right order stood
   up on end, so the three promises carry the three beams */
.what-check {
  flex: none;
  margin-top: 2px;
  color: var(--violet);
}
.what-item:nth-child(2) .what-check { color: var(--blue); }
.what-item:nth-child(3) .what-check { color: var(--azure); }

/* Portfolio editor */
.editor {
  overflow: hidden;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 1px 2px rgba(11, 18, 48, 0.05), 0 18px 40px -18px rgba(23, 48, 188, 0.22);
}

.editor-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0.55rem 1rem;
  background: linear-gradient(180deg, rgba(43, 78, 255, 0.09), rgba(43, 78, 255, 0.05));
  border-bottom: 1px solid var(--border);
}

.editor-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
}
/* the mark ships at its nav size; inside the mock-up's bar it sits with 0.85rem
   text, so it comes down to match. */
.editor-title .mark { display: block; flex: none; width: 20px; height: 20px; }

.editor-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  padding: 0.15rem 0.6rem;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  line-height: 1.7;
  color: var(--body);
}

/* green, not blue: saved-and-live is the one state worth its own colour */
.editor-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--live);
  flex: none;
}

.editor-body { padding: 0.9rem 1rem; }

.entry-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
  padding: 0.6rem 0.8rem;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.entry-grip {
  flex: none;
  color: #C3C8DA;
}

.entry-main { min-width: 0; }

.entry-title {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.entry-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 3px;
}

.chip {
  padding: 0.05rem 0.5rem;
  border-radius: var(--r-pill);
  background: rgba(91, 52, 214, 0.08);
  color: var(--violet);
  font-family: var(--mono);
  font-size: 0.65rem;
  font-weight: 500;
  line-height: 1.7;
}

/* the category is the entry's colour; the year is only a fact about it, and
   two coloured chips in a row made them look like the same kind of thing.
   `:not(:first-child)` so a row the demo button adds — category only — keeps
   its colour. */
.entry-chips .chip:last-child:not(:first-child) {
  background: rgba(11, 18, 48, 0.06);
  color: rgba(11, 18, 48, 0.66);
}

.entry-edit {
  margin-left: auto;
  flex: none;
  color: var(--body);
}

/* rows added by the demo button */
.entry-row.added { animation: entry-pop 0.28s ease-out both; }

@keyframes entry-pop {
  from { opacity: 0; transform: scale(0.94); }
  to { opacity: 1; transform: none; }
}

.editor-foot { padding: 0 1rem 0.9rem; }

.add-entry {
  display: block;
  width: 100%;
  padding: 0.55rem;
  background: transparent;
  border: 1.5px dashed var(--border);
  border-radius: 10px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--violet);
  cursor: pointer;
  transition: background-color .18s ease, border-color .18s ease;
}
.add-entry:hover:not(:disabled) {
  border-color: var(--violet);
  background: rgba(91, 52, 214, 0.07);
}
.add-entry:disabled {
  /* !important beats the inline opacity the entrance animation leaves behind */
  opacity: 0.45 !important;
  cursor: default;
}

/* Portfolio editor → generated site */
.convert-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-block: 14px;
}

.convert-icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: 50%;
  background: linear-gradient(140deg, var(--violet), var(--blue));
  color: var(--white);
  box-shadow: 0 8px 20px -6px rgba(91, 52, 214, 0.50);
}

/* arrow and spinner share one grid cell; the spinner only ever appears
   while motion.js is driving the loading state */
.convert-icon > * { grid-area: 1 / 1; }
.convert-spin { display: none; }

/* Generated site: CSS browser window */
/* the mock casts light rather than shadow — cool over warm */
.browser {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  box-shadow:
    0 2px 6px rgba(11, 18, 48, 0.06),
    0 30px 70px -26px rgba(43, 78, 255, 0.38),
    0 22px 52px -22px rgba(255, 122, 80, 0.40);
}

.browser-bar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--border);
}

.bdot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: rgba(11, 18, 48, 0.13);
  flex: none;
}

.browser-url {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  padding: 0.2rem 0.9rem;
  border-radius: var(--r-pill);
  background: var(--tint);
  font-family: var(--mono);
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--body);
  white-space: nowrap;
}

/* the screenshot is taller than the frame; crop from the top */
.browser-page {
  padding: 0;
  max-height: 440px;
  overflow: hidden;
}

.browser-page img {
  display: block;
  width: 100%;
  height: auto;
}

/* ---------------- 10. Team ---------------- */
/* five people wrap badly on a grid: three across leaves the last two hanging
   off the left edge of a row that is still three columns wide. Flex centres
   every row including a short one, and a 340px card sets the wrap points on
   its own — three fit the container, four never do, and below roughly 700px
   of room they go one to a row. No breakpoints needed for any of it. */
.team-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
}

/* the portfolio-entry cards' border and radius exactly — 10px and one
   hairline, not the 22px --r-card the stats use. These are the same kind of
   object as the rows in the editor on the home page and should read that way. */
.member {
  flex: 0 1 340px;
  padding: 1.5rem 1.6rem;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
}

/* tinted, not white: the card behind it is already white, and a white circle
   on a white card is a hole. `object-fit` and the centring both live here so
   the initials and the photograph that replaces them need the same one class. */
.member-photo {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--tint);
  object-fit: cover;
  font-family: var(--display);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--blue-deep);
}

.member-name {
  margin-top: 1.1rem;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.45;
  color: var(--ink);
}

/* inline with the name, a step back from it: one line reading "who — what",
   which is why the role is a span inside the name rather than its own block */
.member-role {
  font-weight: 500;
  color: var(--body);
}

/* the team section already closed with 110px of its own, so this one only
   carries the gap under the button */
.about-close {
  padding-top: 0;
  text-align: center;
}

.about-note {
  margin-top: 1.1rem;
  font-size: 0.875rem;
  color: var(--body);
}

/* ---------------- 11. Final CTA ---------------- */
/* the glow the band sits in belongs to the field now — violet up its left
   side, azure across it — so this section paints nothing of its own. The band
   keeps its background: it is an object lying on the page rather than a band
   of it, and its rounded corners mean it has no edge to seam at.
   The reprise, deliberately not a repeat — the fan happens once on this page.
   Here the same four colours arrive as corner light on a violet slab, blue
   thrown across the top corner rather than carrying the whole face, with a
   bloom behind the white button so the button reads as the thing emitting it.
   Nothing brighter than --blue sits under the note. */
.cta-band {
  position: relative;
  overflow: hidden;
  padding: 72px 24px;
  border-radius: var(--r-band);
  background:
    radial-gradient(ellipse 440px 180px at 50% var(--cta-y),
      rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0) 74%),
    radial-gradient(ellipse 78% 92% at 90% 2%, rgba(43, 78, 255, 0.50), transparent 66%),
    radial-gradient(ellipse 70% 86% at 8% 100%, rgba(255, 122, 80, 0.26), transparent 62%),
    linear-gradient(162deg, var(--violet) 0%, var(--violet-deep) 100%);
  box-shadow: 0 26px 60px -22px rgba(45, 22, 130, 0.52);
  text-align: center;
}

/* the white grid becomes a lit top edge, so the band reads as a surface the
   light is falling across rather than a rectangle of colour */
.cta-band::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0) 26%);
}

.cta-band > * { position: relative; }

.cta-band h2 {
  max-width: 20ch;
  margin-inline: auto;
  color: var(--white);
}

.cta-band .btn-white { margin-top: 2rem; }

.cta-note {
  margin-top: 1.1rem;
  font-size: 0.9rem;
  color: rgba(255, 255, 255, 0.95);
}

/* ---------------- 12. Footer ---------------- */
/* the last of the light, and none of it is the footer's. It has no background
   and no rule above it: the field's final violet deepening and the warm beam
   leaving at the bottom right both run straight through this box from the
   page above, which is what makes the close read as the light resolving
   rather than as a grey-lavender slab bolted on the end. */
.site-footer {
  padding-block: 44px;
}

.footer-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 28px;
}

.footer-tag {
  margin-top: 0.7rem;
  font-size: 0.9rem;
  color: var(--body);
}

.footer-legal {
  margin-top: 32px;
  font-size: 0.8rem;
  color: var(--body);
}

/* ---------------- 13. Waitlist dialog ----------------
   The one place on the site that asks for something rather than showing
   something, so it is the one surface allowed to sit on top of the field
   instead of floating in it. Everything else here follows the page: white
   card, hairline borders, the mono voice for anything structural, and the
   violet-into-blue run kept for the two things that are choices — the chip
   you pick and the key you press. */

/* the element's own UA styling is stripped back to nothing; .wl-panel is the
   card. `overflow: visible` so the panel's shadow is not clipped by the box
   that positions it — the panel does its own scrolling on short screens. */
.wl {
  max-width: min(452px, calc(100vw - 28px));
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--body);
  overflow: visible;
}

/* a black scrim would put out the light the whole page is made of. This one
   is the deep end of the violet, thin, with the page blurred behind it, so
   the field is still legible underneath and the card reads as being lifted
   off it rather than pasted over a hole. */
.wl::backdrop {
  background: rgba(19, 11, 54, 0.42);
  -webkit-backdrop-filter: blur(7px) saturate(1.1);
  backdrop-filter: blur(7px) saturate(1.1);
}

.wl-panel {
  position: relative;
  max-height: calc(100dvh - 28px);
  overflow-y: auto;
  padding: 30px 30px 26px;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  box-shadow:
    0 1px 2px rgba(11, 18, 48, 0.06),
    0 50px 90px -34px rgba(23, 48, 188, 0.50);
}

/* one trace of the field, bled in from the top corner of the card, so the
   dialog belongs to this page and not to a component library */
.wl-panel::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  height: 190px;
  border-radius: var(--r-card) var(--r-card) 0 0;
  background:
    radial-gradient(120% 100% at 88% -18%, rgba(43, 78, 255, 0.13), transparent 62%),
    radial-gradient(110% 100% at 6% -28%, rgba(91, 52, 214, 0.13), transparent 60%);
  pointer-events: none;
}

.wl-panel > * { position: relative; }

.wl-close {
  position: absolute;
  top: 14px;
  right: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  color: var(--body);
  cursor: pointer;
  transition: background-color .16s ease, color .16s ease, border-color .16s ease;
}
.wl-close:hover {
  background: rgba(11, 18, 48, 0.05);
  border-color: var(--border);
  color: var(--ink);
}
.wl-close:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 2px;
}

.wl-title {
  margin-top: 14px;
  font-family: var(--display);
  font-size: 1.6rem;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--ink);
}

.wl-sub {
  margin-top: 8px;
  font-size: 0.9rem;
  line-height: 1.55;
}

/* ---- fields ---- */

.wl-field {
  margin: 0;
  margin-top: 18px;
  padding: 0;
  border: 0;
}

/* the same structural voice the chips and the editor's status line use: a
   label is a record of what the box is, not a sentence about it */
.wl-label {
  display: block;
  margin-bottom: 7px;
  padding: 0;
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink);
}

.wl-input {
  display: block;
  width: 100%;
  padding: 0.72rem 0.9rem;
  font-family: var(--sans);
  font-size: 0.95rem;
  line-height: 1.4;
  color: var(--ink);
  background: var(--canvas);
  border: 1px solid var(--border);
  border-radius: 12px;
  transition: background-color .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.wl-input::placeholder { color: rgba(68, 75, 102, 0.52); }

/* the field lights up rather than outlining: it goes white, so the box the
   student is in is the only one on the card that is not tinted */
.wl-input:focus {
  outline: none;
  background: var(--white);
  border-color: var(--violet);
  box-shadow: 0 0 0 3px rgba(91, 52, 214, 0.15);
}

/* ---- grade chips ---- */

.wl-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.wl-chip {
  position: relative;
  display: inline-flex;
}

/* the radio covers its own chip and is invisible: the hit area, the keyboard
   arrow-keys and the required-one-of-five check all come free from the real
   control, and none of its styling does */
.wl-chip input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.wl-chip span {
  display: block;
  min-width: 44px;
  padding: 0.42rem 0.9rem;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  color: var(--body);
  font-family: var(--mono);
  font-size: 0.8rem;
  font-weight: 500;
  line-height: 1.5;
  text-align: center;
  transition: background-color .16s ease, border-color .16s ease, color .16s ease, box-shadow .16s ease, transform .16s ease;
}

.wl-chip input:hover + span {
  border-color: rgba(91, 52, 214, 0.42);
  color: var(--violet);
}

.wl-chip input:focus-visible + span {
  outline: 2px solid var(--violet);
  outline-offset: 2px;
}

/* the chosen chip is the same key the primary button is, at chip scale —
   picked up rather than merely filled in */
.wl-chip input:checked + span {
  background-image: linear-gradient(100deg, var(--violet) 0%, var(--violet) 34%, var(--blue) 100%);
  border-color: transparent;
  color: var(--white);
  box-shadow: 0 2px 0 var(--violet-deep), 0 10px 20px -9px rgba(91, 52, 214, 0.55);
  transform: translateY(-1px);
}

/* ---- honeypot, errors, submit ---- */

/* off the page rather than display:none — hidden well enough that no student
   meets it, present enough that a script filling every input still finds it */
.wl-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.wl-error {
  margin-top: 14px;
  color: var(--alert);
  font-size: 0.86rem;
  line-height: 1.45;
}

.wl-submit {
  width: 100%;
  margin-top: 20px;
}
.wl-submit[disabled] {
  cursor: default;
  opacity: 0.72;
  transform: none;
  box-shadow: 0 3px 0 var(--violet-deep);
}

/* ---- confirmation ---- */

.wl-done { text-align: center; }

.wl-done-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  margin-bottom: 4px;
  border-radius: var(--r-pill);
  background: rgba(20, 160, 107, 0.10);
  color: var(--live);
}

.wl-done .wl-sub { margin-bottom: 22px; }

/* ---- motion ---- */
/* the card arrives from slightly under and behind, the way the stat cards and
   the CTA band do, so the dialog enters in the page's own accent rather than
   the browser's */
@keyframes wl-rise {
  from { opacity: 0; transform: translateY(14px) scale(0.975); }
  to   { opacity: 1; transform: none; }
}
@keyframes wl-veil {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.wl[open] .wl-panel { animation: wl-rise .3s cubic-bezier(0.22, 1, 0.36, 1) both; }
.wl[open]::backdrop { animation: wl-veil .3s ease both; }

/* the page behind the dialog must not scroll under it. The scrollbar's width
   is handed back as padding in the same breath it is taken away, so nothing
   on the page steps sideways as the card opens. */
.wl-locked {
  overflow: hidden;
  padding-right: var(--wl-gutter, 0px);
}

/* ---------------- 14. Responsive ----------------
   only the aim moves: a smaller h1 puts the line the pill sits on higher up,
   and the panel behind it has to stay clear of the hero's own clip. */
@media (max-width: 980px) {
  .what-grid {
    grid-template-columns: 1fr;
    gap: 40px;
  }

}

@media (max-width: 900px) {
  :root { --fan-hero: 200px; --cta-y: 220px; }

  .section { padding-block: 64px; }

  .hero::before {
    top: calc(var(--fan-hero) - 126px);
    height: 356px;
  }

  /* nav collapses. The three tracks go back to a two-item bar — brand and
     button — and the wrapper stops being display:contents so it can carry
     both clusters down into one panel. */
  .nav {
    display: flex;
    justify-content: space-between;
  }
  .nav-toggle { display: flex; }
  .nav-menu {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 18px;
    padding: 18px 24px 22px;
    background: var(--white);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 18px 30px rgba(11, 18, 48, 0.06);
  }
  .site-header.open .nav-menu { display: flex; }
  .nav-links {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }
  /* the action closes the panel rather than reading as one more link */
  .nav-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding-top: 16px;
    border-top: 1px solid var(--border);
  }

  .hero { padding-top: 52px; }

  .stats { padding-block: 52px; }
  .stat-grid { grid-template-columns: 1fr; }

  .cta-band { padding: 56px 22px; }

  .footer-top { flex-direction: column; }
}

@media (max-width: 560px) {
  :root { --fan-hero: 181px; --cta-y: 216px; --wash: 0.10; }

  .nav { gap: 12px; }

  .nav-cta {
    padding: 0.55rem 0.9rem;
    font-size: 0.8rem;
  }

  /* on a phone the copy column is the whole page, so the panel cannot also be
     opaque or there is nothing left to see. Held to 80% — the ink has 16:1 to
     spend — which lets the beams read through the headline as colour rather
     than being blocked out behind it. */
  .hero::before {
    top: calc(var(--fan-hero) - 104px);
    width: 90%;
    height: 340px;
    background: linear-gradient(180deg,
      rgba(255, 255, 255, 0.80) 0%,
      rgba(255, 255, 255, 0.78) 34%,
      rgba(255, 255, 255, 0.66) 66%,
      rgba(255, 255, 255, 0.48) 88%,
      rgba(255, 255, 255, 0) 100%);
  }
  .hero::after { width: 220px; }

  .editor-body { padding: 0.9rem 0.8rem; }
  .editor-foot { padding: 0 0.8rem 0.9rem; }

  /* the card is the whole screen now, so it gives back the margin it was
     floating in and the five chips get room to stay on one line */
  .wl-panel { padding: 26px 20px 22px; }
  .wl-title { font-size: 1.42rem; }
  .wl-chip span {
    min-width: 40px;
    padding: 0.42rem 0.72rem;
  }
}

/* the headline's first line stops fitting on one line here, which puts the
   address a whole line lower. The convergence is measured to the pill, so it
   has to come down with it — this is the same re-measure the token at the top
   of the file asks for after any copy change to the h1. */
@media (max-width: 530px) {
  :root { --fan-hero: 227px; }
}

@media (max-width: 380px) {
  /* the address cannot wrap and cannot spill, so at the narrowest widths it
     is the address that gives, not the rule */
  .hero-url { font-size: 0.58em; }
  :root { --fan-hero: 223px; }
}

/* ---------------- 15. Reduced motion ---------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .btn:hover { transform: none; }
  .btn-primary:active { transform: none; }
  .entry-row.added { animation: none; }
  /* the dialog still appears, it just stops travelling to get there */
  .wl[open] .wl-panel,
  .wl[open]::backdrop { animation: none; }
  .wl-chip input:checked + span { transform: none; }
  /* the fan rests mid-breath. It has to be named: a blanket `*` rule would
     never reach it, because `*` does not match a pseudo-element. */
  .fx::before {
    animation: none;
    transform: scale(1.03);
    opacity: 0.94;
  }
}

/* ---------------- 16. Animations ---------------- */
/* `clip` (not `hidden`) so no scroll container is created and the header
   keeps sticking; guards against transform-slide overflow. */
html { overflow-x: clip; }

/* keeps the count-up from jittering as digits change */
.stat-num { font-variant-numeric: tabular-nums; }

/* Lenis momentum scrolling. These classes are only ever set once the library
   initializes, so reduced-motion and no-JS users keep native smooth anchors. */
html.lenis,
html.lenis body { height: auto; }
html.lenis { scroll-behavior: auto; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* Initial hidden states — only when motion.js is expected to run.
   Without JS, with reduced motion, or if the animation library fails to
   load, `js-anim` is absent and everything renders normally. */
html.js-anim :is(
  .badge,
  .hero h1,
  .hero-sub,
  .hero-ctas,
  .hero-note,
  .stat,
  .what-copy,
  .editor,
  .entry-row,
  .add-entry,
  .convert-row,
  .what .browser,
  .cta-band
) { opacity: 0; }

/* Loading state on the convert circle. Scoped to `js-anim` so a static
   render always shows the arrow (and never the spinner). */
html.js-anim .convert-icon.loading .convert-arrow { display: none; }
html.js-anim .convert-icon.loading .convert-spin { display: block; }
