/* Tmreen landing — tokens from src/theme.js BRAND (finalised Tasmeem board,
   2026-08-11). Arabic leads: IBM Plex Sans Arabic; Hanken Grotesk carries EN. */

:root {
  /* The dark grounds are the brand's true deep blues — #1f406e (Tasmeem board
     "Option 1" navy, the app-icon / support-header blue) and its deeper partner
     #16304f. The section-blend hems below were already written for these rgb
     values (22,48,79 / 31,64,110); the tokens had drifted to a near-black
     (#101525) and a grey-navy (#2b3044), which read as "not the right blue". */
  --navy: #1f406e;
  --navy-deep: #16304f;
  --blue: #2264a2;
  --sky: #339dcd;
  --gray: #434858;
  --gray-soft: #a7b1bb;
  --paper: #f7f9f7;
  --ink: #101525;
  --ink-2: #434858;
  --white: #ffffff;
  /* Grass green is the brand accent. It carries money-confirmed and
     live-availability states only — the pitch chalk stays sky so the two
     languages never compete. */
  --green: #3abd51;
  --green-deep: #18382e;
  --green-tint: #e9f8ed;
  --green-line: #c9ecd3;
  --ok: var(--green);   /* paid / released / confirmed */
  /* Apple-compatible hybrid: SF Pro / SF Arabic on Apple devices via
     -apple-system, brand fonts as the fallback everywhere else. */
  --font-ar: -apple-system, "SF Arabic", "IBM Plex Sans Arabic", system-ui, sans-serif;
  --font-en: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Hanken Grotesk", system-ui, sans-serif;
  --col: 1120px;
  --radius: 18px;
  /* A named scale. The page had 58 one-off padding/gap values, which is how
     vertical rhythm drifts section to section — the spacing was never
     actually decided, just typed. New rules reference these. */
  --space-3xs: 4px;  --space-2xs: 8px;  --space-xs: 12px;
  --space-s:  16px;  --space-m:  24px;  --space-l:  36px;
  --space-xl: 56px;  --space-2xl: 88px; --space-3xl: 132px;
  --flow-origin: right center;   /* fills grow with the reading direction */

  /* ── ground tokens ──────────────────────────────────
     Everything that sits ON the page ground reads its colour from here, so
     the whole page flips between grounds by redefining a dozen values rather
     than editing sixty rules. Components that carry their own surface — the
     booking card, the owner card, the green buttons, the app band — are
     deliberately NOT on these tokens: they look the same whichever ground
     they are standing on.

     THE LIGHT GROUND IS THE DEFAULT. The values in this block are the dark
     ground, and they only take effect when the page explicitly opts into it
     with data-ground="dark"; the light set below is scoped to
     html:not([data-ground="dark"]) and therefore wins by default and by
     specificity. It is written this way round rather than by swapping the two
     sets over so that light and dark cannot drift apart into mismatched
     halves — every value that differs is stated once, in one place, against
     its opposite.

       --on        text on the ground, as raw channels for rgba()
       --on-solid  the same at full strength, for headings
       --veil      lifted panels and hairlines, as raw channels
       --ground    the page-length ramp
       --glow-*    the two audience lights in the showcase */
  --on: 255,255,255;
  --on-solid: #ffffff;
  --veil: 255,255,255;
  --ground: linear-gradient(180deg,
    #16304f 0%, #1f406e 14%, #1a3559 24%, #17324f 40%, #1f406e 52%,
    #1a3559 62%, #1d3c68 72%, #1a3559 84%, #16304f 94%, #122a46 100%);
  --ground-base: #1a3559;
  /* Accents that sit directly on the ground. Sky reads beautifully on navy
     and measures 2.8:1 on paper, which is below the line for the small bold
     type it carries, so the light ground takes the deeper brand blue instead.
     --chalk is the same story for the pitch line work. */
  --accent: var(--sky);
  --chalk: var(--sky);
  --pitch-op: .42;
  /* The hero's two lights. Hardcoded until now, which meant the light ground
     inherited values chosen to glow on navy: on paper they read as a faint
     green stain in the corner rather than as light on the pitch. */
  --hero-sky: rgba(51,157,205,.30);
  --hero-green: rgba(58,189,81,.16);
  --hero-arc: rgba(51,157,205,.10);
  --hero-arc-halo: rgba(51,157,205,.045);
  /* Section lights. Two tints, laid on opposite corners of a section and
     masked in and out at its own edges. */
  --lt-green: rgba(58,189,81,.16);
  --lt-sky:   rgba(51,157,205,.20);

  /* The bar's height, derived rather than measured. The nav is its own
     padding twice over, plus the tallest control inside it, plus its bottom
     hairline — so stating those three as tokens and composing them means the
     number can never drift out of sync with the bar the way a hardcoded 69px
     would the first time anyone changes the padding.
     --nav-clear adds a little air below it. */
  /* iPhone safe areas. The fallbacks make these 0 everywhere else, so the
     same expressions are safe on every device. */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  --nav-pad: 14px;
  --nav-ctl: 40px;
  /* The notch is part of the bar's height. Without --safe-t in here the
     buttons sit UNDER the Dynamic Island on every modern iPhone, and the
     court offset below is short by the same amount. */
  --nav-h: calc(var(--safe-t) + var(--nav-pad) * 2 + var(--nav-ctl) + 1px);
  --nav-clear: calc(var(--nav-h) + clamp(10px, 2.4vh, 26px));
  /* The band the scroll cue owns at the foot of the hero: the cue itself plus
     the gap under it plus breathing room above. Reserved as padding so hero
     content is laid out around it rather than on top of it. */
  --hint-band: clamp(74px, 12svh, 112px);
  --hem-in: 18,42,70;
  --glow-players: rgba(26,140,40,.62);
  --glow-owners:  rgba(21,110,172,.50);
  /* Each color-mix() below is preceded by a plain rgba equivalent. Safari
     only learned color-mix in 16.2, and an unsupported declaration is dropped
     outright — so without the fallback these elements render with no
     background at all on an older iPhone, which is how a green pill becomes
     invisible text. The pair costs one line and covers it. */
  --nav-glass: rgba(22,48,79,.72);
  --nav-glass: color-mix(in srgb, var(--navy-deep) 72%, transparent);
  --nav-edge: rgba(255,255,255,.12);
  --nav-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 10px 30px -14px rgba(6,18,36,.55);
  /* The wordmark artwork is near-black (#1d2435), so it is knocked out to
     white on the dark ground and left alone on the light one. */
  --mark-filter: brightness(0) invert(1);
}

/* Panels on the default light ground. A translucent veil is how you lift a
   surface off a dark page; on paper it does nothing at all — the escrow panel, the
   session cards and the comparison column all but disappeared. On this ground
   they become solid white with a hairline and a two-part shadow, which is how
   an object actually sits on paper. */
/* Nav controls on paper. A 12% dark wash reads as a smudge on white, so the
   log-in pill goes solid navy and the affiliate pill takes a proper tint
   rather than a transparency. */
/* The band on paper. The dark ground melts navy into a deep-green top, which
   is right when the page above is dark. On white there is nothing to melt: a
   pale hem laid over a near-black green just turns the top of the band grey.
   The hem is dropped and the band ramps from a mid green instead, so it grows
   out of the page as a clean edge rather than fading in through mud. */
html:not([data-ground="dark"]) .appband::before { display: none; }
/* .finale::after bleeds green-deep up the bottom of the finale so the grass
   is already arriving before the band does. Over navy that reads as depth;
   over a pale page it is a dark grey-green wash sitting on white, which is
   the grey slab that appeared above the band. The light ground does not need
   it — the band's own edge does that job here. */
html:not([data-ground="dark"]) .finale::after { display: none; }
html:not([data-ground="dark"]) .appband {
  background: linear-gradient(180deg, #3faf5d 0%, #319f4d 45%, #2b9c42 100%);
}

html:not([data-ground="dark"]) .nav-login {
  background: var(--navy); color: #ffffff;
}
html:not([data-ground="dark"]) .nav-login:hover { background: #17355c; }
html:not([data-ground="dark"]) .nav-affiliate {
  background: #e7f7ea;
  background: color-mix(in srgb, var(--green) 15%, #fff);
  border-color: rgba(58,189,81,.44);
  border-color: color-mix(in srgb, var(--green) 44%, transparent);
  color: #14602e;
}
html:not([data-ground="dark"]) .nav-affiliate:hover { background: #dcf3e1; background: color-mix(in srgb, var(--green) 24%, #fff); }
html:not([data-ground="dark"]) .lang-toggle { border-color: rgba(18,38,63,.24); }
html:not([data-ground="dark"]) .lang-toggle:hover { background: rgba(18,38,63,.06); }

html:not([data-ground="dark"]) .escrow-panel,
html:not([data-ground="dark"]) .tn-card,
html:not([data-ground="dark"]) .wcol-before {
  background: #ffffff;
  border: 1px solid #dde7f1;
  box-shadow: 0 1px 2px rgba(18,38,63,.05), 0 16px 40px -16px rgba(18,38,63,.20);
}
html:not([data-ground="dark"]) .escrow-panel {
  background:
    radial-gradient(1200px 400px at 50% -10%, rgba(51,157,205,.10), transparent 60%),
    #ffffff;
}
html:not([data-ground="dark"]) .tn-card:hover {
  box-shadow: 0 1px 2px rgba(18,38,63,.06), 0 22px 50px -16px rgba(18,38,63,.28),
              inset 0 0 0 1px rgba(58,190,81,.5);
}
/* The comparison stays subordinate on this ground too — it is the option you
   are being argued out of — but by sitting flat instead of by being dim. */
html:not([data-ground="dark"]) .wcol-before {
  background: #f4f7fa; box-shadow: none;
}

/* ── the light ground ─────────────────────────────────
   Same page, opposite ground. Paper white through the palest brand blues,
   with the two audience colours arriving as light on that paper rather than
   as tint in a dark surface. The app screens inside the device mockups are
   light too, so on this ground they stop fighting the page and start looking
   like part of it.
   This is the page's default ground. /landing/?ground=dark switches to the
   dark set in :root above and remembers the choice; ?ground=light returns. */
html:not([data-ground="dark"]) {
  --on: 22,44,72;
  --on-solid: #12263f;
  --veil: 22,52,92;
  /* The stops used to sit within about eight levels of each other, which on a
     screen is no transition at all — the whole page read as one flat white.
     They now swing between clean white and a tinted band with roughly three
     times the amplitude, and the tints are the brand's own: cool blue through
     the hero and the comparison, a faint teal at the escrow, green where the
     players' half of the showcase lands. Still gradients, so there is still
     not a single hard edge anywhere — but each act now has a tone. */
  --ground: linear-gradient(180deg,
    #ffffff  0%,
    #e9f2fa  7%,
    /* White exactly where the hero ends. The stop used to sit at #e7f0f9 here,
       so the court never resolved to anything — it drifted from tinted to
       slightly-less-tinted and the section below inherited the cast. The court
       now opens on white, takes its colour through the middle, and hands back
       white at the boundary. */
    #ffffff 15%,
    #f8fbfe 25%,
    #e8f3f1 38%,
    #fcfdfe 49%,
    #e6eff8 60%,
    #fbfcfe 71%,
    #e9f4ec 83%,
    #fafcfe 93%,
    #ffffff 100%);
  --ground-base: #f2f7fb;
  --accent: #1a6698;
  /* Chalk has to survive being drawn at 42% on a white page, which the old
     pale blue did not — the pitch all but disappeared and what was left read
     as stray rectangles. Stronger line colour AND a higher pitch opacity, so
     the court is legible as a court. */
  --chalk: #6aa9cf;
  --pitch-op: .68;
  --hero-sky: rgba(51,157,205,.26);
  --hero-green: rgba(58,189,81,.30);
  --hero-arc: rgba(51,157,205,.18);
  --hero-arc-halo: rgba(51,157,205,.07);
  --lt-green: rgba(58,189,81,.30);
  --lt-sky:   rgba(51,157,205,.28);
  /* Pale grass, not paper. Fading the page's own near-white down over the
     band painted a grey smear across the top of the green — white at low
     alpha over a saturated colour desaturates it rather than blending into
     it. A pale green reads as the grass arriving instead. */
  --hem-in: 214,240,220;
  --glow-players: rgba(58,189,81,.38);
  --glow-owners:  rgba(51,157,205,.36);
  /* On paper a white glass bar cannot be defined by its fill — the fill is
     the same colour as the page. It has to be defined by its edge and the
     shadow it casts, so both are stronger here than on the dark ground. */
  --nav-glass: rgba(255,255,255,.76);
  --nav-edge: rgba(18,38,63,.16);
  --nav-shadow: 0 1px 0 rgba(18,38,63,.05), 0 12px 28px -14px rgba(18,38,63,.30);
  --mark-filter: none;
}
html[dir="ltr"] { --flow-origin: left center; }

* { box-sizing: border-box; margin: 0; padding: 0; }
/* NOT scroll-behavior: smooth.
   ScrollTrigger measures by setting the scroll position and reading the
   result; smooth scrolling turns each of those into an animation, so it
   measures mid-flight and every pin's start comes back offset by roughly the
   current scroll. Reproduced exactly: refreshing at y=2200 moved the hero pin
   from 0..675 to -829..-154 — a range above the top of the document, which it
   can never re-enter. That is the "scroll down then back up and the whole page
   breaks" bug: any refresh (a lazy iframe finishing, a phone's URL bar) while
   you are down the page permanently killed the pins. With scroll-behavior auto
   the same refresh leaves them at 0..675. */
html { scroll-behavior: auto; }
html, body { overflow-x: clip; }   /* clip keeps sticky/fixed alive; hidden does not */
/* ── one ground, top to bottom ────────────────────────
   The page used to run navy → white → navy → white → navy → green → white:
   six changes of ground down one page, and every one of them a seam that had
   to be individually softened, hemmed or explained. It read as a stack of
   unrelated slides.

   Now a single ramp lives on the body and every section is transparent, so
   there is nothing left to seam. The stops undulate through the brand's own
   blues rather than holding one flat navy — a flat fill this tall reads as
   dead space — and they are placed to land roughly where the sections do, so
   each one still gets its own tone without owning a background.

   The only deliberate break left is the green app band at the very end, which
   is the point at which the page stops explaining and starts inviting. */
html {
  /* iOS enlarges text in landscape unless told not to — it "helpfully" scales
     up body copy on rotation, which breaks every measured line length and
     clamp() on the page. */
  -webkit-text-size-adjust: 100%;
          text-size-adjust: 100%;
}
body {
  font-family: var(--font-ar);
  /* iOS paints a grey box over anything tappable on touch. The buttons have
     their own :active state, so the default just muddies it. */
  -webkit-tap-highlight-color: transparent;
  color: rgba(var(--on),.86);
  /* A solid colour under the ramp. Without it the page has no
     background-color at all — only an image — so anything that fails to paint
     the gradient lands on the browser default, and any tool resolving an
     ancestor background finds transparent and assumes black. */
  background-color: var(--ground-base);
  background-image: var(--ground);
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
}
html[lang="en"] body { font-family: var(--font-en); }
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; background: none; border: 0; cursor: pointer; color: inherit; }

:focus-visible { outline: 3px solid var(--sky); outline-offset: 3px; border-radius: 4px; }

/* ── nav ─────────────────────────────────────────── */
.nav {
  position: fixed; inset-block-start: 0; inset-inline: 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(var(--nav-pad) + var(--safe-t)) calc(clamp(20px, 4vw, 48px) + var(--safe-r))
           var(--nav-pad) calc(clamp(20px, 4vw, 48px) + var(--safe-l));
  /* Fully transparent over the hero — the buttons float on the court. The
     glass only appears once you leave the hero (.is-stuck below), so the top
     of the page reads as one image rather than a bar sitting on a picture. */
  background: transparent;
}
/* 44px is the smallest a finger reliably hits; the brand row was 35px tall. */
.nav-brand { display: flex; align-items: center; gap: 12px; min-height: 44px; }
/* Official horizontal brand lockup (icon + تمرين/TMREEN wordmark). Height-driven
   with width:auto so the original aspect ratio is preserved on every tier. */
.nav-logo { display: block; height: 38px; width: auto; }
/* A reverse logo for the dark ground.
   The official mark is a sky-blue glyph beside a near-black wordmark (#424856).
   That reads on paper and disappears on navy — measured, the wordmark sat at
   almost the same luminance as the ground behind it. A CSS filter cannot fix
   it either: there is no filter that lifts the dark half without also
   flattening the blue half, so this is a second asset rather than a trick.

   content: url() swaps the source of a replaced element without touching the
   markup, so the light ground still requests exactly one image. */
html[data-ground="dark"] .nav-logo {
  content: url("assets/logo-horizontal-light.png");
}
.nav-actions { display: flex; align-items: center; gap: 10px; }
.lang-toggle {
  font-family: var(--font-en); font-weight: 600; font-size: 14px;
  color: var(--on-solid);
  border: 1px solid rgba(var(--on),.35); border-radius: 10px;
  min-width: 44px; min-height: var(--nav-ctl); padding: 0 12px;
  transition: background .2s ease, border-color .2s ease;
}
.lang-toggle:hover { background: rgba(var(--on),.12); border-color: rgba(var(--on),.6); }
.nav-login {
  color: var(--on-solid); font-weight: 500; font-size: 15px;
  min-height: var(--nav-ctl); display: inline-flex; align-items: center;
  padding: 0 16px; border-radius: 10px; background: rgba(var(--on),.12);
  transition: background .2s ease;
}
.nav-login:hover { background: rgba(var(--on),.22); }
/* Affiliate CTA in the nav — grass-tinted so "earn" reads distinct from "log in". */
.nav-affiliate {
  color: var(--on-solid); font-weight: 600; font-size: 15px;
  min-height: var(--nav-ctl); display: inline-flex; align-items: center;
  padding: 0 16px; border-radius: 10px;
  background: rgba(58,189,81,.24);
  background: color-mix(in srgb, var(--green) 24%, transparent);
  border: 1px solid rgba(58,189,81,.55);
  border: 1px solid color-mix(in srgb, var(--green) 55%, transparent);
  transition: background .2s ease;
}
.nav-affiliate:hover { background: rgba(58,189,81,.38); background: color-mix(in srgb, var(--green) 38%, transparent); }
@media (max-width: 560px) { .nav-affiliate { display: none; } }
/* Promo code chip on the affiliate card. */
.aff-code {
  font-family: var(--font-en); font-weight: 800; font-size: 26px; letter-spacing: 3px;
  color: var(--navy);
  background: #e8f8ec;
  background: color-mix(in srgb, var(--green) 16%, #fff);
  border: 1px dashed rgba(58,189,81,.55);
  border: 1px dashed color-mix(in srgb, var(--green) 55%, transparent);
  border-radius: 12px; padding: 12px 8px; text-align: center; margin: 6px 0 10px;
}

/* ── hero ────────────────────────────────────────────
   Three static layers under the pitch, in paint order:
     1. a diagonal brand ramp (echoes the app icon's -30 degrees) instead of
        one flat fill, so the ground has direction
     2. a sky glow top-trailing and a green glow bottom-leading — the accent
        finally appears in the hero rather than only at the app band
     3. two oversized concentric arcs for depth behind the centre circle
   There was a fourth — a repeating chalk grid, also on the app band and the
   session cards. Removed: at three different scales it read as graph paper
   laid over the brand rather than as texture belonging to it.
   All CSS: the GSAP draw-in and pin-scrub are untouched, and these sit behind
   .pitch so nothing competes with the chalk. */
.hero { color: var(--on-solid); }
/* The glows live on .hero-pin, not .hero. ScrollTrigger pins this element and
   grows .hero to 220% of the viewport to make room for the scrub, so a
   percentage position on .hero resolves against ~1980px and drops the glows
   somewhere unintended. .hero-pin stays exactly one viewport tall, which is
   the box these are composed against. */
.hero-pin {
  min-height: 100svh;
  display: grid; place-items: center;
  position: relative; overflow: hidden;
  padding: var(--nav-clear)
           calc(clamp(20px, 5vw, 64px) + var(--safe-r))
           var(--hint-band)
           calc(clamp(20px, 5vw, 64px) + var(--safe-l));
  isolation: isolate;
}
/* The hero's light, on its own layer so it can be masked.
   It used to be a background on .hero-pin itself, sized in pixels and centred
   at 106% — below the box. With overflow:hidden that meant the green was still
   at most of its strength when it reached the bottom edge, so the hero ended
   in a flat horizontal cut with a green wedge sliced off it. A gradient can
   only be clipped invisibly where it has already faded out, and this one had
   not.

   Everything here is sized as a share of the hero rather than in pixels, so
   it holds its composition at any viewport, and the layer is masked out over
   its last quarter — which guarantees no edge no matter what the gradients
   underneath are doing. */
.hero-pin::before {
  content: ""; position: absolute; inset: 0; z-index: -2; pointer-events: none;
  background:
    radial-gradient(66% 56% at 86% 4%, var(--hero-sky), transparent 68%),
    radial-gradient(58% 50% at 6% 88%, var(--hero-green), transparent 68%);
  /* Clear well before the edge, not at it. At 70% the green was still carrying
     most of its strength when the hero handed over, which is what stopped the
     court ever reaching white. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 86%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 86%);
}
/* concentric arcs — a faint echo of the centre circle, oversized */
.hero-pin::after {
  content: ""; position: absolute; z-index: -1;
  inset-inline-start: 50%; inset-block-start: 46%;
  width: min(150vw, 132vmin); aspect-ratio: 1;
  transform: translate(-50%, -50%);
  pointer-events: none; border-radius: 50%;
  border: 2px solid var(--hero-arc);
  box-shadow: 0 0 0 clamp(4vmin, 12vw, 14vmin) var(--hero-arc-halo);
  /* Clipped by the same overflow as the glows, and the halo's spread is wide
     enough that the cut showed as a curved edge across the bottom-left. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 66%, transparent 96%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 66%, transparent 96%);
}
/* The court fades out at the foot of the hero along with the light.
   Masking the glows was not enough on a phone: the portrait pitch's bottom
   touchline lands within a few pixels of the hero's edge and reads as a hard
   rule ruled across the page — a drawn line does not care that the colour
   behind it is blending. Everything decorative in the hero now ends the same
   way, by fading rather than by stopping. */
/* The court starts below the bar, not behind it. It was pinned to inset:0, so
   the drawn touchline landed at 12px on a phone and 66px on a desktop against
   a 69px bar — the top of the pitch was under the nav at both ends, and on a
   phone the whole top penalty area with it. Offset by the bar's own derived
   height plus a little air, so it clears at every width by construction
   rather than by a number that happens to work at one of them. */
.pitch {
  position: absolute;
  /* Sized to the first viewport, not to the hero block. The block is taller
     than a 900px screen once the title, buttons and hint have their room, and
     the court used to stretch to its bottom edge — so on a laptop the far
     touchline sat ~100px below the fold, and with the hero pinned it never
     scrolled into view. The pin holds the block at the top of the screen, so
     the visible court is exactly what fits between the nav and the fold —
     less half the hint band, which keeps the far touchline clear of the
     "scroll down" hint that lives there without shrinking the court much.
     (Taking the whole band off cost the court 14% of its width.) */
  top: var(--nav-clear); inset-inline: 0;
  height: calc(100svh - var(--nav-clear) - var(--hint-band) / 2);
  width: 100%;
  opacity: var(--pitch-op);
  /* Softens the court into the section below without erasing it. The old
     ramp reached transparent at 92% — and the far touchline sits at y=700 of
     a 760 box, which is 92%: the bottom of the pitch was masked out at every
     width, so the side lines ran off into nothing. The line now keeps 60%
     of its strength and the fade continues past it to the section edge. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 74%, rgba(0,0,0,.6) 92%, rgba(0,0,0,.4) 100%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 74%, rgba(0,0,0,.6) 92%, rgba(0,0,0,.4) 100%);
}
.pitch--port { display: none; }
@media (max-width: 767px) {
  /* A wide court on phones, not a portrait one.
     The portrait drawing fits a phone's shape but reads as a narrow strip of
     markings behind the copy. The landscape one, fitted rather than sliced,
     spans the full width and reads as a pitch seen from the stand — and it is
     the same drawing the desktop shows, so the two stop being different
     pictures of the same idea.

     "meet" not "slice": slice scales to COVER and discards the overflow, which
     is what was cutting the bottom third — goal box and all — off the phone.

     The height is measured against the viewport rather than the container,
     because ScrollTrigger pins .hero-pin and rewrites that box out from under
     any percentage depending on it. svh means the same thing before and after
     the pin engages. */
  .pitch--port { display: none; }
  /* The box is given the court's own shape rather than the hero's, so "meet"
     has nothing to letterbox: the drawing fills the full width and the height
     follows from the aspect ratio. Fitting a 1.58-ratio court into a tall
     phone-shaped box instead produced a short strip floating in the middle —
     the whole court, technically, but nothing that read as a pitch.
     Centred on the hero, which puts it clear of the bar at every phone size. */
  .pitch--land {
    display: block;
    inset-block: auto; inset-inline: 0;
    top: 50%; translate: 0 -50%;
    width: 100%; height: auto;
    aspect-ratio: 1200 / 760;
  }
}
.pitch .draw { stroke: var(--chalk); stroke-width: 2.5; }
.hero-inner { position: relative; text-align: center; max-width: 780px; }

.speedlines { width: 132px; margin: 0 auto 26px; overflow: visible; }
.sl { stroke-width: 9; stroke-linecap: round; fill: none; }
.sl-1 { stroke: var(--sky); }
.sl-2 { stroke: var(--blue); }
.sl-3 { stroke: var(--gray-soft); }

.hero-title {
  font-size: clamp(2.4rem, 6.2vw, 4.4rem);
  font-weight: 700; line-height: 1.28; letter-spacing: -0.01em;
  text-wrap: balance;
}
html[lang="en"] .hero-title { font-weight: 800; line-height: 1.12; }
.hero-sub {
  margin-top: 18px; font-size: clamp(1.05rem, 1.8vw, 1.3rem);
  color: rgba(var(--on),.78); max-width: 34em; margin-inline: auto;
}
.hero-ctas { margin-top: 34px; display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 52px; padding: 0 30px; border-radius: 14px;
  font-size: 1.05rem; font-weight: 700;
  transition: transform .18s ease, background .18s ease, box-shadow .18s ease;
}
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; pointer-events: none; }
.nav-login:active, .nav-affiliate:active, .lang-toggle:active { transform: translateY(1px); }
/* ── the two audiences, as two buttons ────────────────
   Green is the players, blue is the owner — the same law the role dots, the
   pager and the showcase lights already follow. The hero now offers one of
   each rather than a primary and a leftover outline, so the split is stated
   before the page starts explaining it.

   Neither uses white on its own brand colour. White on #3abd51 measures about
   2.2:1 and white on the sky about 2.4:1 — both well under the line for a
   button. The green takes near-black green ink instead, which lands near 7:1
   and looks sharper anyway, and the blue steps down to the deeper brand blue
   where white does hold. */
.btn-players {
  background: var(--green); color: #06301a;
  box-shadow: 0 1px 2px rgba(6,48,26,.28), 0 12px 30px -10px rgba(58,189,81,.5);
}
.btn-players:hover { background: #45cd5d; }
.btn-owners {
  background: var(--blue); color: #ffffff;
  box-shadow: 0 1px 2px rgba(10,40,72,.3), 0 12px 30px -10px rgba(34,100,162,.5);
}
.btn-owners:hover { background: #2b76bc; }
.btn-ghost { color: var(--on-solid); border: 1.5px solid rgba(var(--on),.4); }
.btn-ghost:hover { border-color: var(--on-solid); background: rgba(var(--on),.08); }
.btn-light { background: var(--white); color: var(--navy); }
.btn-light:hover { background: #e8f2fa; }

.scroll-hint {
  /* Its own reserved band at the foot of the hero. It used to sit at a fixed
     26px from the bottom while the content above it was centred, so on a short
     phone the two met — measured, a 360x600 viewport had the hero copy ending
     13px BELOW where the cue starts. .hero-pin now reserves this height as
     bottom padding, so the cue can never be reached by the content. */
  position: absolute; inset-block-end: calc(clamp(14px, 2.4svh, 26px) + var(--safe-b)); inset-inline: 0;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  color: rgba(var(--on),.55); font-size: .85rem;
  animation: hint-bob 2.4s ease-in-out infinite;
}
@keyframes hint-bob { 50% { transform: translateY(6px); } }

/* ── shared section chrome ───────────────────────── */
.eyebrow { font-size: .85rem; font-weight: 700; letter-spacing: .06em; color: var(--accent); text-align: center; }
html[dir="rtl"] .eyebrow { letter-spacing: 0; }   /* Arabic is a connected script */
.eyebrow-light { color: var(--accent); }
.section-title {
  font-size: clamp(1.7rem, 3.6vw, 2.5rem); font-weight: 700;
  text-align: center; margin-top: var(--space-xs);
  /* 1.4 is body leading on a 2.5rem display size — it reads loose and
     unfocused. Large type wants tighter leading and slightly negative
     tracking to hold together as a shape. */
  line-height: 1.22; letter-spacing: -.015em; text-wrap: balance;
  max-width: 20ch; margin-inline: auto;
  color: var(--on-solid);
}
.section-title.start { max-width: 18ch; margin-inline: 0; }
.section-title.light { color: var(--on-solid); }
/* start-aligned variants used inside grids */
.story .eyebrow, .owners .eyebrow, .section-title.start { text-align: start; }

/* ── one booking, played out ─────────────────────── */
.story { position: relative; overflow: hidden; }
/* The oversized centre-circle arc that used to sit here is gone. It existed to
   give a flat white section some depth; the section lights do that now, and at
   62vmin its edge ran straight through the eyebrow, reading as a stray rule
   across the copy rather than as depth behind it. */
.story-pin {
  min-height: 100svh; display: grid; align-items: center;
  padding: 80px clamp(20px, 5vw, 64px);
}
.story-grid {
  max-width: var(--col); margin-inline: auto; width: 100%;
  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(36px, 6vw, 88px);
  align-items: center;
}
.story-sub { margin-top: 16px; color: rgba(var(--on),.72); max-width: 30em; }
.story-legend { list-style: none; margin-top: 28px; display: grid; gap: 14px; }
.lg { display: flex; align-items: center; gap: 12px; color: rgba(var(--on),.75); font-size: .98rem; }
.lg-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  background: var(--sky); box-shadow: 0 0 0 4px rgba(51,159,206,.15);
}
.lg-2 .lg-dot { background: var(--blue); box-shadow: 0 0 0 4px rgba(34,98,160,.14); }
.lg-3 .lg-dot { background: var(--navy); box-shadow: 0 0 0 4px rgba(31,64,110,.14); }

.bcard {
  /* Re-asserts dark text. Everything in here — the title, the player names —
     used to inherit --ink from the body and declared no colour of its own.
     The body is a dark surface now, so without this the card's contents turn
     white on a white card. The other light surfaces (.wcol-after, .ocard)
     already set their own colour and were unaffected. */
  background: var(--white); color: var(--ink);
  border: 1px solid #e3e9f2; border-radius: 22px;
  box-shadow: 0 1px 2px rgba(20,40,80,.06), 0 18px 50px -12px rgba(20,40,80,.18);
  padding: 26px 26px 22px; width: 100%;
}
/* The grid item is the wrapper, so the size and placement live on it; the
   card just fills it. See the note in index.html for why it exists. */
.bcard-pin { max-width: 460px; justify-self: center; width: 100%; }
.b-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; }
.b-title { font-weight: 700; font-size: 1.15rem; }
.b-meta { color: var(--ink-2); font-size: .85rem; margin-top: 2px; }
/* Sized by its contents, not by a guess. The three pills stack in one grid
   cell and cross-fade, which lets the container take the height of the
   tallest instead of a hardcoded 34px — the pills need about 38 with their
   padding, so the confirmed one (tick and all) hung 6px out of the top and
   4px out of the bottom of its own box at every width. */
.b-pills { position: relative; display: inline-grid; min-width: 150px; flex: none; justify-items: end; }
.b-pill {
  grid-area: 1 / 1; white-space: nowrap;
  font-size: .78rem; font-weight: 700; border-radius: 999px; padding: 7px 14px;
}
.pill-wait   { background: #e8eef6; color: var(--navy); border: 1px solid #d5deea; }
.pill-review { background: #eaf2fb; color: var(--blue); border: 1px solid #cfe3f5; opacity: 0; }
.pill-ok     { background: var(--green); color: #fff; border: 1px solid var(--green); opacity: 0; }

.b-progressrow { display: flex; align-items: center; gap: 14px; margin-top: 22px; }
.b-count { font-size: .85rem; font-weight: 700; color: var(--ink-2); white-space: nowrap; }
.b-count #paidCount { font-family: var(--font-en); }
.b-progress { display: flex; gap: 6px; flex: 1; }
.seg {
  height: 8px; flex: 1; border-radius: 4px; background: #e8eef6;
  position: relative; overflow: hidden;
}
.seg::after {
  content: ""; position: absolute; inset: 0; border-radius: 4px;
  background: linear-gradient(90deg, var(--blue), var(--green));
  transform: scaleX(0); transform-origin: var(--flow-origin);
  transition: transform .38s cubic-bezier(.3,.7,.3,1);
}
.seg.on::after { transform: scaleX(1); }

.b-players { list-style: none; margin-top: 20px; display: grid; gap: 10px; }
.p-row {
  display: flex; align-items: center; gap: 12px;
  background: var(--paper); border-radius: 14px; padding: 12px 16px;
}
.p-name { font-weight: 600; flex: 1; }
.p-amt { color: var(--ink-2); font-family: var(--font-en); font-weight: 600; font-size: .95rem; }
.p-amt small { font-size: .78em; color: var(--gray); }
.p-tick { width: 26px; height: 26px; flex: none; }
.p-tick circle { fill: none; stroke: #d5deea; stroke-width: 2; }
.p-tick .pt-check { fill: none; stroke: var(--ok); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
.p-row.paid .p-tick circle { stroke: var(--ok); fill: rgba(58,190,81,.08); }

.b-owner {
  margin-top: 18px; display: flex; align-items: center; gap: 8px;
  color: var(--green-deep); font-weight: 700; font-size: .9rem;
  background: var(--green-tint); border: 1px dashed var(--green-line); border-radius: 12px;
  padding: 10px 14px;
}
html.js .b-owner { opacity: 0; transform: translateY(8px); }
/* Resting state for the card's own entrance — same idiom as .b-owner above, so
   the tween is a .to() and nothing depends on immediateRender ordering. */
html.js .bcard { opacity: 0; transform: translateY(46px); }

/* ── escrow ──────────────────────────────────────── */
.escrow {
  padding: clamp(70px, 9vw, 120px) clamp(20px, 5vw, 64px);
}
.escrow-panel {
  max-width: var(--col); margin-inline: auto;
  /* Was solid navy-deep, which worked when this sat on white. On the dark
     ground it would vanish, so it becomes a lifted surface instead: a thin
     white veil plus a hairline, which reads as raised rather than as a hole. */
  background:
    radial-gradient(1200px 400px at 50% -10%, rgba(51,159,206,.16), transparent 60%),
    rgba(var(--veil),.05);
  border: 1px solid rgba(var(--veil),.13);
  border-radius: 28px; color: var(--on-solid);
  padding: clamp(44px, 6vw, 72px) clamp(24px, 5vw, 72px);
}
.escrow-sub { text-align: center; color: rgba(var(--on),.75); max-width: 62ch; margin: var(--space-s) auto 0; line-height: 1.8; }

.es-strip {
  display: flex; align-items: flex-start; gap: 0;
  margin-top: clamp(40px, 6vw, 64px);
}
.es-node { width: 148px; flex: none; text-align: center; }
.es-circle { position: relative; width: 64px; height: 64px; margin: 0 auto; border-radius: 50%; }
.es-ring { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
.es-ring .draw { stroke: var(--sky); stroke-width: 2.5; fill: none; }
/* Step 4 is the moment the money is actually released — the only green ring. */
#esn-4 .es-ring .draw { stroke: var(--green); }
#esn-4 .es-icon { stroke: var(--green); }
#esn-4 .es-label { color: var(--on-solid); }
.es-icon {
  /* currentColor, not #fff. The panel this sits on follows the ground — dark
     veil on the dark ground, solid white on the light one — so a hardcoded
     white icon disappeared entirely the moment the light ground came in, and
     the four steps rendered as empty rings. */
  /* An explicit box, centred with auto margins — not `inset: 16px` alone.
     An absolutely positioned SVG is a replaced element, and with no width or
     height WebKit sizes it to its containing block and then offsets it by the
     inset: on an iPhone every icon rendered at the full 64px, pushed 16px
     down and to the side, hanging out of its ring. Chrome stretches the box
     to the inset instead, which is why the desktop's mobile view looked
     fine. 50% of the circle at every breakpoint replaces the three per-size
     inset overrides that used to live in the media queries. */
  position: absolute; inset: 0; margin: auto; width: 50%; height: 50%;
  fill: none; stroke: currentColor;
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round;
}
html.js .es-icon { opacity: 0; }
.es-label { font-weight: 700; margin-top: 12px; }
.es-sublabel { font-size: .8rem; color: rgba(var(--on),.6); margin-top: 2px; }
html.js .es-label, html.js .es-sublabel { opacity: 0; transform: translateY(8px); }

.es-link { flex: 1; position: relative; height: 3px; margin-top: 31px; min-width: 40px; }
.es-link { background: rgba(var(--on),.16); border-radius: 2px; }
.es-fill {
  position: absolute; inset: 0; border-radius: 2px;
  background: linear-gradient(90deg, var(--blue), var(--sky));
  transform: scaleX(0); transform-origin: var(--flow-origin);
}
.es-coin {
  position: absolute; top: 50%; inset-inline-start: 0;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--sky); box-shadow: 0 0 14px rgba(51,159,206,.9);
  transform: translate(0, -50%); opacity: 0;
}
.es-refund {
  text-align: center; margin-top: clamp(32px, 5vw, 46px);
  color: rgba(var(--on),.85); font-size: .95rem;
  border: 1.5px dashed rgba(var(--on),.3); border-radius: 999px;
  width: fit-content; margin-inline: auto; padding: 10px 22px;
}
html.js .es-refund { opacity: 0; transform: translateY(10px); }

/* ── why Tmreen: the same match, two ways ────────── */
.why {
  /* A shallow internal ramp: a flat fill this large reads as dead space.
     The first stop is lighter than the section's body colour on purpose. A
     white section sits directly above this one, and a dark surface nearest a
     bright one really is brighter — so the edge catches light instead of
     starting the navy flat against the white. Fading white DOWN onto the navy
     was the other option and it leaves a milky band; lifting the navy's own
     top does the same job and stays on the brand ramp. */
  color: var(--on-solid); overflow: hidden;
  padding: clamp(80px, 10vw, 120px) clamp(20px, 5vw, 64px);
}
.why-inner { position: relative; max-width: var(--col); margin-inline: auto; }
.why-sub { text-align: center; color: rgba(var(--on),.75); margin-top: 12px; }

.why-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(20px, 3vw, 28px);
  margin-top: clamp(36px, 5vw, 56px); align-items: start;
}
.wcol { border-radius: 20px; padding: clamp(22px, 2.4vw, 30px); }
/* Today's way is deliberately the recessive panel — same dark ground, barely
   lifted. The Tmreen column is the only lit surface in the section. */
/* Recessive is the right intent, but at .06 fill and .62 text on this navy the
   column stopped being a comparison and became a smudge — you cannot weigh an
   option you cannot read. Lifted just far enough to hold its content; the white
   Tmreen card is still the only lit surface in the section by a wide margin. */
.wcol-before { background: rgba(var(--veil),.085); border: 1px solid rgba(var(--veil),.19); }
.wcol-after { background: var(--white); color: var(--ink); box-shadow: 0 1px 2px rgba(8,22,44,.2), 0 20px 48px -14px rgba(8,22,44,.45); }

.wcol-head { font-size: .85rem; font-weight: 700; letter-spacing: .06em; }
.wcol-before .wcol-head { color: rgba(var(--on),.62); }
.wcol-after .wcol-head { color: var(--green); }

.wlist { list-style: none; margin-top: 14px; }
.wrow { display: flex; align-items: center; gap: 12px; padding: 14px 0; font-size: .98rem; }
.wrow + .wrow { border-top: 1px solid; }
.wcol-before .wrow { color: rgba(var(--on),.76); }
.wcol-before .wrow + .wrow { border-color: rgba(var(--veil),.16); }
.wcol-after .wrow { color: var(--ink); font-weight: 500; }
.wcol-after .wrow + .wrow { border-color: #eef2f7; }

.w-x, .w-tick { width: 26px; height: 26px; flex: none; }
.w-x circle { fill: none; stroke: rgba(var(--on),.30); stroke-width: 2; }
.w-x path { fill: none; stroke: rgba(var(--on),.62); stroke-width: 2.2; stroke-linecap: round; }
.w-tick circle { fill: rgba(58,190,81,.08); stroke: var(--green); stroke-width: 2; }
.w-tick .wt-check { fill: none; stroke: var(--green); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }

html.js .wrow { opacity: 0; transform: translateY(10px); }

/* Phone: the section locks and the swipe swaps the cards (landing.js). Both
   cards sit in one grid cell so Tmreen's card takes the complaints card's
   exact place; the section is a viewport tall and clears the nav so the
   heading and the whole card are on screen for the entire hold. Scoped to
   html.js:not(.static): with no motion the two cards simply stack as before. */
.why-hint { display: none; }
@media (max-width: 767px) {
  html.js:not(.static) .why { min-height: 100svh; padding-top: var(--nav-clear); padding-bottom: 40px; }
  html.js:not(.static) .why-grid { margin-top: 28px; }
  html.js:not(.static) .wcol { grid-area: 1 / 1; }
  html.js:not(.static) .wcol-after { opacity: 0; }
  html.js:not(.static) .why-hint {
    display: block; text-align: center; margin-top: 22px;
    font-size: .88rem; font-weight: 600; color: rgba(var(--on),.55);
  }
}

/* ── the app itself ──────────────────────────────── */
.appband {
  /* Fixed white, NOT var(--on-solid). This band is a saturated green surface
     whatever the page ground is, so its text does not follow the ground the
     way the rest of the page does — tokenising it turned the heading navy on
     green the moment the light ground was switched on. Same for .ab-line. */
  position: relative; overflow: hidden; text-align: center; color: var(--white);
  /* One ramp, running with the page instead of against it.
     This was a 155deg diagonal from brand green down to green-deep — bright at
     the top, dark at the bottom. That produced two visible seams rather than
     none. At the top, .appband::before melts the navy above into this band, but
     a diagonal means the top edge is not a single colour, so the melt only ever
     lined up on one side of the page. At the bottom, the band went dark just as
     .footer::before starts its hem at full brand green, so the tail read
     navy → bright → dark → bright → white.
     Now it descends: green-deep under the navy (which is what .finale::after is
     already fading toward), through to the exact green the footer hem begins
     at. Straight down, so both edges are one colour and both seams close. */
  background: linear-gradient(180deg, var(--green-deep) 0%, #24704a 46%, #2b9c42 100%);
  padding: clamp(72px, 9vw, 104px) clamp(20px, 5vw, 64px);
}
.ab-inner { position: relative; z-index: 1; max-width: 760px; margin-inline: auto; }
.ab-icon {
  /* The white Tmreen mark on the green band — the two-tone glyph is knocked
     out to solid white (same trick as the nav wordmark), with a soft shadow
     so it lifts off the grass. */
  width: 120px; height: auto; margin-inline: auto;
  filter: brightness(0) invert(1) drop-shadow(0 10px 22px rgba(13,51,23,.35));
}
/* Same display-type treatment as .section-title — this one was missed and kept
   body leading at 2.5rem. */
.ab-title { font-size: clamp(1.7rem, 3.6vw, 2.5rem); font-weight: 700; margin-top: 24px; line-height: 1.22; letter-spacing: -.015em; }
.ab-line { color: rgba(255,255,255,.88); margin-top: 6px; }
.appband .btn-light { margin-top: 26px; }

/* ── tonight: live inventory, photo-free ─────────── */
.tonight { padding: clamp(80px, 11vw, 140px) clamp(20px, 5vw, 64px); max-width: var(--col); margin-inline: auto; }
.tonight[hidden] { display: none; }   /* the section is switched off in index.html */
.tonight-sub { text-align: center; color: rgba(var(--on),.72); max-width: 62ch; margin: var(--space-xs) auto 0; line-height: 1.8; }
.tn-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: clamp(36px, 5vw, 56px); }
.tn-card {
  position: relative; display: block; border-radius: 20px; overflow: hidden;
  padding: 26px 24px 22px; min-height: 240px; color: var(--on-solid);
  /* Lifted off the ground rather than filled with it — same reason as the
     escrow panel. */
  background:
    radial-gradient(420px 260px at 80% -20%, rgba(51,159,206,.26), transparent 60%),
    rgba(var(--veil),.055);
  border: 1px solid rgba(var(--veil),.13);
  transition: transform .25s cubic-bezier(.2,.7,.3,1), box-shadow .25s ease;
}
.tn-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 22px 50px rgba(16,35,64,.35), inset 0 0 0 1px rgba(58,190,81,.55);
}
/* Hover does not exist on a phone, so give the tap something of its own. */
.tn-card:active { transform: scale(.985); }
.tn-glyph { width: 116px; margin-bottom: 14px; }
.tn-glyph .draw { stroke: var(--sky); stroke-width: 3; transition: stroke .25s ease; }
.tn-card:hover .tn-glyph .draw { stroke: var(--green); }
.tn-name { font-weight: 700; font-size: 1.3rem; position: relative; }
.tn-hint { color: rgba(var(--on),.65); font-size: .9rem; margin-top: 4px; position: relative; }
.tn-meta { color: rgba(var(--on),.75); font-size: .85rem; margin-top: 3px; position: relative; }
.tn-price {
  position: absolute; inset-block-start: 22px; inset-inline-end: 22px;
  font-family: var(--font-en); font-weight: 800; font-size: 1.05rem; color: var(--accent);
}
.tn-spots {
  display: inline-block; margin-top: 12px; position: relative;
  font-size: .78rem; font-weight: 700; color: var(--navy-deep);
  background: var(--sky); border-radius: 999px; padding: 5px 12px;
}
.tn-arrow {
  position: absolute; inset-block-end: 18px; inset-inline-start: 22px;
  font-size: 1.3rem; color: rgba(var(--on),.8);
  transition: transform .25s ease;
}
html[dir="ltr"] .tn-arrow { transform: scaleX(-1); }
.tn-card:hover .tn-arrow { transform: translateX(-5px); }
html[dir="ltr"] .tn-card:hover .tn-arrow { transform: scaleX(-1) translateX(-5px); }

/* ── owners ──────────────────────────────────────── */
/* ── the dark act ────────────────────────────────────
   Owners, affiliate, showcase and finale are four sections but one movement:
   the page keeps getting deeper until the green field breaks through at the
   app band. Each picks up exactly where the previous one ended, so there is
   no seam between them to halo. */
.owners { color: var(--on-solid); }
/* Owners and Affiliate are the same shape — eyebrow, start-aligned title, lede,
   button, card — stacked back to back, so the second one read as the first one
   scrolling past again. Mirroring the second turns the repeat into a diptych.
   Content, order in the DOM and reading order are all unchanged; only which
   side of the page each half lands on flips.
   Two columns only: below 768px the grid collapses to one, and an order swap
   there would stack the card above the heading it belongs to. */
@media (min-width: 768px) {
  .owners.affiliate-sec .owners-grid { grid-template-columns: 1fr 1.1fr; }
  .owners.affiliate-sec .owners-copy { order: 2; }
  .owners.affiliate-sec .ocard { order: 1; }
}
.owners-grid {
  max-width: var(--col); margin-inline: auto;
  display: grid; grid-template-columns: 1.1fr 1fr; gap: clamp(36px, 6vw, 88px);
  align-items: center;
  padding: clamp(80px, 11vw, 130px) clamp(20px, 5vw, 64px);
}
.owners-sub { margin: 16px 0 28px; color: rgba(var(--on),.75); max-width: 32em; }

.ocard {
  background: var(--white); color: var(--ink);
  border-radius: 22px; padding: 24px;
  box-shadow: 0 1px 2px rgba(0,0,0,.22), 0 26px 60px -16px rgba(0,0,0,.5);
  max-width: 400px; justify-self: center; width: 100%;
}
.o-top { display: flex; justify-content: space-between; align-items: center; }
.o-badge {
  background: var(--green); color: #fff; border: 1px solid var(--green);
  font-size: .78rem; font-weight: 700; border-radius: 999px; padding: 6px 13px;
}
.o-when { color: var(--gray); font-size: .8rem; }
.o-title { font-weight: 700; font-size: 1.1rem; margin-top: 16px; }
.o-meta { color: var(--ink-2); font-size: .88rem; margin-top: 3px; }
.o-amount {
  font-family: var(--font-en); font-weight: 800; font-size: 2rem; color: var(--navy);
  margin-top: 10px; display: flex; align-items: baseline; gap: 6px;
}
.o-amount small { font-size: .9rem; color: var(--gray); font-weight: 600; }
.o-actions { display: flex; gap: 12px; margin-top: 18px; }
.o-approve, .o-decline {
  flex: 1; text-align: center; border-radius: 12px; padding: 12px 0; font-weight: 700; font-size: .95rem;
}
.o-approve { background: var(--green); color: #fff; }
.o-decline { border: 1.5px solid #e3e9f2; color: var(--ink-2); }

/* ── the app, from the inside: two swiping phones ──── */
.showcase {
  position: relative; isolation: isolate;
  color: var(--on-solid); overflow: hidden;
  padding: clamp(80px, 10vw, 120px) clamp(20px, 5vw, 64px);
}

/* ── two audiences, two lights ────────────────────────
   Green on the players' side, sky on the owner's, meeting in the middle.

   ONE full-bleed layer carrying both gradients, not a box per side. The box
   version banded badly: each pseudo was 68% wide and its gradient was still at
   most of its strength when it hit the box edge, so the edge itself became a
   hard vertical line and the section read as three flat panels rather than one
   wash. A gradient can only be clipped invisibly where it has already reached
   transparent — easier to give it the whole section and let it fade out on its
   own terms.

   Direction is handled by swapping the two stop positions rather than by
   logical properties, because `at 78%` inside a background resolves against
   physical coordinates and would not mirror with the columns. Players sit in
   the first grid column: the right in RTL, the left in LTR.

   Deep and saturated rather than pale. A bright colour laid thinly over navy
   lightens the ground instead of tinting it — the result is fog, a washed
   steel-blue and a grey-green that read as haze rather than as light. Deep
   azure and deep grass at higher alpha shift the hue while leaving the ground
   as dark as it was, which is both richer and closer to what a floodlit pitch
   at night actually looks like. Brand sky and brand green are still what every
   element on top uses — the dots, the pager, the chalk; these two are the
   light falling on the ground, not the brand colours themselves.

   The green's own blue channel is what decides whether this reads as green or
   as teal. Grass green #3abd51 carries B=81, almost the same as the navy's 89,
   so adding it raises G without ever lowering B and the side lands around hue
   186 — cyan. The wash green is mixed down to B=48, which pulls the players'
   flank to roughly 150 and it finally reads as grass. */
.showcase::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(74% 66% at 78% 46%, var(--glow-players), transparent 72%),
    radial-gradient(74% 66% at 22% 46%, var(--glow-owners), transparent 72%);
}
html[dir="ltr"] .showcase::before {
  background:
    radial-gradient(74% 66% at 22% 46%, var(--glow-players), transparent 72%),
    radial-gradient(74% 66% at 78% 46%, var(--glow-owners), transparent 72%);
}
/* Columns stack on a phone, so the merge turns vertical with them. Players are
   first in the DOM, so green is on top in both directions. */
@media (max-width: 767px) {
  .showcase::before,
  html[dir="ltr"] .showcase::before {
    background:
      radial-gradient(88% 34% at 50% 20%, var(--glow-players), transparent 74%),
      radial-gradient(88% 34% at 50% 80%, var(--glow-owners), transparent 74%);
  }
}
.sc-inner { max-width: var(--col); margin-inline: auto; }
.sc-sub { text-align: center; color: rgba(var(--on),.75); max-width: 42em; margin: 12px auto 0; }
.sc-grid {
  display: grid; grid-template-columns: auto auto; gap: clamp(28px, 5vw, 72px);
  margin-top: clamp(44px, 6vw, 64px); align-items: start; justify-items: center;
}
.device { display: flex; flex-direction: column; align-items: center; width: 100%; }
.dev-role { display: inline-flex; align-items: center; gap: 9px; font-weight: 700; font-size: 1.05rem; margin-bottom: 18px; }
.dev-role .rd { width: 9px; height: 9px; border-radius: 50%; flex: none; }
/* Green is the players, sky is the owner — the same law the rest of the
   brand runs on. These two were the other way round. */
.device--player .dev-role .rd { background: var(--green); box-shadow: 0 0 0 4px rgba(58,190,81,.2); }
.device--owner .dev-role .rd  { background: var(--sky); box-shadow: 0 0 0 4px rgba(51,157,205,.2); }

/* ── the devices ──────────────────────────────────────
   Two different machines, because the two audiences genuinely use different
   ones: a player books from a phone in the car park, an owner works the day's
   schedule from a tablet on the counter. Sizes are per-device custom
   properties so the bezel, radius and padding all follow from one place. */
/* --app-w/--app-h are the LOGICAL viewport the embedded app renders at, not
   the size it appears on screen. The frame is laid out at a real device
   viewport (a 390x845 phone, an 834x1120 tablet) and then scaled down to fit
   the bezel, so the app makes its own layout decisions exactly as it would on
   that hardware. Rendering it directly at the bezel's ~274px instead made
   every control look enormous and sliced the page off at the bottom. */
.device--player { --dev-w: 300px; --dev-ar: 300 / 620; --dev-r: 44px; --dev-pad: 13px;
                  --app-w: 390px; --app-h: 845px; }
.device--owner  { --dev-w: 470px; --dev-ar: 470 / 620; --dev-r: 26px; --dev-pad: 16px;
                  --app-w: 834px; --app-h: 1120px; }

.phone {
  position: relative;
  width: var(--dev-w, 300px); max-width: 84vw;
  aspect-ratio: var(--dev-ar, 300 / 620);
  background: var(--navy-deep);
  border-radius: var(--dev-r, 44px); padding: var(--dev-pad, 13px);
  border: 1px solid rgba(255,255,255,.09);
  box-shadow: 0 2px 4px rgba(0,0,0,.3), 0 30px 70px -18px rgba(0,0,0,.6), inset 0 0 0 1.5px rgba(255,255,255,.06);
}
/* No notch. A cut-out is a lie here: the embedded app has no safe-area inset
   to sit under, so an opaque blob simply covered its header — the most useful
   part of every screen we show. A clean bezel reads as hardware without
   eating content. */

.screens {
  position: relative; width: 100%; height: 100%;
  border-radius: calc(var(--dev-r, 44px) - var(--dev-pad, 13px) + 2px);
  overflow: hidden; background: var(--paper);
  /* iOS does not apply overflow:hidden + border-radius to composited
     children, and an iframe is always composited there — so on an iPhone the
     app's screen ran square past the rounded corners and, when iOS grew the
     iframe to its content, straight over the bottom of the bezel. clip-path
     is applied in the compositor and clips everything. */
  clip-path: inset(0 round calc(var(--dev-r, 44px) - var(--dev-pad, 13px) + 2px));
  isolation: isolate;
}
.screen { position: absolute; inset: 0; overflow: hidden; }

/* ── live app embed ───────────────────────────────────
   The devices show the REAL app rather than screenshots, so the marketing page
   can never drift from the product: ship a change to a screen and the landing
   page shows it on the next load.

   One app instance per device, not one per screen. The showcase cycles through
   several screens, and booting the bundle once per screen would load it six
   times on a marketing page — while swapping iframe.src would reload it on
   every step, flashing white and replaying the splash. Instead the page asks
   the running instance to navigate (see DemoBridge), which is instant and
   keeps the app's own transitions. */
/* The scaler owns `transform`, the frame owns motion. Keeping them on
   separate elements matters: GSAP rewrites the whole transform property when
   it animates, so an element carrying both a CSS scale and a tween loses the
   scale on the first frame — and again whenever --app-scale is re-measured. */
.scr-scaler {
  position: absolute; top: 0; left: 0;
  width: var(--app-w, 390px); height: var(--app-h, 845px);
  /* --app-scale is measured in JS from the real bezel width, so this stays
     correct when the device shrinks on a narrow screen. */
  transform: scale(var(--app-scale, 1));
  transform-origin: 0 0;
  /* Hard clip at the logical viewport. iOS may still grow the iframe to its
     content height however it is sized; whatever it grows to is cut here,
     so the bezel below the screen can never be covered. */
  overflow: hidden; clip-path: inset(0);
}
.scr-frame {
  /* Explicit pixels, not percentages. iOS Safari ignores width/height on an
     iframe and sizes it to its CONTENT — so a percentage resolves against an
     already-wrong box and the embedded app renders at some arbitrary size
     inside the bezel. Pinning it to the same logical viewport .scr-scaler
     uses gives iOS a number it will honour.
     min-* as well: iOS respects those even when it overrides the others. */
  width: var(--app-w, 390px); height: var(--app-h, 845px);
  min-width: var(--app-w, 390px); min-height: var(--app-h, 845px);
  max-width: none;
  border: 0; display: block; background: var(--paper);
  pointer-events: none;   /* the device is a picture, not a control */
  opacity: 0;
}
html.sc-embed .scr-frame { opacity: 1; }

/* Screenshot fallback: shown until the app reports it has actually MOUNTED —
   not merely that the iframe fired `load`, which happens while the splash is
   still on screen and used to expose it. Also covers no-JS and a failed build,
   so the bezel is never empty. */
.scr-img { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }
.scr-shot {
  position: absolute; inset: 0; z-index: 2;
  transition: opacity .45s ease;
}
html.sc-embed .scr-shot { opacity: 0; pointer-events: none; }

/* A quiet brand-blue veil while the embed boots, so the fallback image does
   not look like a frozen app. */
.scr-shot::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(31,64,110,.10), rgba(22,48,79,.28));
}

/* captions + progress dots under each phone */
.dev-caps { position: relative; margin-top: 22px; width: 100%; max-width: 300px; min-height: 3.2em; }
.dev-cap { position: absolute; inset-inline: 0; inset-block-start: 0; text-align: center; font-size: .95rem; color: rgba(var(--on),.82); opacity: 0; }
.dev-cap.is-on { opacity: 1; }
.dev-cap:not(:first-child) { display: none; }
html.sc-live .dev-cap { display: block; transition: opacity .3s ease; }
.dev-dots { display: flex; gap: 6px; margin-top: 16px; }
.dev-dots span { width: 7px; height: 7px; border-radius: 999px; background: rgba(var(--on),.25); transition: width .3s ease, background .3s ease; }
.device--player .dev-dots span.is-on { width: 20px; background: var(--green); }
.device--owner .dev-dots span.is-on  { width: 20px; background: var(--sky); }

@media (max-width: 767px) {
  .sc-grid { grid-template-columns: 1fr; gap: clamp(48px, 12vw, 64px); }
}

/* ── finale ──────────────────────────────────────── */
.finale {
  position: relative; overflow: hidden; text-align: center;
  color: var(--on-solid);
  padding: clamp(90px, 12vw, 150px) clamp(20px, 5vw, 64px);
}
.finale::after {
  content: ""; position: absolute; inset-inline: 0; inset-block-end: 0;
  height: clamp(140px, 22vh, 220px); z-index: -1; pointer-events: none;
  background: linear-gradient(to bottom, rgba(58,189,81,0) 0%, rgba(24,56,46,.55) 100%);
}
.finale-pitch { position: absolute; inset: 0; width: 100%; height: 100%; opacity: .22; }
.finale-pitch .draw { stroke: var(--chalk); stroke-width: 2.5; }
.finale-inner { position: relative; }
/* The largest type on the page after the hero, and it was inheriting 1.75. */
.finale-title { font-size: clamp(2rem, 4.6vw, 3.2rem); font-weight: 700; line-height: 1.14; letter-spacing: -.02em; }
.finale-sub { color: rgba(var(--on),.75); margin: 12px auto 28px; max-width: 34em; }

/* ── footer ──────────────────────────────────────── */
.footer {
  /* position:relative is load-bearing: .footer::before is the grass hem, and
     without a positioned ancestor it resolves against the initial containing
     block and paints its green band at the TOP OF THE PAGE instead. */
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 56px calc(20px + var(--safe-r)) calc(46px + var(--safe-b)) calc(20px + var(--safe-l));
}
.footer-line { color: rgba(var(--on),.72); font-size: .95rem; }
.footer-legal { color: rgba(var(--on),.45); font-size: .85rem; }
/* The footer links measured 15px tall — a line of text with no box around it,
   well under a usable tap target. Padding gives them a real one without
   changing how the row reads. */
.footer-legal a { display: inline-flex; align-items: center; min-height: 44px; padding-inline: 2px; }
.footer-legal a:hover { color: var(--accent); }

/* ── section lights ───────────────────────────────────
   The page ground is one continuous ramp, which removed every hard seam but
   also left the middle of the page tonally flat. These put the brand's two
   colours back into it as light: one tint per corner, coloured by whose part
   of the story the section tells — green where it is about players, sky where
   it is about the owner.

   Two things make them safe to add anywhere. Everything is a share of the
   section (percentages, vmin) rather than a pixel size, so a light keeps its
   composition from a phone to a wide desktop instead of being a fixed blob
   that swamps the small end. And each layer is masked to nothing at its own
   top and bottom edge, so a light can never be caught mid-strength by a
   section boundary — which is exactly what was cutting a flat green edge
   across the bottom of the hero.

   Positions are set per section below via --lp1 / --lp2; the defaults put one
   light high-trailing and one low-leading. */
.story, .escrow, .why, .tonight, .owners, .finale { position: relative; isolation: isolate; }
.story::after, .escrow::after, .why::after,
.tonight::after, .owners::after, .finale::before {
  content: ""; position: absolute; inset-block: 0; z-index: -1; pointer-events: none;
  inset-inline: 0;
  background:
    radial-gradient(62% 56% at var(--lp1, 86% 14%), var(--l1, transparent), transparent 70%),
    radial-gradient(58% 52% at var(--lp2, 10% 86%), var(--l2, transparent), transparent 70%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
}
/* .tonight is max-width capped, so its own box stops well short of the page
   edges. A centred 100vw child escapes the cap without touching the layout;
   html/body already carry overflow-x:clip so it cannot open a scrollbar. */
.tonight::after {
  inset-inline: auto; left: 50%; width: 100vw; translate: -50% 0;
}

/* Whose story each section is telling. */
.story   { --l1: var(--lt-green); --l2: var(--lt-sky);   }
.escrow  { --l1: var(--lt-sky);   --l2: var(--lt-green); --lp1: 14% 12%; --lp2: 88% 88%; }
.why     { --l1: var(--lt-sky);   --l2: var(--lt-sky);   --lp1: 90% 10%; --lp2: 8% 90%; }
.tonight { --l1: var(--lt-green); --l2: var(--lt-green); --lp1: 84% 12%; --lp2: 14% 88%; }
.owners  { --l1: var(--lt-sky);   --l2: var(--lt-sky);   }
.owners.affiliate-sec { --l1: var(--lt-green); --l2: var(--lt-sky); }
.finale  { --l1: var(--lt-green); --l2: var(--lt-sky);   --lp1: 20% 20%; --lp2: 80% 80%; }

/* ── the one remaining boundary ─────────────────────
   Nine of these used to exist, one per change of ground, each hemmed or
   ramped or explained. With a single ramp on the body there is only one real
   boundary left on the page: navy into the green app band, and green back out
   into the footer. Everything else is now the same surface either side, so
   there is nothing to cross-fade.

   z-index:-1 paints above the section's own background but under its content,
   which needs a stacking context — without `isolation: isolate` the negative-z
   child escapes to the root and paints behind the background instead of on
   top of it. */
.appband, .footer { isolation: isolate; }
.appband::before, .footer::before {
  content: ""; position: absolute; inset-inline: 0;
  pointer-events: none; z-index: -1;
}
/* the page's dark end melts into the grass */
.appband::before { top: 0; height: 150px; background: linear-gradient(to bottom,
  rgb(var(--hem-in)) 0%, rgba(var(--hem-in),.5) 34%, rgba(var(--hem-in),0) 100%); }
/* and the grass settles back out of it. Lighter than it was: the footer is no
   longer white, so the hem has far less distance to travel. */
.footer::before { top: 0; height: 120px; background: linear-gradient(to bottom,
  rgba(43,156,66,.72) 0%, rgba(43,156,66,.26) 42%, rgba(43,156,66,0) 100%); }

/* ── live signal on the inventory eyebrow ─────────── */
.tn-eyebrow { display: flex; align-items: center; justify-content: center; gap: 8px; color: var(--green); }
.live-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--green);
  flex: none; position: relative;
}
.live-dot::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  animation: live-pulse 2s ease-out infinite;
}
@keyframes live-pulse {
  0%   { box-shadow: 0 0 0 3px rgba(58,190,81,.45); }
  70%  { box-shadow: 0 0 0 13px rgba(58,190,81,0); }
  100% { box-shadow: 0 0 0 13px rgba(58,190,81,0); }
}

/* ── nav condenses once the hero starts leaving ─────
   Glass, not a panel. This was navy-deep at 96% — opaque for all practical
   purposes, so the blur behind it never showed and the bar read as a solid
   slab dropped on the page. Three things make it read as glass instead:
   real translucency, a saturation boost so the colour behind comes through
   richer rather than washed grey, and a lit top edge with a soft shadow under
   it so the pane has thickness.

   72% is the floor, not a taste call. The page runs white sections under this
   bar, and the controls on it are white. At 72% over white the bar resolves
   to ~#4E637B, which holds white text at about 5.3:1 — above the 4.5:1 line.
   Going lighter looks better over the navy and quietly fails over the story,
   escrow and tonight sections. */
.nav {
  transition: padding .28s ease, background .28s ease,
              backdrop-filter .28s ease, box-shadow .28s ease, border-color .28s ease;
  border-block-end: 1px solid transparent;
}
.nav.is-stuck {
  padding-block: 8px;
  background: var(--nav-glass);
  -webkit-backdrop-filter: saturate(175%) blur(18px);
  backdrop-filter: saturate(175%) blur(18px);
  border-block-end-color: var(--nav-edge);
  box-shadow: var(--nav-shadow);
}
/* Safari and Firefox without backdrop-filter would get a 72% wash with nothing
   behind it doing any work, which is just a weak panel. Give them the opaque
   bar the design started from. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav.is-stuck { background: rgba(22,48,79,.94); background: color-mix(in srgb, var(--navy-deep) 94%, transparent); }
}
.nav.is-stuck .nav-brand img { transform: scale(.92); transition: transform .28s ease; }

/* ── reveals: hidden ONLY when JS is running ─────── */
html.js .reveal { opacity: 0; transform: translateY(26px); }
html.js .reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity .5s ease, transform .55s cubic-bezier(.2,.7,.3,1);
}

/* ── responsive ─────────────────────────────────────
   Three tiers, aligned with landing.js:
     phone   <600      compact layouts, on-enter animation, vertical escrow rail
     tablet  600-1023  desktop layouts, NO pinning
     desktop >=1024    pinned hero + story, full scrub            */

/* tablet and down: no pinned sections, so no 100svh reservations */
/* The story pins at every width now, so its pin box has to be a viewport tall
   here as well — it was collapsed to min-height:0 back when phones scrolled
   straight past it instead of holding it. */
@media (max-width: 1023px) {
  .story-pin { min-height: 100svh; padding-block: clamp(56px, 9svh, 88px) clamp(40px, 6svh, 64px); }
}
/* Stacked (≤767): landing.js pins the card itself here rather than the
   section, so the pin box no longer needs to be a viewport tall — a 100svh
   reservation would just leave a blank band under the card once it releases.
   Natural height, and the grid stops vertically centring so the copy sits
   directly above the card instead of floating mid-box. */
@media (max-width: 767px) {
  .story-pin { min-height: 0; align-items: start; padding-block: clamp(48px, 8svh, 72px) clamp(36px, 6svh, 56px); }
}

/* story/why/owners grids stack below 768 (2-col at 600-767 is too tight) */
/* ── the escrow strip on a phone ──────────────────────
   It had no mobile rule at all: four nodes at a fixed 148px with flex:none,
   plus three connectors, is 592px of unshrinkable content on a 390px screen.
   It overflowed the panel and the steps read as stray dots.

   Stacked here, with the connectors hidden. The connectors animate a
   horizontal scaleX fill, which does nothing legible on a vertical layout, so
   the sequence is carried by the rings lighting in turn — which is the part
   that actually tells the story. There used to be a static grey rail drawn
   behind the circles in their place; it read as a stray line through the
   icons and was removed on request. */
@media (max-width: 767px) {
  .es-strip {
    position: relative; flex-direction: column; align-items: stretch;
    gap: 20px; max-width: none; margin-inline: 0;
  }
  /* Every cell placed explicitly. Without this the three children auto-flow
     and the sublabel lands in the 56px icon column — which is why it was
     setting one word per line down a sliver of a column. Auto-placement is
     never safe on a grid whose first track is an icon width. */
  .es-node {
    width: auto; display: grid;
    grid-template-columns: minmax(56px, auto) minmax(0, 1fr);
    grid-template-rows: auto auto;
    column-gap: 16px; row-gap: 2px;
    align-items: center; text-align: start;
  }
  .es-circle { grid-column: 1; grid-row: 1 / 3; width: 56px; height: 56px; margin: 0; }
  .es-label    { grid-column: 2; grid-row: 1; align-self: end;   margin-top: 0; }
  .es-sublabel { grid-column: 2; grid-row: 2; align-self: start; margin-top: 0; text-wrap: pretty; }
  .escrow-panel { padding: clamp(28px, 7vw, 44px) clamp(18px, 5vw, 28px); }
  .es-link { display: none; }
  /* The travelling coin belongs to the horizontal connectors, which are gone
     on this layout — left alone it parked itself beside the rail as a stray
     grey dot. */
  .es-coin { display: none; }
}

@media (max-width: 767px) {
  .story-grid, .owners-grid, .why-grid { grid-template-columns: 1fr; }
  .tn-grid { grid-template-columns: 1fr; }
  .bcard-pin, .bcard, .ocard { justify-self: stretch; max-width: none; }
}

/* ── phone ── */
@media (max-width: 599px) {
  .nav-logo { height: 30px; }
  .hero-pin { min-height: 92svh; padding-top: 84px; }
  .hero-ctas .btn { width: 100%; }
  .section-title { font-size: 1.55rem; }
  .story-legend { gap: 10px; }
  .bcard { padding: 20px 18px 16px; }


  /* Escrow sizing only. The layout itself is set once in the 767px block
     above — there used to be a second, older set of rules here that restyled
     the same six selectors with different values, so below 600px the strip
     rendered as a hybrid of the two: 44px circles from this block inside the
     grid from that one, with the connectors hidden by one and sized by the
     other. One layout, one place; this just tightens it for narrow screens. */
  .es-strip { gap: 16px; margin-top: 30px; max-width: none; }
  .es-node { grid-template-columns: 48px 1fr; column-gap: 14px; }
  .es-circle { width: 48px; height: 48px; }
  .es-refund { font-size: .85rem; padding: 9px 16px; }
}

/* tablet keeps the horizontal escrow strip but tightens the nodes */
@media (min-width: 600px) and (max-width: 1023px) {
  .es-node { width: 118px; }
  .es-sublabel { display: none; }
}

/* ── reduced motion: everything readable, nothing moves ── */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .scroll-hint { animation: none; }
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ── static fallback: no motion, or no GSAP ──────────
   Every hidden-until-animated state above is gated on html.js, so the only
   thing that can reveal them is script — which means the media query alone was
   never enough. It covered prefers-reduced-motion but not the other branch that
   takes the same early return: GSAP failing to load. In that case the page kept
   the .js class, kept every opacity:0, and simply never ran the timelines, so
   the escrow labels, the owner-approval row and the whole comparison list
   stayed invisible with no way back. The script now sets html.static for both
   branches and the resets hang off that. */
html.static .reveal { opacity: 1; transform: none; transition: none; }
html.static .pitch .draw, html.static .es-ring .draw,
html.static .tn-glyph .draw, html.static .finale-pitch .draw { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
html.static .es-icon, html.static .es-label, html.static .es-sublabel,
html.static .es-refund, html.static .b-owner, html.static .wrow,
html.static .bcard { opacity: 1 !important; transform: none !important; }
html.static .es-fill, html.static .seg::after { transform: scaleX(1) !important; }
html.static .pill-wait { opacity: 0 !important; }
html.static .pill-ok { opacity: 1 !important; }
html.static .p-row .p-tick circle { stroke: var(--ok); }

/* ═══════════════════════════════════════════════════════════════════════
   PREMIUM FOOTER  (redesign)
   A self-contained deep-navy surface — like .appband it carries its own
   ground, so it reads the same whichever page ground it stands on. All colour
   is stated against the navy directly (not the --on tokens). RTL throughout
   via logical properties; nothing here assumes a physical side.
   ═══════════════════════════════════════════════════════════════════════ */
.footer {
  position: relative;
  display: block;                 /* was a centred flex column */
  overflow: hidden;               /* clips the wordmark watermark */
  isolation: isolate;
  background:
    radial-gradient(120% 90% at 82% -10%, rgba(51,157,205,.12), transparent 60%),
    linear-gradient(180deg, #1c3c66 0%, var(--navy) 34%, var(--navy-deep) 100%);
  color: #fff;
  padding-block: clamp(48px, 6vw, 84px) calc(28px + var(--safe-b, 0px));
  padding-inline: calc(clamp(20px, 5vw, 56px) + var(--safe-r, 0px))
                  calc(clamp(20px, 5vw, 56px) + var(--safe-l, 0px));
}
/* the grass hem from the app band settles into the navy */
.footer::before {
  top: 0; height: 110px; z-index: 0;
  background: linear-gradient(to bottom,
    rgba(43,156,66,.55) 0%, rgba(43,156,66,.18) 46%, rgba(43,156,66,0) 100%);
}

/* ── decorative layer: wordmark watermark + one soft green light ── */
.foot-decor { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.foot-decor::after {
  content: ""; position: absolute; inset-block-end: -20%; inset-inline-start: -10%;
  width: 60%; height: 80%;
  background: radial-gradient(closest-side, rgba(58,189,81,.14), transparent 72%);
}
.foot-watermark {
  position: absolute; inset-block-end: -.28em; inset-inline: 0;
  text-align: center; font-family: var(--font-ar); font-weight: 700;
  font-size: clamp(150px, 34vw, 460px); line-height: .8; letter-spacing: -.03em;
  color: rgba(255,255,255,.035); user-select: none; white-space: nowrap;
}

.foot-shell { position: relative; z-index: 1; max-width: var(--col); margin-inline: auto; }

/* ── CTA (quiet, integrated — the finale above already shouts) ── */
.foot-cta {
  display: flex; align-items: center; justify-content: space-between; gap: clamp(20px, 4vw, 44px);
  padding: clamp(24px, 3.4vw, 38px) clamp(22px, 3.6vw, 40px);
  border-radius: var(--radius);
  border: 1px solid rgba(255,255,255,.10);
  background:
    radial-gradient(90% 140% at 88% 0%, rgba(51,157,205,.16), transparent 60%),
    linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 24px 60px -34px rgba(0,0,0,.6);
}
.foot-cta-title { font-size: clamp(1.4rem, 2.7vw, 2rem); font-weight: 700; line-height: 1.16; letter-spacing: -.02em; }
.foot-cta-sub { margin-block-start: 8px; color: rgba(255,255,255,.68); font-size: clamp(.95rem, 1.4vw, 1.05rem); line-height: 1.7; }
.foot-cta-btn { flex: none; box-shadow: 0 1px 2px rgba(6,48,26,.3), 0 16px 34px -12px rgba(58,189,81,.6); }
.foot-cta-btn:hover { box-shadow: 0 1px 2px rgba(6,48,26,.3), 0 20px 42px -12px rgba(58,189,81,.75); transform: translateY(-2px); }

/* ── dividers ── */
.foot-rule { height: 1px; border: 0; margin-block: clamp(32px, 4.4vw, 52px);
  background: linear-gradient(to left, transparent, rgba(255,255,255,.16), transparent); }
.foot-rule--soft { margin-block: clamp(24px, 3vw, 34px);
  background: linear-gradient(to left, transparent, rgba(255,255,255,.09), transparent); }

/* ── main grid: brand + four columns ── */
.foot-main { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 3fr); gap: clamp(36px, 5vw, 72px); align-items: start; }

.foot-brand-logo { display: inline-flex; }
.foot-brand-logo img { height: clamp(40px, 4vw, 50px); width: auto; }
.foot-brand-desc { margin-block: clamp(16px, 2vw, 22px); color: rgba(255,255,255,.64); font-size: .98rem; line-height: 1.85; max-width: 40ch; }

.foot-social { display: flex; gap: 10px; list-style: none; padding: 0; margin: 0; }
.foot-soc {
  display: grid; place-items: center; width: 42px; height: 42px; border-radius: 13px;
  color: rgba(255,255,255,.72); background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.12);
  transition: color .18s ease, background .18s ease, border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.foot-soc:hover {
  color: #fff; background: rgba(51,157,205,.22); border-color: rgba(51,157,205,.6);
  transform: translateY(-3px); box-shadow: 0 10px 22px -10px rgba(51,157,205,.7);
}

/* the four link columns */
.foot-cols { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: clamp(24px, 3vw, 40px) clamp(18px, 2.4vw, 32px); }
.foot-col-t {
  position: relative; font-size: .82rem; font-weight: 700; letter-spacing: .02em;
  color: rgba(255,255,255,.94); margin-block-end: 16px; padding-inline-start: 12px;
}
.foot-col-t::before { content: ""; position: absolute; inset-inline-start: 0; top: 50%;
  transform: translateY(-50%); width: 4px; height: 14px; border-radius: 3px;
  background: linear-gradient(var(--green), var(--sky)); }
.foot-col ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 2px; }
.foot-col a {
  position: relative; display: inline-flex; align-items: center; min-height: 40px;
  color: rgba(255,255,255,.6); font-size: .95rem;
  transition: color .18s ease, padding .2s ease;
}
.foot-col a::before {
  content: ""; position: absolute; inset-inline-start: 0; top: 50%; transform: translateY(-50%);
  width: 0; height: 2px; border-radius: 2px; background: var(--green); transition: width .2s ease;
}
.foot-col a:hover, .foot-col a:focus-visible { color: #fff; padding-inline-start: 14px; }
.foot-col a:hover::before, .foot-col a:focus-visible::before { width: 8px; }

/* ── trust strip: what the platform is, no numbers ── */
.foot-trust { display: flex; align-items: center; flex-wrap: wrap; gap: 14px 22px; }
.foot-trust-t { font-weight: 700; font-size: 1rem; color: #fff; }
.foot-trust-list { display: flex; flex-wrap: wrap; gap: 10px 12px; list-style: none; padding: 0; margin: 0; }
.foot-trust-list li {
  display: inline-flex; align-items: center; gap: 9px; padding: 9px 15px 9px 13px;
  border-radius: 999px; background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.1);
  color: rgba(255,255,255,.82); font-size: .9rem; font-weight: 500;
  transition: background .18s ease, border-color .18s ease;
}
.foot-trust-list li:hover { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.18); }
.foot-trust-ic { display: grid; place-items: center; width: 26px; height: 26px; border-radius: 8px;
  color: var(--green); background: rgba(58,189,81,.14); }
.foot-trust-list li:nth-child(2) .foot-trust-ic { color: var(--sky); background: rgba(51,157,205,.16); }

/* ── bottom bar ── */
.foot-bottom { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px 24px; }
.foot-copy { color: rgba(255,255,255,.5); font-size: .85rem; }
.foot-made { color: rgba(255,255,255,.5); font-size: .85rem; display: inline-flex; align-items: center; gap: 7px; }
.foot-flag { font-size: 1rem; line-height: 1; }

/* focus visibility (keyboard) */
.footer a:focus-visible { outline: 2px solid var(--sky); outline-offset: 3px; border-radius: 6px; }
.foot-soc:focus-visible { outline: 2px solid var(--sky); outline-offset: 3px; }

/* ── responsive ── */
@media (max-width: 1023px) {
  .foot-main { grid-template-columns: 1fr; gap: clamp(32px, 5vw, 48px); }
  .foot-brand-desc { max-width: 52ch; }
}
@media (max-width: 767px) {
  .foot-cta { flex-direction: column; align-items: flex-start; text-align: start; }
  .foot-cta-btn { width: 100%; }
  .foot-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foot-trust { flex-direction: column; align-items: flex-start; }
  .foot-bottom { flex-direction: column; align-items: flex-start; }
}
@media (max-width: 340px) {
  .foot-cols { grid-template-columns: 1fr; }
}

/* ── motion off ── */
@media (prefers-reduced-motion: reduce) {
  .foot-cta-btn, .foot-cta-btn:hover, .foot-soc, .foot-soc:hover,
  .foot-col a, .foot-col a::before, .foot-trust-list li { transition: none; transform: none; }
}
