/* ═══════════════════════════════════════════════════════════════════════
   Page.css — everything below the hero: the scrolling shell, the three
   feature modules, the scroll-reveal system, and the footer.

   Loads after Dock.css and before Modal.css/Responsive.css.
   ═══════════════════════════════════════════════════════════════════════ */

/* ⚠️ The hero is deliberately NOT dark-themed — it's a photographic wallpaper
   with a replica of the light-appearance app on it, and reads as a product
   shot in either scheme. Pinning color-scheme keeps the browser rendering its
   form controls light when the OS is dark. */
.Hero{ color-scheme: light; }

/* A <picture> wrapper is inline by default, which would leave the module 2
   screenshot's negative margin resolving against an inline box. */
.Module-media picture{ display: block; }

.Page{
  position: relative;
  background: var(--page-bg);
  /* ⚠️ This is what actually stops the sideways rubber-band, NOT the
     `overflow-x: clip` on <html> in Base.css. Cover--3 deliberately bleeds
     past the right edge, and at the top of the mobile range that put it 17px
     beyond the viewport; the root's clip is supposed to propagate to the
     viewport and prevent scrolling, but Chrome still let window.scrollX reach
     17. Clipping here, one level down, is unambiguous.
     `clip`, never `hidden`: `hidden` on one axis forces the other axis to
     `auto`, which would make .Page a scroll container and kill the module ->
     footer vertical bleed. `clip` is the one value that pairs with visible. */
  overflow-x: clip;
}

/* ── Hero ───────────────────────────────────────────────────────────────
   The original single-screen desktop, now a normal 100vh section in flow.
   It recedes on scroll (see .Desktop in Desktop.css) and then leaves.

   `overflow: clip` rather than `hidden`: the desktop scales DOWN so it never
   actually overflows, but a stray shadow shouldn't be able to spill into the
   first module either. `clip` avoids creating a scroll container.
   ───────────────────────────────────────────────────────────────────── */
.Hero{
  position: relative;
  height: 100vh;
  overflow: clip;
  /* What shows in the margin as the desktop recedes. Matches the page below
     so the inset reads as the desktop stepping back into the page, not as a
     dark frame around it. Was a deep navy; that read as a hole punched in
     the page rather than depth. */
  background: var(--page-bg);
}

/* The recede is carried entirely by scale + corner radius against the light
   surround. An earlier dark tint overlay was removed — it made the desktop
   look dimmed rather than further away. If depth ever needs reinforcing,
   reach for a shadow on .Desktop, NOT opacity/filter on it or any ancestor:
   either creates a backdrop root and kills all 13 backdrop-filters inside. */

/* ── Shared module scaffolding ─────────────────────────────────────────── */
.Module{
  position: relative;
  padding: clamp(52px, 7vh, 92px) var(--gutter);
}

/* The gap between the hero and the first module carries more weight than the
   gaps between modules — it's the transition out of the fake desktop. Only
   this one keeps the larger spacing. */
.Hero + .Module{
  padding-top: clamp(96px, 13vh, 156px);
}

.Module-inner{
  max-width: var(--measure);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(32px, 6vw, 88px);
}

/* ── Optical centring for the copy ──────────────────────────────────────
   `align-items: center` centres the text BOX against the image box, and that
   consistently reads as sitting too low. Two reasons, and neither shows up in
   the geometry: the eye places the centre of a composition slightly above the
   true middle, and a headline's box starts ~9px above its own cap height, so
   the visible text begins lower than the box it's centred by.

   A bottom margin makes the text's outer box taller below the copy; centring
   that taller box lifts the visible text by HALF the margin. Same trick as
   .Module-media--install img above, in the other direction.

   ⚠️ The two modules need different amounts, and it isn't a style choice.
   Measured against each image's actual artwork (not its padded box), module 1
   was already centred to within 3px, but module 2's copy sat a real 17px low —
   its screenshot's transparent padding is near-symmetric (2.9% top / 7.7%
   bottom), so it gets almost none of the free lift that module 1 gets from its
   very lopsided render (1.6% / 21.4%). Module 2 therefore has to correct that
   17px first and then take the same optical lift on top.

   Both land the copy's centre ~3% of the artwork height above the artwork's
   centre. Fixed pixels are safe here because this only applies above 960px and
   .Module-inner is capped at --measure, so the artwork height barely moves. */
.Module--split .Module-text{ margin-bottom: 38px; }   /* lifts copy 19px */
.Module--reverse .Module-text{ margin-bottom: 72px; } /* lifts copy 36px */

.Module-title{
  font-size: clamp(30px, 3.4vw, 46px);
  line-height: 1.1;
  letter-spacing: -.032em;
  font-weight: 700;
  color: var(--ink);
  text-wrap: balance;
}
.Module-title--center{
  text-align: center;
  max-width: 16ch;
  margin: 0 auto clamp(24px, 3vw, 44px);
}

.Module-body{
  margin-top: 20px;
  max-width: 46ch;
  font-size: clamp(14px, 1.05vw, 15.5px);
  line-height: 1.62;
  color: var(--ink-soft);
  letter-spacing: -.006em;
}

.Module-media img{
  width: 100%;
  height: auto;
}

/* Module 1 — the render fills its column.
   ⚠️ ~28% of this image's width is transparent padding, so the visible iPod
   is only ~72% of whatever box you give it. 560px of box = ~400px of iPod.
   Source is 1050px wide, so that's still a comfortable 2x. */
.Module-media--install{
  display: flex;
  justify-content: center;
}
/* The <picture> (for the dark-mode variant) must not become the flex item:
   the image's -34% margin below resolves against its containing block, and
   it has to stay this column, exactly as it was before the wrapper existed. */
.Module-media--install picture{ display: contents; }
/* ⚠️ Optical centring. This file carries ~1.7% transparent padding above the
   artwork and ~22% below, so the visible iPod sits well above the centre of
   its own box — grid `align-items: center` then lines the copy up with empty
   space and it reads as sitting too low.

   Pulling the layout box up by roughly twice that difference re-centres the
   row on the artwork instead of the box. It's a percentage so it tracks the
   column width, and since the image's height scales with its width the ratio
   holds at every breakpoint. Re-measure if the asset is ever re-exported with
   different padding. */
.Module-media--install img{
  max-width: 560px;
  width: 100%;
  margin-bottom: -34%;
}

/* Module 2 — reversed order, image bleeding off the LEFT viewport edge.
   html{overflow-x:clip} in Base.css is what stops that becoming a scrollbar.

   ⚠️ The bleed extends the image LEFT only. An earlier version widened it on
   both sides, which pushed the screenshot into the gap and left the headline
   sitting right on its edge. Growing it leftwards keeps the image's right
   edge exactly on the column boundary, so the grid gap is the real, visible
   separation between picture and copy. */
.Module--reverse .Module-inner{
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
  gap: clamp(48px, 7vw, 112px);
}
.Module--reverse .Module-media{
  max-width: none;
  overflow: visible;
}
/* ⚠️ No border-radius or box-shadow here. Themes-Light-v1.webp is exported
   with transparent padding and its own drop shadow already baked in — adding
   CSS ones frames the transparent box and doubles the shadow. Same applies to
   every other image in these modules. */
.Module--reverse .Module-media img{
  width: calc(100% + var(--gutter) + 7vw);
  max-width: none;
  margin-left: calc((var(--gutter) + 7vw) * -1);
}

/* ── Module 3: the scatter ──────────────────────────────────────────────
   ⚠️ Must NOT clip horizontally — several covers sit at negative offsets on
   purpose and are meant to run off both edges. overflow-y is clipped so the
   composition can't bleed into the footer, but x stays visible and is caught
   by html{ overflow-x: clip }.
   ───────────────────────────────────────────────────────────────────── */
/* Overhangs into the footer on purpose — the negative margin pulls the next
   section up under it. .Scatter is pointer-events:none so the overhang can't
   swallow clicks on the footer, and .Footer's top padding keeps the overlap
   clear of any actual content. */
.Module--scatter{
  overflow-x: visible;
  overflow-y: visible;
  position: relative;
  z-index: 1;
  /* ⚠️ This number looks far too large for "a little bit of overlap", and
     that's the transparent padding talking: the lowest album art stops around
     79% of the .Scatter box, so ~200px of the negative margin is just closing
     empty space. The VISIBLE overlap is only ~25-30px. Measure artwork edges,
     not element boxes, if you retune it. */
  margin-bottom: clamp(-240px, -17vw, -172px);
  padding-bottom: 0;
}

.Scatter{
  position: relative;
  max-width: var(--measure);
  margin: 0 auto;
  /* ⚠️ aspect-ratio, not a vw height. Every element in here is sized and
     placed as a PERCENTAGE of this box, so the whole composition scales as
     one unit and the overlaps hold at every width. Mixing units — the iPod in
     vw, the covers in % — is what let them drift apart and stop touching. */
  aspect-ratio: 1240 / 1180;
  min-height: 600px;
  /* Purely decorative, and it deliberately overhangs the footer — make sure
     it can never intercept a click meant for the signup form below. */
  pointer-events: none;
}

.Scatter-ipod{
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  /* ~28% of this box is transparent padding, so 49% of the scatter is ~35%
     of visible iPod. Percentage, like everything else here — see .Scatter. */
  width: 49%;
  height: auto;
  z-index: 2;
  /* Top-anchored, not bottom-anchored: anchoring to the bottom meant any
     excess height pushed the render up over the headline. */
  top: 9%;
  bottom: auto;
  /* No drop-shadow: the render already carries one. */
}

/* Covers are positioned by left/top/--w only. Their transform belongs
   exclusively to the parallax in Page.js — the reveal animation owns text
   transforms, never these, so the two can't fight over the property. */
/* ⚠️ No radius, no shadow — the cover art is exported with rounded corners,
   ~8% transparent padding, and a soft shadow already in the pixels. CSS ones
   sit on the padding box and read as a white frame. The padding is also why
   the --w values below look larger than the art appears. */
.Cover{
  position: absolute;
  width: var(--w);
  height: auto;
  transform: translate3d(0, var(--py, 0px), 0);
  z-index: 1;
}

/* Four covers carry z-index 3 and sit OVER the iPod, clipping its edges —
   that overlap is what sells "dropped onto it" rather than "arranged around
   it". --7 deliberately stays at z-index 1 so it tucks BEHIND the render,
   which is what gives the pile depth rather than a flat ring.
   The iPod occupies roughly 29%–65% of the box width, so anything meant to
   overlap has to intrude into that band. */
/* ⚠️ Vertical positions are tuned against the iPod's VISIBLE bottom, not its
   box. The render carries ~22% transparent padding below the artwork, so a
   cover placed level with the box's lower half sits in empty space and reads
   as floating below rather than clipping the device. */
/* Sizes are set as a RATIO of the iPod, taken from the reference comp — the
   iPod has to read as the dominant object with the covers as satellites. Get
   the ratio wrong and the covers compete with it, which is what happened when
   they were tuned in isolation.

   Layering, front to back: Flume (4) over the tiger (3) over the iPod (2),
   with Bambi left at the default 1 so it tucks BEHIND the device. That
   front-to-back variation is what makes it read as a pile rather than a ring.

   Vertical positions are tuned against the iPod's VISIBLE bottom, not its box
   — the render carries ~22% transparent padding underneath. */
/* All percentages of .Scatter, so the composition scales as one unit.

   For reference when retuning: the iPod's box spans 25.5%-74.5% and its
   VISIBLE artwork only 32%-68% horizontally, 10%-78% vertically (it carries
   ~14% transparent padding each side and ~22% below). A cover has to reach
   into that inner band to actually touch the device — landing inside the box
   but outside the artwork just looks like a near miss. Each cover is itself
   ~17% padding per side. */
.Cover--1{ --w: 31.5%; left: 15%;  top: -2%; z-index: 3; }  /* clips iPod top-left    */
.Cover--2{ --w: 20.6%; left: 21%;  top: 66%; z-index: 3; }  /* clips iPod bottom-left */
.Cover--3{ --w: 15.7%; left: 97%;  top:  7%; }              /* bleeds off the right   */
.Cover--4{ --w: 31.0%; left: 67%;  top: 32%; z-index: 4; }  /* sits over the tiger    */
.Cover--5{ --w: 22.6%; left: -6%;  top: 44%; }
.Cover--6{ --w: 19.4%; left: 59%;  top: 56%; z-index: 3; }  /* clips iPod right       */
.Cover--7{ --w: 20.6%; left: 61%;  top:  2%; }              /* BEHIND the iPod        */
.Cover--8{ --w: 13.0%; left: -14%; top: 38%; }              /* bleeds off the left    */

/* ── Scroll reveal ──────────────────────────────────────────────────────
   Images are already in place when a section arrives; only TEXT animates —
   fading up out of a strong blur.

   ⚠️ Scoped to .Module and .Footer deliberately. This rule sets `filter`,
   and `filter` on anything inside the hero creates a backdrop root that
   destroys every backdrop-filter in the fake desktop. A bare [data-reveal]
   selector would be a loaded gun pointed at the glass.

   ⚠️ Only applies under .is-revealJS, added by Page.js on boot — without it
   a no-JS visitor would get permanently invisible text.
   ───────────────────────────────────────────────────────────────────── */
.is-revealJS .Module [data-reveal],
.is-revealJS .Footer [data-reveal]{
  opacity: 0;
  transform: translateY(26px);
  filter: blur(16px);
  transition:
    opacity   .95s cubic-bezier(.16, 1, .3, 1),
    transform .95s cubic-bezier(.16, 1, .3, 1),
    filter    .95s cubic-bezier(.16, 1, .3, 1);
  /* ⚠️ No delay in the resting state. The stagger lives on .is-in below, so
     it applies on the way IN only — a reset that also staggered would leave
     elements mid-fade if the reader scrolled back down quickly. */
  transition-delay: 0s;
  will-change: opacity, transform, filter;
}

.is-revealJS .Module.is-in [data-reveal],
.is-revealJS .Footer.is-in [data-reveal]{
  opacity: 1;
  transform: none;
  filter: blur(0);
  transition-delay: calc(var(--i, 0) * 110ms);
  /* ⚠️ There is deliberately NO `will-change` here, and it must not come
     back. It used to say `will-change: auto`, which removed the compositing
     hint at the exact instant the transition began — the one moment it is
     actually needed. Safari for iOS responded by dropping the layer and
     snapping straight to the end state: module 2's copy stopped animating
     at all and simply popped, while 1 and 3 happened to survive. The base
     rule keeps the hint for the whole transition instead; the cost is a
     handful of text elements staying composited, which changes no bounds
     (a settled block is blur(0) and untranslated) and so leaves the module
     3 pop-in clearances untouched. */
}

/* ── Footer ─────────────────────────────────────────────────────────────
   The scatter above deliberately overhangs into this section. That's why
   .Footer has no top border and generous top padding: the overhang lands in
   empty space and can never sit on the signup form.
   ───────────────────────────────────────────────────────────────────── */
.Footer{
  position: relative;
  background: var(--footer-bg);
  /* ⚠️ The top padding must stay comfortably LARGER than the scatter's
     negative margin-bottom above, or a low-hanging album cover lands on the
     signup form. Measured at 1440x900: 96px of overhang against 170px of
     padding leaves ~74px of clearance. Change one, re-measure the other. */
  padding: clamp(170px, 16vh, 210px) var(--gutter) 34px;
}

/* Horizontal: icon left, label + field right. */
.Footer-signup{
  max-width: 620px;
  margin: 0 auto clamp(72px, 11vh, 124px);
  display: flex;
  align-items: center;
  gap: clamp(20px, 3vw, 34px);
}
.Footer-icon{
  flex: none;
  width: clamp(76px, 8vw, 104px);
  height: auto;
  filter: drop-shadow(0 4px 12px rgba(0,0,0,.18));
}
.Footer-signupBody{
  flex: 1 1 auto;
  min-width: 0;
}
.Footer-tagline{
  font-size: 15px;
  color: var(--ink-soft);
  letter-spacing: -.01em;
  margin-bottom: 10px;
}

/* The button sits INSIDE the field. .SignupForm's own rules put it below and
   right-aligned, which is right in the hero window but not here. */
.SignupForm--footer{ margin-top: 0; }
.SignupForm-field{
  position: relative;
  display: block;
  width: 100%;
}
/* ── The unified control: button inside the field ─────────────────────────
   The footer's control. The mobile hero used to share this geometry; it now
   has its own, drawn to Eddy's mobile mockups (Responsive.css). The desktop
   window's form keeps the stacked layout — it replicates the real app's
   window and carries no modifier class, so nothing here reaches it. */
.SignupForm--footer .SignupForm-input{
  /* Room for the button, plus a little breathing space. */
  padding: 15px 148px 15px 17px;
  border-radius: 11px;
}
.SignupForm--footer .SignupForm-input{
  background: rgba(0,0,0,.04);
  border-color: rgba(0,0,0,.08);
}
.SignupForm--footer .SignupForm-button{
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  margin: 0;
  align-self: auto;
  padding: 9px 16px;
  border-radius: 8px;
  white-space: nowrap;
}
/* ⚠️ The visible pill is 35px tall, under the 44px minimum touch target, and
   it cannot simply be made taller: it has to fit inside a 51px field. This
   expands the TAP area to 45px while changing nothing you can see.
   Horizontal inset is 0 on purpose — any negative value would reach back over
   the input's text and swallow taps meant for the field. */
.SignupForm--footer .SignupForm-button::after{
  content: "";
  position: absolute;
  inset: -5px 0;
}

.SignupForm--footer .SignupForm-button:active{ transform: translateY(-50%) scale(.97); }
.SignupForm--footer .SignupForm-status{ text-align: left; }
/* On success the whole field goes, not just its two children. */
.SignupForm--footer.is-done .SignupForm-field{ display: none; }

/* ── Footer bar ───────────────────────────────────────────────────────
   No rule line — the tone change from --page-bg to --footer-bg is the only
   division this needs.
   ───────────────────────────────────────────────────────────────────── */
.Footer-bar{
  max-width: var(--measure);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

/* The copyright overlaps the signature, which is faded well back so the text
   stays legible across it. The signature is absolutely positioned so it can
   sit behind without pushing the text anywhere. */
.Footer-mark{
  position: relative;
  display: flex;
  align-items: center;
  min-height: 74px;
  padding-left: 8px;
}
.Footer-signature{
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-52%);
  width: 132px;
  height: 82px;
  background-color: var(--ink);
  opacity: .10;
  pointer-events: none;
  -webkit-mask: url('/Assets/Icons/Signature-v1.svg') no-repeat center / contain;
          mask: url('/Assets/Icons/Signature-v1.svg') no-repeat center / contain;
}
.Footer-copy{
  position: relative;         /* above the signature */
  font-size: 12px;
  color: var(--ink-soft);
  white-space: nowrap;
  padding-left: 34px;
}

.Footer-links{
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  color: var(--ink-soft);
  flex-wrap: wrap;
  justify-content: flex-end;
}
.Footer-sep{ color: rgba(0,0,0,.2); }
.Footer-credit{ white-space: nowrap; }

/* One rule for both the <button> and the <a>s so they're visually identical
   despite being different elements. */
.Footer-link{
  font: inherit;
  color: var(--ink-soft);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  border-bottom: 1px solid rgba(0,0,0,.18);
  transition: color .15s ease, border-color .15s ease;
}
.Footer-link:hover{
  color: var(--ink);
  border-bottom-color: var(--ink);
}
.Footer-link:focus-visible{
  outline: 2px solid #0a84ff;
  outline-offset: 3px;
  border-radius: 2px;
}

/* ── Reduced motion ─────────────────────────────────────────────────────
   Note this is the THIRD such block in the project (Responsive.css and
   Library.css have their own). Page.js also skips parallax and the hero
   recede entirely under this preference; these rules cover the reveal, which
   is CSS-driven.
   ───────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce){
  .is-revealJS .Module [data-reveal],
  .is-revealJS .Footer [data-reveal]{
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
  }
  .Cover{ transform: none; }
}


/* ═══════════════════════════════════════════════════════════════════════
   Breakpoints for the scrolling content.

   These live here rather than in Responsive.css on purpose: they override
   nothing above them in the cascade, so they don't need to be last, and
   moving them would turn Responsive.css into a dumping ground. The mobile
   HERO rules DO have to live in Responsive.css, because those override
   Desktop.css and this file.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Stack the split modules ────────────────────────────────────────────
   ⚠️ This kicks in at 960px, well before the desktop hero gives up at 768.
   That leaves a real 769–960px band showing a scaled desktop hero above
   single-column modules. It's intentional, but check it when changing either
   number — it's the easiest place for the two layouts to disagree.
   ───────────────────────────────────────────────────────────────────── */
@media (max-width: 960px){
  .Module-inner,
  /* ⚠️ The .Module--reverse variant must be named explicitly. Its desktop
     rule is `.Module--reverse .Module-inner` (specificity 0,2,0), which beats
     a bare `.Module-inner` (0,1,0) even from inside this media query — so
     without this selector module 2 silently refuses to stack on phones. */
  .Module--reverse .Module-inner{
    grid-template-columns: 1fr;
    gap: 32px;
  }
  /* Text always leads, even on the reversed module — a headline below its
     image reads as a caption. */
  .Module--reverse .Module-text{ order: -1; }

  /* The optical lift above only means something when the copy is centred
     BESIDE an image. Stacked, that margin is just a stray gap. */
  .Module--split .Module-text,
  .Module--reverse .Module-text{ margin-bottom: 0; }

  .Module-body{ max-width: none; }
  .Module-media--install img{ max-width: 420px; }

  /* Keep a single-edge bleed rather than going full width: edge-to-edge at
     phone widths makes the app UI inside the screenshot unreadable. */
  .Module--reverse .Module-media img{
    width: calc(100% + var(--gutter));
    margin-left: calc(var(--gutter) * -1);
  }
}

@media (max-width: 768px){
  .Module{ padding: 72px var(--gutter); }
  /* ⚠️ 64px, not the 32px the composition wants visually. It is the clearance
     that keeps module 3 from popping in on iOS — see the reveal note below:
     the blurred, pre-reveal headline's compositing layer reaches 41px below
     its box (27px blur outset + 14px translate), and the scatter's topmost
     cover starts ABOVE the scatter box by 1.1% of the scatter's height — ~5px
     on a 375px phone but ~10px at 768px, because the whole composition scales
     with width. 64 leaves 18px to spare at 375 and 13px at 768. Reducing it
     reintroduces the pop-in, silently, on iOS only. */
  .Module-title--center{ margin-bottom: 64px; }

  /* ⚠️ Extra breathing room on the TEXT only, not on .Module. The gutter has
     to stay where it is: .Scatter widens itself by exactly `gutter * 2` to
     span the viewport, and module 2's screenshot offsets its bleed by the
     gutter too — so widening .Module's padding would silently drag both of
     those off their marks. Padding the copy leaves every bleed untouched. */
  .Module-text,
  .Module-title--center{
    padding-left: 14px;
    padding-right: 14px;
  }

  /* ── The reveal on phones keeps its blur, and the SPACING around it is what
     prevents module 3 popping in. Read this before touching either.

     `will-change` and `filter: blur()` both make WebKit give the text its own
     compositing layer, and a blur layer's bounds reach ~3× the radius beyond
     the box — 27px for the 9px blur here — plus the 14px the text sits lower
     before it reveals: 41px below the headline's box. Anything positioned
     that paints ABOVE that layer and overlaps it gets composited too, and
     composited layers are painted and uploaded only as they near the visible
     area, which on iOS lags a brisk scroll. Module 3's scatter is exactly
     such an element, so with the old 32px gap the entire composition became
     one late-painting layer. Removing the blur fixed it; Eddy preferred to
     keep the blur and add room instead, so the clearances below are what
     hold the fix now:
       · .Module-title--center margin-bottom 64px on phones (the scatter's
         highest cover starts 1.1% of the scatter's height above its box —
         ~5px at 375, ~10px at 768 — so the layer's 41px reach leaves 18px
         and 13px to spare respectively)
       · .Footer padding-top 168px (see its own note further down)
     ⚠️ Shrink either of those and the pop-in comes straight back, silently,
     on iOS only. Softer than desktop for the same reason as before: a big
     blur/translate is more noticeable, and more expensive, on a phone. */
  .is-revealJS .Module [data-reveal],
  .is-revealJS .Footer [data-reveal]{
    transform: translateY(14px);
    filter: blur(9px);
  }

  /* Module 2's screenshot bleeds off the LEFT edge here, as it does on
     desktop. The 960 rule above only brings it flush to the viewport edge;
     this pushes it past. The right edge stays on the content column so the
     copy above it keeps a clean margin. */
  .Module--reverse .Module-media img{
    width: calc(100% + var(--gutter) + 24vw);
    margin-left: calc((var(--gutter) + 24vw) * -1);
  }

  /* ── Module 3: the scatter ──────────────────────────────────────
     The desktop arrangement is the spec, so these are derived from it rather
     than eyeballed. .Scatter is widened to span the FULL viewport (it is
     normally inset by the module's gutter), which makes every percentage
     below a straight fraction of the screen — the same relationship the
     covers have to the viewport on desktop. aspect-ratio then scales the
     whole composition as one unit, exactly like the desktop one.

     ⚠️ Cover numbers are not interchangeable. An earlier pass showed covers
     1-4 and hid 5-8, which looked wrong because the numbering has nothing to
     do with the composition: Cover--3 (Lana) is a barely-visible sliver at
     the far right on desktop, while Cover--6 (Polyphia) and Cover--7
     (Hippocampus) are two of the most prominent. The six kept below are the
     ones that carry the desktop arrangement.

     ⚠️ Positions are computed from each file's ACTUAL artwork box, not its
     element box, and the transparent padding differs per file — the artwork
     is 66.4% of the element for Polyphia but 82.4% for Flume, so equal --w
     values render visibly unequal covers. The iPod is the extreme case: its
     art is 73.2% of the element's width, and the bottom 23% of the element's
     height is empty shadow space. Measure with a canvas alpha scan before
     retuning any of this; eyeballing it is what produced the earlier version. */
  .Scatter{
    width: calc(100% + var(--gutter) * 2);
    margin-left: calc(var(--gutter) * -1);
    /* ⚠️ min-height must be cleared, or the desktop rule's 600px wins. */
    aspect-ratio: 375 / 436;
    height: auto;
    min-height: 0;
  }
  /* 66% of the screen → 48.3% of visible iPod, against 50.1% on desktop. */
  .Scatter-ipod{ width: 66%; top: 12.3%; }

  /* Off-screen at these proportions. Hiding them also stops the lazy loader
     fetching two covers nobody can see. */
  /* ⚠️ No 3D transform on phones — this is the fix for the pop-in.
     The base .Cover rule carries translate3d(0, var(--py), 0) for the desktop
     parallax, and a 3D transform, even at a zero offset, makes WebKit give
     each cover its own compositing layer; the iPod overlaps those layers, so
     it gets promoted too. The headline stays in the page's pre-rasterised
     tiles, but composited layers only get their backing painted and uploaded
     as they near the visible area, and on iOS that lags a brisk scroll: the
     images land a frame or two after the text. Images only, text never, in
     Safari as well as Firefox — exactly what was reported. With no transform
     they paint into the same tiles as the text and arrive with it.
     The only earlier reset lived in prefers-reduced-motion, which is why four
     rounds of loading/decoding work never touched it.
     Phones still get a (gentler) parallax, but through margin-top, which is
     plain layout and creates no layers — Page.js writes --pym here instead of
     --py. ⚠️ Never move it back onto transform, translate or top-with-
     will-change on phones. */
  .Cover{ transform: none; margin-top: var(--pym, 0px); }

  .Cover--3, .Cover--8{ display: none; }

  /* ⚠️ Deliberately no z-index overrides — the desktop stacking is already
     what's wanted (1, 2 and 6 in front of the iPod; 5 and 7 behind it), and
     forcing covers in front is what previously parked one on the click wheel. */
  .Cover--1{ --w: 39.9%; left:   3.8%; top: -1.1%; }  /* over the screen, left  */
  .Cover--7{ --w: 24.5%; left:  67.5%; top:  4.7%; }  /* behind, upper right    */
  /* ⚠️ Flume is LARGE and mostly off-screen — that's the desktop look. On
     desktop it's 71% of the iPod's width and simply runs off the right edge;
     an earlier pass sized it from the sliver visible in the reference
     screenshot (~40%) and shipped a small Flume by mistake. 42% here is the
     size the mobile composition had before that; placed so its artwork starts
     ~10px right of the iPod's edge (adjacent, as on desktop) with ~59% of it
     on screen, the same visible fraction as the desktop reference. */
  .Cover--4{ --w: 42.0%; left:  76.0%; top: 31.5%; }  /* large, bleeds off right */
  .Cover--5{ --w: 20.6%; left: -10.5%; top: 45.5%; }  /* bleeds off the left    */
  .Cover--6{ --w: 28.9%; left:  65.5%; top: calc(61.4% - 16px); }  /* over the lower right; nudged up 8px at Eddy's request */
  .Cover--2{ --w: 23.9%; left:   8.0%; top: calc(79.5% + 8px); }  /* over the lower left; nudged down 8px at Eddy's request — it's the lowest cover, so this also deepens the footer bleed to 34px */

  /* Stacked and centred rather than the desktop's icon-beside-field row.
     There are then two signup forms on a phone (hero + footer) — deliberate:
     the footer one gives the section its height, and the reader who scrolls
     the whole page shouldn't have to go back up to sign up. */
  .Footer-signup{
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 4px;
    max-width: 340px;
    margin-bottom: 44px;   /* 56 − 12, offsetting the tagline gap above */
  }
  .Footer-icon{ width: 84px; margin-bottom: 4px; }

  /* ⚠️ The icon and tagline are lifted off the field by widening the gap INSIDE
     the block, not by moving the block up. That distinction is load-bearing:
     .Footer-signup is the footer's reveal element, and its blurred pre-reveal
     layer reaches 45px above its own top. Raising the block would eat straight
     into the clearance that keeps module 3 from popping in — which is only
     ~19px at 768px, so there is room for about 3px, not 12. Growing the gap
     below the tagline instead leaves the block's TOP exactly where it was, so
     the clearance is untouched, and the 12px is taken back off the block's
     bottom margin so the footer's overall height and everything below it stay
     put. Net effect is the one asked for: more air above the field. */
  .Footer-tagline{ margin-bottom: 22px; }
  .Footer-signupBody{ width: 100%; }
  .SignupForm--footer .SignupForm-status{ text-align: center; }

  /* ⚠️ NO bleed on phones — module 3 stops ABOVE the footer here, unlike every
     larger breakpoint, where the desktop rule's negative margin still pulls the
     footer up under the composition. Keep it that way.

     ⚠️ Nothing on the page moves as a result. Only the LINE between the two
     sections drops: the module keeps .Module's 72px bottom padding, the margin
     goes to 0 so the footer starts at the module's own bottom edge, and the
     footer's top padding shrinks by exactly the distance the line travelled
     (see .Footer below). The composition, the icon, the field and the links all
     stay at the same place in the document. */
  .Module--scatter{ margin-bottom: 0; }

  /* ⚠️ This must cancel the bleed exactly, or the footer's contents shift.
     The old geometry put the icon at `184px − (2.8% + 98px)` below the module's
     bottom edge; with the margin now 0 the footer starts at that bottom edge,
     so the padding has to BE that figure: 86px − 2.8%. Roughly 76px at 375 and
     65px at 768. Change the bleed maths above and this has to move with it.

     The clearance that keeps module 3 from compositing is unchanged, because
     nothing moved: the footer's blurred reveal layer reaches 45px above its own
     top, which still lands 31px (375) / 20px (768) below the module's bottom.
     It is simply no longer paid for out of a 184px pad. */
  .Footer{ padding-top: calc(86px - 2.8%); }
  .Footer-bar{
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 22px;
  }
  .Footer-mark{
    justify-content: center;
    min-height: 66px;
    padding-left: 0;
  }
  .Footer-signature{ left: 50%; transform: translate(-50%, -52%); }
  .Footer-copy{ padding-left: 0; }
  .Footer-links{ justify-content: center; }
  /* "Designed and built by @SMSeddy" is nowrap and always wraps to its own
     line at phone widths, which left the "|" before it dangling at the end of
     the row above. Give the credit a row of its own and drop that separator,
     rather than depending on where the text happens to break.
     ⚠️ :nth-of-type counts SPANS, and .Footer-credit is a span too — the
     spans are sep, sep, credit, so (2) is the second separator. */
  .Footer-sep:nth-of-type(2){ display: none; }
  .Footer-credit{ flex-basis: 100%; margin-top: 2px; }
}
