/* ═══════════════════════════════════════════════════════════════════════════════════
   GHOST CASE — HOME · MOBILE ONLY
   ───────────────────────────────────────────────────────────────────────────────────
   Linked from index.html with media="(max-width:767px)", so the browser can never
   apply a single line of it to the web layout. THE RULE: web styles live inline in
   index.html; every mobile change from 08-03 onward lives HERE. If a mobile fix wants
   to edit an inline rule, it overrides it from this file instead.
   Loaded LAST, so it wins on order at equal specificity.
   ═══════════════════════════════════════════════════════════════════════════════════ */

/* ── 1 · HERO ───────────────────────────────────────────────────────────────────────
   GHOST CASE has to read bigger than the line under it. The sub-lines sit on a 14px
   floor (max(14px,.215em)), so they do not grow with the wordmark — raising the hero
   size only grows GHOST CASE, which is exactly what we want. Sized by measurement:
   the wordmark lands ~92vw wide, the "Collect it. Track it. Trade it." line ~87vw. */
@media (max-width:767px) {
  /* the binding term was the clamp's 9vw, not the outer min() — both had to move.
     12.2vw puts GHOST CASE at ~291px against a 252px sub-line on a 393px screen. */
  .intro__title.gc-hero { font-size: min(clamp(1.6rem, 12.2vw, 10.5rem), 16vw) !important; }
  .gc-hero .gc-sub { font-size: 12px; letter-spacing: .185em; }
}

/* ── 1b · THE THREE CARD SECTIONS — PACING ──────────────────────────────────────────
   Each .wrap reserved a full screen of layout, but the animation now finishes inside
   110% of a screen (js/mobile.js), so the tail of every wrap was dead scroll — measured
   as one 420px stretch with nothing on screen at all after the third stack. Trimming
   the reserved height pulls the next headline up into that space. The motion itself is
   viewport-relative, so it is untouched by this. */
@media (max-width:767px) {
  /* TRADE WITH THE COMMUNITY only. That stack's own choreography carries yPercent:-100
     — it climbs as it comes — so once its depth was fixed it arrived on top of its own
     headline. Its stage drops; the motion is untouched. */
  [data-stack-3].wrap__inner { transform: translate(80px, 130px) scale(.46) !important; }
  /* +130 not -40: measured against the stack Tom kept, this one ran 150-190px HIGH
     through the middle of its run (y353/y137 where stack 2 sits at y527/y198), so it
     was crossing the screen's centre early and leaving out of the top. */

  /* NOT min-height: the stage (.content) is a 100vh box and the cards fly centred
     inside it, so shrinking it would move the flight path off the middle of the screen.
     The stage keeps its height and the SECTION AFTER it is pulled up into the dead tail
     instead — the cards are already gone by then (the timeline ends at 0.32 of a screen
     past the wrap's top, this reclaims from 0.70 on). */
  .wrap { margin-bottom: -60vh; }
  /* ...but NOT the last one. The pull exists to hand one card section to the next; on
     the third there is no next card section, only the colorway grid, and pulling that
     up ran the binders straight through the flying cards. It gets air instead. */
  .wrap.m-last-stack { margin-bottom: clamp(10px, 2vh, 24px) !important; }
}

/* ── 2 · HEADER ─────────────────────────────────────────────────────────────────────
   390px of bar cannot carry a wordmark, a title, a patrol ghost, two buttons and an
   avatar. On the phone the GHOST CASE mark IS the logo (the VOIDD wordmark and its ✕
   go), and VIEW ALL SETS drops — SIGN UP is the one action that matters here, and
   every set is one tap away from the avatar menu. */
@media (max-width:767px) {
  /* the inline sheet targets `.hdr img.logo`, so this has to match it and win — and
     mobile overrides carry !important as house rule (Lessons 04-23). */
  .hdr img.logo,
  .hdr .x { display: none !important; }
  .hdr .stat:not(.glow-blue) { display: none !important; }
  /* THE LOGO IS THE GHOST CASE WORDMARK. The inline sheet hides .ttl on the phone,
     which is what left the bar reading empty — a lone pixel ghost and a button. It
     comes back as the logo: mark first, wordmark next to it, both hard left. */
  /* SHOW it, do not restyle it: the wordmark keeps the web's own type (Share Tech
     Mono 15px / 2.5px tracking, set in the inline sheet). The only phone change is
     that it is visible at all, and that it sits hard left as the logo. */
  .hdr .ttl { display: block !important; order: -1; margin-left: 2px; white-space: nowrap;
    /* the web's own size too — a phone rule further up the inline sheet shrinks it to
       9px, which is a caption, not a logo. 15px / 2.5px is what the web bar carries. */
    font-size: 15px !important; letter-spacing: 2.5px !important; }
  /* .sp stays the flexible spacer it is on the web — it is the patrol ghost's run, and
     shrinking it parked the ghost on top of the wordmark's first letter. */
}

/* ── 3 · MAKE IT YOURS (colorway) ────────────────────────────────────────────────────
   The inline sheet has a phone rule for this section, but a `max-width:900px` block
   further DOWN the same sheet re-collapses it to one column with the copy underneath —
   which is what shipped. This restores the web's composition at phone scale: binders
   on the left half, copy beside them on the right, the copy sticky so it arrives with
   the section and holds while the binders animate past. */
@media (max-width:767px) {
  .colorway { display: grid !important; grid-template-columns: 64% minmax(0, 1fr) !important;   /* the 25% bigger binders need the room */
    align-items: start !important; padding-right: 0 !important;
    /* the section has to be TALLER than a screen or the sticky copy has no range to
       stick in — at one screen tall it just scrolled away with the binders. This is the
       scroll the binder animation plays through, same as the web. */
    min-height: 105vh !important;   /* measured: the copy holds for the whole binder pass, with no dead tail */
    margin-bottom: clamp(120px, 16vh, 220px) !important; }
  /* the binders animate along a path sized for a desktop column; in a 204px column they
     travelled straight across the copy. The column clips them, so they run in their own
     half and slide out of it — the copy is never underneath a binder. */
  /* The grid fills its own column (see js/mobile.js grid4). The z-move still brings the
     rows forward, which renders them ~258px across; this trims the whole scene to the
     195px Tom asked for and nudges it to sit centred in the 204px half — measured, not
     guessed. The rows do not all sit at the same x — the z-move staggers them by ~10px
     — so the envelope of ALL rows is what has to fit: .735 with a 7px nudge lands every
     row inside 0..204, with the three binders spanning ~193 and each ~60-64px. */
  /* 5px above and below each binder — vertical only, the side gutter is untouched.
     The scene renders at .919, so the layout row-gap has to be 10/0.919 = ~11px to land
     as 10px on screen. */
  .colorway .bgrid-wrap { row-gap: calc(var(--grid-gap, 1vw) + 11px) !important; }
  .colorway .bgrid { width: auto !important; margin-left: 0 !important; padding: 0 !important;
    transform: translateX(7px) scale(.735) !important;
    /* the SCALE is unchanged — the 25% comes from the column: each cell is a third
       of it, so 204px -> 255px makes every binder 25% bigger and the fan's envelope
       grows by exactly the same 25%, which is why the column had to grow with it. */ transform-origin: 0% 50% !important; }
  /* the copy is its own height, not a 100vh box: a full-height sticky element has only
     (section - 100vh) of range, which on the phone was ~190px — it unstuck while the
     binders were still animating. At content height it holds for the whole pass, and
     `top` places it where a centred block would sit. */
  /* the copy stops CENTRED on the screen, not a third of the way down: its box is
     ~193px tall, so half of that off the middle puts its centre on 50vh. */
  /* it settles SOONER: starting the block higher in the section means it reaches its
     sticky resting place 80px sooner. -60 is the limit: at -110 the copy starts colliding
     with the trade cards still flying out of the section above (measured 4 frames, 8 at
     -160), and Tom does not want art sitting on live text. */
  .colorway-copy { position: sticky !important; top: calc(50vh - 96px) !important;
    margin-top: -60px !important;
    height: auto !important; align-self: start !important;
    display: flex !important; flex-direction: column !important; justify-content: flex-start !important;
    align-items: flex-start !important; text-align: left !important; transform: none !important;
    /* EQUAL AIR EITHER SIDE of the text: the same gap from the binders as from the right
       edge of the screen. Measured — the fan's furthest right through the whole pass is
       237 and the usable width ends at 379 (the scroll rail takes the rest), so 12px each
       side puts the text in 249..367 and gives it the space the old 20/9 split wasted. */
    padding: 0 12px 0 12px !important; margin-left: -16px !important; }
  .colorway-copy .section-title,
  .colorway-copy .section-sub,
  .colorway-note { text-align: left !important; margin-inline: 0 !important; }
  .colorway-copy .section-title { font-size: clamp(16px, 5vw, 24px) !important; max-width: none; }
  /* the headline has to clear the scroll rail on the right edge */
  /* 8px, not 16: the text is the tightest thing on this screen and the rail already
     has its own reserved strip (body padding-right), so it does not need a second
     one here. */
  .colorway-copy { padding-right: 12px !important; }
  .colorway-copy .section-sub { font-size: 9px !important; letter-spacing: .9px; margin-top: .7rem; }
  /* THE RAGGED EDGE was the "extra space", not the padding. Measured: the box is 117px
     but individual lines filled 39-97% of it — Kode Mono at 8.5px with .6px tracking fits
     ~17 characters, so a word like STITCHING, breaks the line early and leaves 40-70px
     empty. Tighter tracking buys ~2 characters a line and justify flushes the right edge:
     average fill 76% -> 88%, and every line but the last is now full. */
  /* NOT justified. The box does not move — recorded it for 123 straight frames while
     scrolling and it swings 0.00px — so what Tom sees sliding left and right is the text
     inside it. justify sets every line but the last by stretching the gaps between words,
     so the moment anything reflows a line (iOS re-adjusting text size when the address bar
     moves, a font finishing loading, a sub-pixel width change) every word on every line
     lands somewhere new. In a column this narrow, with words this long, one word moving to
     the previous line visibly shoves the whole paragraph around. Left-aligned, a word sits
     where it sits and a reflow can only change where lines break.
     text-size-adjust pins it further: iOS otherwise rescales text on its own as the
     viewport changes, which is a reflow we never asked for. */
  .colorway-note { font-size: 8.5px !important; letter-spacing: .2px !important;
    line-height: 1.75; margin-top: 10px; text-align: left !important;
    -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  .cw-cta { height: 30px; padding: 0 11px; font-size: 8px; letter-spacing: 1.1px; margin-top: 12px; }

  /* THE COPY STARTS CLIMBING SOONER. It arrives, parks dead centre, and then sat there
     for 748px of scrolling before it began to move again — measured chromium, copy.top
     pinned at 209 from scrollY 2727 all the way to 3475. That is more than a screen of
     the binders sliding past a caption nailed to the middle of the phone, which is what
     Tom means by wanting it to start moving up sooner.
     A sticky element is released by the BOTTOM of its containing block, and its own
     bottom margin pulls that release line up by exactly its own size — so 200px of margin
     hands back 200px of the dead hold and nothing else. Everything the section does is
     untouched: it arrives at the same scroll position, parks at the same place on screen,
     travels at the same rate once released, and the section's own height does not move
     (the row is sized by the binder column at 1012px, and the copy column only grows from
     228 to 428). The binders are still a full screen deep on the phone when the copy lets
     go, so it climbs WITH them rather than after them. */
  .colorway-copy { margin-bottom: 200px !important; }
}

/* the colorway's own lead-in, trimmed so the section arrives right behind the cards */
@media (max-width:767px) {
  .bgrid-sec { padding-top: 0 !important; }
}

/* ── 4 · EVERY SET · EVERY ERA (the 170+ sets scroll) ────────────────────────────────
   ONE binder size for the whole section. The cover is sized so that, at the aspect the
   spread opens to, the open book lands at ~75% of the screen — the proportion Tom
   marked as good. Cover 36.4vw (143px at 393) -> 200px tall; the spread is locked to
   that same 200px by js (window.GC_M.everySet), so it opens into place. */
@media (max-width:767px) {
  .es-thumb { width: 36.4vw !important; }

  /* the set names, either side of the binder. --lane never went below 300px, so on a
     393px screen each column had ~46px and the labels ran clean off both edges. The lane
     is now the binder plus a margin, and a label that is still too long ellipsises
     rather than escaping. Tiny is fine — Tom said so — legible and inside is not. */
  /* padding-LEFT/RIGHT only. The `padding` shorthand with !important also killed the
     padding-BOTTOM that applyHold() writes inline (2772px of it) — that padding IS the
     scroll the open-book run happens in, so the handover span went negative and the
     binder never opened. This was the real cause both times I blamed the label height. */
  .dual-wave { --lane: 168px !important; padding-left: 6px !important; padding-right: 6px !important; }
  /* KEEP the 7.5px the inline sheet already sets. Dropping it to 6.5 shortened every row,
     which shortened the whole wave — and the hold that hands over to the live binder is
     derived from the wave's own height, so the binder stopped opening at all. The labels
     are fixed by the lane and the clamp below, not by shrinking the type further. */
  /* ONE LINE EACH, like the web — the wrapped version read as a different pattern. Smaller
     type (6px) so the names still fit the half beside the binder; anything longer than
     the lane ellipsises rather than escaping the screen.
     The rows sit close together, like the web's list. (An earlier note here claimed the
     gap had to carry the wave's height or the binder would not open — that was wrong;
     the real cause was the padding shorthand below eating applyHold's inline padding.
     With that fixed the handover span comes from the hold, not from the column.) */
  .wave-col { gap: 5px !important; }
  .wave-item { font-size: 6px !important; letter-spacing: .4px !important;
    max-width: calc((100vw - var(--lane)) / 2 - 48px) !important;   /* room for the bigger step */
    white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; }
}

/* the character binders hand straight to EVERY SET — that 52vh of air was a full screen
   of nothing on a phone */
@media (max-width:767px) {
  .bgrid-sec .bgrid[data-grid="chars"] { margin-bottom: clamp(60px, 10vh, 130px) !important; }
}

/* ── 5 · MEET GHOSTY (the gear wall) ─────────────────────────────────────────────────
   Full screen on the phone. The pin starts under the nav on the web, which on a 660px
   screen costs the wall a tenth of its height; the nav is translucent, so the wall can
   run behind it. */
@media (max-width:767px) {
  .fg-pin { top: 0 !important; height: 100vh !important; }
}

/* ── 6 · THE ISLAND ──────────────────────────────────────────────────────────────────
   One paragraph size, smaller — the clamp mixed vw and vh, so on a phone the lines came
   out at two different sizes depending on which term won. And the console runs as THREE
   columns: label, D-pad, label — instead of the wrapped pile the flex row produced. */
@media (max-width:767px) {
  .gi-para { font-size: 9px !important; line-height: 1.75 !important; letter-spacing: .4px !important; }

  /* TWO LABELS A SIDE. The pad is 98px tall and each label only 26px, so with one label
     per side there was dead air beside the controls while the other two lines stretched
     across above and below them — the bar read as three unrelated strips. Stacking them
     two-a-side fills the space the pad already occupies and the whole thing becomes one
     block: your ghost and his island down the left, the chest and levelling down the
     right, controls in the middle. */
  .gi-hud { display: grid !important; grid-template-columns: 1fr auto 1fr !important;
    grid-template-areas: "t pad r" "l pad b" !important;
    align-items: center !important; justify-items: center !important;
    gap: clamp(5px, 1.6vw, 12px) !important; flex-wrap: nowrap !important;
    width: min(var(--gi-w, 92vw), 92vw) !important; }
  .gi-hud-t { grid-area: t !important; }
  .gi-hud-l { grid-area: l !important; }
  .gi-pad   { grid-area: pad !important; }
  .gi-hud-r { grid-area: r !important; }
  .gi-hud-b { grid-area: b !important; }
  /* they are columns now, not full-width strips, so each goes back to title-over-sub */
  .gi-hud-t, .gi-hud-b { flex-direction: column !important; align-items: inherit !important;
    gap: 2px !important; }
  .gi-hud-t { justify-self: start !important; text-align: left !important; }
  .gi-hud-b { justify-self: end !important; text-align: right !important; }
  /* the title is centred inside its own box by the base sheet, so in a left column it sat
     indented from the sub-line under it. Each line takes its column's alignment instead. */
  .gi-hud > div > span { text-align: inherit !important; }
  .gi-hud > * { align-self: center !important; }
  .gi-hud > * { flex: 0 0 auto !important; min-width: 0 !important; }
  .gi-hud-l { justify-self: start !important; text-align: left !important; }
  .gi-hud-r { justify-self: end !important; text-align: right !important; }
  .gi-hud-ttl { font-size: 9px !important; letter-spacing: 1px !important; }
  .gi-hud-sub { font-size: 7px !important; letter-spacing: .6px !important; }
  /* the D-pad shrinks so the two label columns actually get a column each — at the web's
     62px buttons the pad ate 198 of the 361px bar and the labels overlapped it. */
  .gi-btn { width: 44px !important; height: 30px !important; line-height: 30px !important; }
  .gi-play { width: 38px !important; height: 26px !important; line-height: 26px !important;
    font-size: 10px !important; letter-spacing: 1.4px !important; }
  .gi-pad { gap: 4px !important; }
}

/* the feature columns are placed by js now (window.GC_M.everySet) — it measures the
   heading, the binder and the columns and gives all four gaps the same size. The old
   `top: 62vh !important` here beat the inline style it writes, which is why the gap
   under the binder stayed at 23px while everything else moved. Only the anchor stays. */
@media (max-width:767px) {
  .es-feat { bottom: auto !important; }
}


/* the bullets SCROLL UP into place instead of fading in from the side. The web slides
   them in horizontally (translateX 28px) on a half-second opacity fade — on the phone
   the two columns sit under the book, so the natural move is vertical. Opacity snaps
   (0.12s, just enough to not pop) and the visible part of the transition is the travel. */
@media (max-width:767px) {
  .es-feat-l .ef,
  .es-feat-r .ef { transform: translateY(20px) !important;
    transition: opacity .12s linear, transform .45s cubic-bezier(.2,.7,.3,1) !important; }
  .es-feat-l .ef.in,
  .es-feat-r .ef.in { transform: translateY(0) !important; }
}

/* ── 7 · HERO CARD LABELS ────────────────────────────────────────────────────────────
   The white plate under each card was 47px of a 100px card — the padding and the
   per-row bottom padding were both sized for the desktop card. Tighter, and every line
   carries the same weight (the set line and the year line were 400 against the title's
   700, which read as three different labels rather than one). */
@media (max-width:767px) {
  .intro .card__info { padding: 4px 6px !important; }
  .intro .card__info .card__title { padding-bottom: 1px !important; line-height: 1.15 !important; }
  .intro .card__info .card__subtitle { padding-bottom: 0 !important; line-height: 1.15 !important; }
  .intro .card__info .card__meta,
  .intro .card__info .card__description { line-height: 1.15 !important; }
  .intro .card__info .card__title,
  .intro .card__info .card__meta,
  .intro .card__info .card__subtitle,
  .intro .card__info .card__description { font-weight: 700 !important; }
}

/* ── 8 · THE ISLAND — game and console the same width ───────────────────────────────
   The stage runs 94vw and the HUD min(--gi-w, 92vw), so the console sat 7px narrower
   than the game it belongs to. */
@media (max-width:767px) {
  .gi-hud { width: 94vw !important; max-width: 94vw !important; }
}

/* ── 9 · HERO CARD CORNERS ───────────────────────────────────────────────────────────
   The card art carries its own corner radius, baked into the PNG: measured off the
   image's alpha, the first opaque pixel is 30px in on a 734px-wide card = 4.09% of the
   width. At the phone's 110px render that is 4.5px, where the frame and the label plate
   were both sitting at 10px — visibly rounder than the card between them. */
@media (max-width:767px) {
  .intro .card { border-radius: 4.5px !important; }
  .intro .card__info { border-radius: 4.5px !important; }
}

/* the phone bar clears the scroll rail too — the avatar was touching it (see the --rail
   measurement in index.html; the inline phone rule sets a flat 10px pad) */
@media (max-width:767px) {
  .hdr { padding-right: calc(var(--rail, 16px) + 12px) !important; }
}

/* ── 10 · THE CARD STACKS IN SAFARI ──────────────────────────────────────────────────
   The phone scales the whole 3D scene with a transform on .wrap__inner — the same
   element that carries the perspective. A transformed element defaults to
   transform-style: flat, which flattens everything under it. Chromium still applied the
   perspective to the cards; WebKit did not, so on an iPhone the stack rendered at one
   fixed size the whole way through — the cards never flew at you. Measured in WebKit:
   96px before, growing to 300+ after.
   Keeping the 3D context alive through that scale is the whole fix. */
@media (max-width:767px) {
  .wrap__inner { transform-style: preserve-3d !important; }
}

/* ── 11 · THE STACK CARDS ON iOS — the thin white lines ──────────────────────────────
   Reproduced in WebKit: a stack card computed 338x12px with its image box at 0x0, where
   Chromium gives 338x467. That 12px bar IS the sliver Tom filmed — not a missing image,
   a collapsed box.
   Why: the card is a grid whose only child is .card__img, and that child sizes itself
   from `aspect-ratio: 1` with no definite width. Chromium resolves it through the grid
   stretch; WebKit resolves it to zero, so the card has nothing to be tall for and the
   art has no box to paint in.
   Nothing here relies on either engine's guess any more: the card carries its own 2:3
   box and the image fills it. */
@media (max-width:767px) {
  .content .card { height: calc(86vw * 1.5) !important;
    /* one full-height cell: the base card is a 2-column grid built for the labelled hero
       card, and in the stacks the image is the only child — height:100% was resolving
       against a content-sized row, so the art filled 124px of a 507px card. */
    grid-template-columns: 1fr !important; grid-template-rows: 1fr !important; }
  .content .card__img { grid-area: auto !important; width: 100% !important; height: 100% !important;
    aspect-ratio: auto !important; max-width: none !important; background-size: cover !important; }
}

/* ── 12 · SCROLL PERFORMANCE ─────────────────────────────────────────────────────────
   backdrop-filter on a FIXED, full-width bar is the most expensive thing you can ask an
   iPhone to do while scrolling: every frame it re-samples and blurs whatever is passing
   underneath. The bar keeps its look with a heavier alpha instead of a live blur. */
@media (max-width:767px) {
  /* THE BAR KEEPS ITS GLASS. Tom's call: the tint reads wrong, the blur is the look.
     It stays at the web's own rgba(6,6,14,.30) + blur(18px) saturate(160%) — nothing
     overridden here. The scroll cost it was blamed for came mostly from the other three
     (Lenis + normalizeScroll fighting, and the full-screen gradient repainting every
     frame); those are gone, so the bar can afford to be glass again. Watch this if the
     phone ever feels heavy on scroll — a smaller blur radius is the cheap lever. */
  /* ordinary boxes far down the page keep their blur off — they are never on screen
     during a fast scroll and there are dozens of them */
  .gc-card, .gc-price, .gc-faq details { backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important; }
  /* a full-viewport element promised to change its background image keeps a giant layer
     alive for nothing once the drift is off */
  .bg-layer { will-change: auto !important; }
}

/* ── 13 · THE BULLET COLUMNS RIDE WITH THE BINDER ────────────────────────────────────
   While the binder is parked it is position:fixed and costs nothing to hold still; the
   moment it starts climbing out of the section it goes back to being an ordinary
   absolutely-positioned box inside the wave, and from then on the BROWSER scrolls it —
   every frame, for free. The bullet columns never got that half of the treatment. They
   stay fixed to the viewport and are dragged upward by a translateY that javascript
   rewrites on every scroll callback, and a callback that does not arrive is a frame where
   the binder moved and the text did not.

   That is measurable, not theoretical. Stepping WebKit through the climb 60px at a time:
   the binder went 136.8 -> 76.8 -> 16.8 -> -43.2 -> -103.2, one clean 60 per step, while
   the text went -51.1 -> -111.1 -> -171.1 -> -231.1 -> -231.1: it missed a step, fell 60px
   behind the binder, then jumped 120 to catch up. Further down it stopped updating
   altogether and sat still for 130px while the binder kept going. That stutter-and-snap is
   what Tom is describing.

   So during the climb the text is put in the same frame of reference as the binder:
   absolutely positioned inside the wave at the document offset it was already occupying,
   with the javascript transform cancelled. Both boxes are then fixed points in the page,
   and the browser scrolls the two of them together — they cannot drift apart, because
   nothing is computing their positions any more. js/mobile.js does the handover, watching
   the binder's own position property so the two switch on the same event.

   The exit rule below still wins (three classes to this rule's two), so the way they leave
   at the end is unchanged. */
@media (max-width:767px) {
  .es-feat.m-ride { position: absolute !important;
    /* the driver keeps writing translateY() inline as the section plays out; this is what
       stops it moving the box a second time on top of the scroll it is already riding */
    transform: none !important; }
}

/* ── 14 · THE BULLET COLUMNS LEAVE BY SCROLLING ──────────────────────────────────────
   They are fixed, so the section's `offscreen` class was switching them off while they
   were still on screen — measured at y=13 with opacity going 1 -> 0. That reads as the
   list blinking out rather than leaving. On the phone they keep their opacity and ride
   the binder up and off; js hides them only once they are genuinely past the top. */
@media (max-width:767px) {
  /* They are fixed, and the driver that moves them stops updating the moment the section
     leaves — so simply keeping them visible parked them at y=13 forever. Instead they get
     carried off the top: the same class that used to blank them now slides them away, and
     the js hide only catches them after they are past the edge. */
  .dual-wave.offscreen .es-feat { opacity: 1 !important; visibility: visible !important;
    transform: translateY(-110vh) !important;
    transition: transform .55s cubic-bezier(.45, 0, .9, .6) !important; }
  /* both selectors: the bare one for the in-section case, the offscreen one because the
     exit-slide rule above is 3 classes to this rule's 2 and was silently winning the
     visibility fight — m-gone never actually hid anything while `offscreen` was on */
  .es-feat.m-gone, .dual-wave.offscreen .es-feat.m-gone { visibility: hidden !important; }
}

/* ── 15 · THE ISLAND HOLDS ────────────────────────────────────────────────────────────
   Every section above this one pins — the binder wall, EVERY SET, the trade steps and the
   gear wall all park under the nav and hold while you keep scrolling. The island did not:
   it was a plain block that slid past like ordinary page content, which is why it felt
   different from everything before it. It is also the one section you are meant to PLAY
   with, so sliding away under your thumb is the worst possible behaviour for it.

   The inner block becomes sticky and the section grows the scroll room it holds through.
   The two sections after it — pricing and the FAQ — are deliberately left alone; they are
   reading material and should scroll freely.

   The scroll room is a spacer INSIDE the section, not extra padding on it. A sticky
   element is held within its containing block's CONTENT box, and padding sits outside
   that box — so the first attempt grew the section by 580px and gave the pin exactly 1px
   of travel (content box 488, element 487). It read as no pin at all. An ::after block
   grows the content box itself, which is the thing the pin is measured against.

   Phone only. The web layout has the same gap, but web is frozen. */
@media (max-width:767px) {
  /* parks BELOW the nav, not under it. At top:0 the block's own heading slid beneath the
     glass header and the first two lines of the paragraph were unreadable the whole time
     it was pinned. Clearing the nav also happens to centre it: 487 tall in 635 leaves 74
     top and bottom, which is the header height. */
  .ghostisle .gi-inner { position: sticky !important; top: var(--hdr-h, 78px) !important;
    z-index: 5 !important; }
  .ghostisle::after { content: '' !important; display: block !important; height: 115vh !important; }
}

/* ── 16 · STOP THE TAB DYING ON A FAST SCROLL ─────────────────────────────────────────
   Scrolling hard broke the whole page on Tom's iPhone — it would go black and come back,
   which is iOS killing and reloading the tab, not a script error (a random-fling stress
   run throws nothing and leaves the geometry intact).

   The cause is layer count. `will-change: transform` promotes an element to its own
   compositor layer and keeps it there for the life of the page, and the EVERY SET wave
   carries it on every cover: 268 of them, out of 291 promoted elements on the whole page.
   At an iPhone's 3x pixel ratio that is hundreds of separate buffers held at once, and
   iOS drops the tab rather than the layers. will-change is a hint for something about to
   animate, not a decoration for a list — MDN says as much.

   Removing it does not stop the wave animating; the transforms still run, they just share
   the page's layer instead of each demanding a private one. Phone only: a desktop GPU has
   the headroom, and the web version is frozen. */
@media (max-width:767px) {
  .wave-item { will-change: auto !important; }
  /* width is not a compositable property — this hint buys nothing and only adds
     containment pressure on the same fast scroll */
  .tf-tx { will-change: auto !important; }
}
