/* moonflower.social — the brand site.
 *
 * Same rules as the umbrella site: no cookies, no JavaScript, nothing loaded
 * from a third party, no webfonts. So it needs no consent banner and has
 * nothing that can fail on someone else's outage.
 *
 * ⚠ THE PALETTE IS COPIED FROM docs/mockups/moonflower.html :root, NOT retyped
 * from brand.md's table. That table is the readable index; the mockup is the
 * spec. Re-typing it is how Moonflower shipped cards a shade too dark on
 * Android — `--surface` was transcribed from the mockup gallery's phone bezel
 * (`--frame`), which styles gallery chrome and means nothing here.
 * docs/ops/brand-sites.md §4.1.
 *
 * `--frame`, `--blk`, `--ghost-bg` and the `--danger-*` set are deliberately
 * NOT copied: they are app-only tokens with no counterpart on a web page.
 */

:root {
  /* From docs/mockups/moonflower.html :root — variant B, "Blue Hour". */
  --bg: #0e2233;
  --surface: #1d3947;
  --tile: #1a3441;
  --line: #26424f;
  --blk2: #33566a;
  --ink: #edf4f6;
  --muted: #8fa9b5;
  --brand: #f7f6ee;
  --brand-ink: #0e2233;
  --accent: #9fe8c9;
  --hero: linear-gradient(160deg, #1c4258, #0e2233);
  --hero-ink: #ffffff;

  --sans: -apple-system, system-ui, "Segoe UI", Roboto, sans-serif;
  --serif: ui-serif, Georgia, "Times New Roman", serif;

  --measure: 62ch;
  --page: 44rem;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
}

.wrap {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 1.5rem;
}

/* ---------- header ---------- */
.masthead {
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}
.masthead .wrap {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding-top: 1.5rem;
  padding-bottom: 1.5rem;
}
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--serif);
  font-size: 1.5rem;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
}
/* The masthead icon, built rather than photographed: the tile is CSS - the
   brand gradient and the app icon's 22.7% corner radius - and the mark inside
   is inline SVG. Crisp at any zoom or pixel density, where a 180px raster
   scaled to 2.4rem is not.

   ⚠ THE GEOMETRY IS COPIED, so it can drift, and it did - four times, before
   the source it was copied from was itself corrected. What makes this safe now:
   docs/brand/preview.html's #m-moonflower carries the TUCKED crescent the app
   icon actually ships (circle(58,45) r27.2, cut circle(66,38.6) r24). Copy from
   there, not from an older file and not from memory, and re-measure against
   ios/Apps/Gale/.../icon-1024.png after any change - the bloom should be ~51%
   of the tile and centred.

   The rasters are NOT drawn here. favicon.ico and apple-touch-icon.png are
   downscaled from that same shipped asset, because a raster can be derived and
   therefore should be. */
.wordmark .appicon {
  width: 4.8rem;
  height: 4.8rem;
  flex: none;
  display: block;
  border-radius: 22.7%;
  background: var(--hero);
  overflow: hidden;

  /* The silver lining iOS draws around an app icon, and the reason the web has
     to draw it itself: it is not in the asset — icon-1024.png is flat gradient
     to its corners — the home screen adds it. Nothing adds it to a web page.
     A hairline all round, brighter along the top edge where a light source
     would catch it, in the brand cream rather than white so it stays in
     palette.

     Then the soft drop shadow iOS puts under an icon, which is what lifts the
     tile off the ground rather than letting it sit flat on it. Kept low and
     wide: on a dark masthead a tight or heavy shadow reads as a smudge, not as
     depth. Outer shadows are listed after the insets purely for legibility —
     the order does not matter between inner and outer. */
  box-shadow:
    inset 0 0 0 1px rgba(247, 246, 238, 0.14),
    inset 0 1px 0 rgba(247, 246, 238, 0.24),
    0 1px 2px rgba(0, 0, 0, 0.28),
    0 6px 16px rgba(0, 0, 0, 0.34);
}
.wordmark .appicon svg { display: block; width: 100%; height: 100%; }
.masthead nav {
  margin-left: auto;
  display: flex;
  gap: 1.25rem;
  flex-wrap: wrap;
}
.masthead nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.95rem;
}
.masthead nav a:hover { color: var(--accent); text-decoration: underline; }
.masthead nav a[aria-current="page"] { color: var(--ink); }

/* ---------- hero ---------- */
/* The one place the brand's own gradient appears, as it does in the app. */
.hero {
  background: var(--hero);
  border-bottom: 1px solid var(--line);
  color: var(--hero-ink);
}
.hero .wrap { padding-top: 4rem; padding-bottom: 3.5rem; }
.hero h1 { margin-bottom: 1rem; }
.hero .lead { color: #d7e7ef; }

/* ---------- content ---------- */
main { padding: 3rem 0 4rem; }
.hero + main { padding-top: 2.5rem; }

h1 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 2.25rem;
  line-height: 1.2;
  letter-spacing: -0.015em;
  margin: 0 0 1.25rem;
}
h2 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.4rem;
  line-height: 1.3;
  margin: 2.75rem 0 0.75rem;
}
h3 {
  font-size: 1rem;
  font-weight: 600;
  margin: 2rem 0 0.5rem;
  color: var(--brand);
}
p, li { max-width: var(--measure); }
p { margin: 0 0 1.1rem; }
ul { padding-left: 1.25rem; margin: 0 0 1.1rem; }
li { margin-bottom: 0.4rem; }

.lead { font-size: 1.2rem; color: var(--muted); }

a { color: var(--accent); }
a:hover { text-decoration-thickness: 2px; }

/* ⚠ Dark ground: focus rings need their own colour. The app's `--brand` is
   near-white and cannot carry a state against this background — the same
   problem brand.md's "Dark-theme fixes" block solves for the app's active tab
   and ON toggle. docs/ops/brand-sites.md §4.1. */
a:focus-visible,
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ---------- panels ---------- */
.note {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1.25rem 1.5rem;
  margin: 0 0 1.5rem;
  max-width: var(--measure);
}
.note p:last-child { margin-bottom: 0; }

.details {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.5rem 2rem;
  margin: 0 0 1.5rem;
  padding: 1.5rem;
  background: var(--tile);
  border: 1px solid var(--line);
  border-radius: 6px;
  max-width: var(--measure);
}
.details dt { color: var(--muted); }
.details dd { margin: 0; }

@media (max-width: 34rem) {
  .details { grid-template-columns: 1fr; gap: 0.15rem; }
  .details dd { margin-bottom: 0.75rem; }
}

/* ---------- footer ---------- */
/* Companies Act ss.82-84. Owed on EVERY page, not just this one. */
.legal {
  border-top: 1px solid var(--line);
  background: var(--tile);
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.6;
}
.legal .wrap { padding-top: 2rem; padding-bottom: 2.5rem; }
.legal p { max-width: var(--measure); margin: 0 0 0.5rem; }
.legal a { color: var(--muted); }
.legal nav { margin-top: 1rem; display: flex; gap: 1.25rem; flex-wrap: wrap; }

/* ---------- forms ---------- */
/* The first form on any Alsanthea site. Neither this site nor the umbrella has
 * ever had one, so these controls EXTEND the existing vocabulary rather than
 * copying it from somewhere: the 6px radius, the 1px --line border and the
 * --surface / --tile panels are the ones .note and .details already use.
 *
 * ⚠ NO JAVASCRIPT, here as everywhere else on this site. The form is a plain
 * same-origin POST to /api/*, routed to the Lambda by an ordered CloudFront
 * behavior, and every state of the flow is its own URL — /waitlist/, then
 * thanks/ and confirmed/. There is no inline validation and no inline error:
 * a rejected submit redirects back here with the reason.
 *
 * ⚠ /waitlist/preferences/ is NOT here, and must not be: it is rendered by the
 * Lambda, because a static page cannot show a person their own settings without
 * script. docs/ops/waitlist.md §4.2, §4.3.
 *
 * Checkboxes are REAL inputs coloured with `accent-color`, not spans dressed up
 * to look like them. A faked checkbox has to reimplement focus, keyboard and
 * the screen-reader contract, and it needs script to do it.
 */
.form { display: flex; flex-direction: column; gap: 1.1rem; max-width: var(--measure); }

.field { display: block; }
.field .lbl {
  display: block;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--brand);
  margin-bottom: 0.35rem;
}
.field .lbl .opt-note { font-weight: 400; color: var(--muted); }

input[type="email"],
input[type="tel"],
input[type="text"],
select {
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--blk2);
  border-radius: 6px;
  background: var(--tile);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
}
input::placeholder { color: #6f8b99; }
select { appearance: none; background-image: none; }

.pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }

/* The experience multi-select. docs/ops/waitlist.md §6.3. */
.opts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
  max-width: var(--measure);
  margin: 0 0 1.5rem;
}
.opt {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  padding: 1rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
}
.opt:hover { border-color: var(--blk2); }
.opt:has(input:checked) { border-color: var(--accent); }
.opt .t { display: block; font-weight: 600; font-size: 1rem; line-height: 1.4; }
.opt .d {
  display: block;
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1.5;
  margin-top: 0.3rem;
  max-width: none;
}

/* Consent. Three ticks, two purposes and a channel — docs/ops/waitlist.md §3.6. */
.consent {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  max-width: var(--measure);
  padding: 1.35rem 1.5rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.tick { display: flex; gap: 0.75rem; align-items: flex-start; cursor: pointer; }
.tick .txt { display: block; font-size: 0.95rem; line-height: 1.55; }
.tick .txt .sub { display: block; color: var(--muted); }

input[type="checkbox"] {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0.3rem 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.actions {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  margin-top: 1.75rem;
}
.actions .hint { color: var(--muted); font-size: 0.95rem; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  padding: 0 1.75rem;
  background: var(--accent);
  color: var(--brand-ink);
  border: 1px solid var(--accent);
  border-radius: 6px;
  font-family: var(--sans);
  font-size: 1.05rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}
.btn:hover { background: #c4f2de; border-color: #c4f2de; }
.btn.quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--blk2);
  min-height: 48px;
  font-size: 1rem;
}
.btn.quiet:hover { background: var(--tile); border-color: var(--muted); }

.smallprint { color: var(--muted); font-size: 0.9rem; line-height: 1.6; }

@media (max-width: 40rem) {
  .opts { grid-template-columns: 1fr; }
  .pair { grid-template-columns: 1fr; }
  .btn { width: 100%; }
}

/* ---------- the waitlist call to action ---------- */
/* ⚠ NOT a banner under the header. The masthead link carries the action on
 * every page, and a block at the END of the content carries it only where a
 * reader has just finished something — /, /about/, /safety/ and the 404.
 *
 * ⛔ Never on /privacy/, /terms/ or /support/. Those are the store-prerequisite
 * documents these sites exist for (docs/ops/brand-sites.md §1), and a signup
 * block above or below a privacy notice makes it read as a funnel rather than a
 * document. Support opens "a person reads these" — a promo block there
 * contradicts the page.
 */
.masthead nav a.cta {
  color: var(--accent);
  border: 1px solid var(--blk2);
  border-radius: 999px;
  padding: 0.15rem 0.75rem;
  text-decoration: none;
}
.masthead nav a.cta:hover {
  border-color: var(--accent);
  background: var(--tile);
  text-decoration: none;
}
.masthead nav a.cta[aria-current="page"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--brand-ink);
}

.cta-block { margin-top: 2.75rem; }
.cta-block p:last-child { margin-bottom: 0; }
.cta-block .btn { margin-top: 0.5rem; }

/* ---------- the honeypot ---------- */
/* ⛔ THIS MUST BE A CLASS, NEVER AN INLINE STYLE, and that is the CSP rather
 * than a preference. The response-headers policy sets `style-src 'self'` with
 * no `'unsafe-inline'`, which blocks `style=""` ATTRIBUTES as well as <style>
 * blocks — so an inline rule silently does nothing in production while working
 * perfectly on a local server that sends no CSP.
 *
 * That is exactly how this shipped: the trap rendered visibly on the live site,
 * labelled "Leave this empty", where a real person could read it and fill it in
 * — and the handler rejects anything that fills it. A bot defence that catches
 * people is worse than none.
 *
 * Off-screen rather than display:none or hidden — a bot that skips hidden
 * fields is not caught by a field it can see is hidden.
 */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Small print that follows a form, spaced off it. Was an inline style; see .hp
 * for why nothing on this site may be. */
form + .smallprint { margin-top: 1.75rem; }

/* ============================================================================
 * HOME PAGE — the scrolling story
 *
 * ⚠ NO JAVASCRIPT IS POSSIBLE HERE, and that is enforced rather than agreed:
 * the distribution serves `default-src 'none'` with no script-src, so the
 * browser refuses to run a script even if one were added. `style-src 'self'`
 * also blocks inline style attributes, which is why every rule lives in this
 * file (see .hp).
 *
 * So the scroll behaviour is CSS scroll-driven animation. Everything below is
 * PROGRESSIVE: without `animation-timeline` support the page is an ordinary
 * document with all content visible, because the reveal only animates from a
 * hidden state INSIDE @supports. Nothing is hidden by default.
 * ========================================================================= */

/* ---------- bands ---------- */
/* A page of bands sets its own rhythm, so `main` must not add padding on top of
   the first one: that leaves a strip of body colour between the hero and the
   band, and it reads as a seam rather than as spacing. The first band drops its
   top border for the same reason — the hero already ends in one. */
.story { padding: 0; }
.story > .band:first-child { border-top: 0; }

.band { padding: 4.5rem 0; border-top: 1px solid var(--line); }
.band-alt { background: var(--surface); }
.band h2 { margin-top: 0; }
.band .section-lead { font-size: 1.1rem; color: var(--muted); max-width: var(--measure); }
.band .aside {
  margin-top: 2rem;
  padding-left: 1rem;
  border-left: 2px solid var(--blk2);
  color: var(--muted);
  max-width: var(--measure);
}

/* ---------- hero actions ---------- */
.hero-actions {
  display: flex;
  align-items: center;
  gap: 1rem 1.25rem;
  flex-wrap: wrap;
  margin-top: 1.75rem;
}
.btn-note { color: #a8c4d0; font-size: 0.95rem; }

/* ---------- 1 · the loop everyone recognises ---------- */
/* Numbered, tight and deliberately anticlimactic: the list IS the joke, so it
 * is set as a list rather than buried in prose. */
.loop {
  list-style: none;
  counter-reset: loop;
  padding: 0;
  margin: 0 0 1.75rem;
  max-width: var(--measure);
}
.loop li {
  counter-increment: loop;
  position: relative;
  padding: 0.55rem 0 0.55rem 2.6rem;
  border-bottom: 1px solid var(--line);
  font-size: 1.05rem;
}
.loop li::before {
  content: counter(loop);
  position: absolute;
  left: 0;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.loop .loop-back { color: var(--accent); border-bottom: 0; }
.loop .loop-back::before { content: "\21bb"; }
.loop-note { max-width: var(--measure); }

/* ---------- 2 · the comparison ---------- */
/* The heading stays put while the rows pass it, so the contrast is always
 * labelled. It is the cheapest honest version of "the content updates as you
 * scroll", and it costs nothing where sticky is unsupported. */
.sticky-h2 {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 0 1.5rem;
  padding: 1rem 0;
  background: var(--surface);
}
.versus { list-style: none; padding: 0; margin: 0; }
.versus li {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  padding: 0.9rem 0;
  border-top: 1px solid var(--line);
  max-width: none;
}
.versus .v-old { color: var(--muted); }
.versus .v-new { color: var(--ink); font-weight: 500; }
.versus .v-new::before { content: "\2192\00a0"; color: var(--accent); }

/* One column on a phone: the pair stacks, and the arrow does the work the
 * second column did on a laptop. */
@media (max-width: 40rem) {
  .versus li { grid-template-columns: 1fr; gap: 0.2rem; padding: 1rem 0; }
  .sticky-h2 { font-size: 1.25rem; padding: 0.75rem 0; }
}

/* ---------- 3 · steps ---------- */
.steps {
  list-style: none;
  counter-reset: step;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 2rem;
}
.steps li { counter-increment: step; max-width: var(--measure); }
.steps h3 { margin: 0 0 0.4rem; font-size: 1.15rem; color: var(--brand); }
.steps h3::before {
  content: counter(step);
  display: inline-grid;
  place-items: center;
  width: 1.7rem;
  height: 1.7rem;
  margin-right: 0.7rem;
  border-radius: 50%;
  background: var(--accent);
  color: var(--brand-ink);
  font-size: 0.9rem;
  vertical-align: 0.05rem;
}
.steps p { margin: 0; }

@media (min-width: 56rem) {
  .steps { grid-template-columns: repeat(3, 1fr); gap: 2.5rem; }
  .steps li { max-width: none; }
}

/* ---------- 4 · badges ---------- */
.badges { list-style: none; padding: 0; margin: 0; }
.badges li {
  display: grid;
  grid-template-columns: 11rem 1fr;
  gap: 0.4rem 1.5rem;
  align-items: baseline;
  padding: 0.9rem 0;
  border-top: 1px solid var(--line);
  max-width: none;
}
.badge {
  justify-self: start;
  padding: 0.2rem 0.7rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  color: var(--accent);
  font-size: 0.85rem;
  white-space: nowrap;
}

@media (max-width: 40rem) {
  .badges li { grid-template-columns: 1fr; }
}

/* ---------- 7 · close ---------- */
.close p { max-width: var(--measure); }

/* ---------- the scroll reveal ---------- */
/* Guarded twice. @supports means a browser without scroll-driven animations
 * never hides anything; prefers-reduced-motion means a reader who asked for
 * stillness gets a static page. Both fall back to "everything visible", the
 * only acceptable failure for a page whose job is to be read. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 55%;
    }
    .versus li.reveal,
    .badges li.reveal { animation-range: entry 0% entry 40%; }
  }
}

@keyframes rise {
  from { opacity: 0; transform: translateY(1.25rem); }
  to { opacity: 1; transform: none; }
}

/* ---------- badges, as the app draws them ---------- */
/* ios/AeolusKit/Sources/UI/Components.swift renders each badge as a ringed
 * glyph with the label beneath, in brand.primary — which for Moonflower is the
 * near-white "white bloom", not the mint accent. Active is the exception: a
 * five-segment ring that fills through the earned level, so it is drawn as a
 * ring here too rather than flattened into another chip.
 *
 * SF Symbols do not exist on the web, so the glyphs are redrawn as paths and
 * coloured by these rules — never by attributes on the SVG, which `style-src
 * 'self'` would not have blocked but which would have put the palette in ten
 * places instead of one. */
.badges li {
  grid-template-columns: 7.5rem 1fr;
  gap: 0.6rem 1.75rem;
  padding: 1.15rem 0;
  align-items: start;
}
.bdg {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  text-align: center;
}
.bdg-mark {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  border: 2px solid var(--brand);
}
.bdg-mark svg { width: 1.5rem; height: 1.5rem; display: block; }
.bdg-label { font-size: 0.8rem; color: var(--muted); line-height: 1.3; }

/* The glyph shapes. `fill` is a solid symbol; `knock` is the notch cut out of
 * it, painted in the page ground so it reads as a hole; `tick` is a stroked
 * check, which the app draws heavy. */
.bdg-mark .fill { fill: var(--brand); }
.bdg-mark .knock {
  fill: none;
  stroke: var(--surface);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.bdg-mark .tick {
  fill: none;
  stroke: var(--brand);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Active: the ring IS the badge, so it loses the surrounding circle. Segments
 * are arcs of one circle, normalised to pathLength 100 and rotated 72 apart. */
.bdg-mark.bdg-active { border: 0; }
.bdg-mark .ring { width: 3rem; height: 3rem; }
/* The unearned segments must still be VISIBLE — they are what makes the badge
 * read as "three of five" rather than as an arc of unknown length. --blk2 is
 * an unselected-option border and disappears against --surface; --muted is the
 * app's `.secondary` track. */
.ring .seg {
  fill: none;
  stroke: var(--muted);
  stroke-width: 4;
  stroke-linecap: round;
}
.ring .seg.on { stroke: var(--brand); }
.ring .lvl {
  fill: var(--brand);
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 600;
  text-anchor: middle;
  dominant-baseline: central;
}

@media (max-width: 40rem) {
  .badges li { grid-template-columns: 1fr; }
  .bdg { flex-direction: row; gap: 0.75rem; text-align: left; }
  .bdg-label { font-size: 0.95rem; }
}
