/*
 * home-hero.css — the daylight mountain parallax. Scope: body.apex-home.
 * This hero is the user's, to be improved and never replaced.
 *
 * Pass 7, 2026-08-29. Pass 6 snapshotted to _archive/home-pass7-2026-08-29/.
 *
 * ★ FOUR CUT-OUTS OF ONE PHOTOGRAPH
 *
 * 2.4 far peak, 2.3 mid range, 2.2 the two valley walls with the V between
 * them, 2.1 the snowy foreground rocks. Stacked at bottom 0 with NO offset
 * they recompose the original frame exactly, each carrying its own
 * transparent sky. Running three of them deletes the foreground; adding a
 * shared offset crops the photograph. REST IS THE COMPOSED PICTURE.
 *
 * ★ THE WHOLE PARALLAX LIVES IN THE HERO'S EXIT
 *
 * Passes 3-6 walked here the long way. A pinned runway (130svh, then a 30svh
 * hold) is by definition scroll during which no content can arrive, and every
 * version of it produced the same complaint from a different angle: gigantic,
 * empty, pushing a picture uphill. So there is no runway, no hold, no sticky,
 * and nothing pinned. The hero is a PLAIN BLOCK, 86svh so the page itself
 * shows above the fold, and the plates separate as it leaves:
 *
 *  1 TWO NUMBERS. The script writes --u (0..1, smoothstepped over the first
 *    45% of the exit — the separation) and --ex (px the hero has departed —
 *    the lags) once per frame on .hx-scroll. Everything else is CSS.
 *
 *  2 THE SEAL. Plate 2.1 is the only plate that is ever fully opaque, and
 *    only across its bottom 19.3%. That band is the sole seal on the frame
 *    bottom, so 2.1 is RIGID during the exit (--lag: 0) and the three plates
 *    behind it, which seal nothing, hang back on their lags to buy all the
 *    depth. The geometry table lower down derives everything from --ph.
 *
 *  3 NO FILTER, NOT EVEN STATIC. Blur destroys photograph detail and reads
 *    as "blurry", not as distance. On a white sky, plain opacity IS the haze.
 *
 *  4 NOTHING FADES. The copy leaves because the page moves on; the landing
 *    gradient dissolves the picture's bottom into the sheet's white so the
 *    photograph never ends in a ruled line.
 *
 * Kept: the reading scrim, the word waterfall on the h1, the hx-anim gate,
 * and the composed rest state as the CSS default.
 */

body.apex-home {
    --hx-sans: "Instrument Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
    --hx-ease: cubic-bezier(.22, 1, .36, 1);
    background: #fff;
}

/* ── the stage ──
   Now just the variable holder: the script writes --u and --ex here, every
   plate reads them. The hero inside is an ordinary block at every setting of
   every gate, which is also why no-JS needs no special height rules. */
body.apex-home .hx-scroll { position: relative; z-index: 0; --u: 0; background: #fff }

/* ★ NO HOLD, NO STICKY, NOTHING PINNED (pass 7). The 30svh hold was the last
   remnant of the runway idea, and it was still 30svh of scroll during which
   nothing new could arrive. Now the hero is a PLAIN BLOCK: the whole parallax
   runs inside its exit, driven off nothing but scrollY. The range separates
   over the first 45% of the hero's departure, while the headline is still on
   screen, and the plates keep spreading on their lags for the rest of it. The
   scroll cost of the parallax is zero, and the machinery for it is one
   listener writing two numbers. */

body.apex-home .hx-hero {
    position: relative;
    /* ★ THE FULL FRAME. It was cut to 86svh to satisfy a fold test that
       wanted the next section peeking at scrollY 0; the owner's verdict on
       that page was "the hero is too small", and the brief beats the
       heuristic. The range is the brand and it now gets the whole window.
       The plates are sized off this height, so a taller frame is also a
       BIGGER photograph, which is most of what "too small" meant. The cap
       keeps a very tall desktop window from turning it into a poster. */
    height: min(100svh, 1120px);
    min-height: 620px;
    /* 6 Nov: his subtitle and three results made the copy taller than a
       laptop window, and at 1280x720 the rocks sat on the last line. The
       frame now grows with the copy when it has to, and is still the full
       window when it doesn't. */
    height: auto;
    min-height: max(620px, min(100svh, 1120px));

    /* ★ THE PLATE IS SIZED OFF THE FRAME'S HEIGHT, NOT ITS WIDTH.
       Sizing by width was the mistake behind "too zoomed in and blurry". The
       plates are landscape (1.793:1) and anchored at the bottom, so a plate
       wide enough to fill a 16:9 frame vertically has to be enormous
       horizontally: at 172% of the frame you are looking at 58% of the
       photograph, magnified, which costs detail on every object in it.
       Driving off the height instead fills the frame with the LEAST possible
       magnification. On a 1440x900 frame that is 1937px of plate across a
       1440px frame, so you see three quarters of the picture instead of half,
       and every plate is still downsampled from its 2752px source rather than
       stretched.

       Magnification is then CAPPED rather than broken out into breakpoints.
       Filling the height is only worth having while it is cheap; on a tall
       frame it is not, because a 1.793:1 plate tall enough for a 900x1200
       window is nearly three times that window wide. min(..., 118vw) holds
       every frame at or under 2.12x, and that cap is exactly the value the old
       phone breakpoint was setting by hand, so the breakpoint is gone. One
       expression covers a 1600 desktop and a 360 phone. */
    --ph: min(max(112svh, 62vw), 118vw);
    --plate-w: calc(var(--ph) * 1.793);
    --plate-x: calc((100% - var(--plate-w)) / 2);
    --seal: calc(var(--ph) * .193);
    --over: calc(var(--seal) * .82);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* the exit. Written in px by the script over the last stretch of the
       runway, at exactly page speed, so the release has no step in it. */
    /* Daylight. The range is a cut-out with no sky of its own, so the sky IS
       this gradient: white overhead settling into the faintest cool haze at
       the horizon, which is what distance looks like on a clear day. */
    background: linear-gradient(180deg, #ffffff 0%, #fbfdff 34%, #f2f7fd 62%, #e8f0f9 84%, #dfe9f5 100%);
}

/* A daylight sky is not flat. This is the faintest possible warmth overhead
   and a cool wash toward the horizon, well under the threshold where it would
   read as a gradient the reader can name. */
body.apex-home .hx-sky {
    position: absolute; inset: 0; pointer-events: none;
    background:
        radial-gradient(ellipse 96% 62% at 50% -14%, rgba(255, 252, 244, .9) 0%, rgba(255, 252, 244, 0) 62%),
        radial-gradient(ellipse 70% 46% at 62% 78%, rgba(178, 205, 232, .3) 0%, rgba(178, 205, 232, 0) 70%);
}

/* ── stars and snow are gone with the night sky ──
   Both were white-on-dark devices. On a white sky a white star is invisible
   and a white flake is invisible, and the snow canvas was redrawing the full
   frame every tick for something nobody could see. Removing them also removes
   two animated pseudo-elements and a per-frame canvas clear from the scroll
   budget, which is part of what "jittery" was paying for. The markup for both
   is gone from inc/home-body.php; initSnow() is gone from assets/js/home.js.
   The archived night version is at _archive/home-pass4-2026-08-29/. */

/* ── THE LANDING ──────────────────────────────────────────────────────
   The hero used to end in a straight horizontal cut through the foreground
   rocks, with the white sheet starting hard against it. Nothing in a
   photograph ends in a ruled line, so it read as the picture being clipped
   rather than the page beginning.

   This is the fix, and it is one element: the bottom of the hero dissolves
   into exactly the white the sheet is made of, so the mountains do not end,
   they go into the page. It rides inside the hero, so it holds at rest, all
   the way through the exit, and at every frame size, and there is nothing to
   keep in sync between the two surfaces.

   The stops are weighted to the last third on purpose. A linear fade over the
   same distance greys the foreground rocks the whole way up and the ground
   under the copy goes soft; this leaves the rocks alone until the final
   stretch and then closes quickly. The last 5% is SOLID white rather than
   arriving at white exactly on the final pixel row: at the far left and right
   the plate is bare rock rather than snow, and rock at 92% white is still
   faintly there, which left a hairline of it along the cut. */
body.apex-home .hx-landing {
    position: absolute; left: 0; right: 0; bottom: -1px;
    height: clamp(110px, 17svh, 220px);
    z-index: 3;
    pointer-events: none;
    background: linear-gradient(to bottom,
        rgba(255, 255, 255, 0)    0%,
        rgba(255, 255, 255, .10) 30%,
        rgba(255, 255, 255, .30) 52%,
        rgba(255, 255, 255, .62) 72%,
        rgba(255, 255, 255, .90) 86%,
        rgba(255, 255, 255, 1)   95%,
        rgba(255, 255, 255, 1)  100%);
}

body.apex-home .hx-cue { z-index: 6 }

/* ── the range ──
   Two containers, not one, so .hx-content can sit BETWEEN them in the DOM and
   the near planes pass in front of the headline. Both are decorative.

   No object-fit (these are cut-outs with a fixed horizontal registration;
   cropping one and not another de-registers the scene), no min-height, no
   mask. The only per-plate difference is how close it is, expressed as --pz
   (how much it grows as the camera approaches) and --py (how far it descends
   past you), both scaled by the one inherited --u. */
body.apex-home .hx-range { position: absolute; inset: 0; pointer-events: none }
body.apex-home .hx-range--back  { z-index: 1 }
body.apex-home .hx-range--front { z-index: 3 }

/* ══ THE GEOMETRY, and the two measurements it is built on ══════════════
   Every earlier pass guessed at this. These are the plates' actual alpha
   profiles, measured by rasterising each webp and counting opaque pixels per
   row (fractions of the plate's own height, 0 = top, 1 = bottom):

     plate      first pixel   half covered   FULLY covered   last pixel
     2.4 far      .453           .677          never            .874
     2.3 mid      .525           .565          never           1.000
     2.2 walls    .408           .897          never            .982
     2.1 fore     .507           .762          .807            1.000

   ★ ONE. Only plate 2.1 is ever fully opaque, and only across its bottom
   19.3%. Every other plate has holes in every single row — 2.4 has nothing at
   all in its bottom 12.6%, and 2.2 is two walls with the V straight through
   the middle. **Plate 2.1 is the only thing sealing the frame.**

   ★ TWO. That seal is 19.3% of the plate's height and no more. The previous
   pass translated 2.1 by 92svh, five times deeper than its own seal, so the
   seal left the frame almost immediately and the V, the ragged mid ridge and
   the far peak's empty base all opened onto the background. THAT was "large
   gaps of background visible between the pieces", and no amount of retuning
   the other three could have fixed it.

   So the geometry follows the seal:

   - The plates are widened to --plate-w for everyone, not just narrow frames.
     A wider plate is a taller plate, and a taller plate has a deeper seal.
   - --over pushes the whole stack down by the seal depth at rest, so 2.1's
     opaque band sits across the bottom of the frame with more of it below the
     fold, in reserve.
   - Every plate then RISES by --rise * --over. 2.1 rises exactly --over, which
     spends its reserve and lands its bottom edge on the frame bottom: sealed
     at the first frame, sealed at the last, sealed at every frame between.
   - --over is in vw because the plate's height is width-driven. Both the seal
     and the travel then scale together and the guarantee holds at every size.

   ★ NO SCALE. Not on the plates, not on the sky, not on the copy. A scale on
   a filtered layer re-rasterises its blur every frame, which is uncompositable
   in exactly the way the animated blur was, and it is what "zooms in" and
   "jittery" were both describing. Translation only. */
body.apex-home .hx-layer {
    position: absolute;
    left: var(--plate-x); right: auto; bottom: 0;
    width: var(--plate-w);
    /* ⚠ main.css carries the usual global img { max-width: 100% } and it
       silently clamps a widened plate back to the viewport, ending it in a
       hard vertical edge partway across the frame. */
    max-width: none;
    /* both axes stated, identically for all four, so the scene can never
       de-register the way object-fit or a lone min-height de-registers it */
    height: var(--ph);

    --rise: 0;   /* share of --over travelled during the hold, by u = 1 */
    --lag: 0;    /* share of the hero's exit this plate DOES NOT travel */

    /* Two movements, one transform. --u is the hold; --ex is how far the hero
       has already exited, in px, written by the script. A plate with --lag .44
       hangs 44% of the exit behind the frame, which is what distance does. */
    transform: translate3d(0, calc(
        var(--over) * (1 - var(--rise) * var(--u, 0))
        + var(--lag) * var(--ex, 0px)
    ), 0);
    will-change: transform;
}

/* ★ THE PEAK RISES (pass 17, the owner's brief: "have the main mountain
   in the parallax (2.4) actually rise on scroll"). The old table gave 2.4
   a NEGATIVE rise, a settle, and the deepest lag, so the far peak read as
   sinking behind the range while everything else lifted. Both numbers are
   flipped: a strong positive rise through the separation, and a lag
   SMALLER than the mid range's, so the peak keeps climbing relative to
   the ridge in front of it for the whole exit. That is what approaching
   a range actually looks like: the near ridges drop below your line of
   sight and the summit stands up out of them. It costs nothing, because
   2.4 seals nothing and its own ragged base stays behind the three
   plates in front of it (verified by screenshot at u=0, u=1 and
   mid-exit; its lowest pixels sit at .874 of the plate and never clear
   2.3's ridge). The rest of the table is UNTOUCHED, per the brief. */
body.apex-home .hx-layer--4 { --rise: .58; --lag: .1 }
body.apex-home .hx-layer--3 { --rise: .18; --lag: .28 }
body.apex-home .hx-layer--2 { --rise: .58; --lag: .14 }
/* ★ The foreground is RIGID to the hero: --lag 0. It is the only plate that
   seals the bottom of the frame, so the instant it moves relative to the frame
   during the exit, the seal opens. Nailing it to the hero makes the exit
   unconditionally safe, and every bit of the exit's differential is then bought
   by the three plates BEHIND it, which seal nothing and cost nothing. */
body.apex-home .hx-layer--1 { --rise:  .85; --lag: 0 }

/* ★ Atmosphere is STATIC, and it ramps the whole way from the far peak to the
   rocks. Distance catches sky light and goes pale, soft and flat; the nearest
   plane is in its own shadow. That ramp is what makes four cut-outs read as
   depth rather than as four stickers. Pass 1 recomputed these every frame,
   which is uncompositable by definition and was the entire source of the
   jitter. will-change names transform only, for the same reason. */
/* Daylight aerial perspective, and it is the opposite of the night ramp. On a
   dark sky distance went darker and flatter; on a white sky distance is EATEN
   BY THE SKY — it loses saturation and contrast and lifts toward white until
   the far peak is barely a suggestion. This is also what covers the honest
   fact that the plates separate as they move: what opens between two ridges is
   sky, and on a white sky it now looks like sky rather than like a hole. */
/* ★ NO filter. NOT EVEN A STATIC ONE.
   Graduated blur is the textbook way to fake aerial perspective and it was
   wrong here twice over. It literally destroys detail, which on a photograph
   this good is the one thing you cannot afford; and stacked with the old
   brightness(1.24) contrast(.6) it turned the far range into pale mush. That
   pair is what "blurry" was describing, and none of it was the image.

   Real haze does not blur a mountain, it VEILS it: scattered light lifts the
   distance toward the colour of the sky. The sky here is white, and these
   plates are cut-outs with white behind them, so plain opacity IS the veil,
   physically and exactly. It is also a compositor property, so it stays pin
   sharp and costs nothing per frame. */
body.apex-home .hx-layer--4 { opacity: .46 }
body.apex-home .hx-layer--3 { opacity: .70 }
body.apex-home .hx-layer--2 { opacity: .88 }

/* Valley air, between the walls and the rocks. It travels with the near
   planes rather than hanging in the frame like a curtain. */
/* Valley haze, sitting between the walls and the rocks so the atmosphere is
   inside the range rather than laid over it. White now, and it rises with the
   near planes at a rate of its own. */
body.apex-home .hx-mist {
    position: absolute; bottom: 0; left: 0; right: 0;
    height: 54%;
    transform: translate3d(0, calc(var(--over) * (1 - .66 * var(--u, 0)) + .07 * var(--ex, 0px)), 0);
    background: linear-gradient(to top,
        rgba(255, 255, 255, .58) 0%,
        rgba(246, 250, 255, .3) 38%,
        rgba(246, 250, 255, 0) 100%);
    pointer-events: none;
}

/* The sky is at infinity, so it does not move at all. It used to take a 5%
   scale; a scaled layer under four other scaled layers is exactly the cost
   this pass is removing. */

/* ── the copy ── */
/* ★ A reading scrim behind the words. The plates sit where the art puts them
   and the copy has to survive whatever is under it, snow included. A hero
   cannot depend on the picture staying where it was when the type was set. */
/* ⚠ Two things here, both learned the hard way.
   The 100deg gradient runs nearly left to right, so it is still fully opaque
   at the LEFT edge for the whole height of the box. At rest the box is the
   whole hero and its bottom edge is off screen; the moment the copy travels,
   that edge became a hard horizontal line ruled across the mountains. The
   mask is what guarantees a soft bottom whatever the horizontal ramp does.
   And every stop is rgba(...,0), never `transparent`: `transparent` is
   rgba(0,0,0,0), so a gradient into it interpolates through black and lays a
   grey bruise across the picture. */
body.apex-home .hx-content::before {
    content: "";
    position: absolute; inset: -6% -4% -10%; z-index: -1;
    pointer-events: none;
    background:
        linear-gradient(100deg, rgba(255, 255, 255, .88) 0%, rgba(255, 255, 255, .58) 44%, rgba(255, 255, 255, 0) 76%),
        linear-gradient(180deg, rgba(255, 255, 255, .72) 0%, rgba(255, 255, 255, .34) 58%, rgba(255, 255, 255, 0) 100%);
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 100%);
}
/* ★ z-index 4 — ABOVE both ranges, and it has to be.
   This started at 2, between .hx-range--back (1) and .hx-range--front (3), so
   the valley walls and the foreground rocks would pass in front of the
   headline. It looked right in the abstract and was wrong on the page: at
   rest the big rock at the left of plate 2.1 covers the primary button from
   its left edge to the middle of the word "call". A camera effect that eats
   the call to action is not a camera effect, it is a defect, and there is no
   threshold to switch at either, because the rocks cover LESS at rest than at
   any later point in the run.
   The depth is carried by the plates themselves, which is where it belongs:
   over the runway the far peak grows 6%, the mid range 20%, the valley walls
   64% and the foreground 104%. The copy reads as receding because it sinks,
   grows and fades while the near planes rush past it.
   The two range containers still earn their keep: .hx-mist sits between the
   valley walls and the foreground rocks, so the atmosphere is inside the
   range rather than laid over it.
   Opacity is a plain calc: the UA clamps it to 0..1, so no clamp() is needed
   to hold it at full strength for the first half. */
body.apex-home .hx-content {
    position: relative; z-index: 4;
    flex: 1;
    display: flex; align-items: center;
    /* clear of the floating header, so the logo isn't pressed against it */
    padding-top: clamp(6rem, 11.5vh, 8rem);
    /* never less than the band the foreground rocks fill */
    padding-bottom: max(clamp(6rem, 15vh, 10rem), calc(var(--seal) * .76));
    /* ★ NOTHING FADES ANY MORE. The copy used to ramp its own opacity down
       across the runway, which meant it spent most of that runway as grey that
       was too faint to read and too present to ignore, and the reader was left
       looking at a picture with a ghost on it. Now it simply leaves with the
       hero, the way anything at the top of a page leaves: full ink until it is
       off the screen. The only motion of its own is a short lift during the
       hold, so it separates from the range instead of sitting on it. */
    transform: translate3d(0, calc(-9 * var(--u, 0) * 1svh), 0);
    will-change: transform;
}

/* ★ THE SAME SHELL THE SHEET USES, deliberately. It measured
   min(100% - 4rem, 1240px) against the sheet's min(1200px, 92vw), which put
   the headline 20px to the left of every heading below it at 1440 and 19px
   at 1280: two left edges down one page. */
body.apex-home .hx-shell { width: min(1200px, 92vw); margin-inline: auto }
body.apex-home .hx-inner { max-width: 60rem }

/* ★ THE TITLE IS A CLIMB (pass 18). Pass 17 set the closing phrase in an
   italic serif and drew a stroke under it. The owner: "the underline and
   style of the title is screaming generic ai." Correct, on both counts.
   That treatment is the default hero of every AI landing page shipped in
   the last two years, and the stroke was ornament: a line under "by hand"
   carries no meaning, it only points at the words it sits under.

   The replacement adds nothing to the page. It takes away the second
   family, the second colour and the drawn line, and spends the whole
   effect on the setting itself:

   1 THREE RANKS THAT GROW. .60 → .72 → 1.06 of the base. The sentence
     climbs, which is the one metaphor this company owns outright, and it
     lands on the reader's own problem at the largest size on the page.
   2 WEIGHT, NOT FAMILY. 500 for the two setup ranks, 700 for the phrase.
     Emphasis inside one face reads as authored; a borrowed serif reads as
     a template. The <em> is still an <em>, so the emphasis is real to a
     screen reader too.
   3 ONE INK. A tinted first rank was tried and rejected: on white it goes
     grey, and grey reads as de-emphasis rather than as distance.
   4 THE BREAK IS AUTHORED. text-wrap: balance guessed, and it guessed
     badly, hanging "you are" off the end of the first line. */
body.apex-home .hx-hero h1 {
    margin: 0;
    color: #0a1730;
    font-family: var(--hx-sans);
    /* the ranks each set their own size; this is the fallback only */
    font-size: clamp(1.8rem, 1.34rem + 2.95vw, 4rem);
    font-weight: 500;
    line-height: .96;
    letter-spacing: -.035em;
}
body.apex-home .hx-rank { display: block; font-style: normal }
/* Lachlan, 6 Nov: the company name and logo above the title */
body.apex-home .hx-brand { display: block; width: clamp(8.5rem, 6.8rem + 7vw, 13rem); height: auto; margin: 0 0 clamp(1.1rem, 2.4vh, 1.8rem) }
/* ★ EACH RANK CARRIES ITS OWN CURVE, rather than being a multiple of one
   base size. A single base with em multipliers looked right at 1440 and
   fell apart everywhere else: at 834 it put the opening rank at 31px, a
   subhead, and on a phone the whole title read as body copy. Three fluid
   ramps instead, each solved through the two widths that matter (390 and
   1440), so the steps compress on their own as the viewport narrows and
   the climb survives at every size. Caps hold the largest rank on the 6rem
   display ceiling. */
body.apex-home .hx-rank--far  { font-size: clamp(1.6rem, 1.23rem + 2.38vw, 3.4rem) }
body.apex-home .hx-rank--mid  { font-size: clamp(1.8rem, 1.34rem + 2.95vw, 4rem) }
body.apex-home .hx-rank--hand {
    font-size: clamp(2.5rem, 1.57rem + 4.86vw, 5.95rem);
    font-weight: 700;
    /* the heaviest rank takes the tightest tracking so it holds as one mass */
    letter-spacing: -.045em;
    /* and a little air above it, so the jump in size reads as a step rather
       than as a collision with the rank it grew out of */
    margin-top: .05em;
}

body.apex-home .hx-word {
    display: inline-block; overflow: hidden; vertical-align: top;
    padding-bottom: .1em; margin-bottom: -.1em;
}
body.apex-home .hx-word > span { display: inline-block }
html.hx-anim body.apex-home .hx-hero h1.is-split .hx-word > span {
    transform: translateY(115%);
    transition: transform .86s var(--hx-ease);
    /* ★ the ranks arrive in order and the last one takes its time: the
       word stagger runs straight through, and each rank adds a beat on
       top of it, so the sentence is built upward rather than dealt out */
    transition-delay: calc(var(--w, 0) * 40ms + var(--r, 0) * 95ms + 120ms);
}
html.hx-anim body.apex-home .hx-hero h1.is-split.is-in .hx-word > span { transform: none }
html.hx-anim body.apex-home .hx-hero h1.is-split .hx-rank--hand .hx-word > span { transition-duration: 1.06s }

/* No panel. The lead used to sit inside a translucent rounded card, which
   read as a widget floating on the sky rather than as somebody talking. */
body.apex-home .hx-lead {
    margin: clamp(1.2rem, 2.4vh, 1.7rem) 0 0;
    /* 6 Nov, owner: "the hero is too high, space the text better width wise".
       At 37rem his subtitle stacked into a tall column; the full title width
       keeps each result on one line and takes the hero back to one screen. */
    max-width: 56rem;
    color: #33415e;
    font: 400 var(--a-t-lede)/1.6 var(--hx-sans);
    text-wrap: pretty;
}
body.apex-home .hx-lead p { margin: 0 }
body.apex-home .hx-lead p + * { margin-top: .7em }
body.apex-home .hx-results { list-style: none; margin: .7em 0 0; padding: 0; display: grid; gap: .35em }
body.apex-home .hx-results li { position: relative; padding-left: 1.15em }
body.apex-home .hx-results li::before { content: ""; position: absolute; left: .1em; top: .68em; width: .42em; height: .42em; border-radius: 50%; background: #2c5fd6 }
body.apex-home .hx-results + p { margin-top: .8em }
body.apex-home .hx-results b { font-weight: 600; color: #0a1730 }
body.apex-home .hx-actions {
    margin-top: clamp(1.5rem, 3vh, 2.2rem);
    display: flex; align-items: center; flex-wrap: wrap; gap: .85rem 1rem;
}
body.apex-home .hx-btn {
    display: inline-flex; align-items: center; gap: .6rem;
    padding: .92rem 1.5rem;
    /* 12px, matching every other button on the site. NO PILLS. */
    border: 1px solid transparent; border-radius: 12px;
    font: 600 .96rem/1 var(--hx-sans);
    text-decoration: none;
    transition: background .3s var(--hx-ease), border-color .3s var(--hx-ease),
                transform .3s var(--hx-ease), box-shadow .3s var(--hx-ease);
}
/* The same two buttons the light sheet below uses. A hero on white has no
   reason to invent its own pair. */
body.apex-home .hx-btn--solid {
    background: #22458a; border-color: #22458a; color: #fff;
    box-shadow: 0 1px 0 rgba(13, 35, 80, .06), 0 16px 30px -18px rgba(13, 35, 80, .55);
}
body.apex-home .hx-btn--solid:hover { background: #1a3570; border-color: #1a3570; transform: translateY(-2px) }
body.apex-home .hx-btn--ghost {
    background: #fff;
    border-color: var(--a-line);
    color: #0a1730;
    box-shadow: 0 1px 0 rgba(13, 35, 80, .04);
}
/* Over a photograph the UA ring is unreliable; name it. */
body.apex-home .hx-btn:focus-visible {
    outline: 2px solid #22458a;
    outline-offset: var(--a-focus-off);
}
body.apex-home .hx-btn--ghost:hover { border-color: rgba(13, 35, 80, .3); transform: translateY(-2px) }
body.apex-home .hx-btn__arw {
    width: 15px; height: 9px; flex: none;
    background: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 15 9'%3E%3Cpath d='M0 4.5h12.4M9 1l3.6 3.5L9 8' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 15 9'%3E%3Cpath d='M0 4.5h12.4M9 1l3.6 3.5L9 8' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    transition: transform .3s var(--hx-ease);
}
body.apex-home .hx-btn:hover .hx-btn__arw { transform: translateX(3px) }


/* ── the cue. A drawn line, no tracked uppercase label. ── */
body.apex-home .hx-cue {
    position: absolute; bottom: 1.9rem; left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    width: 1px; height: 38px;
    overflow: hidden;
    opacity: 0;
    animation: hxFadeIn 1.6s 1.2s forwards;
    transition: opacity .45s ease;
}
body.apex-home .hx-cue.is-hidden { opacity: 0; animation: none }
body.apex-home .hx-cue__line {
    display: block; width: 1px; height: 38px;
    background: linear-gradient(to bottom, rgba(13, 35, 80, 0), rgba(13, 35, 80, .45));
    animation: hxScrollLine 2.2s 1.4s infinite;
}
@keyframes hxFadeIn { from { opacity: 0 } to { opacity: 1 } }
@keyframes hxScrollLine {
    0%   { transform: scaleY(0); transform-origin: top }
    50%  { transform: scaleY(1); transform-origin: top }
    51%  { transform: scaleY(1); transform-origin: bottom }
    100% { transform: scaleY(0); transform-origin: bottom }
}

/* ★ THE COMPOSITION IS WIDTH-DRIVEN, SO IT WIDENS BY ASPECT RATIO.
   At width:100% a plate is W/1.793 tall and its mountains occupy the bottom
   third of that. On a tall frame — a phone, a tablet held upright — the whole
   photograph lands as a band along the bottom with a dead sky above it. On a
   short wide laptop it does not, so keying this to viewport WIDTH would fix
   the phone and leave the tablet broken. All four plates widen by the same
   amount from the same origin, so the register stays exact; the crop closes in
   on the middle of the photograph, which is where the far peak and the
   foreground rocks are.
   ⚠ max-width: none. main.css carries the usual global img { max-width: 100% }
   and it silently clamps this straight back to the viewport, leaving the plate
   ending in a hard vertical edge partway across the frame. */
/* 150% is now the everywhere value, because the seal needs the height. A tall
   frame still wants more: at --plate-w:150% a portrait phone lands the whole
   photograph as a band along the bottom. One extra step, and it moves the two
   tokens rather than the plate rules, so --over follows automatically. */
/* No plate breakpoint. The min()/max() in --ph does the whole job, phone
   included, and a breakpoint here would only be a second opinion about it. */

@media (max-width: 720px) {
    body.apex-home .hx-content { padding-bottom: clamp(6rem, 16vh, 9rem) }
    /* 6 Nov: his longer subtitle and three results don't fit one phone screen.
       The hero grows with the copy instead, and keeps room under it for the
       penguin, so the scenery never sits on the words. */
    body.apex-home .hx-hero { height: auto; min-height: 100svh }
    body.apex-home .hx-content { padding-top: 6.4rem; padding-bottom: 17rem }
    body.apex-home .hx-brand { width: 7.5rem; margin-bottom: .9rem }
    /* never "AI" alone on the second line */
    body.apex-home .hx-rank--mid { display: block; text-wrap: balance }
    body.apex-home .hx-lead { font-size: 1.02rem; line-height: 1.55 }
}

@media (prefers-reduced-motion: reduce) {
    /* the base offset stays: it is the composition, not the motion. Only the
       scroll-driven separation and lag are dropped. The hero is already a
       plain block, so there is no height or position to undo any more. */
    body.apex-home .hx-scroll { --u: 0 !important; --ex: 0px !important }
    body.apex-home .hx-content { transform: none !important; opacity: 1 !important }
    body.apex-home .hx-cue,
    body.apex-home .hx-cue__line { animation: none }
    body.apex-home .hx-cue { opacity: 0 }
}
