/* Fit for Love — homepage.
   Colour is the product's own day palette, never the comp's sampled pixels.
   Geometry is measured from the approved comp. */

:root {
  /* the day palette */
  --page:     #F8E9F0;
  --sheet:    #FCF3F7;
  --raised:   #FFFBFD;
  --ink:      #3B0F25;
  --ink-worn: #783B56;
  --you:      #D93DB4;   /* one partner */
  --partner:  #A051C4;   /* the other  */
  /* The two hues are display colours: at body size they fail 4.5:1 on the pale
     grounds, so small type uses these darkened siblings. The hue is kept and
     only the lightness moves, so the two-people coding still reads. */
  --you-text:     #B63397;   /* 4.58:1 */
  --partner-text: #954BB6;   /* 4.52:1 */
  --you-fill:     #CA39A7;   /* white label on it: 4.51:1 */

  /* an edge is light catching a surface, never a drawn line */
  --edge:     rgba(59, 15, 37, 0.10);
  --edge-strong: rgba(59, 15, 37, 0.22);

  /* one unit = one pixel of the 1536-wide comp */
  --u: calc(min(100vw, 1760px) / 1536);
  /* the width the hero actually occupies; the card is measured against it */
  --B: min(100vw, 1760px);
  /* 0 at rest, 1 once the page has been scrolled; set by home.js */
  --shrink: 0;

  /* The app's two typefaces (ffl-design rule 3): Fraunces is the voice, Onest
     is everything you read or press. The page loads both; until 19 Oct 2026
     this named EB Garamond and Karla, which it never loaded, so most visitors
     read it in Georgia and their system sans. */
  --serif: 'Fraunces', Georgia, 'Times New Roman', serif;
  --sans:  'Onest', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

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

body {
  background: var(--sheet);
  color: var(--ink);
  font-family: var(--serif);
  font-variation-settings: 'SOFT' 100, 'WONK' 0;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection { background: rgba(217, 61, 180, 0.18); color: var(--ink); }

html { caret-color: var(--you-text); scrollbar-color: rgba(59,15,37,0.28) var(--sheet); scrollbar-width: thin; }

a { color: inherit; }

/* Read, never seen. The page's own statement is the equation, which rewrites
   itself as you scroll and so cannot be the heading; this names the page once
   for a crawler and a screen reader and occupies no space. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* ── the first viewport ─────────────────────────────────────────────── */

.hero {
  position: relative;
  width: 100%;
  aspect-ratio: 1536 / 1024;
  overflow: hidden;
  background:
    radial-gradient(115% 70% at 50% 40%, var(--page) 0%, rgba(248, 233, 240, 0) 72%),
    var(--sheet);
}

/* ── the hero is the map ────────────────────────────────────────────── */
/* The plate is 1536x1024 and so is the hero, which is why this can be a true
   full bleed: no crop, and a pin written as a percentage of the hero lands on
   exactly the feature it was placed on. The layer takes no z-index on purpose
   — it must not open a stacking context, or the pin cards inside it could
   never rise over the line and the form that follow them. */

.hero__map { position: absolute; inset: 0; }

.hero__map-img {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The ground the line and the form sit on. It was a linear fade, which is a
   band: parallel edges the full width of the page, and the eye reads the top
   of it as a drawn line across the map. An ellipse rising from the foot has no
   edge to find — it is only ever light pooling where the text is.
   z-index 1 keeps it over the plate and under the pins, which would otherwise
   be washed out by it wherever one sits low on the map. */
.hero__map::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(60% 30% at 50% 97%,
      rgba(252, 243, 247, 0.98) 0%,
      rgba(252, 243, 247, 0.9) 40%,
      rgba(252, 243, 247, 0.5) 68%,
      rgba(252, 243, 247, 0) 88%),
    linear-gradient(to bottom, var(--sheet) 0%, rgba(252, 243, 247, 0) 13%);
}


/* ── header ─────────────────────────────────────────────────────────── */

/* Layering in the hero, bottom to top: the plate (0), the wash that gives the
   foot its ground (1), the map's labels (2), and the page's own furniture —
   wordmark, line, form — above all of it (3). */
.hero__head {
  position: absolute;
  z-index: 3;
  left: 1.95%; top: 1.17%; right: 1.82%;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: calc(10 * var(--u));
  font-size: calc(27 * var(--u));
  line-height: 1;
  letter-spacing: 0;
  color: var(--ink);
  text-decoration: none;
}
/* the app icon beside the name (owner, 19 Oct 2026: "the app logo in the top left corner for sure") */
.wordmark__mark {
  display: block;
  width: calc(30 * var(--u));
  height: auto;
  border-radius: 26%;
  box-shadow: 0 1px 2px rgba(59, 15, 37, 0.12), 0 6px 14px -8px rgba(59, 15, 37, 0.4);
}

.navlink {
  font-family: var(--sans);
  font-size: calc(15 * var(--u));
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--ink);
  text-decoration: none;
  /* the link's ink is ~17px tall; the hit area must clear 24px */
  padding: 6px 2px;
  margin: -6px -2px;
  border-bottom: 1px solid transparent;
  transition: border-color 220ms ease-out;
}
.navlink:hover,
.navlink:focus-visible { border-bottom-color: rgba(59, 15, 37, 0.35); }

/* ── the equation, and the bar it floats in ─────────────────────────── */

/* ── the top: one floating group, lattice and card together ─────────── */
/* It is fixed above the whole page from the first pixel, it never hands off to
   a second element, and scrolling only makes it smaller. The lattice travels
   with the card because it is part of the same object, not hero decoration. */

.top {
  position: fixed;
  z-index: 60;
  left: 50%;
  top: calc(var(--B) * 0.0720);
  width: calc(var(--B) * 0.8000);
  height: calc(var(--B) * 0.1550);
  margin-left: calc(var(--B) * -0.4000);
  transform-origin: 50% 0;
  /* driven continuously by scroll, so there is no step between the two sizes */
  transform:
    translateY(calc(var(--B) * -0.0984 * var(--shrink)))
    scale(calc(1 - 0.24 * var(--shrink)));
  will-change: transform;
  pointer-events: none;   /* it floats over the page without swallowing clicks */
}

.top__lattice {
  position: absolute;
  inset: 0;
  /* the rules, then a translucent pane beneath them. The mask fades pane and
     rules together, so the whole panel dissolves into the page as one thing,
     and the page stays partly readable through it. */
  background:
    repeating-linear-gradient(to right,  rgba(217, 61, 180, 0.22) 0 1px, transparent 1px calc(100% / 12)),
    repeating-linear-gradient(to bottom, rgba(217, 61, 180, 0.22) 0 1px, transparent 1px calc(100% / 4)),
    linear-gradient(rgba(255, 252, 254, 0.72), rgba(255, 252, 254, 0.72));
  -webkit-backdrop-filter: blur(12px) saturate(1.08);
          backdrop-filter: blur(12px) saturate(1.08);
  /* An ellipse whose radii stay INSIDE the box, so the fade actually reaches
     zero before the element's rectangle and no straight edge can show.
     Percentage radii are measured from the centre: anything over 50% finishes
     off-box and leaves a hard edge, which is what 74%/62% was doing. The ramp
     starts early and is fully out by 86%, so the panel reads as a soft round
     pool rather than a panel with corners. */
  -webkit-mask-image: radial-gradient(46% 48% at 50% 50%,
              rgba(0,0,0,1) 0%, rgba(0,0,0,0.94) 22%,
              rgba(0,0,0,0.48) 58%, rgba(0,0,0,0) 86%);
          mask-image: radial-gradient(46% 48% at 50% 50%,
              rgba(0,0,0,1) 0%, rgba(0,0,0,0.94) 22%,
              rgba(0,0,0,0.48) 58%, rgba(0,0,0,0) 86%);
}

.equation-bar {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--B) * 0.6400);
  height: calc(var(--B) * 0.0545);
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  padding: 6px 22px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.97);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
          backdrop-filter: blur(16px) saturate(1.15);
  box-shadow:
    0 calc(14 * var(--u)) calc(38 * var(--u)) calc(-14 * var(--u)) rgba(59, 15, 37, 0.20),
    0 calc(3 * var(--u))  calc(10 * var(--u)) calc(-5 * var(--u))  rgba(59, 15, 37, 0.08);
  -webkit-mask-image: radial-gradient(96% 300% at 50% 50%, #000 82%, transparent 100%);
          mask-image: radial-gradient(96% 300% at 50% 50%, #000 82%, transparent 100%);
}

.equation {
  margin: 0;
  font-size: calc(63 * var(--u));
  line-height: 1;
  letter-spacing: -0.004em;
  white-space: nowrap;
  /* the comp's numeral sits on the baseline, not below it */
  font-variant-numeric: lining-nums;
  font-feature-settings: 'lnum' 1;
}

.equation__count  { color: var(--you); }
.equation__unit,
.equation__eq     { color: var(--ink); }
.equation__reward { color: var(--partner); }

/* the phone's own line, where the floating sum used to say it (below) */
.hero__claim { display: none; }

/* ── the line beneath ───────────────────────────────────────────────── */

.subline {
  position: absolute;
  z-index: 3;
  /* The comp put this in the middle of the hero, which was empty then. Over a
     map that spot is the busiest part of the plate and the line disappears
     into the trees, so it moves down to join the form on the wash. */
  left: 50%; top: 77%;
  transform: translateX(-50%);
  /* it lies over a live map: a paragraph box is invisible and would still
     swallow a pin underneath it */
  pointer-events: none;
  margin: 0;
  width: 46%;
  text-align: center;
  font-size: calc(18 * var(--u));
  line-height: 1.45;
  color: var(--ink-worn);
}

/* ── the waitlist ───────────────────────────────────────────────────── */

.waitlist {
  /* The comp had this at 91.41%, which a 1440x900 window cuts in half now the
     hero is a 3:2 plate rather than the comp's own frame. It sits with the
     line instead, and the whole group clears the fold. */
  position: absolute;
  z-index: 3;
  left: 37.11%; top: 85.5%; width: 25.46%;
  height: calc(34 * var(--u));
  display: flex;
  align-items: stretch;
  gap: calc(8 * var(--u));
}

.waitlist__label {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

.waitlist__hp {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  opacity: 0;
}

.waitlist__input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  padding: 0 calc(22 * var(--u));
  font-family: var(--sans);
  font-size: calc(11 * var(--u));
  line-height: 1.2;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--edge);
  border-radius: 999px;
}
.waitlist__input::placeholder { color: #6E3550; opacity: 1; }
.waitlist__input:focus-visible {
  outline: 2px solid var(--you);
  outline-offset: calc(2 * var(--u));
  border-color: transparent;
}

.waitlist__submit {
  flex: 0 0 auto;
  width: calc(135 * var(--u));
  height: 100%;
  padding: 0;
  font-family: var(--sans);
  font-weight: 500;
  font-size: calc(12 * var(--u));
  letter-spacing: 0.01em;
  line-height: 1.2;
  color: #FFFFFF;
  /* lifted by light, like every other surface here */
  background: linear-gradient(180deg, var(--you-fill) 0%, #BC3499 55%, #A82B88 100%);
  box-shadow:
    0 calc(6 * var(--u)) calc(16 * var(--u)) calc(-6 * var(--u)) rgba(217, 61, 180, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.30);
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 200ms ease-out, transform 200ms ease-out;
}
.waitlist__submit:hover { background: #B22E93; }
.waitlist__submit:active { transform: translateY(1px); }
.waitlist__submit:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: calc(2 * var(--u));
}

/* ── the rest of the page ───────────────────────────────────────────── */
/* Beyond the first viewport the layout stops being measured against the comp
   and becomes fluid: the comp fixed the world, not the whole scroll. Type
   returns to rem so it stays readable when the window is not 1536 wide. */

section {
  padding: clamp(60px, 6.5vw, 108px) clamp(24px, 6vw, 80px);
  /* the card floats over everything, so an anchored heading must not land under it */
  scroll-margin-top: calc(var(--B) * 0.075);
}

.foot { background: var(--raised); }

/* ── the close ──────────────────────────────────────────────────────── */

.close {
  background:
    radial-gradient(90% 120% at 50% 0%, var(--page) 0%, rgba(248, 233, 240, 0) 70%),
    var(--sheet);
  text-align: center;
}

.close__title {
  margin: 0 auto;
  max-width: 18ch;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(2rem, 4.2vw, 3.4rem);
  line-height: 1.12;
  letter-spacing: -0.012em;
  color: var(--ink);
  text-wrap: balance;
}
.close__title em {
  background: linear-gradient(90deg, var(--you-text), var(--partner-text));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

.close__t {
  margin: 22px auto 0;
  text-wrap: pretty;
  max-width: 48ch;
  font-family: var(--serif);
  font-size: clamp(1.1rem, 1.7vw, 1.4rem);
  line-height: 1.55;
  color: var(--ink-worn);
}

.waitlist--close {
  position: static;
  width: min(440px, 100%);
  height: auto;
  margin: clamp(32px, 4vw, 48px) auto 0;
  gap: 10px;
}
.waitlist--close .waitlist__input { height: 52px; padding: 0 22px; font-size: 1rem; }
.waitlist--close .waitlist__submit { width: auto; height: 52px; padding: 0 26px; font-size: 0.95rem; }

.close__honest {
  margin: 26px auto 0;
  max-width: 52ch;
  font-family: var(--sans);
  font-size: 0.92rem;
  line-height: 1.65;
  color: var(--ink-worn);
}

/* ── foot ───────────────────────────────────────────────────────────── */

.foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding: 30px clamp(24px, 8vw, 160px) 42px;
  border-top: 1px solid var(--edge);
  background: var(--sheet);
  font-family: var(--sans);
  font-size: 0.9rem;
  color: var(--ink-worn);
}

.foot__mark { font-family: var(--serif); font-size: 1.05rem; color: var(--ink); }
.foot__nav { display: flex; gap: 26px; }
.foot__nav a {
  color: var(--ink-worn);
  text-decoration: none;
  padding: 6px 2px;
  margin: -6px -2px;
  border-bottom: 1px solid transparent;
}
.foot__nav a:hover, .foot__nav a:focus-visible { border-bottom-color: var(--edge-strong); }

/* ── the equation keeps rewriting itself ────────────────────────────── */

.equation__count,
.equation__unit,
.equation__reward {
  display: inline-block;
  transition: opacity 210ms cubic-bezier(0.4, 0, 0.2, 1),
              transform 210ms cubic-bezier(0.4, 0, 0.2, 1),
              text-shadow 460ms ease-out;
}

/* the old value falls away downward */
.is-out .equation__count,
.is-out .equation__unit,
.is-out .equation__reward {
  opacity: 0;
  transform: translateY(0.42em);
}

/* the new value is placed above, then released into position */
.is-in .equation__count,
.is-in .equation__unit,
.is-in .equation__reward {
  opacity: 0;
  transform: translateY(-0.42em);
  transition: none;
}

/* and it lights briefly as it lands, each half in its own hue */
.is-glow .equation__count,
.is-glow .equation__reward {
  text-shadow: 0 0 0.42em currentColor;
}



/* ── what the form says back ────────────────────────────────────────── */

.waitlist__note {
  margin: 12px 0 0;
  min-height: 1.4em;
  font-family: var(--sans);
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--ink-worn);
}
.waitlist__note.is-good { color: var(--partner-text); }
.waitlist__note.is-bad  { color: #C4304A; }

.hero .waitlist__note {
  position: absolute;
  left: 37.11%; top: calc(91.41% + 42 * var(--u));
  width: 25.46%;
  margin: 0;
  font-size: calc(12 * var(--u));
}

.waitlist__submit:disabled { opacity: 0.6; cursor: default; }


/* ── other viewports ────────────────────────────────────────────────── */

/* The hero is as wide as the window, whatever the window is (owner, 19 Oct
   2026: zoomed out, the capped hero sat as a box in the middle of the page
   with bare pink either side), and never taller than the screen. The plate
   covers it, so a window that is not 3:2 crops the plate a little; the pins
   lie on the plate's own drawn area (home.js measures it), so each still
   lands on the feature it was placed on. The sum and the form keep their
   capped measure: a card over a bigger map. */
.hero { max-height: 100svh; }

/* Tablet: the measured hero still holds proportionally, but the sum needs
   room to breathe before it is allowed to wrap. */
@media (max-width: 1240px) {
  .equation { font-size: calc(72 * var(--u)); }
  .subline  { width: 62%; font-size: calc(20 * var(--u)); }
}

/* Phone: the comp's absolute geometry is abandoned deliberately. Holding a
   1536x1024 frame at 390px would render the equation at 18px — technically
   faithful and practically unreadable. The page becomes flow instead, and the
   equation keeps the largest type on the screen, which is the actual promise. */
@media (max-width: 820px) {
  /* The first screen is the map, the way it is on a desk (owner, 19 Oct
     2026: "a new photo for the phone view that covers the whole first
     page just like in the pc view"): a tall plate drawn for phones
     (assets/map/plate-phone.webp) covers the whole first screen, the line
     that says what this is sits at the top on a wash, the pins in the
     middle, the line and the form at the foot on a wash. The sum's measured
     geometry is still abandoned: the card itself is gone here. */
  .hero {
    aspect-ratio: auto;
    display: block;
    height: 100svh;
    min-height: 620px;
    max-height: 980px;
    padding: 0;
    background: var(--sheet);
  }

  /* The floating sum is a desktop object: on a phone it covered the top of
     every section (owner, 19 Oct 2026: "it doesn't work in phone view"), so
     here it is gone, and the hero says what the page is in a plain line. */
  .top { display: none; }
  section { scroll-margin-top: 12px; }

  /* the washes that carry the words: one from the top, one from the foot */
  .hero__map::after {
    background:
      linear-gradient(180deg, rgba(252, 243, 247, 0.97) 0%, rgba(252, 243, 247, 0.9) 18%, rgba(252, 243, 247, 0.35) 28%, rgba(252, 243, 247, 0) 36%),
      linear-gradient(0deg, rgba(252, 243, 247, 0.98) 0%, rgba(252, 243, 247, 0.92) 18%, rgba(252, 243, 247, 0.35) 28%, rgba(252, 243, 247, 0) 36%);
  }

  .hero__head {
    left: 20px; right: 20px; top: calc(14px + env(safe-area-inset-top, 0px));
    align-items: center;
  }
  .wordmark { font-size: 1.3rem; gap: 8px; }
  .wordmark__mark { width: 28px; }
  .navlink  { font-size: 0.9rem; }

  .hero__claim {
    display: block;
    position: absolute;
    z-index: 3;
    left: 20px; right: 20px;
    top: calc(62px + env(safe-area-inset-top, 0px));
    margin: 0 auto;
    max-width: 13ch;
    text-align: center;
    font-family: var(--serif);
    font-weight: 600;
    font-size: clamp(2.1rem, 10vw, 2.8rem);
    line-height: 1.04;
    letter-spacing: -0.02em;
    color: var(--ink);
    text-wrap: balance;
  }
  .hero__claim em {
    background: linear-gradient(90deg, var(--you-text), var(--partner-text));
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
  }

  .subline {
    top: auto;
    bottom: calc(104px + env(safe-area-inset-bottom, 0px));
    left: 20px; right: 20px;
    width: auto;
    max-width: 34ch;
    margin-inline: auto;
    transform: none;
    font-size: 1rem;
  }

  /* A phone shows the pins at their places on ITS plate, between the two
     washes; the two pins with no place there stand down, and four remain,
     two of each kind. */
  .pins .pin { left: var(--px, var(--x)); top: var(--py, var(--y)); }   /* outweighs the base rule, which comes later in this file */
  .pins .pin--crowd { display: none; }

  .hero .waitlist {
    left: 20px; right: 20px; top: auto;
    bottom: calc(26px + env(safe-area-inset-bottom, 0px));
    width: auto;
    height: auto;
    gap: 8px;
  }
  .hero .waitlist__input  { height: 50px; padding: 0 20px; font-size: 1rem; flex: 1 1 10ch; }
  .hero .waitlist__submit { width: auto; height: 50px; padding: 0 20px; font-size: 0.95rem; flex: 0 0 auto; }
  .hero .waitlist__note {
    left: 20px; right: 20px; top: auto;
    bottom: calc(4px + env(safe-area-inset-bottom, 0px));
    width: auto;
    margin: 0;
    font-size: 0.8rem;
    text-align: center;
  }
}

/* ── the streak: someone is counting on you ─────────────────────────── */
/* Two numbers and a headline, nothing else (owner, 7 Oct 2026: "most people
   are not going to read everything so just showing these numbers will have
   an effect on them"). Light ground, the app's dawn with the two people's
   lights in it. The figures are Wallace, Raglin & Jastremski, "Twelve month
   adherence of adults who joined a fitness program with a spouse vs without
   a spouse", J Sports Med Phys Fitness, 1995: 6.3% of the couples dropped
   out (so 94% kept going, rounded from 93.7) and 43% of the married people
   who joined alone did. The labels say exactly that; never stretch them,
   never add a number we cannot cite. */

.streak {
  position: relative;
  overflow: hidden;
  text-align: center;
  background:
    radial-gradient(40% 55% at 8% 20%, rgba(255, 143, 208, 0.55) 0%, rgba(255, 143, 208, 0) 70%),
    radial-gradient(40% 55% at 94% 70%, rgba(185, 138, 240, 0.5) 0%, rgba(185, 138, 240, 0) 70%),
    linear-gradient(180deg, #FFEAF4 0%, #FFDCEC 50%, #FFF0F7 100%);
  padding-block: clamp(80px, 9vw, 140px);
}

.streak__title {
  margin: 0 auto;
  max-width: 20ch;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.7rem, 3.2vw, 2.6rem);
  line-height: 1.12;
  letter-spacing: -0.012em;
  color: var(--ink);
  text-wrap: balance;
}
.streak__title em { color: var(--partner-text); }

.odds {
  margin: clamp(30px, 4vw, 56px) auto 0;
  max-width: 980px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(16px, 4vw, 64px);
}
.odds__col { margin: 0; display: grid; justify-items: center; gap: 10px; }
.odds__n {
  font-family: var(--serif);
  font-size: clamp(5rem, 15vw, 12rem);
  line-height: 0.9;
  letter-spacing: -0.045em;
  font-variant-numeric: lining-nums tabular-nums;
}
.odds__col--both .odds__n {
  background: linear-gradient(90deg, var(--you), var(--partner));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  filter: drop-shadow(0 14px 30px rgba(217, 61, 180, 0.25));
}
/* what's hard is grey-violet, never the brand's colours */
.odds__col--alone .odds__n { color: #A898B4; }
.odds__k {
  max-width: 17ch;
  font-family: var(--sans);
  font-size: clamp(1rem, 1.5vw, 1.2rem);
  line-height: 1.4;
  color: var(--ink-worn);
  text-wrap: balance;
}
.odds__col--both .odds__k { color: var(--ink); font-weight: 600; }
.odds__src {
  margin: clamp(26px, 3vw, 40px) auto 0;
  font-family: var(--sans);
  font-size: 0.76rem;
  line-height: 1.5;
  color: rgba(120, 59, 86, 0.75);
}

@media (max-width: 620px) {
  .odds { gap: 12px; }
  .odds__n { font-size: clamp(3.6rem, 21vw, 5rem); }
  .odds__k { font-size: 0.92rem; }
}

/* ── the rewards: a wall of them, moving ───────────────────────────── */
/* Not a count of packs (owner, 7 Oct 2026: "19 places to start" read as
   nineteen rewards). Every public reward from the catalogue, each with its
   own drawing, in three rows that drift past in opposite directions and are
   shuffled on every visit, so the page reads as "more than you can look
   at". The list is built from fitfriends-v2 shared/packs.ts, which is kept
   equal to the ClickUp "Rewards and Packs" doc, by website/rewards-art/
   build.py. Spicy is left off the public page. */

.packs {
  position: relative;
  padding-inline: 0;
  text-align: center;
  overflow: hidden;
  /* the app's Rewards satin, its field and pools without the folds */
  background:
    radial-gradient(48% 46% at 88% 36%, rgba(255, 163, 225, 0.42) 0%, rgba(255, 163, 225, 0) 70%),
    radial-gradient(50% 52% at 8% 92%, rgba(156, 104, 255, 0.45) 0%, rgba(156, 104, 255, 0) 70%),
    linear-gradient(165deg, #D11BA0 0%, #C81EA8 40%, #B11D99 75%, #931683 100%);
  padding-block: clamp(72px, 8vw, 130px);
}
.packs__title {
  margin: 0;
  padding-inline: clamp(24px, 6vw, 80px);
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(2rem, 4.2vw, 3.4rem);
  line-height: 1.12;
  letter-spacing: -0.012em;
  color: #fff;
  text-wrap: balance;
}
.packs__title em { color: #FFD6F1; }


.rewardwall {
  margin-top: clamp(36px, 4.5vw, 64px);
  display: grid;
  gap: clamp(10px, 1.2vw, 16px);
}
.rewardwall.is-looping {
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
}

/* Each row is its own horizontal scroller: swipe it, scroll it with a
   trackpad, or drag it with the mouse, and it carries on by itself a moment
   after you let go (home.js). It holds its cards twice and wraps at the
   halfway point, so it never runs out in either direction. */
.rewardwall__row {
  list-style: none;
  margin: 0;
  padding: 8px clamp(24px, 6vw, 80px) 16px;
  display: flex;
  gap: clamp(12px, 1.4vw, 18px);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  cursor: grab;
}
.rewardwall__row::-webkit-scrollbar { display: none; }
.rewardwall__row.is-dragging { cursor: grabbing; }
.is-looping .rewardwall__row { padding-inline: 0; }
.rw, .rw img { -webkit-user-select: none; user-select: none; -webkit-user-drag: none; }

/* each reward is a small print: its drawing on white paper, a little askew */
.rw {
  flex: none;
  width: clamp(200px, 19vw, 250px);
  padding: 9px 9px 13px;
  display: grid;
  gap: 5px;
  text-align: left;
  background: #fff;
  border-radius: 18px;
  transform: rotate(var(--r, 0deg));
  box-shadow: 0 18px 30px -18px rgba(40, 0, 32, 0.6);
}
.rw img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 560 / 200;
  object-fit: cover;
  border-radius: 11px;
}
.rw__t {
  padding: 4px 5px 0;
  font-family: var(--serif);
  font-size: 1.04rem;
  line-height: 1.22;
  color: var(--ink);
}
.rw__c {
  padding: 0 5px;
  font-family: var(--sans);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--you-text);
}
.rw--m .rw__c { color: var(--partner-text); }
.rw--l .rw__c {
  background: linear-gradient(90deg, var(--you-text), var(--partner-text));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  justify-self: start;
}

/* ── the journal: the app's own notebook, lying open on its desk ────── */
/* The page is the Journal tab's page (fitfriends-v2 components/book): a
   sheet of photographed paper, a running head with the week of the year,
   the week's name, every photograph a PRINT with a white border and an ink
   cast, its caption in the colour of whoever it belongs to, a line written
   on the page and signed, the page number at the foot. The ground is the
   desk the book lies on: dark leather lit from above by one rose lamp, a
   violet spill from the right, a fine grain. Two pages on a desk, one on a
   phone (owner, 19 Oct 2026: "match the fitness journal way more to the one
   of the app"). */

.journal {
  position: relative;
  overflow: hidden;
  color: #fff;
  background:
    radial-gradient(70% 42% at 50% 0%, rgba(255, 120, 200, 0.36) 0%, rgba(255, 120, 200, 0) 70%),
    radial-gradient(45% 50% at 100% 58%, rgba(150, 90, 230, 0.18) 0%, rgba(150, 90, 230, 0) 70%),
    linear-gradient(180deg, #2E0D25 0%, #180617 48%, #0D0309 100%);
  padding-block: clamp(72px, 8vw, 130px);
}
/* the grain of the leather */
.journal::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.09;
  mix-blend-mode: overlay;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

.journal__head { position: relative; max-width: 62ch; margin-inline: auto; text-align: center; }

.journal__title {
  margin: 0;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(2rem, 4.2vw, 3.4rem);
  line-height: 1.12;
  letter-spacing: -0.012em;
  color: #fff;
  text-wrap: balance;
}
.journal__title em {
  background: linear-gradient(90deg, #FF8AD8, #C08BFF);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

.journal__lede {
  margin: 14px auto 0;
  font-family: var(--sans);
  font-size: 1.12rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.78);
}

/* the book: two boards of berry cloth with the pages inside them */
.book {
  position: relative;
  max-width: 940px;
  margin: clamp(36px, 4.5vw, 64px) auto 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  padding: 12px;
  border-radius: 16px;
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.10) 0%, rgba(255, 255, 255, 0) 45%),
    linear-gradient(160deg, #8A2474 0%, #5E1A52 55%, #3E0F38 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 0 0 1px rgba(0, 0, 0, 0.25),
    0 2px 4px rgba(0, 0, 0, 0.4),
    0 46px 90px -40px rgba(0, 0, 0, 0.9);
}
/* the gutter: each page falls into the crease between them */
.book::after {
  content: "";
  position: absolute;
  top: 12px; bottom: 12px; left: 50%;
  width: 56px;
  translate: -50% 0;
  pointer-events: none;
  background: linear-gradient(90deg,
    rgba(59, 15, 37, 0) 0%, rgba(59, 15, 37, 0.14) 38%, rgba(59, 15, 37, 0.3) 50%, rgba(59, 15, 37, 0.14) 62%, rgba(59, 15, 37, 0) 100%);
}

.page {
  position: relative;
  padding: 24px 24px 44px 30px;
  color: var(--ink);
  background: #F4EAF0 url(/assets/journal/paper-a.webp) center / cover no-repeat;
  border-radius: 14px 3px 3px 14px;
  box-shadow: inset -1px 0 0 rgba(59, 15, 37, 0.06);
}
.page--b {
  padding: 24px 30px 44px 24px;
  background-image: url(/assets/journal/paper-b.webp);
  border-radius: 3px 14px 14px 3px;
  box-shadow: inset 1px 0 0 rgba(59, 15, 37, 0.06);
}

/* the running head: WEEK 41 · 6 TO 12 OCT, the week itself in full ink */
.page__head {
  margin: 0;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 0.72rem;
  line-height: 1.3;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: #8B5670;
}
.page__wk { color: var(--ink); }

.page__name {
  margin: 8px 0 0;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.45rem, 2.1vw, 1.8rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.page__emoji { font-size: 0.8em; margin-left: 2px; }

/* the photographs: a hero across the page, the rest in a row under it */
.page__collage {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px 10px;
  margin-top: 16px;
}
.print--hero { grid-column: 1 / -1; }

/* a print: white paper round the picture, tipped a hair, an ink cast under it */
.print {
  margin: 0;
  padding: 3px 3px 4px;
  background: #fff;
  border-radius: 2px;
  transform: rotate(var(--tilt, 0deg));
  box-shadow:
    0 1px 2px rgba(59, 15, 37, 0.14),
    0 10px 18px -12px rgba(59, 15, 37, 0.55);
}
.print--hero { padding: 5px 5px 6px; }
.print img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
}
.print--hero img { aspect-ratio: 3 / 2; }
.print figcaption {
  margin-top: 5px;
  font-family: var(--sans);
  font-weight: 800;
  font-size: 0.56rem;
  line-height: 1.3;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--hue, #8B5670);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.print--hero figcaption { font-size: 0.62rem; }
.print--you  { --hue: var(--you-text); }
.print--them { --hue: var(--partner-text); }
.print--gift figcaption {
  background: linear-gradient(90deg, var(--you-text), var(--partner-text));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

/* the line on the page, in one of your hands, signed */
.page__line {
  margin: 18px 0 0;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 600;
  font-size: 1.05rem;
  line-height: 1.4;
  letter-spacing: -0.005em;
  color: #5A2A42;
  text-wrap: pretty;
}
.page__sign {
  margin-left: 6px;
  font-family: var(--sans);
  font-style: normal;
  font-weight: 800;
  font-size: 0.62rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  vertical-align: 1px;
}
.page__sign--you  { color: var(--you-text); }
.page__sign--them { color: var(--partner-text); }
.page__line--ghost { color: #B08AA0; }

/* the week being written says what it is working towards */
.page__working {
  margin: 14px 0 0;
  font-family: var(--sans);
  font-weight: 800;
  font-size: 0.6rem;
  line-height: 1.3;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--you-text);
}

.page__no {
  position: absolute;
  left: 0; right: 0; bottom: 14px;
  margin: 0;
  text-align: center;
  font-family: var(--serif);
  font-weight: 600;
  font-size: 0.78rem;
  color: #8B5670;
}

@media (max-width: 820px) {
  /* one page on a phone, the way the app shows it */
  .book { grid-template-columns: 1fr; padding: 9px; border-radius: 14px; }
  .book::after { display: none; }
  .page--b { display: none; }
  .page { padding: 20px 18px 40px 20px; border-radius: 3px 12px 12px 3px; }
  .page__collage { gap: 12px 8px; }
}

/* ── the map ────────────────────────────────────────────────────────── */
/* Not a live map: a real one needs a key, a bill and a location, and a map of
   nowhere would be a lie told with someone else's tiles. An authored plate is
   honest about being an illustration. Live partner location was cut from the
   product and must never appear here.

   It is the first thing on the page rather than a section further down. The
   claim the page opens with is that this is where the two of them actually
   went, and the sum floats over it. One surface, two layers — a plain mark
   for somewhere they train, a photograph for something they earned there.
   The two are told apart by form and never by hue: magenta and violet are
   spoken for on this page, they mean the two people, and a third meaning
   would break that. */

/* The legend the map needs, in a strip directly under it: the hero holds the
   sum, the line and the form already, and a key inside it would be a fifth
   thing competing in a composition measured for four. */
.pins { list-style: none; margin: 0; padding: 0; position: absolute; left: 0; top: 0; width: 100%; height: 100%; }

/* Every pin is read at a glance rather than opened: the labels are the point,
   so they are on the map, not behind a tap. That is also why there are six and
   not nine — six open labels is a legible map, nine is a wall. */
.pin { position: absolute; left: var(--x); top: var(--y); z-index: 2; }
/* a pin whose spot is cropped away, or under the line and the form (home.js) */
.pin.is-off { display: none; }

.pin__dot {
  position: absolute;
  left: 50%; top: 50%;
  translate: -50% -50%;
  width: clamp(11px, 1.05vw, 15px);
  height: clamp(11px, 1.05vw, 15px);
  border-radius: 50%;
  /* Hue by whose it is, the same as the sum overhead: the workout is yours and
     carries your magenta, the reward is theirs to give and carries their
     violet. Never by category — the two hues mean the two people. */
  background: var(--you-fill);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.92),
              0 5px 12px -4px rgba(59, 15, 37, 0.55);
  transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1),
              box-shadow 260ms ease-out;
}
.pin--reward .pin__dot { background: var(--partner); }

/* Glass, because it sits on a drawn map and has to let the place it marks keep
   showing through: a blurred, saturated pull of whatever is beneath, a bright
   rim where light would catch the top edge, and one specular sweep across it. */
.pin__chip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 13px);
  translate: calc(-50% + var(--nudge, 0px)) 0;
  display: flex;
  align-items: center;
  /* Sized against the viewport, not in fixed rem: the plate is a percentage of
     the window, so a chip that does not shrink with it collides with its
     neighbours at every width between the phone and the wide layout. */
  gap: clamp(6px, 0.64vw, 9px);
  padding: clamp(5px, 0.56vw, 8px) clamp(9px, 0.98vw, 14px) clamp(5px, 0.56vw, 8px) clamp(6px, 0.7vw, 10px);
  border-radius: 14px;           /* {rounded.plate} */
  white-space: nowrap;
  background: linear-gradient(147deg,
    rgba(255, 255, 255, 0.72) 0%,
    rgba(255, 255, 255, 0.42) 48%,
    rgba(255, 255, 255, 0.58) 100%);
  -webkit-backdrop-filter: blur(14px) saturate(1.75);
          backdrop-filter: blur(14px) saturate(1.75);
  /* The rim is an inset ring, not a border: on this page an edge is light
     catching a surface and never a drawn line — and a hairline border under a
     wide shadow is the generic generated-UI signature besides. */
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.6),
    inset 0 1px 0 rgba(255, 255, 255, 0.96),
    inset 0 -7px 14px -9px rgba(255, 255, 255, 0.8),
    0 12px 26px -13px rgba(59, 15, 37, 0.5),
    0 2px 6px -3px rgba(59, 15, 37, 0.24);
  transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1),
              box-shadow 260ms ease-out, background-color 260ms ease-out;
}

/* the sweep: one band of light across the top-left, as glass catches it */
.pin__chip::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(118deg,
    rgba(255, 255, 255, 0.78) 0%,
    rgba(255, 255, 255, 0.22) 30%,
    rgba(255, 255, 255, 0) 52%);
}

.pin__thumb {
  position: relative;
  width: clamp(20px, 2.1vw, 30px);
  height: clamp(20px, 2.1vw, 30px);
  border-radius: clamp(6px, 0.63vw, 9px);
  object-fit: cover;
  flex: none;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.75),
              0 3px 8px -3px rgba(59, 15, 37, 0.45);
}

.pin__lines { position: relative; display: grid; gap: 1px; }

.pin__kind {
  font-family: var(--sans);
  /* not uppercased: this page sets nothing in caps, and at this size the
     tracking alone already reads as a label */
  font-size: clamp(0.58rem, 0.7vw, 0.7rem);
  letter-spacing: 0.05em;
  color: var(--you-text);
}
.pin--reward .pin__kind { color: var(--partner-text); }

.pin__name {
  font-family: var(--serif);
  font-size: clamp(0.76rem, 1.05vw, 1.02rem);
  line-height: 1.2;
  color: var(--ink);
}

.pin__cost {
  font-family: var(--sans);
  font-size: clamp(0.6rem, 0.78vw, 0.74rem);
  line-height: 1.3;
  color: var(--ink-worn);
}

/* Hovering lifts the glass and brightens it, and the mark it belongs to grows
   with it, so it is never ambiguous which place the label is naming. */
.pin:hover .pin__chip {
  transform: translateY(-3px);
  background: linear-gradient(147deg,
    rgba(255, 255, 255, 0.9) 0%,
    rgba(255, 255, 255, 0.66) 48%,
    rgba(255, 255, 255, 0.8) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 1),
    inset 0 -7px 14px -9px rgba(255, 255, 255, 0.9),
    0 0 0 1px rgba(217, 61, 180, 0.34),
    0 20px 38px -16px rgba(59, 15, 37, 0.5);
}
.pin--reward:hover .pin__chip { box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 1),
    inset 0 -7px 14px -9px rgba(255, 255, 255, 0.9),
    0 0 0 1px rgba(160, 81, 196, 0.38),
    0 20px 38px -16px rgba(59, 15, 37, 0.5); }

.pin:hover .pin__dot {
  transform: scale(1.3);
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.98),
              0 7px 16px -4px rgba(59, 15, 37, 0.6);
}

/* the chips at the ends still carry --nudge, which home.js sets on load and on
   resize now that they are open from the start rather than on a tap */
.pin--w .pin__chip { left: 0; translate: var(--nudge, 0px) 0; }
.pin--e .pin__chip { left: auto; right: 0; translate: var(--nudge, 0px) 0; }

@media (max-width: 620px) {
  .pin__chip { bottom: calc(100% + 8px); }
}

/* ── the loop ───────────────────────────────────────────────────────── */
/* Five steps and then back to one. Each step is a drawing and two lines: a
   title and one short line under it, never more (owner, 7 Oct 2026: "SUPER
   clear and concise", the pictures do the explaining). The drawings are the
   app's own: the same couple in the same pink pencil as the reward drawings,
   made with website/steps-art/gen.py. Each step is coloured by WHOSE move it
   is: magenta is one of them, violet is the other, by meaning and never by
   index, so the run reads as the thing passing back and forth, and the ground
   deepens as it goes, because the last step is the one worth arriving at. */

.loop {
  background: var(--raised);
  padding: clamp(72px, 8vw, 130px) 0 0;
}

.steps {
  list-style: none;
  margin: clamp(40px, 5vw, 72px) 0 0;
  padding: 0;
}

/* whose move it is: the two hues, and nothing else */
.step--you  { --hue: var(--you);     --hue-text: var(--you-text);     --tilt: -1.2deg; }
.step--them { --hue: var(--partner); --hue-text: var(--partner-text); --tilt: 1.2deg; }
.step--both { --hue: var(--you);     --hue-text: var(--you-text);     --tilt: -0.6deg; }

.step--1 { --tint: 4%; }
.step--2 { --tint: 5.5%; }
.step--3 { --tint: 7%; }
.step--4 { --tint: 9%; }
.step--5 { --tint: 11%; }

.step {
  padding: clamp(40px, 5vw, 76px) clamp(24px, 6vw, 80px);
  background: color-mix(in srgb, var(--hue) var(--tint), var(--raised));
}
/* the last one belongs to both of them, so its ground is both */
.step--both {
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--you) var(--tint), var(--raised)) 0%,
    color-mix(in srgb, var(--partner) var(--tint), var(--raised)) 100%);
}

.step__inner {
  max-width: 1080px;
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
  align-items: center;
  gap: clamp(28px, 5vw, 80px);
}
/* the partner's steps run the other way, so the page passes back and forth
   the way the week does */
.step--them .step__inner { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); }
.step--them .step__art { order: 2; }

/* A drawing is a print: white paper lying on the band, a little askew, with
   an ink cast under it. */
.step__art { margin: 0; }
.step__art img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: #fff;
  border-radius: 22px;
  transform: rotate(var(--tilt));
  box-shadow: 0 1px 2px rgba(59, 15, 37, 0.06),
              0 26px 48px -30px rgba(59, 15, 37, 0.55);
}

.step__say { display: grid; gap: 12px; align-content: center; }

.step__mark {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.step__n {
  font-family: var(--serif);
  font-size: clamp(2.2rem, 3.6vw, 3rem);
  line-height: 0.9;
  color: var(--hue-text);
}
.step--both .step__n { color: var(--ink); }
.step__who {
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--hue-text);
}
.step--both .step__who { color: var(--ink-worn); }

.step__title {
  margin: 0;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  line-height: 1.1;
  letter-spacing: -0.012em;
  color: var(--ink);
  text-wrap: balance;
}
.step__title em { color: var(--hue-text); }
.step--both .step__title em {
  background: linear-gradient(90deg, var(--you-text), var(--partner-text));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

.step__t {
  margin: 0;
  max-width: 32ch;
  font-family: var(--sans);
  font-size: 1.12rem;
  line-height: 1.55;
  color: var(--ink-worn);
  text-wrap: pretty;
}

@media (max-width: 820px) {
  .step__inner,
  .step--them .step__inner {
    grid-template-columns: 1fr;
    gap: 22px;
  }
  .step--them .step__art { order: 0; }
}

/* ── the steps, told by scrolling ──────────────────────────────────── */
/* Switched on by home.js (.is-scrolly), so without the script the steps are
   the plain list above. The stage pins to the screen while the track scrolls
   past it: each fifth of the track brings the next step in, and the stage's
   ground warms from the page's pale pink toward the rewards' magenta as it
   goes (owner, 7 Oct 2026: "each time you scroll the screen gets a bit more
   saturated"). home.js writes --stage; the steps lie on it with no bands of
   their own. The last two steps sit on the deepest pinks, so their coloured
   words take the deep sibling of their person's colour. */

/* The section has no title band: its name sits in the stage's top-left
   corner, lowercase in the page's serif like the wordmark, and stays still
   while the steps change under it (owner, 7 Oct 2026). Four steps get 75vh
   of scroll each and the last one only 30vh, so the page moves on soon
   after the last step lands (home.js uses the same numbers). */
.loop.is-scrolly { padding-top: 0; }
.loop.is-scrolly .steps-track {
  position: relative;
  /* 45vh a step and 20vh for the last (owner, 20 Oct 2026: "scroll less for the next step to appear"; was 75/30) */
  height: calc(4 * 45vh + 20vh + 100vh);
}
.loop.is-scrolly .steps-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  background:
    radial-gradient(70% 55% at 50% 38%, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0) 70%),
    var(--stage, #FCF3F7);
  transition: background-color 480ms ease-out;
}
.loop.is-scrolly .steps { position: absolute; inset: 0; margin: 0; }
.loop.is-scrolly .step {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  /* clear of the floating equation card at the top, and of the dots */
  padding-top: clamp(96px, 13vh, 150px);
  padding-bottom: 64px;
  background: none;
  opacity: 0;
  transform: translateY(48px);
  pointer-events: none;
  transition: opacity 520ms ease-out, transform 640ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.loop.is-scrolly .step.is-past { transform: translateY(-48px); }
.loop.is-scrolly .step.is-active { opacity: 1; transform: none; pointer-events: auto; }
.loop.is-scrolly .step__inner { width: 100%; }
.loop.is-scrolly .step__title { font-size: clamp(1.9rem, 3.6vw, 3.1rem); }
.loop.is-scrolly .step__t { font-size: clamp(1.05rem, 1.4vw, 1.25rem); }
.loop.is-scrolly .step__art img { width: min(100%, calc(56vh * 4 / 3)); margin-inline: auto; }
.loop.is-scrolly .step--4,
.loop.is-scrolly .step--5 { --hue-text: #6E1260; }
.loop.is-scrolly .step--4 { --hue-text: #4A1C80; }
.loop.is-scrolly .step--4 .step__t,
.loop.is-scrolly .step--5 .step__t { color: var(--ink); }
.loop.is-scrolly .step--5 .step__title em {
  background: linear-gradient(90deg, #6E1260, #4A1C80);
  -webkit-background-clip: text;
          background-clip: text;
}

.steps__label {
  margin: 0 0 clamp(24px, 3vw, 40px);
  padding-inline: clamp(24px, 6vw, 80px);
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.25rem, 1.9vw, 1.75rem);
  line-height: 1;
  color: var(--ink);
}
.loop.is-scrolly .steps__label {
  position: absolute;
  z-index: 2;
  left: 0;
  /* level with the floating equation bar once the page has scrolled */
  top: calc(var(--B) * 0.033);
  translate: 0 -50%;
  margin: 0;
}

/* The stage's progress is a column of dots beside the steps (owner, 19 Oct
   2026: "make the progress bar vertical and next to the steps", then of a
   numbered rail: "way too distracting, make it simple bubbles"): five small
   dots down the left of the stage, the current one a short pill in ink,
   the way the dots read when they lay along the foot. The step's content
   keeps clear of them. */
.steps__dots { display: none; }
.loop.is-scrolly .step { padding-left: calc(clamp(24px, 6vw, 80px) + 40px); }
.loop.is-scrolly .steps__dots {
  position: absolute;
  z-index: 2;
  left: clamp(24px, 6vw, 80px);
  top: 50%;
  translate: 0 -50%;
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.loop.is-scrolly .steps__dots li {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: rgba(59, 15, 37, 0.13);
  transition: height 320ms cubic-bezier(0.2, 0.8, 0.2, 1), background-color 320ms ease-out;
}
/* the current one is only a shade darker, never the ink (owner, 19 Oct 2026: "even more subtle, no hard colour") */
.loop.is-scrolly .steps__dots li.is-on { height: 22px; background: rgba(59, 15, 37, 0.38); }

@media (max-width: 820px) {
  /* quicker still: 32vh a step and 14vh for the last (home.js uses the same numbers) */
  .loop.is-scrolly .steps-track { height: calc(4 * 32vh + 14vh + 100vh); }
  /* no floating card on a phone, so the step starts near the top; the dots sit at the edge */
  .loop.is-scrolly .step { padding: 72px 20px 40px 44px; }
  .loop.is-scrolly .steps__label { top: 26px; translate: 0; padding-inline: 44px; }
  .loop.is-scrolly .steps__dots { left: 16px; }
  .loop.is-scrolly .step__art img { width: min(100%, calc(36vh * 4 / 3)); }
  .loop.is-scrolly .step__inner { gap: 16px; }
  .loop.is-scrolly .step__say { gap: 8px; }
  .loop.is-scrolly .step__title { font-size: clamp(1.6rem, 7vw, 2rem); }
}

/* ── the journey: the app's road, at night above the clouds ─────────── */
/* The Journey page of the app (fitfriends-v2 components/journey): a night
   high above a sea of cloud, near-black at the top where the two of them
   started and a rose dawn coming up from under the clouds further down,
   where the road is going. The road winds down the page; a mark every ten
   workouts together with the prize that waits there, a rank on some; the
   part behind the two of them is lit in the blend, a tube of light with a
   white core and dots of light gliding down it; ahead it is a faint thread.
   The next big mark is lit gold with a pool of light under it and two
   sparkles over it. Scrolling walks the two of them down the road (home.js
   writes the lit length, moves them, and lights the marks they pass). */

.journey {
  position: relative;
  overflow: hidden;
  color: #fff;
  text-align: center;
  padding-block: clamp(72px, 8vw, 130px) clamp(56px, 7vw, 110px);
  background: #0A0418 url(/assets/journey/night-sky.webp) center 58% / cover no-repeat;
}
/* the ends of the section close on the sky's own night, whatever the crop */
.journey::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, #0A0418 0%, rgba(10, 4, 24, 0) 16%),
    linear-gradient(0deg, #0A0418 0%, rgba(10, 4, 24, 0) 18%);
}

/* wisps hanging between the sky and the road, drifting */
.journey__wisp {
  position: absolute;
  left: -12%;
  width: 124%;
  height: auto;
  opacity: 0.26;
  pointer-events: none;
  animation: wisp 26s ease-in-out infinite alternate;
}
.journey__wisp--1 { top: 36%; }
.journey__wisp--2 { top: 68%; opacity: 0.18; animation-duration: 34s; animation-direction: alternate-reverse; }
@keyframes wisp { from { transform: translateX(-2%); } to { transform: translateX(2%); } }

.journey__head {
  position: relative;
  max-width: 62ch;
  margin-inline: auto;
  padding-inline: clamp(24px, 6vw, 80px);
}
.journey__title {
  margin: 0;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(2rem, 4.2vw, 3.4rem);
  line-height: 1.12;
  letter-spacing: -0.012em;
  text-wrap: balance;
  text-shadow: 0 1px 14px rgba(10, 4, 24, 0.6);
}
.journey__title em { color: #FF9BDD; }
.journey__lede {
  margin: 14px auto 0;
  max-width: 46ch;
  font-family: var(--sans);
  font-size: 1.1rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.86);
  text-wrap: pretty;
  text-shadow: 0 1px 10px rgba(10, 4, 24, 0.7);
}

.road {
  position: relative;
  width: min(100%, 440px);
  margin: clamp(28px, 4vw, 48px) auto 0;
}
.road__svg { display: block; width: 100%; height: auto; overflow: visible; }

/* the thread ahead, and the road's lead-in and run-on */
.road__thread, .road__ahead {
  fill: none;
  stroke: rgba(255, 255, 255, 0.22);
  stroke-width: 2;
  stroke-linecap: round;
}
.road__thread { stroke: rgba(255, 255, 255, 0.13); }

/* the lit string: drawn in by dash offset, which home.js writes */
.road__draw { stroke-dasharray: 1000; stroke-dashoffset: 1000; }
.road__glow {
  fill: none;
  stroke: url(#road-blend);
  stroke-width: 24;
  stroke-linecap: round;
  opacity: 0.5;
  filter: url(#road-soft);
}
.road__lit { fill: none; stroke: url(#road-blend); stroke-width: 7; stroke-linecap: round; }
.road__maskline { fill: none; stroke: #fff; stroke-width: 9; stroke-linecap: round; }
.road__core { fill: none; stroke: rgba(255, 255, 255, 0.72); stroke-width: 2.2; stroke-linecap: round; }
.road__dots {
  fill: none;
  stroke: #fff;
  stroke-width: 4.2;
  stroke-linecap: round;
  stroke-dasharray: 0.01 24;
  animation: road-glide 1.6s linear infinite;
}
@keyframes road-glide { to { stroke-dashoffset: -24.01; } }

/* a mark: a disc of glass with the prize on it, the blend once reached */
.mark__disc {
  fill: rgba(255, 255, 255, 0.09);
  stroke: rgba(255, 255, 255, 0.26);
  stroke-width: 1.2;
  transition: fill 520ms ease-out, stroke 520ms ease-out;
}
.mark.is-reached > .mark__disc { fill: url(#road-disc); stroke: rgba(255, 255, 255, 0.5); }
.mark__start { fill: #fff; opacity: 0.9; }
/* a prize not yet reached is still the prize, unlit */
.mark:not(.is-reached):not(.mark--next) use { opacity: 0.55; }
/* paper lying on the road casts the dark room's shadow, as the app's prizes do */
.pz--paper { filter: drop-shadow(0 4px 4px rgba(0, 0, 0, 0.5)); }

/* every mark lies in a pool of its own light: the blend for a ticket or a
   pass, ice for the freeze, gold for the key. A pool ahead of the two of
   them is faint; the next mark's breathes, and it alone has sparkles that
   turn at rest (the freeze's and the big ticket's wake once reached). */
.mark__pool { fill: url(#road-pool); opacity: 0.85; transition: opacity 600ms ease-out; }
.mark__pool--blend  { fill: url(#road-pool-blend); }
.mark__pool--violet { fill: url(#road-pool-blend); }
.mark__pool--ice    { fill: url(#road-pool-ice); }
.mark:not(.is-reached):not(.mark--next) .mark__pool { opacity: 0.3; }
.mark--next .mark__disc { fill: rgba(255, 255, 255, 0.16); stroke: rgba(255, 230, 170, 0.55); }
.mark--next .mark__pool { animation: pool 3.2s ease-in-out infinite alternate; }
@keyframes pool { from { opacity: 0.65; } to { opacity: 1; } }
.mark__spark { fill: #FFE9A8; animation: spark 2.6s ease-in-out infinite; }
.mark__spark--2 { animation-delay: -1.3s; }
.mark__spark--ice { fill: #E4F8FF; }
.mark:not(.is-reached):not(.mark--next) .mark__spark { animation: none; opacity: 0.3; }
.mark--big .mark__spark { fill: #FFC4EC; }
@keyframes spark { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }

/* the two of you, each in your colour, on the lit end of the road */
.couple__halo { fill: url(#road-halo); }
.couple__ring { fill: none; stroke-width: 3; }
.couple__ring--you  { stroke: #FF7BD1; }
.couple__ring--them { stroke: #C07BFF; }

/* the words beside each mark, in HTML so they stay crisp at any width */
.road__labels {
  list-style: none;
  margin: 0;
  padding: 0;
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.road__label {
  position: absolute;
  top: calc(var(--y) * 1%);
  width: 46%;
  translate: 0 -50%;
  display: grid;
  gap: 2px;
  text-align: left;
  opacity: 0;
  transition: opacity 500ms ease-out;
}
.road__label.is-placed { opacity: 1; }
/* a mark on the left of the road's swing takes its words on the right, and the other way */
.road__label.is-right { left: calc(var(--x) * 1% + 40px); }
.road__label.is-left  { right: calc((100 - var(--x)) * 1% + 40px); text-align: right; }
.road__t {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 1.12rem;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: #fff;
  text-shadow: 0 1px 10px rgba(10, 4, 24, 0.8);
}
.road__k {
  font-family: var(--sans);
  font-size: 0.8rem;
  line-height: 1.35;
  color: rgba(255, 255, 255, 0.72);
  text-shadow: 0 1px 8px rgba(10, 4, 24, 0.85);
  text-wrap: pretty;
}
.road__label--next .road__t { color: #FFE2A0; }
.road__label--ice .road__t { color: #D6F3FF; }
.road__label.is-reached .road__t { color: #FFC4EC; }

/* how far they are, under the two of them */
.road__count {
  position: absolute;
  left: calc(var(--x, 50) * 1%);
  top: calc(var(--y, 0) * 1% + 30px);
  translate: -50% 0;
  margin: 0;
  white-space: nowrap;
  font-family: var(--sans);
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.82);
  text-shadow: 0 1px 8px rgba(10, 4, 24, 0.85);
  opacity: 0;
  transition: opacity 500ms ease-out;
}
.road__count.is-placed { opacity: 1; }
.road__count strong {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 1.3rem;
  letter-spacing: -0.02em;
  color: #fff;
  font-variant-numeric: lining-nums tabular-nums;
}

.journey__foot {
  position: relative;
  margin: clamp(28px, 4vw, 44px) auto 0;
  padding-inline: 24px;
  font-family: var(--sans);
  font-size: 0.95rem;
  color: rgba(255, 255, 255, 0.7);
  text-shadow: 0 1px 8px rgba(10, 4, 24, 0.85);
}

@media (max-width: 620px) {
  .road { width: calc(100% + 8px); margin-inline: -4px; }
  .road__t { font-size: 1rem; }
  .road__k { font-size: 0.74rem; }
  .road__label.is-right { left: calc(var(--x) * 1% + 34px); }
  .road__label.is-left  { right: calc((100 - var(--x)) * 1% + 34px); }
}

/* Last in the file on purpose: this must win over every rule above it, and
   source order is what decides between selectors of equal weight. */
@media (prefers-reduced-motion: reduce) {
  .equation__count, .equation__unit, .equation__reward,
  .equation-bar, .top,
  .pin__chip, .pin__dot { transition: none; }
  .loop.is-scrolly .step, .loop.is-scrolly .step.is-past { transform: none; }
  .journey__wisp, .road__dots, .mark__pool, .mark__spark { animation: none; }
  .loop.is-scrolly .steps__dots li, .loop.is-scrolly .steps-stage,
  .mark__disc, .mark__pool, .road__label, .road__count { transition: none; }
}
