/* THREE LINES, THREE STORIES, TOLD BY HOW THE TYPE ARRIVES. (2026-10-03, lab only)
 * ---------------------------------------------------------------------------
 * Tomislav: "I want something that tells a story and tell it according to the words,
 * Get found (tell a story with animation), then Build Trust (tell another story) be
 * chosen (another one) and all with how the font appears." And, to be sure I had it:
 * "So story should be told in the way how the font appears."
 *
 * The previous version threw every letter in from a different random-looking
 * direction. That is motion without meaning: impressive for a second, then just
 * busy. The problem was never the size of the movement, which is why making it
 * bigger three times running never fixed it. The movement said nothing. Each line
 * now moves the way its own words mean.
 *
 * LAB ONLY. Loaded by lab.html and nothing else.
 */

/* ── the stage ────────────────────────────────────────────────────────────
   One perspective for the whole headline, so all three lines share a single
   viewpoint and the three stories read as happening in one space.

   preserve-3d must run the whole way down, h1 to line to word to character, or the
   browser flattens the grandchildren and every rotation collapses into a 2D skew
   that looks broken rather than dimensional. tools/check-hero-type.mjs reads the
   computed matrix and fails if it is not matrix3d.

   NO CAMERA SWING. It was added to fix a flatness that was really a storytelling
   problem, it cost measurable frame rate while the video was still downloading, and
   with three distinct line behaviours it muddied all three. */
/* THE VANISHING POINT SITS ON THE LEFT EDGE (2026-10-03). Tomislav: "I want all the
   headline and subtitles aligned to the left... when the breathing effect comes in
   the subtitles go too much to the left."
   Measured, he was right and it was not the headline: its three lines stayed within
   2px of each other, but the lede swung 38px horizontally and the eyebrow 41px while
   the headline swung 21px, so they slid in and out of alignment WITH it.
   The cause is where the vanishing point sat. Pushing an element in z scales it ABOUT
   THE PERSPECTIVE ORIGIN, so with the origin at 30% every element slid sideways by an
   amount proportional to its own distance from that point - and a short line of lede
   text sits at a different distance from it than a long headline. Put the origin on
   the left edge and depth no longer has a sideways component at all: everything grows
   and shrinks against the same flush left margin. */
.hero__h1.t3d{ perspective: 680px; perspective-origin: 0% 50%; }
.hero__h1.t3d .ln,
.hero__h1.t3d .w{ transform-style: preserve-3d; }

/* The line mask comes off: a letter arriving from near the vanishing point is
   outside its line box for most of its travel, and the mask would delete the
   entrance. Each line's own behaviour is the reveal now. */
.hero__h1.t3d .ln{ overflow: visible; }

.hero__h1.t3d .ch{
  display: inline-block;
  transform-style: preserve-3d;
  /* Hinged at the foot. Essential for Build Trust, where letters stand up from the
     baseline like something being set in place, and harmless to the other two. */
  transform-origin: 50% 100%;
  backface-visibility: hidden;
  /* Each letter on its own layer, so the browser composites finished glyphs rather
     than redrawing type at a new angle every frame. */
  will-change: transform, opacity;
}

/* The word itself no longer moves, its letters do. And it has to be put back to
   neutral: in styles.css .w is NOT neutral at rest, it sits at opacity 0 and
   translated 118% down, and the old animation's `forwards` was what revealed it.
   Switching that animation off once left every word invisible with the letters
   animating perfectly inside transparent parents, and the checks still passed.
   will-change goes too: a promoted layer flattens its 3D descendants. */
.hero__h1.t3d .w{
  animation: none !important;
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* THE HEADLINE APPEARED TWICE, AND THE FIRST ONE WAS A GHOST (2026-10-04).
 * Tomislav: "at the very start of the animation, before the headline, the golden line
 * shoots through and Get Found appears for a very brief moment and then fully
 * disappears, and then the eyebrow appears and then the actual proper second
 * appearance of Get Found comes into play... remove that nonsense glitch."
 *
 * He was watching two different animations fight over the same words.
 *
 * styles.css runs the LIVE site's reveal off .is-lit: each word rises at 0.92s and
 * 1.06s with a `forwards` fill, so "Get Found" lands and stays. The lab's per-letter
 * run-in cannot start until splitHeadline has cut the words into letters, and that
 * waits for the eyebrow fitter to settle, which takes another second or so. The
 * moment it does, .t3d lands, the rule above switches the live animation off, and the
 * letters inside take over from their own opening frame - far away and invisible. So
 * the words arrive, sit there, and vanish.
 *
 * The fix is to stop the first one ever starting. While the heading is not yet split,
 * its words keep their resting state, which styles.css already defines as opacity 0.
 * The gold sweep and the eyebrow are untouched, so the opening is exactly what he
 * asked for: the line crosses, the eyebrow arrives, and then the real headline runs.
 *
 * GATED ON A CLASS THE SCRIPT ADDS, NOT ON :not(.t3d) ALONE, so this can only ever
 * hide words that something is definitely going to reveal. If the script never runs,
 * or reduced motion is on - where splitHeadline deliberately does nothing - the class
 * is never added and the words behave exactly as they do on the live site. A rule
 * that can hide the headline forever when a script fails is not an acceptable fix. */
html.h1-staged .hero.is-lit .hero__h1:not(.t3d) .w{ animation: none !important; }

/* ═══ 1. GET FOUND ════════════════════════════════════════════════════════
 * Something distant, located, and brought near.
 *
 * The letters begin almost at the vanishing point. At -2000px under a 680px
 * perspective the browser draws them around a quarter size, so they genuinely read
 * as far off rather than as small copies, and they rush straight down the z axis
 * toward the reader. No tumbling and no sideways drift: a search does not wander,
 * it converges. They resolve left to right, each letter found in turn.
 */
@keyframes t3dFound{
  /* --fz is set per letter, each one deeper than the last, so the line recedes away
     from the reader instead of sitting flat at one distance. The first letter starts
     2100px back and the last past 3600px: at a 680px perspective that is roughly a
     quarter size down to under a sixth, which is a real perspective rather than a
     row of small copies. The slight turn resolves as it arrives, so each letter
     TURNS TO FACE you as it is found. */
  0%   { opacity: 0;   transform: translate3d(0, 0, var(--fz, -2100px)) rotateY(26deg) }
  28%  { opacity: .5;  transform: translate3d(0, 0, calc(var(--fz, -2100px) * .42)) rotateY(12deg) }
  /* Past the plane and back, so it arrives with momentum and settles the way
     something that has travelled a long way stops. */
  86%  { opacity: 1;   transform: translate3d(0, 0, 78px) rotateY(-2deg) }
  100% { opacity: 1;   transform: translate3d(0, 0, 0) rotateY(0) }
}

/* ═══ 2. BUILD TRUST ══════════════════════════════════════════════════════
 * One thing placed carefully after another.
 *
 * No flying, and deliberately the least spectacular of the three. Each letter rises
 * from under the baseline and STANDS UP, hinged at its foot, like a brick being set.
 * Square, upright, no overshoot and no bounce, because a settle that wobbles is
 * exactly the wrong word for trust. The restraint is the point: this line would be
 * worse, and would mean less, if it were busier.
 */
@keyframes t3dBuild{
  /* Further under the line and laid flatter, so the stand-up is a real quarter turn
     you watch happen rather than a nudge. It comes forward slightly as it rises and
     is pressed back level, which is the weight of something being set in place. */
  0%   { opacity: 0; transform: translate3d(0, 88%, -220px) rotateX(-106deg) }
  22%  { opacity: 1 }
  76%  { transform: translate3d(0, 0, 38px) rotateX(3deg) }
  100% { opacity: 1; transform: translate3d(0, 0, 0) rotateX(0) }
}

/* ═══ 3. BE CHOSEN ════════════════════════════════════════════════════════
 * One thing singled out from the rest.
 *
 * The letters arrive quietly and nearly together, because this line is not about
 * its parts. The gesture belongs to the whole line: once assembled it steps FORWARD
 * toward the reader and settles back level. Being chosen is being brought forward.
 * The gold rule draws underneath as it lands.
 */
@keyframes t3dChosenChar{
  0%   { opacity: 0; transform: translate3d(0, 20px, -260px) }
  60%  { opacity: 1 }
  100% { opacity: 1; transform: translate3d(0, 0, 0) }
}
@keyframes t3dChosenLine{
  0%, 50% { transform: translateZ(0) }
  /* Forward far enough to be unmistakable - at 680px perspective 96px draws about
     16% larger - then back level, so the headline is not left permanently
     mismatched in size. The step forward is a gesture, not a layout. */
  76%     { transform: translateZ(96px) }
  100%    { transform: translateZ(0) }
}
/* AND THE OTHER TWO LINES STAND BACK while it does, which is the part that actually
   means "chosen". A thing is not chosen by stepping forward on its own; it is chosen
   out of the others. So as Be Chosen comes toward the reader, Get Found and Build
   Trust withdraw and dim, then return. The staging does the work the word does. */
@keyframes t3dStandBack{
  0%, 50% { transform: translateZ(0);     opacity: 1 }
  76%     { transform: translateZ(-150px); opacity: .55 }
  100%    { transform: translateZ(0);     opacity: 1 }
}

/* ── the timing ───────────────────────────────────────────────────────────
   SLOWED AND SMOOTHED THROUGHOUT (2026-10-03). Tomislav: "slow all of it down a bit
   and smooth it out so it's slower and more smooth for a better premium feel."
   Every duration is up by about 40% and every stagger with it, so the RHYTHM is
   unchanged and only the tempo moves - retyping the numbers unevenly is how a
   cascade picks up a stumble, which this file's own history records.
   The curves changed too, and that is the "smooth" half. The entrances were on
   cubic-bezier(0.16, 1, 0.3, 1), which is almost exponential: it covers most of the
   distance immediately and then crawls. Dramatic, but it reads as a snap followed by
   a wait. They are now cubic-bezier(0.33, 1, 0.68, 1), a plain cubic ease-out, which
   decelerates evenly - slower in feel at the same duration.
   THE BREATHING KEEPS ITS SPEED. Tomislav: "only slow down the animation when they
   appear, afterwards the breathing speed it's already good." So the cycles stay at
   11, 12, 13 and 14 seconds; only their START times move, because the entrance they
   wait for is now longer. They did move onto cubic-bezier(0.37, 0, 0.63, 1), a true
   sine ease in and out, which is a smoothness change rather than a speed one: for
   something that never stops, sine is the only curve with no corner at the turn.
   Any other and the eye catches the moment it changes direction.
   Each line gets the stagger its own story needs. Found scans left to right at
   52ms. Build lays one letter at a time, slower at 68ms, because deliberate is the
   whole idea. Chosen is nearly simultaneous at 16ms, because that line moves as one
   thing rather than as nine. */
.hero.is-lit .hero__h1.t3d .ln:nth-child(1) .ch{
  animation: t3dFound 1.75s cubic-bezier(0.33, 1, 0.68, 1) both;
  animation-delay: calc(.45s + var(--i, 0) * 70ms);
}
.hero.is-lit .hero__h1.t3d .ln:nth-child(2) .ch{
  animation: t3dBuild 1.2s cubic-bezier(0.33, 1, 0.68, 1) both;
  animation-delay: calc(2.15s + var(--i, 0) * 92ms);
}
.hero.is-lit .hero__h1.t3d .ln:nth-child(3) .ch{
  animation: t3dChosenChar 1.15s cubic-bezier(0.33, 1, 0.68, 1) both;
  animation-delay: calc(4.25s + var(--i, 0) * 22ms);
}
.hero.is-lit .hero__h1.t3d .ln:nth-child(3){
  animation: t3dChosenLine 3.1s cubic-bezier(0.33, 1, 0.68, 1) 4.25s both;
}
.hero.is-lit .hero__h1.t3d .ln:nth-child(1),
.hero.is-lit .hero__h1.t3d .ln:nth-child(2){
  animation: t3dStandBack 3.1s cubic-bezier(0.33, 1, 0.68, 1) 4.25s both;
}

/* ═══ 4. RUN YOUR AUDIT ═══════════════════════════════════════════════════
 * Tomislav: "Make also a story for audit button (the trigger word is run) so story
 * how it comes or runs/moves - apply it as a 3D animation story as well."
 *
 * The word is RUN, so it does not rise politely into place like the lede above it.
 * It comes in fast and low from the left, turned so you catch its edge first the way
 * you see anything moving across you, runs PAST its mark, and pulls up. The
 * deceleration is the whole character of it: things that run do not stop dead, they
 * overshoot and settle.
 *
 * AND THE ARROW CARRIES ITS OWN MOMENTUM. It starts tucked back inside the button,
 * is still travelling when the button has stopped, swings out past the edge, and
 * returns. That half-beat of lag is what sells the button as a thing that arrived
 * rather than a thing that appeared, and it costs one extra keyframe.
 *
 * BACKWARDS, NOT BOTH, AND THIS MATTERS. An animation with `both` or `forwards`
 * keeps ownership of transform after it ends, which would permanently block
 * motion.css's `.cta:hover svg{translateX(4px)}` and `.cta:active{scale(.965)}` -
 * the button would arrive beautifully and then be dead to the touch. `backwards`
 * applies the opening frame before the delay and RELEASES at the end. Both
 * animations finish on identity, so the handover is invisible.
 */

/* ═══ NOTHING DARKENS THE PICTURE ═════════════════════════════════════════
 * Tomislav: "Remove this ugly dark layer over the hero, I want it exactly done as in
 * the lab, I want a bright original video, remove that dark layer straight away...
 * It's so bad with this dark layer covering this beautiful shots."
 *
 * The lab has looked like this for weeks and the live page did not, because the live
 * page still carried the scrim the old soft footage needed. Two gradients across the
 * whole frame - one darkening the column the type sits in to 76%, one taking the
 * bottom to 92% - plus a 190px band over the nav, and on a phone a single pass that
 * covers the entire picture. Over 4K aerials of Christchurch that is a waste of the
 * only thing the hero is there to show.
 *
 * SO THE TYPE CARRIES ITSELF INSTEAD. That is not the scrim removed and the problem
 * ignored: legibility still has to be bought, and this buys it on the LETTERS rather
 * than on the picture. Three shadows per element, each wider and fainter than the
 * last, so the dark hugs the glyph and dissolves within a few pixels. A hundred
 * pixels away from a letter the photograph is untouched, where a gradient would still
 * be taking a quarter of the light out of it.
 *
 * No hard edge on any of them. He called a black outline "not premium" and he was
 * right: an edge reads as a sticker, a halo reads as light.
 *
 * Measured after, not assumed - tools/check-hero-contrast.mjs reads the real painted
 * pixels of the live film under every piece of hero type at every breakpoint.
 */
/* NOTHING IN THE HERO PAINTS UNTIL THE ENTRANCE IS READY TO RUN. (2026-10-09)
   The class is set by an inline script in the head, before the first paint, and taken
   off in the same frame the hero is lit. Between those two moments the picture shows
   and the type does not, which is the clean start Tomislav asked for.

   VISIBILITY, NOT DISPLAY, AND THAT IS THE WHOLE TRICK. Three different things in
   here measure these elements before they move: the eyebrow fitter solves the pill
   against the headline's width, stageCTA reads the button's resting box to centre it,
   and the panel grid is built from that same box. display:none would collapse every
   one of those boxes to zero and each measurement would come back wrong.
   visibility:hidden keeps the layout exactly as it is and only stops it painting, so
   everything can still be measured while none of it is on screen.

   The media and the scrim are deliberately NOT in this list: the picture is supposed
   to be there from the first frame. */
html.hero-arming .hero__inner .eyebrow--pill,
html.hero-arming .hero__inner .hero__h1,
html.hero-arming .hero__inner .hero__lede,
html.hero-arming .hero__inner .cta,
html.hero-arming .hero__inner .cta-stage,
html.hero-arming .hero__edge{ visibility: hidden !important; }

.hero .hero__scrim{ display: none !important; }
/* And the fade-up-from-black goes with it. app.js no longer creates the veil at all;
   this is here so a cached copy of that file, or the lab's own markup which carries
   the element inline, cannot put the black sheet back. */
.hero .hero__veil{ display: none !important; }

/* And the picture itself is left alone. The 4% scale was there to hide the edges of
   a softer file; on footage this size it is a resample of every frame for nothing,
   and a resample is exactly the softness being complained about. */
.hero .hero__video{ transform: none; }

.hero__h1{
  text-shadow:
    0 1px 2px rgba(6,9,12,.55),
    0 2px 14px rgba(6,9,12,.52),
    0 6px 46px rgba(6,9,12,.44);
}
.hero__lede{
  font-weight: 600;
  color: #fff;
  text-shadow:
    0 1px 2px rgba(6,9,12,.6),
    0 2px 12px rgba(6,9,12,.55),
    0 4px 30px rgba(6,9,12,.45);
}
/* The nav was the one thing the scrim was genuinely protecting: its links measured
   3.85:1 at 1280 over a bright frame against a 4.5:1 floor, which is why the 190px
   band existed at all. They get the same halo, which holds them without putting a
   dark bar across the sky to protect five words. */
.nav--over .nav__link,
.hero__edge{
  text-shadow:
    0 1px 2px rgba(6,9,12,.65),
    0 2px 12px rgba(6,9,12,.6);
}
/* The pill keeps a backing because it is a shape, not type: a halo on four tracked
   capitals over moving foliage is not enough, and the blur keeps the picture reading
   through it rather than blocking it out. */
.hero__inner .eyebrow--pill{
  background: rgba(10,10,10,.5);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

/* ═══ THE BUTTON IS BUILT, NOT REVEALED ══════════════════════════════════
 * Tomislav: "think like Lego blocks are being stacked, that's how I want to kinda see
 * our CTA button for audit being built and created on the spot and then brought to
 * the center where it is now."
 *
 * Thirty blocks fall in from above and out of depth and stack into the button's
 * shape, bottom row first, left to right along each row. When the last one lands the
 * stack fuses into the real button, and only then does the finished thing glide
 * across to the centre of the screen.
 *
 * FOUR THINGS MAKE IT READ AS BUILDING RATHER THAN SCATTERING, and they are the same
 * lesson the headline taught: vary the journey, never the arrival.
 *   they always come from ABOVE        things being built fall into place
 *   bottom row lands first             order is the entire idea
 *   no bounce                          a block that bounces is a ball
 *   every block arrives square         the tumble happens on the way, not at the end
 */
/* PREMIUM, NOT PIXELY (2026-10-03). Tomislav: "I like the idea it's a good
 * direction but make it look more premium, less pixely and cheap."
 *
 * Three things were making it cheap, and none of them were the idea:
 *
 *   IT WAS A GRID, AND ANY GRID OF EQUAL RECTANGLES READS AS PIXELS. Making the
 *   squares bigger was tried first and did not fix it: six larger tiles in two rows
 *   still read as a grid. Seven FULL HEIGHT slats cannot. A tall narrow piece is a
 *   machined part - a louvre, a split-flap board, a watch bracelet - and it comes
 *   with its own motion: a slat has a hinge down its left edge, so it SWINGS SHUT
 *   rather than falling. Seven panels closing left to right is a mechanism. Squares
 *   dropping into a grid is a loading screen.
 *
 *   THE PIECES HAD NO SURFACE. Flat gold with no light on it is a coloured shape,
 *   which is why it read as paper. Each slat now shows its slice of ONE specular band
 *   laid across the whole button, with a bright machined edge down one side, a dark
 *   one down the other and a cast shadow, so it has thickness. Height is what makes
 *   the band legible: across twelve small tiles the highlight only ever lit one or
 *   two and read as odd-coloured squares; across seven tall slats it is a single rake
 *   of light travelling over one object. It all fades out as the slat lands, so the
 *   finished button is the flat gold it has always been and nothing about its resting
 *   look changes.
 *
 *   THE TUMBLE WAS CONFETTI. rotateZ spun the squares on the screen plane, and
 *   rotateX ran from 55 to 103 degrees, which takes a piece THROUGH edge-on: a one
 *   pixel line that reads as a rendering fault rather than an object. The swing is a
 *   single hinge on Y and stops well short of 90.
 */
@keyframes brickDrop{
  0%   { opacity: 0;
         transform: translate3d(0, -11px, var(--z,78px)) rotateY(var(--ry,-38deg)) }
  /* The fade is spread across the first THIRD of the move rather than snapped on at
     the start. A panel that is already solid when it begins travelling reads as a
     graphic being moved; one that resolves as it comes reads as a thing arriving. */
  38%  { opacity: 1 }
  /* Closes flush and stops. The curve does the settling, not an overshoot: a panel
     that bounces shut is a toy. */
  100% { opacity: 1; transform: translate3d(0,0,0) rotateY(0) }
}
/* The surface. Everything that makes a plate look like metal lives here and is gone
   by the time it lands, which is why the button itself needs no change at all. */
@keyframes brickFace{
  0%, 30% { opacity: 1 }
  100%    { opacity: 0 }
}
.cta-stage .cta-bricks i{ opacity: 0 }
.cta-stage .cta-bricks i::after{
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  /* One band across the whole button, each plate showing its own slice: the custom
     properties come from the measured button in cta-assemble.js. */
  background-image: linear-gradient(116deg,
    rgba(255,255,255,0)   14%,
    rgba(255,255,255,.26) 38%,
    rgba(255,255,255,.62) 50%,
    rgba(255,255,255,.26) 62%,
    rgba(255,255,255,0)   86%);
  background-size: var(--bgw, 100%) var(--bgh, 100%);
  background-position: var(--bgx, 0) var(--bgy, 0);
  /* The machined edges that give a slat thickness: lit down the leading side, dark
     down the trailing one, with the top highlight the button itself already uses. */
  box-shadow:
    inset 1px 0 0 rgba(255,255,255,.55),
    inset -1px 0 0 rgba(10,10,10,.30),
    inset 0 1px 0 rgba(255,255,255,.52),
    0 14px 30px -12px rgba(10,10,10,.62);
}
/* SLOWER AND SOFTER (2026-10-04). Tomislav: "make that appearance even more premium
   and smooth." Three changes, all in the same direction:
     1.00s instead of 0.72s      - nothing premium is in a hurry
     a gentler landing curve     - cubic-bezier(.16,1,.3,1) decelerates over a far
                                   longer tail than (.22,1,.36,1), so a panel drifts
                                   the last few pixels instead of arriving and
                                   stopping
     115ms instead of 140ms      - with a longer travel the panels now overlap more,
                                   so the seven read as ONE continuous movement
                                   rather than as seven separate placements
   The angle and the depth came down as well, 48 to 38 degrees and 90 to 78px: less
   of a flap, more of a panel being laid. Total build 1.69s, which still lands clear
   of the fuse at 8.32s. */
.hero.is-lit .cta-stage.is-building .cta-bricks i{
  animation: brickDrop 1s cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: calc(6.3s + var(--o, 0) * 115ms);
}
.hero.is-lit .cta-stage.is-building .cta-bricks i::after{
  animation: brickFace 1s cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: calc(6.3s + var(--o, 0) * 115ms);
}

/* THE FUSE. The stack goes and the real button arrives in the same third of a second,
   both gold and both exactly the same shape, so it reads as the blocks becoming the
   button rather than as one thing swapping for another. */
/* NOT A CROSSFADE ANY MORE, AND THAT WAS THE GLITCH. (2026-10-04)
 * Tomislav: "after the cool premium appearance it gets into some kind of glitch or a
 * stutter and becomes very cheap in appearance briefly and it seems like it
 * disappears and then like it gets a bit improved and it moves to the center."
 *
 * He was watching two translucent layers add up to less than one. The stack faded out
 * while the button faded in, both over the same third of a second - and halfway
 * through, each was at about half opacity, so what was on screen was 75% of a button
 * over a moving aerial. It went pale, the photograph showed through it, and then it
 * came back. Exactly "it seems like it disappears".
 *
 * A crossfade between two things that are meant to be the SAME object is always wrong.
 * So there is no fade out at all: the real button fades in ON TOP of a stack that
 * stays fully opaque, and the stack is switched off only once the button is solid.
 * Both are the same gold and the same shape, so nothing changes at the moment of the
 * switch - the only thing that actually happens on screen is the words arriving on a
 * gold bar that was already there.
 *
 * The button has to be ABOVE the stack for that to work, and it was not: .cta-bricks
 * comes later in the DOM, so it painted over the button. .cta-breath gets a z-index
 * below to put the real thing in front.
 *
 * The timing closes a dead beat as well. The last panel seats at 7.99s and the fuse
 * used to wait until 8.32s, so the finished stack sat there doing nothing for a third
 * of a second before anything else happened. The words now begin arriving the moment
 * the last panel lands. */
@keyframes ctaFuse{ from { opacity: 0 } to { opacity: 1 } }
/* Held, then gone in one frame, under a button that is already solid. */
@keyframes bricksHide{ 0%, 99.9% { opacity: 1 } 100% { opacity: 0 } }
.hero.is-lit .hero__inner .cta{
  animation: ctaFuse .5s cubic-bezier(0.33, 1, 0.68, 1) 8s both;
}
.hero.is-lit .cta-stage.is-building .cta-bricks{
  animation: bricksHide .5s linear 8s both;
}
/* The real button in front of the stack it replaces. Without this the stack paints
   over it, because it comes later in the DOM and neither was positioned. */
.hero__inner .cta-breath{ position: relative; z-index: 2 }

/* THEN IT TRAVELS. Built where it belongs in the layout, then carried to the middle.
   The glide lives on the stage and the sway on the wrapper inside it, because two
   animations writing transform on one element do not blend - the later simply wins. */
@keyframes ctaGlide{
  0%   { transform: translate3d(0, 0, 0) }
  100% { transform: translate3d(var(--cta-dx,0px), var(--cta-dy,0px), 0) }
}
/* THEN IT TRAVELS, AND IT LEAVES AS THE WORDS FINISH ARRIVING. 1.5s on a longer
   tailed curve than before: cubic-bezier(.22,1,.3,1) spends far more of its time
   decelerating, so the button drifts into the centre instead of arriving and
   stopping. It starts at 8.55s, a breath after the fuse completes at 8.5s, so the
   whole thing reads as one continuous movement from the first panel to the centre -
   which is what he asked for. */
.hero.is-lit .hero__inner .cta-stage{
  animation: ctaGlide 1.5s cubic-bezier(0.22, 1, 0.3, 1) 8.55s both;
}

/* ═══ AND THEN IT BREATHES ════════════════════════════════════════════════
 * Tomislav: "leave the font alive and breathing when it stops and just sits there."
 *
 * Once all three stories have landed, the headline does not freeze. It drifts, very
 * slightly, on a long cycle: about a degree of turn, half a degree of tilt and ten
 * pixels of depth. At rest you should not be able to say it is moving, only that it
 * is not dead.
 *
 * FIFTEEN SECONDS, AND THE NUMBER MATTERS. Apple's guidance names slow looping
 * oscillation near 0.2 Hz, one cycle every five seconds, as the band that makes
 * people feel unwell and makes an interface feel unstable. This runs at 0.067 Hz,
 * well clear of it. A five second sway would have been the obvious choice and the
 * wrong one.
 *
 * On the h1 and not on the lines, deliberately. The lines already own a transform
 * from the Chosen staging, and two animations writing transform on one element do
 * not blend - the last simply wins - so the sway lives on their parent and carries
 * all three together.
 */
@keyframes t3dBreathe{
  /* NUDGED DOWN TWICE MORE. "Just nudge down a breathing effect it's still a bit
     much", then "and the eyebrow and the main headline... but not that much."
     Turn 6 -> 2.6 -> 1.6 -> 0.9 degrees. Depth 34 -> 26 -> 18 -> 10px.
     DEPTH IS WHAT SLIDES THE WHOLE BLOCK SIDEWAYS, even with the vanishing point on
     the left edge, because the text does not start at that edge - there is padding
     between them, and everything scales about the origin by its distance from it. So
     cutting depth is what quiets the horizontal travel he is seeing, and cutting the
     turn is what keeps the right-hand end still. */
  0%,100% { transform: rotateY(0deg)    rotateX(0deg)    translateZ(0) }
  25%     { transform: rotateY(.9deg)   rotateX(-.4deg)  translateZ(10px) }
  50%     { transform: rotateY(0deg)    rotateX(.45deg)  translateZ(0) }
  75%     { transform: rotateY(-.9deg)  rotateX(-.3deg)  translateZ(-8px) }
}
/* Starts after the last line has settled, so it never competes with the entrance. */
.hero.is-lit .hero__h1.t3d{
  animation: t3dBreathe 11s cubic-bezier(0.37, 0, 0.63, 1) 8.6s infinite both;
  transform-origin: 0% 50%;
}

/* EVERYTHING ELSE BREATHES TOO, ON ITS OWN PHASE.
 * "All the font and cta button." Three different cycle lengths, deliberately out of
 * step: 17s, 19s and 21s never come back into sync, so the hero never settles into
 * one shared pulse. Things breathing in unison is how a page looks mechanical, and
 * it is the single giveaway that separates this from a template.
 * All three stay clear of the 0.2 Hz band that makes an interface feel unstable.
 */
@keyframes softBreathe{
  /* No rotateY: on a short line a horizontal turn is almost pure sideways travel.
     Depth 52 -> 22 -> 12px, for the same reason as the headline above. */
  0%,100% { transform: translate3d(0,0,0)       rotateX(0deg) }
  33%     { transform: translate3d(0,-2px,12px) rotateX(-.6deg) }
  66%     { transform: translate3d(0,2px,-9px)  rotateX(.5deg) }
}
@keyframes ctaBreathe{
  /* THE CENTRE IS ITS HOME NOW, not a stop on the way. (2026-10-03)
   * Tomislav: "once the CTA button for audit comes and appears don't let it sit
   * there where it is atm but move it to the center of the screen but across all
   * devices, get it centered. So it's final stop is the center."
   *
   * The wrapper carries a PERMANENT translation to the middle of the viewport and
   * breathes around that point, rather than the button making a trip and returning.
   * Every frame below is anchored on --cta-dx/--cta-dy, which hero-type-3d.js
   * measures, so 0% IS the centre and the sway happens there.
   *
   * On the wrapper and not the button, which is what keeps it tappable: an infinite
   * animation writing transform owns transform for good and would permanently block
   * the hover lift and `.cta:active{scale(.965)}`. */
  /* A SMALL ELEMENT NEEDS FAR MORE THAN A BIG ONE TO READ THE SAME. Measured, the
     button was turning 12.4 degrees - as much as the headline - and still only
     travelling 9px across the screen where the headline travelled 32 and the eyebrow
     43. Rotation on something 170px wide barely displaces anything, and its depth is
     projected through .hero__inner's 1500px perspective where the headline gets 680,
     so the same z buys it a quarter of the size change. Hence its own perspective
     below, much more depth, and real lateral travel, which is the one thing that
     always registers on a small object. */
  /* THE CENTRING IS NOT IN HERE ANY MORE, and that was a measured bug. With the
     permanent --cta-dx inside this transform the perspective scaled it too: on a
     desktop that offset is about 450px, and swinging the depth between +112 and -78
     multiplied it by 1.22 and then 0.89, so the button slid 198px back and forth
     across the screen. The centring now sits on the stage OUTSIDE the perspective,
     and only these small deltas are projected. */
  /* THE BALANCE MATTERS AS MUCH AS THE AMOUNT. At 112px of depth the button swung
     28% in width, which does not read as alive, it reads as a pulsing advert. Depth
     is pulled back to a 14% swing and the energy moved into TRAVEL and TURN, which
     is how a real object sitting in space behaves: it drifts and turns, it does not
     throb. */
  /* NO TILT. (2026-10-03) Tomislav: "Don't tilt it, it doesn't look good, just
     breathing and looking alive but don't tilt it."
     He is right, and it is worth saying why rather than just deleting it. A turning
     headline reads as parallax because it is wide: its far end travels further than
     its near end, which the eye reads as depth. A button is small and, worse, it is
     a RECTANGLE WITH A HARD EDGE AND A FLAT FILL - turn that and you do not see
     depth, you see a flat card skewing, which is the exact cheap effect this whole
     hero has been trying not to be.
     So it keeps the two things that do read well at this size: it drifts, and it
     comes toward you and goes away again. Both amplitudes are up to make up for the
     turn being gone. */
  /* EASED BACK on request: "make it breathe a bit less, it's a bit too much now."
     Travel roughly halved and the depth with it, so it is about a 9% change of size
     rather than 16. The point of this was always that the button is not dead, not
     that it is busy. */
  0%,100% { transform: translate3d(0, 0, 0) }
  30%     { transform: translate3d(17px, -9px, 32px) }
  65%     { transform: translate3d(-14px, 7px, -24px) }
}

/* The breathing is listed FIRST and the entrance SECOND on purpose: of two
   animations writing transform the later one wins while it is running, so the
   entrance owns the element until it releases, and the sway takes over after. */
.hero.is-lit .eyebrow--pill{
  animation: softBreathe 12s cubic-bezier(0.37, 0, 0.63, 1) 9.0s infinite both,
             heroRise 1.4s cubic-bezier(0.33,1,0.68,1) .18s backwards;
}
.hero.is-lit .hero__lede{
  animation: softBreathe 13s cubic-bezier(0.37, 0, 0.63, 1) 9.4s infinite both,
             heroRise 1.4s cubic-bezier(0.33,1,0.68,1) 6.25s backwards;
}
/* ITS OWN STAGE, so depth actually tells on something this small. Inside
   .hero__inner the button is projected through a 1500px perspective, which at this
   size is nearly flat; the headline gets 680 and reads three-dimensional because of
   it. A wrapper around the wrapper carries a much closer viewpoint for the button
   alone, and leaves the eyebrow and lede on the gentler one they are tuned for. */
.hero__inner .cta-stage{
  display: inline-block;
  perspective: 620px;
  perspective-origin: 50% 50%;
  /* The permanent centring lives HERE, outside the perspective, so the breathing
     cannot scale it. It also means the button is centred even if the sway never
     runs at all. */
  transform: translate3d(var(--cta-dx,0px), var(--cta-dy,0px), 0);
  position: relative;
  z-index: 7;
}
.hero__inner .cta-breath{
  display: inline-block;
  transform-style: preserve-3d;
  will-change: transform;
}
.hero.is-lit .hero__inner .cta-breath{
  animation: ctaBreathe 14s cubic-bezier(0.37, 0, 0.63, 1) 10.2s infinite both;
}

/* NO GOLD RAKES. (2026-10-03) Tomislav: "remove those golden lines from the right
   side, it just looks messy. Keep only one short one like usual under Be Chosen,
   that's it."
   Each line used to have a gold bar grow across it and exit to the right, three of
   them, staggered. With the headline now arriving letter by letter and a camera that
   no longer moves, those bars were a second idea competing with the first - and
   three lines leaving the frame on the right is exactly the mess he describes. The
   short rule under "Be Chosen" stays, because that one is punctuation rather than
   motion: it closes the sentence.
   Hidden rather than deleted: .ln__sweep is markup in index.html too, and the live
   site still uses it. This file is the lab's only. */
/* THE GOLD LINES COME BACK, IN THE LIVE SITE'S RHYTHM. (2026-10-04)
 * Tomislav: "the eyebrow appears first, like a millisecond before the first golden
 * line. then the first golden line shoots and then Get Found appears. and then while
 * Get Found is in the beginning process of revealing close to the end the second line
 * shoots across and Build Trust appears... i don't want like what is now, that the 2
 * golden lines are shot across at the very beginning and then everything else goes
 * after."
 *
 * He was describing two clocks running at once. The sweeps fire off `.is-lit` at 0.6s
 * and 1.24s, the way the live site times them, while the lab's headline cannot start
 * until the words have been split - about 1.6s in. So both bars crossed an empty
 * hero, and the headline arrived afterwards with nothing beside it.
 *
 * Fixed in two places. lab.html now holds `.is-lit` back until the split, so there is
 * ONE clock and the live rhythm is reproducible at all. And the sweeps are retimed to
 * the lab's own headline rather than to the live one, keeping the live spacing:
 *
 *   eyebrow   0.18s   first, by a hair
 *   sweep 1   0.34s   the bar starts across line one
 *   GET FOUND 0.45s   the letters follow it in
 *   sweep 2   1.88s   crosses while line one is still finishing
 *   BUILD     2.15s   the letters follow it in
 *
 * Line three keeps the short rule under "Be Chosen" and gets no bar, which is what he
 * asked for on 2026-10-03 and has not changed. */
.hero__h1.t3d .ln:nth-child(3) .ln__sweep{ display: none !important; }
.hero__h1.t3d .ln__sweepclip{
  position: absolute;
  left: 0; right: 0; bottom: 0; top: 0;
  overflow: hidden;
  pointer-events: none;
}
.hero__h1.t3d .ln__sweepclip .ln__sweep{ bottom: .14em; }
.hero.is-lit .hero__h1.t3d .ln:nth-child(1) .ln__sweep{
  animation: heroSweep 2.3s var(--ease-fluid) .34s both;
}
.hero.is-lit .hero__h1.t3d .ln:nth-child(2) .ln__sweep{
  animation: heroSweep 2.3s var(--ease-fluid) 1.88s both;
}

/* ── everything else in the hero ──────────────────────────────────────────
   The eyebrow, lede and button arrive from graded depths so the block assembles
   front to back rather than as three identical slides, and they are retimed to land
   after the headline has told all three of its stories. */
.hero__inner{ perspective: 1500px; perspective-origin: 0% 46%; }
.eyebrow--pill, .hero__lede{
  transform-origin: 50% 100%; backface-visibility: hidden;
}
.hero__inner .cta{
  backface-visibility: hidden;
  /* IT HAS TO BE VISIBLE AT REST, and this is the bug Tomislav hit on his phone:
     "it comes all over the main headline and then disappears completely."
     styles.css line 1472 is `.hero__inner .cta{opacity:0}` - the button is invisible
     by default and the old heroRise animation's `forwards` fill was what revealed
     it. Our run-in uses `backwards` fill on purpose, so that hover and press still
     work afterwards, and backwards RELEASES at the end - dropping the button back to
     its default, which is invisible. It ran its whole story beautifully and then
     vanished for good.
     This is the identical trap .w fell into higher up this file. Twice in one file
     is a pattern: BEFORE replacing an animation, check what the element looks like
     with no animation at all. */
  opacity: 1;
}
@keyframes heroRise{
  from{ opacity: 0; transform: translate3d(0, 26px, var(--rise-z, -260px)) rotateX(var(--rise-x, -18deg)) }
  to{ opacity: 1; transform: translate3d(0, 0, 0) rotateX(0) }
}
.eyebrow--pill   { --rise-z: -520px; --rise-x: -30deg }   /* deepest  */
.hero__lede      { --rise-z: -240px; --rise-x: -16deg }
.hero__inner .cta{ --rise-z: -130px; --rise-x: -10deg }   /* nearest  */

.hero.is-lit .rule            { animation: heroRule 1.4s cubic-bezier(0.33,1,0.68,1) 5.95s forwards }
/* The button has its own story below; heroRise no longer claims it. */
@keyframes heroRule{ to{ transform: scaleX(1) translateZ(8px); opacity: 1 } }

/* ── reduced motion ───────────────────────────────────────────────────────
   Fewer and gentler, not zero. hero-type-3d.js does not split the heading at all in
   this case, so there are no per-letter spans and nothing for a screen reader to
   spell out. The sequence survives as a plain fade. */
@media (prefers-reduced-motion: reduce){
  /* REDUCED MOTION MEANS FEWER AND GENTLER, NEVER GONE. (rebuilt 2026-10-03)
   *
   * This block was broken by an edit of mine and the result was as bad as it gets: a
   * selector list that was meant to say `animation: none` ended up saying
   * `display: none`, and it contained all three headline lines, the headline itself
   * and the audit button. Anyone with reduced motion switched on was being served an
   * empty hero. No words, no call to action, just the film.
   *
   * It survived because the reduced-motion check only asserted that the heading was
   * not split into letters - which is perfectly true of a heading that is not there
   * at all. A check that can be satisfied by an absence is the recurring theme of
   * this whole file, and the check now asserts the headline and the button are
   * VISIBLE and have real size.
   *
   * Everything below is `animation: none` only. Nothing in this block hides anything. */
  .hero__h1.t3d, .hero__inner{ perspective: none }
  .hero.is-lit .hero__h1.t3d,
  .hero.is-lit .hero__h1.t3d .ln:nth-child(1),
  .hero.is-lit .hero__h1.t3d .ln:nth-child(2),
  .hero.is-lit .hero__h1.t3d .ln:nth-child(3),
  .hero.is-lit .hero__inner .cta,
  .hero.is-lit .hero__inner .cta svg,
  .hero.is-lit .hero__inner .cta-stage,
  .hero.is-lit .hero__inner .cta-breath,
  .hero.is-lit .eyebrow--pill,
  .hero.is-lit .hero__lede{ animation: none }

  /* The bricks are the one thing that genuinely should not appear: they are a
     construction effect with nothing to say once the button is simply there. */
  .cta-bricks{ display: none }

  /* With the glide switched off, the stage's own static transform is what puts the
     button in the middle, and hero-type-3d.js skips measuring under reduced motion -
     so it falls back to 0 and the button rests where the layout puts it. That is the
     correct quiet behaviour: centred by animation, left alone without it. */
  @keyframes heroRise{ from{opacity:0} to{opacity:1} }
  @keyframes heroRule{ to{transform:scaleX(1); opacity:1} }
  .hero__h1 .ln__sweep{ transform: none }
}


/* ── THE HEADER LINKS ─────────────────────────────────────────────────────
 * Tomislav: "make the header links pop like the subtitle under the main headlines,
 * the same impactful white bright color."
 *
 * Measured, the gap was bigger than it looked. The subtitle is pure white at weight
 * 600 with a shadow under it. The header links were bone at 82% OPACITY and weight
 * 300 - so they were both dimmer and thinner, and over a bright moving picture that
 * reads as washed out rather than quiet.
 *
 * They now take the subtitle's exact colour and its shadow. Weight goes to 500
 * rather than the subtitle's 600: at 14px in a row of six items, 600 stops being
 * emphasis and becomes a block of bold, and the brightness is what he asked for.
 *
 * The active and hover states keep their own identity - they have to stay
 * distinguishable from a link that is now already white.
 */
/* !important on the colour, deliberately, and styles.css makes the same call three
   lines from the rule that beats this one: "not worth a specificity arms race every
   time that class changes shape". The nav link colour is set in FIVE places in
   styles.css - .nav--over, .nav--solid, :not(.is-tight) and two more - each tuned
   for a different background the header can sit over. Writing a selector that beats
   all five means writing five, and they would break the next time a variant class is
   added. This is one property, on one element, in a file the lab alone loads. */
.nav__links a{
  color: #fff !important;
  /* 600, THE SAME AS THE SUBTITLE. I hedged at 500 first, reasoning that 600 across
     six items at 14px would read as a block of bold rather than as emphasis.
     Tomislav looked at the result: "it's not the same as subtitles, cause subtitles
     are thicker and more visible, make the Header Links also like that." He asked
     for the subtitle's treatment and I gave him most of it, which is the version of
     not doing what was asked that is hardest to spot. */
  font-weight: 600 !important;
  /* The subtitle's own three-layer shadow, copied exactly rather than approximated.
     The third layer is the one that matters: a wide 30px pool at 45% is what lifts
     white type off a bright moving picture. The links had two layers and stopped at
     12px, which is why they still read flatter even once the colour matched. */
  text-shadow: rgba(6, 9, 12, .6) 0 1px 2px,
               rgba(6, 9, 12, .55) 0 2px 12px,
               rgba(6, 9, 12, .45) 0 4px 30px !important;
}
/* Hover keeps the gold, so a link still reads as reacting now the resting state is
   already full white. */
.nav__links a:hover{ color: var(--gold) !important; }
/* An underline that was tuned against 82% bone is invisible under full white. */
.nav__links a::after{ background: rgba(255,255,255,.85); }

/* The pill stays invisible until it has been sized, so no intermediate guess is ever
   seen. eyebrow-fit.js adds .eyebrow-sized to <html> the moment it writes. Without
   JavaScript the class never arrives, so the :not() guard would hide it forever -
   hence the html.js condition, which this site already sets in the head. */
/* GATED ON A CLASS THE SCRIPT ADDS, NOT ON html.js (2026-10-05, on bringing this to
   the live page). Written as `html.js:not(.eyebrow-sized)` it hid the eyebrow for
   anyone whose browser ran no module at all - nothing would ever add the class, and
   the pill would be invisible for good. h1-staged is added by the same script that
   later adds eyebrow-sized, so the hide can only ever apply where something is
   definitely going to lift it. No script, no hide. */
html.h1-staged:not(.eyebrow-sized) .hero__inner .eyebrow--pill{ visibility: hidden; }

/* ── MAKING THE AUDIT BUTTON POP ──────────────────────────────────────────
 * Tomislav: "make that CTA button for audit pop more, be creative with it and make
 * it visible and obvious."
 *
 * The obvious move is a glow, and styles.css rules it out in as many words on this
 * exact element: "Depth from a contact shadow, never a glow." That is a real design
 * decision and a good one - a glow around a gold button on a bright photograph turns
 * to mud, because there is no dark behind it for the light to read against. So this
 * does it two other ways.
 *
 * ONE: A HEAVIER SEAT. The same contact-shadow idea the button already uses, pushed
 * further. A longer cast and a tighter dark line directly under it make it read as an
 * object lying ON the picture rather than printed into it. The inset highlight along
 * the top edge goes up too, which is what gives a solid colour an edge to catch.
 *
 * TWO: A SHEEN THAT CROSSES IT. Light travelling over a polished surface, about once
 * every six seconds. This is the same gold rake that used to cross the headline
 * lines, which he liked, moved to the one element that should be asking for a click.
 * It is a specular highlight rather than a glow: it lives INSIDE the button's shape,
 * so it cannot smear into the picture behind it.
 *
 * Both are cheap. The seat is a static shadow with no per-frame cost, and the sheen
 * is one pseudo-element moving on transform alone.
 */
/* THE SAME BUTTON AS "GET IN TOUCH". (2026-10-05)
   Tomislav: "I want that the CTA audit button looks exactly the same, the exact copy
   of all on it, like the one on the drop down menu which says 'Get in touch'" and
   "make the CTA button on mobile a bit smaller, it looks a bit too big."
   One instruction answers both: the nav button is the smaller one, so copying it
   exactly is also what takes the weight off the mobile hero. Every number here is
   lifted from .nav__cta.cta rather than chosen - font size, padding, radius, the
   press, and the plain .cta shadow in place of the heavier one this hero had grown.
   What is NOT copied is the label, which has to stay "Run your audit" because that is
   what the button does, and the light that crosses it, which is motion rather than
   look and which he asked for twice. */
.hero__inner .cta{
  position: relative;
  /* overflow clips the light (::after) to the pill; the focus ring is an outline,
     drawn outside the box, so keyboard focus is untouched. */
  overflow: hidden;
  /* Size, pill shape and finish now come from the base premium .cta (2026-10-10).
     The hero button is the full premium pill, not the nav-sized copy it used to be.
     Only its recurring light (::after) and the build animation stay hero-specific. */
}
/* The hero keeps ONE light - its recurring ::after. Switch off the base hover glint
   (::before) here so the two never cross on the same button. */
.hero__inner .cta::before{ display: none; }
.hero__inner .cta:active{ transform: scale(.97); }
.hero__inner .cta::after{
  content: "";
  position: absolute;
  top: -60%; bottom: -60%; left: 0;
  width: 42%;
  pointer-events: none;
  background: linear-gradient(100deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,.30) 38%,
    rgba(255,255,255,.78) 50%,
    rgba(255,255,255,.30) 62%,
    rgba(255,255,255,0) 100%);
  transform: translateX(-160%) skewX(-18deg);
}
/* EVERY 3.5 SECONDS, NOT EVERY 6.5 (2026-10-03). Tomislav: "make that white light go
   a bit more often, like every 3-4 seconds." The crossing still takes about as long
   as it did; what shortens is the WAIT between passes, so the light reads as a
   surface catching the light rather than as a loading bar. */
@keyframes ctaSheen{
  0%, 66%  { transform: translateX(-160%) skewX(-18deg) }
  100%     { transform: translateX(420%)  skewX(-18deg) }
}
.hero.is-lit .hero__inner .cta::after{
  animation: ctaSheen var(--sheen, 3.5s) cubic-bezier(.3, 0, .2, 1) 11.4s infinite both;
}

/* THE TURNING PANELS ARE GONE (2026-10-04). Tomislav: "go back to what it was before
   this now, cause this is way too much and its just terrible... the only thing i want
   from this take is more often white light to pass through it, that's it."
   Removed outright rather than disabled: the seven cloned panels, the 360 degree
   wave, the slab lunge, the cast shadow under it, the visibility swap that hid the
   real button while the panels had its place, the hover kill-switch that existed only
   to make all that safe, and the .cta-flip and .cta-face wrappers that carried them.
   What is kept from that round is one line: the light crosses every 3.5s instead of
   every 6.5s, which is the part he asked for.
   Reduced motion: the light stops and the button keeps its ordinary resting look.
   Written as its own block on purpose - a `display:none` was once spliced into a
   shared selector list in this file and took the entire hero with it. */
@media (prefers-reduced-motion: reduce){
  .hero.is-lit .hero__inner .cta::after{ animation: none; opacity: 0 }
}

