/*
 * Aura, ported to the web.
 *
 * Every value here is lifted from the app rather than invented: the palette
 * mirrors src/ui/theme.ts and global.css, and the two typefaces are the same
 * files the app bundles. A support site that looks like a different product
 * than the app it supports is its own small broken promise.
 *
 * Fonts are SELF-HOSTED, deliberately. The privacy policy on this very site
 * says no third party receives anything about you, and a page that fetches
 * its typefaces from Google's CDN hands your IP address to Google on every
 * visit. That would make the policy false on the page where it is stated.
 */

@font-face {
  font-family: 'Outfit';
  src: url('/fonts/Outfit_400Regular.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: 'Outfit';
  src: url('/fonts/Outfit_500Medium.ttf') format('truetype');
  font-weight: 500;
  font-display: swap;
}

@font-face {
  font-family: 'Outfit';
  src: url('/fonts/Outfit_700Bold.ttf') format('truetype');
  font-weight: 700;
  font-display: swap;
}

@font-face {
  /* Hand-subsetted single weight, 29KB down from 322KB. The app ships the
     identical file, so the prose reads the same in both places. */
  font-family: 'Source Serif 4';
  src: url('/fonts/SourceSerif4-Subset.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}

:root {
  /* Ground. Hue held in 330-20deg with lightness stepping down, so every stop
     is genuinely warm (R > B) rather than merely reading warm. */
  --dusk: #23171b;
  --deep: #170f11;
  --night: #0e090a;

  --clay: #e8b4a0;
  --rose: #c98d86;

  --paper: #f6ece5;
  --prose: #d9c6c0;
  --dim: #a98d92;

  --rule: rgba(232, 180, 160, 0.14);
  --measure: 34rem;

  /* Verdict colours, lifted from theme.ts. Never the only signal: every use
     pairs them with the verdict text itself, because a sage/terracotta
     distinction is one a meaningful share of readers cannot make. */
  --yes: #9db38c;
  --no: #c98d86;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* The app is dark-only and so is this. There is no second palette to
     switch to, so the declaration is honest rather than aspirational. */
  color-scheme: dark;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--dusk);
  background-image: linear-gradient(180deg, var(--dusk) 0%, var(--deep) 62%, var(--night) 100%);
  background-attachment: fixed;
  color: var(--prose);
  font-family: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  font-size: 19px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.wrap {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 24px 96px;
  /* 24px mirrors SPACE in src/ui/theme.ts */
}

/* --- Masthead ------------------------------------------------------------ */

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 28px 0 0;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: 'Outfit', system-ui, sans-serif;
  font-weight: 500;
  font-size: 1.16rem;
  letter-spacing: 0.01em;
  color: var(--paper);
  text-decoration: none;
}

.wordmark img {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  display: block;
}

.nav {
  display: flex;
  gap: 20px;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.92rem;
}

.nav a {
  color: var(--dim);
  text-decoration: none;
  padding-bottom: 2px;
  border-bottom: 1px solid transparent;
}

.nav a:hover,
.nav a[aria-current='page'] {
  color: var(--clay);
  border-bottom-color: var(--clay);
}

/* --- Type ---------------------------------------------------------------- */

main {
  /* The eyebrow sits directly under the wordmark, and without this it reads
     as a tagline attached to it rather than a label for the page. */
  display: block;
  margin-top: 56px;
}

.eyebrow {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--rose);
  margin: 0 0 14px;
}

h1 {
  font-size: clamp(1.9rem, 6vw, 2.6rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--paper);
  font-weight: 400;
  margin: 0 0 18px;
}

h2 {
  font-size: 1.5rem;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--paper);
  font-weight: 400;
  /* The rule under a heading is the app's benefitsHead treatment, which is
     how a section announces itself there too. */
  margin: 44px 0 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--rule);
}

p {
  margin: 0 0 16px;
}

a {
  color: var(--clay);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

a:hover {
  color: var(--paper);
}

.lede {
  font-size: 1.26rem;
  color: var(--dim);
  margin-bottom: 34px;
}

.stamp {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.88rem;
  letter-spacing: 0.06em;
  color: var(--dim);
  margin: 0 0 42px;
}

/* --- Beat figure, the signature ------------------------------------------ */

/*
 * Two carriers a few hertz apart and the envelope their difference produces.
 * This is the entire mechanism the app is built on, and the one thing on the
 * site that could not belong to any other product, so it is the only place
 * any motion or ornament is spent.
 */
.beat {
  margin: 36px 0 40px;
  padding: 22px 20px 18px;
  border: 1px solid var(--rule);
  border-radius: 16px;
  background: rgba(232, 180, 160, 0.03);
}

/*
 * The traces are drawn twice as wide as the viewBox so the animation has
 * somewhere to scroll from. Without a clip they render past the card and
 * across the page, because the drawing extends to x=1440 in a 720 window.
 */
.beat-window {
  overflow: hidden;
  border-radius: 6px;
}

.beat svg {
  display: block;
  width: 100%;
  height: auto;
}

.beat-envelope {
  animation: drift 8s linear infinite;
}

.beat-read {
  display: flex;
  gap: 26px;
  flex-wrap: wrap;
  margin-top: 16px;
  font-family: 'Outfit', system-ui, sans-serif;
}

.beat-read div {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.beat-read dt,
.beat-read .k {
  font-size: 0.68rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dim);
}

.beat-read .v {
  font-size: 1.4rem;
  font-weight: 500;
  color: var(--paper);
  font-variant-numeric: tabular-nums;
}

.beat-read .v.accent {
  color: var(--clay);
}

@keyframes drift {
  from {
    transform: translateX(0);
  }

  to {
    /* One full repeat of BOTH traces, so the loop has no visible seam.
       f1=12 and f2=16 cycles across 1440px share a gcd of 4, so the pattern
       and its envelope both come back into phase every 1440/4 = 360px. The
       old value of 120px predated the corrected paths and seamed visibly. */
    transform: translateX(-360px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .beat-envelope {
    animation: none;
  }
}

/* --- Cards --------------------------------------------------------------- */

.cards {
  display: grid;
  gap: 12px;
  margin: 28px 0 0;
  padding: 0;
  list-style: none;
}

.card {
  display: block;
  padding: 17px 19px;
  border: 1px solid var(--rule);
  border-radius: 14px;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.18s ease, background-color 0.18s ease;
}

.card:hover {
  border-color: var(--clay);
  background: rgba(232, 180, 160, 0.06);
  color: inherit;
}

.card .k {
  display: block;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 1rem;
  font-weight: 500;
  color: var(--paper);
  margin-bottom: 3px;
}

.card .d {
  display: block;
  font-size: 0.95rem;
  color: var(--dim);
  line-height: 1.5;
}

/* --- Footer -------------------------------------------------------------- */

.foot {
  margin-top: 72px;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.92rem;
  color: var(--dim);
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  justify-content: space-between;
}

.foot a {
  color: var(--dim);
}

.foot a:hover {
  color: var(--clay);
}

/* --- Accessibility floor -------------------------------------------------- */

:focus-visible {
  outline: 2px solid var(--clay);
  outline-offset: 3px;
  border-radius: 3px;
}

.skip {
  position: absolute;
  left: -9999px;
}

.skip:focus {
  left: 24px;
  top: 12px;
  z-index: 10;
  padding: 8px 14px;
  background: var(--deep);
  border: 1px solid var(--clay);
  border-radius: 8px;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.92rem;
}

@media (max-width: 480px) {
  body {
    font-size: 17.5px;
  }

  .masthead {
    padding-top: 22px;
  }

  /* Keep the three readings on one line. "Difference" is the point the other
     two are making, and wrapping it onto its own row separates it from the
     pair it is derived from. */
  .beat-read {
    gap: 16px;
  }

  .beat-read .v {
    font-size: 1.06rem;
  }

  .beat-read dt,
  .beat-read .k {
    font-size: 0.62rem;
    letter-spacing: 0.14em;
  }
}

/* --- Hero ---------------------------------------------------------------- */

/*
 * The site was a single 34rem reading column, which is right for the legal
 * pages and too narrow for a homepage that has to show the app. The wrapper
 * widens only where a page opts in, so support/privacy/terms keep their
 * measure and stay easy to read.
 */
.hero {
  display: grid;
  grid-template-columns: 1fr;
  gap: 40px;
  align-items: center;
  margin-bottom: 8px;
}

.hero-copy {
  min-width: 0;
}

.hero-copy .lede {
  margin-bottom: 22px;
}

.hero-points {
  list-style: none;
  margin: 0 0 28px;
  padding: 0;
  display: grid;
  gap: 9px;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.92rem;
  color: var(--prose);
}

/* A mark rather than a bullet, and drawn in clay so the three commitments
   read as claims being made rather than as a feature list. */
.hero-points li {
  position: relative;
  padding-left: 22px;
  line-height: 1.5;
}

.hero-points li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.52em;
  width: 9px;
  height: 5px;
  border-left: 1.5px solid var(--clay);
  border-bottom: 1.5px solid var(--clay);
  transform: rotate(-45deg);
}

.hero-cta {
  display: flex;
  flex-wrap: wrap;
  /* Centred against the badge rather than baseline-aligned: the badge is an
     image with no text baseline of its own, so anything beside it has to be
     centred on the box or it sits high. */
  align-items: center;
  gap: 18px;
}

/* The eyebrow carries a bottom margin from its use above the h1, which pushes
   it off centre when it sits beside the badge instead of above a heading. */
.hero-cta .eyebrow {
  margin: 0;
}

.btn {
  display: inline-block;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 11px 18px;
  border-radius: 999px;
  border: 1px solid var(--clay);
  background: var(--clay);
  color: var(--deep);
  text-decoration: none;
}

.btn:hover {
  background: var(--paper);
  border-color: var(--paper);
  color: var(--deep);
}

.btn-quiet {
  background: transparent;
  color: var(--clay);
}

.btn-quiet:hover {
  background: transparent;
  border-color: var(--paper);
  color: var(--paper);
}

/*
 * The phone. A plain image rather than a drawn device frame: the screenshot
 * already contains the status bar and the app's own rounded corners, and a
 * second bezel around it reads as a stock mockup.
 */
.hero-shot {
  justify-self: center;
  max-width: 300px;
  width: 100%;
}

.hero-shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 26px;
  border: 1px solid var(--rule);
  /* Lifts the shot off a background it shares a palette with. */
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}

/* --- The beat figure, updated -------------------------------------------- */

/* The sum of the two tones: what actually reaches the ear. */
.beat-sum {
  fill: none;
  stroke: var(--clay);
  stroke-width: 1.4;
  stroke-linejoin: round;
  opacity: 0.9;
}

/* The envelope: the swell and thin that IS the beat, drawn as the shape the
   sum is bounded by. Faint, because it is an annotation on the trace rather
   than a second signal. */
.beat-env {
  fill: none;
  stroke: var(--rose);
  stroke-width: 1;
  stroke-dasharray: 3 4;
  opacity: 0.5;
}

.figure-note {
  font-size: 1.02rem;
  color: var(--dim);
  margin-top: -18px;
  margin-bottom: 34px;
}

/* --- Sections ------------------------------------------------------------ */

.section {
  margin-top: 8px;
}

.section-lede {
  color: var(--dim);
  margin-bottom: 22px;
}

.features,
.pledges {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 22px;
}

.features h3,
.pledges h3 {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--paper);
  margin: 0 0 6px;
}

.features p,
.pledges p {
  margin: 0;
  font-size: 1.02rem;
  color: var(--dim);
  line-height: 1.55;
}

/* The pledges are the argument, not a feature list, so each one is set as a
   card with a clay edge: it should read as something being committed to. */
.pledges li {
  border-left: 2px solid var(--rule);
  padding-left: 16px;
}

.pledges li:hover {
  border-left-color: var(--clay);
}

/* --- FAQ ----------------------------------------------------------------- */

.faq {
  display: grid;
  gap: 2px;
}

.faq details {
  border-bottom: 1px solid var(--rule);
}

.faq summary {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--paper);
  padding: 15px 30px 15px 0;
  cursor: pointer;
  list-style: none;
  position: relative;
}

.faq summary::-webkit-details-marker {
  display: none;
}

/* A chevron that rotates, drawn in CSS so there is no icon file to load. */
.faq summary::after {
  content: '';
  position: absolute;
  right: 6px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -5px;
  border-right: 1.5px solid var(--clay);
  border-bottom: 1.5px solid var(--clay);
  transform: rotate(45deg);
  transition: transform 160ms ease;
}

.faq details[open] summary::after {
  transform: rotate(-135deg);
  margin-top: -2px;
}

.faq details p {
  margin: 0 0 18px;
  font-size: 1.02rem;
  color: var(--dim);
  line-height: 1.6;
  max-width: 34rem;
}

@media (prefers-reduced-motion: reduce) {
  .faq summary::after {
    transition: none;
  }
}

/* --- Wide layout --------------------------------------------------------- */

@media (min-width: 860px) {

  /* Only the homepage widens. The legal pages keep their reading measure. */
  body:has(.hero) .wrap {
    max-width: 62rem;
  }

  .hero {
    grid-template-columns: 1.15fr 0.85fr;
    gap: 56px;
    margin-bottom: 20px;
  }

  .hero-shot {
    justify-self: end;
    max-width: 320px;
  }

  .features,
  .pledges {
    grid-template-columns: 1fr 1fr;
    gap: 26px 40px;
  }

  /* Prose stays at a readable measure even though the page is wider. */
  .figure-note,
  .section-lede {
    max-width: 42rem;
  }
}

/* --- Evidence grades ----------------------------------------------------- */

/*
 * The visual break between two text-heavy sections, and the only place on the
 * site where the product's central claim is shown rather than described.
 *
 * Built as a scannable list of rows rather than prose: a reader should be able
 * to take in the whole scorecard without reading a sentence, and only drop
 * into the notes for the ones that surprise them.
 */
.grades {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
}

.grade {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px 18px;
  padding: 15px 0 15px 15px;
  border-top: 1px solid var(--rule);
  /* The tint carries the verdict, and is set per modifier below. */
  border-left: 2px solid var(--rule);
}

.grade:last-child {
  border-bottom: 1px solid var(--rule);
}

.grade-topic {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--paper);
}

/*
 * The verdict word does the work; the colour only reinforces it. Set in the
 * mono face at label size so it reads as a grade rather than as more prose.
 */
.grade-verdict {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  align-self: center;
}

.grade-note {
  font-size: 1rem;
  color: var(--dim);
  line-height: 1.55;
}

.grade-good {
  border-left-color: var(--yes);
}

.grade-good .grade-verdict {
  color: var(--yes);
}

.grade-ok {
  border-left-color: var(--clay);
}

.grade-ok .grade-verdict {
  color: var(--clay);
}

/*
 * Deliberately not red, and deliberately not hidden. These are the rows that
 * cost the product something, and dressing them as warnings would make them
 * look like a defect rather than a disclosure.
 */
.grade-weak {
  border-left-color: var(--no);
}

.grade-weak .grade-verdict {
  color: var(--no);
  font-weight: 700;
}

@media (min-width: 720px) {
  .grade {
    /* Topic and verdict share a row; the note spans beneath both, so the
       left column stays a clean scannable list of names. */
    grid-template-columns: minmax(9rem, 14rem) 1fr;
    align-items: baseline;
  }

  .grade-note {
    grid-column: 1 / -1;
    max-width: 46rem;
  }
}

@media (min-width: 860px) {
  .grade {
    /* Wide enough for all three across, which is the fastest to scan. */
    grid-template-columns: minmax(9rem, 12rem) minmax(8rem, 11rem) 1fr;
  }

  .grade-note {
    grid-column: 3;
    max-width: none;
  }
}

/* --- In-page screenshots ------------------------------------------------- */

/*
 * Two phones side by side, in one break.
 *
 * The page is otherwise a stack of same-width text blocks, and a screenshot
 * inset to that same measure just becomes another one. A pair sitting
 * together reads as a product shot; the same two spaced down the page read as
 * decoration between paragraphs.
 */
.shots {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin: 44px 0;
  align-items: start;
}

.shot {
  margin: 0;
}

.shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 22px;
  border: 1px solid var(--rule);
  /* Lifts the shot off a background it shares a palette with. */
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.45);
}

.shot figcaption {
  margin-top: 12px;
  font-size: 1rem;
  color: var(--dim);
  line-height: 1.5;
}

@media (min-width: 720px) {
  .shots {
    gap: 36px;
    /* Held short of the full column so the pair sits as a figure rather than
       filling the page edge to edge. */
    max-width: 44rem;
    margin-inline: auto;
  }

  .shot figcaption {
    font-size: 1.02rem;
  }
}

/* --- App Store badge ----------------------------------------------------- */

/*
 * Apple ships this asset on a white field with a wide margin baked in, so on
 * a dark ground it renders as a white slab. The wrapper crops to the black
 * pill: the image is scaled up inside a shorter box and pulled into place,
 * which shows only the badge itself.
 *
 * Cropping rather than recolouring matters. Apple's guidelines forbid altering
 * the badge artwork, and clipping the surrounding whitespace leaves the mark
 * itself untouched.
 */
.badge {
  display: inline-block;
  width: 168px;
  height: 56px;
  overflow: hidden;
  border-radius: 9px;
  line-height: 0;
}

.badge img {
  /* 1024x512 source; the pill occupies roughly the middle 47% x 30%. */
  width: 352px;
  height: 176px;
  max-width: none;
  margin: -60px 0 0 -92px;
  display: block;
}

.badge:hover {
  opacity: 0.88;
}

/* --- Evidence page ------------------------------------------------------- */

/*
 * Generated by website/gen-evidence.js from src/content/claims.ts, so the
 * grades and citations here are the same objects the app renders. Retyping
 * them would let the two drift, and a site quoting a study the app no longer
 * cites is exactly the failure this page exists to refuse.
 */
.method {
  font-size: 0.98rem;
  color: var(--dim);
  line-height: 1.55;
  max-width: 42rem;
}

.claim {
  margin-top: 40px;
  padding-top: 26px;
  border-top: 1px solid var(--rule);
}

.claim-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 16px;
  margin-bottom: 14px;
}

.claim-head h2 {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 1.45rem;
}

/* The verdict word carries the grade; the colour only reinforces it, and the
   negative grades are additionally bold. Same rule as the app. */
.claim-verdict {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin: 0;
}

.claim-good .claim-verdict {
  color: var(--yes);
}

.claim-ok .claim-verdict {
  color: var(--clay);
}

.claim-weak .claim-verdict {
  color: var(--no);
  font-weight: 700;
}

.claim p {
  max-width: 42rem;
}

.ref-head {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
  margin: 26px 0 12px;
}

.refs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 18px;
}

.ref {
  border-left: 2px solid var(--rule);
  padding-left: 16px;
}

.ref-line {
  margin: 0 0 8px;
  font-size: 1rem;
  color: var(--paper);
}

.ref-journal {
  color: var(--dim);
  font-style: italic;
}

/*
 * Every reference states what was studied, what was found and how much weight
 * it carries. Three consistent fields, so a citation can be judged at a glance
 * rather than read line by line: a reference with a missing sample size looks
 * like evidence while providing none.
 */
.ref-meta {
  display: grid;
  grid-template-columns: minmax(4.5rem, auto) 1fr;
  gap: 3px 14px;
  margin: 0;
  font-size: 0.94rem;
}

.ref-meta dt {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
  padding-top: 0.25em;
}

.ref-meta dd {
  margin: 0;
  color: var(--prose);
}

/* Whatever undercuts a study. Citing a weak result AS weak is what makes the
   strong ones credible, so it is tinted rather than tucked away. */
.ref-caveat {
  color: var(--no);
}

.disclaimer {
  margin-top: 48px;
  padding: 18px 20px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: rgba(232, 180, 160, 0.03);
  font-size: 0.98rem;
  color: var(--dim);
}
/* --- The moving horizon on the hero shot --------------------------------- */

/*
 * The screenshot is a still, and the one element in it that is never still in
 * the app is the horizon. This lays a drifting wave over exactly that band so
 * the hero shows what the app actually does.
 *
 * The parent needs a positioning context, and the image needs to keep its own
 * corner radius so the overlay does not spill past the rounded frame.
 */
.hero-shot {
  position: relative;
  overflow: hidden;
  border-radius: 26px;
}

/*
 * Spans 32.4% to 46.6% of the frame height: measured off the capture, where
 * the horizon stroke sits at y 840 to 890 of 2532 and its glow fades by 1180.
 * Those are the only two numbers to change if the screenshot is retaken.
 *
 * Inset horizontally by the same 1px the image border occupies, so the wave
 * stops at the frame rather than sitting on top of it.
 */
.hero-horizon {
  position: absolute;
  left: 0;
  top: 32.4%;
  height: 14.2%;
  pointer-events: none;

  /*
   * Twice the frame width, because the paths are drawn to x=1440 in a 720
   * viewBox and the drift travels one full frame before looping. The frame's
   * own overflow:hidden does the clipping.
   *
   * preserveAspectRatio is none so the 720x100 viewBox stretches to fill this
   * box exactly: the wave has to sit where the horizon is, not keep its own
   * proportions and float somewhere near it.
   */
  width: 200%;
}

.hero-horizon-drift {
  animation: horizon-drift 14s linear infinite;
}

.hero-horizon-line {
  fill: none;
  stroke: var(--paper);
  stroke-width: 1.1;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  opacity: 0.55;
}

/* The second trace, fainter and out of phase: the same depth the app's own
   horizon gets from drawing its echo. */
.hero-horizon-echo {
  fill: none;
  stroke: var(--rose);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: 0.3;
}

@keyframes horizon-drift {
  from { transform: translateX(0); }
  /* Half the drawn width, which is one full traversal of the visible frame. */
  to { transform: translateX(-50%); }
}

/*
 * Motion here is decoration, not information: the still screenshot underneath
 * already says everything the animation does. So it stops entirely rather
 * than slowing down.
 */
@media (prefers-reduced-motion: reduce) {
  .hero-horizon-drift {
    animation: none;
  }
}
