/* =============================================================
   landing-hero-funnel.css — the "Data Funnel" hero.

   Requires landing-tokens.css (loaded first).

   Colour tokens are NOT redefined here. The block below aliases the
   shared landing tokens into component-scoped `--dfun-*` names so
   the funnel body can be restyled in one place without touching
   the marketing sections. Theming lives entirely in
   landing-tokens.css, keyed off <html data-theme>.

   Namespaced with `dfun-` because Bootstrap 4 is loaded globally
   (.card / .badge / .btn-primary would otherwise collide).

   Deliberate differences from the reference implementation:
   • Cards are rendered server-side (Blade), not injected by JS, so the
     funnel still reads correctly with JS disabled and is crawlable.
   • Sparkline bar heights are fixed, not Math.random(), so re-renders
     never cause layout jitter.
   • Honours prefers-reduced-motion (the reference does not).
   • Animations pause when the hero scrolls out of view or the tab is
     hidden — backdrop-filter on ~80 cards is expensive otherwise.
   • Clipping is scoped to the hero. The reference sets
     `body { overflow:hidden; height:100vh }`, which would break the
     rest of the landing page.
   ============================================================= */

/* ── 1. Funnel tokens ─────────────────────────────────────── */
.dfun-hero {
/* Aliases onto the shared landing tokens. */
    --dfun-bg: var(--bg-base);
    --dfun-card-bg: var(--card-bg);

    /*
     * Card glass.
     *
     * Derived from --bg-elevated rather than an independent rgb() so the
     * funnel cards are provably the same material as every card below the
     * hero, just translucent. An earlier version used a standalone
     * rgba(38,38,48,.62), which is what made the funnel read as a separate
     * component pasted above a different design system.
     *
     * The alpha has to be local: at full opacity the grid behind the funnel
     * stops showing through and the cards become the opaque panels that
     * looked artificial.
     */
    --dfun-card-fill: color-mix(in srgb, var(--bg-elevated) 66%, transparent);
    --dfun-card-edge: rgba(255, 255, 255, 0.14);
    --dfun-card-shadow-tint: rgba(0, 0, 0, 0.75);
    --dfun-chip-alpha: 78%;

    --dfun-text: var(--text-primary);
    --dfun-text-2: var(--text-secondary);
    --dfun-text-3: var(--text-tertiary);

    --dfun-primary: var(--primary);
    --dfun-primary-hover: var(--primary-hover);
    --dfun-primary-glow: var(--primary-glow);
    --dfun-primary-wash: var(--primary-tint);
    --dfun-positive: var(--positive);
    --dfun-positive-bg: var(--positive-soft);

    /* Responsive funnel geometry. The base values live on :root in
       landing-tokens.css; these overrides are scoped here so they
       never leak into the marketing sections. */
    --dfun-r1-y: var(--r1-y); --dfun-r1-z: var(--r1-z); --dfun-r1-rot: var(--r1-r);
    --dfun-r2-y: var(--r2-y); --dfun-r2-z: var(--r2-z); --dfun-r2-rot: var(--r2-r);
    --dfun-r3-y: var(--r3-y); --dfun-r3-z: var(--r3-z); --dfun-r3-rot: var(--r3-r);
    --dfun-r4-y: var(--r4-y); --dfun-r4-z: var(--r4-z); --dfun-r4-rot: var(--r4-r);

    /* JS-driven parallax tilt */
    --dfun-rot-x: var(--rotate-x);
    --dfun-rot-y: var(--rotate-y);

position: relative;
    /*
     * No clip.
     *
     * The hero used to be `overflow: hidden`, which is what created a
     * visible division: the funnel was guillotined exactly at the section
     * boundary and the grid stopped on the same line. Letting the stage
     * overhang means the cards carry on into the section below and the two
     * read as one continuous surface.
     *
     * Horizontal safety comes from the 150vw stage being centred on a
     * fixed-positioned-relative box plus `overflow-x: hidden` on
     * html/body, both of which the project already sets.
     */
    overflow: visible;
    isolation: isolate;
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    padding-top: 7.5rem;   /* clears the fixed navbar */
    padding-bottom: 0;
    /* The grid now lives on .lp-page so it spans both sections. */
    background-color: var(--dfun-bg);
    transition: background-color 0.4s ease;
}

/*
 * Dark-theme values for the funnel are no longer declared here.
 * landing-tokens.css redefines --bg-base / --card-bg / --primary
 * under [data-theme="dark"], and the aliases above follow along.
 * The only dark-specific adjustment the funnel needs is a touch
 * more separation between card and canvas, because
 * rgba(255,255,255,.05) borders read as noise on #050508.
 */
[data-theme="dark"] .dfun-hero {
    --dfun-card-border: rgba(255, 255, 255, 0.13);
    --dfun-card-shadow: 0 14px 40px -12px rgba(0, 0, 0, 0.85);
}

/* Light theme: the glass is the same token at a slightly higher mix so it
   still reads as a distinct plane over the light canvas. A white fill at the
   dark theme's opacity is invisible against #fcfcfc and the card edge becomes
   the only thing describing the shape. */
[data-theme="light"] .dfun-hero {
    --dfun-card-fill: color-mix(in srgb, var(--bg-elevated) 74%, transparent);
    --dfun-card-edge: rgba(15, 15, 20, 0.10);
    --dfun-card-shadow-tint: rgba(24, 24, 32, 0.30);
    --dfun-chip-alpha: 90%;
}

/*
 * The seam overlay is gone.
 *
 * It existed only to hide the clip line the hero's `overflow: hidden` cut
 * across the cards. With no clip there is nothing to cover, and keeping it
 * would paint an opaque band over exactly the part of the funnel that now
 * needs to remain visible where it crosses into the next section.
 */

/*
 * Readability scrim.
 *
 * The funnel is a live 3D layer, so cards drift up behind the headline and
 * buttons. Without a scrim the CTA text competes with moving cards and
 * contrast drops below AA on several card fills.
 *
 * Height is capped at 46% so it stops well above the funnel. It used to run
 * to 60%, which reached down into the stage and knocked out the two back
 * rows — the scrim has to protect the copy without flattening the effect it
 * sits in front of.
 */
.dfun-hero::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 46%;
    z-index: 2;
    pointer-events: none;
    background: linear-gradient(
        to bottom,
        var(--dfun-bg) 0%,
        var(--dfun-bg) 40%,
        color-mix(in srgb, var(--dfun-bg) 74%, transparent) 70%,
        transparent 100%
    );
}

/* ── 2. Hero copy ─────────────────────────────────────────── */
.dfun-copy {
    position: relative;
    z-index: 20;
    text-align: center;
    max-width: 820px;
    margin: 0 auto;
    padding: 0 1.25rem;
}

.dfun-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.9rem;
    margin-bottom: 1.5rem;
    border-radius: var(--mz-radius-pill);
    background: var(--dfun-primary-glow);
    border: 1px solid var(--dfun-primary-glow);
    color: var(--dfun-primary);
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.4;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}

.dfun-badge .dfun-pulse {
    width: 6px;
    height: 6px;
    border-radius: var(--mz-radius-circle);
    background: var(--dfun-primary);
    flex-shrink: 0;
    box-shadow: 0 0 0 0 var(--dfun-primary-glow);
    animation: dfun-pulse 2.4s ease-out infinite;
}

@keyframes dfun-pulse {
    0%   { box-shadow: 0 0 0 0 var(--dfun-primary-glow); }
    70%  { box-shadow: 0 0 0 9px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}

.dfun-title {
    font-family: 'Poppins', sans-serif;
    font-size: clamp(2.5rem, 5.2vw, 4rem);
    font-weight: 800;
    line-height: 1.05;
    letter-spacing: -0.035em;
    color: var(--dfun-text);
    margin: 0 0 1.25rem;
}

/*
 * Highlight bar behind the emphasised words.
 *
 * The reference used a ::after bar, which breaks as soon as the phrase wraps:
 * the inline box becomes a union of both lines and the bar stretches across
 * both. A sliced background with box-decoration-break:clone repeats per line
 * instead, so the markup can wrap freely.
 */
.dfun-title em {
    font-style: normal;
    color: var(--dfun-primary);
    background-image: linear-gradient(var(--dfun-primary-wash), var(--dfun-primary-wash));
    background-repeat: no-repeat;
    background-size: 100% 0.22em;
    background-position: 0 0.86em;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
}

.dfun-sub {
    font-size: 1.0625rem;
    line-height: 1.65;
    color: var(--dfun-text-2);
    max-width: 540px;
    margin: 0 auto 2.25rem;
}

.dfun-ctas {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 0.875rem;
    flex-wrap: wrap;
}

.dfun-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.95rem 1.9rem;
    border-radius: var(--mz-radius-sm);
    font-family: 'DM Sans', sans-serif;
    font-size: 0.975rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    transition: transform 0.2s var(--mz-ease), box-shadow 0.2s var(--mz-ease),
                background-color 0.2s var(--mz-ease), color 0.2s var(--mz-ease);
}

.dfun-btn-primary {
    position: relative;
    overflow: hidden;
    background: var(--dfun-primary);
    color: #fff;
    box-shadow: 0 10px 25px -8px var(--dfun-primary-glow);
}

.dfun-btn-primary:hover,
.dfun-btn-primary:focus {
    color: #fff;
    background: var(--dfun-primary-hover);
    transform: translateY(-2px);
    box-shadow: 0 16px 34px -8px var(--dfun-primary-glow);
}

/* Sheen sweep on hover */
.dfun-btn-primary::before {
    content: "";
    position: absolute;
    top: 0;
    left: -100%;
    width: 100%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.22), transparent);
    transition: left 0.55s ease;
}

.dfun-btn-primary:hover::before {
    left: 100%;
}

.dfun-btn-ghost {
    background: transparent;
    color: var(--dfun-text);
    box-shadow: inset 0 0 0 1px var(--dfun-card-edge);
}

.dfun-btn-ghost:hover,
.dfun-btn-ghost:focus {
    color: var(--dfun-text);
    background: var(--dfun-card-bg);
    box-shadow: inset 0 0 0 1px var(--dfun-primary);
    transform: translateY(-2px);
}

.dfun-proof {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 1.25rem;
    flex-wrap: wrap;
    margin-top: 1.75rem;
}

.dfun-proof-item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8125rem;
    color: var(--dfun-text-3);
}

.dfun-proof-item i {
    color: var(--dfun-positive);
    font-size: 0.9rem;
}

.dfun-proof-sep {
    width: 1px;
    height: 1.1rem;
    background: color-mix(in srgb, var(--dfun-card-edge) 45%, transparent);
}

/* ── 3. Funnel (3D marquee system) ────────────────────────── */
.dfun-stage {
    position: absolute;
    left: 50%;
    /*
     * Overhangs the hero by 14vh so the funnel physically continues into
     * the section below instead of stopping dead at the boundary. This is
     * what makes the two sections read as one surface — the cards are
     * genuinely mid-scroll when the next heading appears above them.
     */
bottom: -14vh;

    /*
     * Exactly one viewport wide, not 150vw.
     *
     * The width is what makes the marquee read as endless. This box is the
     * mask window: it lines up with the screen edges and fades there,
     * permanently. Anything scrolling past is clipped, so a card can never
     * present a hard edge inside the viewport.
     *
     * At 150vw the box overhung the screen by 360px on each side, so its
     * fade sat permanently off-view and the only edge anyone ever saw was
     * the row's own — which travels with the scroll and therefore swept a
     * visible "start" across the screen every cycle.
     */
    width: 100vw;
    /* Tall enough that all four rows sit inside the stage with their
       depth intact. The stage now spans most of the hero so the rows can
       actually separate — at 46–58vh they overran the box, the back ones
       projected above it into the copy scrim, and the "funnel" read as a
       single flat row of cards. */
    height: 92vh;
    min-height: 0;
    transform: translateX(-50%);
    perspective: 1200px;
    perspective-origin: 50% 100%;
    z-index: 1;
    pointer-events: none;   /* clicks fall through to the hero */

/*
     * Two masks, intersected.
     *
     * Vertical  — dissolves the rows toward the section boundary. Confined
     *              to the last quarter on purpose: an earlier version faded
     *              from 38%, which silently erased the three lower rows and
     *              collapsed the perspective effect.
     * Horizontal— fades at the stage's own edges, which sit exactly on the
     *              screen edges. Because the stage doesn't scroll, this
     *              fade never moves, so the strip has no start or end that
     *              can ever be caught on screen.
     *
     * `intersect` keeps both effects; `add` would union them and let the
     * horizontal band paint over the vertical dissolve.
     *
     * Scoped to the stage rather than the whole hero so the headline and
     * CTAs keep full contrast — the reference masks .hero wholesale, which
     * also dims the copy.
     */
    --dfun-mask-v: linear-gradient(
        to bottom,
        #000 0%,
        #000 70%,
        rgba(0, 0, 0, 0.55) 86%,
        transparent 100%
    );
    --dfun-mask-h: linear-gradient(
        to right,
        transparent 0%,
        rgba(0, 0, 0, 0.35) 5%,
        #000 11%,
        #000 89%,
        rgba(0, 0, 0, 0.35) 95%,
        transparent 100%
    );

    -webkit-mask-image: var(--dfun-mask-v), var(--dfun-mask-h);
    mask-image: var(--dfun-mask-v), var(--dfun-mask-h);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
}

.dfun-stage-inner {
    position: absolute;
    bottom: 0;
    left: 50%;
    width: 100%;
    height: 100%;
    transform-style: preserve-3d;
    transform: translateX(-50%) rotateX(var(--dfun-rot-x)) rotateY(var(--dfun-rot-y));
    transition: transform 0.25s var(--mz-ease-out);
}

.dfun-row {
    position: absolute;
    bottom: 9%;
    left: 50%;
    display: flex;
    gap: 1.25rem;
    width: max-content;
    transform-style: preserve-3d;
    will-change: transform;

    /*
     * No mask on the row.
     *
     * The row used to carry its own horizontal fade, which is the direct
     * cause of a visible start and end. A mask belongs to its element, so a
     * row's fade sits at the *row's* edges — and the row is what scrolls.
     * Every cycle its leading edge, and the transparent zone in front of it,
     * travelled across the screen: a band of background that read unmistakably
     * as "the row begins here".
     *
     * The fade now lives on .dfun-stage, which never moves, so the only
     * edges on screen are the screen's own.
     */
}

.dfun-row-1 { animation: dfun-scroll-a 42s linear infinite; }
.dfun-row-2 { animation: dfun-scroll-b 48s linear infinite; }
.dfun-row-3 { animation: dfun-scroll-a 36s linear infinite; }
.dfun-row-4 { animation: dfun-scroll-b 32s linear infinite; }

/*
 * Each keyframe restates the full transform. A CSS animation replaces the
 * whole `transform` property, so the static tilt cannot be inherited —
 * it has to be repeated per frame. `-50%` of a `width: max-content` row
 * holding exactly two copies of the set is what makes the loop seamless.
 */
@keyframes dfun-scroll-a {
    from { transform: translateX(0)       rotateX(20deg) rotateZ(var(--dfun-r1-rot)) translateY(var(--dfun-r1-y)) translateZ(var(--dfun-r1-z)); }
    to   { transform: translateX(-50%)    rotateX(20deg) rotateZ(var(--dfun-r1-rot)) translateY(var(--dfun-r1-y)) translateZ(var(--dfun-r1-z)); }
}

@keyframes dfun-scroll-b {
    from { transform: translateX(-50%)    rotateX(20deg) rotateZ(var(--dfun-r2-rot)) translateY(var(--dfun-r2-y)) translateZ(var(--dfun-r2-z)); }
    to   { transform: translateX(0)       rotateX(20deg) rotateZ(var(--dfun-r2-rot)) translateY(var(--dfun-r2-y)) translateZ(var(--dfun-r2-z)); }
}

/* Rows 3 and 4 use a shallower tilt — nearer the viewer, so less foreshortening. */
.dfun-stage .dfun-row:nth-child(3) {
    animation-name: dfun-scroll-c;
}
.dfun-stage .dfun-row:nth-child(4) {
    animation-name: dfun-scroll-d;
}

@keyframes dfun-scroll-c {
    from { transform: translateX(0)       rotateX(15deg) rotateZ(var(--dfun-r3-rot)) translateY(var(--dfun-r3-y)) translateZ(var(--dfun-r3-z)); }
    to   { transform: translateX(-50%)    rotateX(15deg) rotateZ(var(--dfun-r3-rot)) translateY(var(--dfun-r3-y)) translateZ(var(--dfun-r3-z)); }
}

@keyframes dfun-scroll-d {
    from { transform: translateX(-50%)    rotateX(15deg) rotateZ(var(--dfun-r4-rot)) translateY(var(--dfun-r4-y)) translateZ(var(--dfun-r4-z)); }
    to   { transform: translateX(0)       rotateX(15deg) rotateZ(var(--dfun-r4-rot)) translateY(var(--dfun-r4-y)) translateZ(var(--dfun-r4-z)); }
}

/* ── 4. Cards ─────────────────────────────────────────────── */
.dfun-card {
width: 264px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
    padding: 1.125rem;
    border-radius: var(--mz-radius-md);

    /*
     * Glass, not a floating panel.
     *
     * The earlier treatment was a near-opaque fill plus a 1px outline and a
     * tight shadow. Against a soft gradient-and-grid background that reads
     * as a UI component dropped on top of the art — the edges never agree
     * with the scene, and every card looks pasted on.
     *
     * Three changes make the cards belong to the background instead:
     *   1. Low-alpha fill, so the grid and glow show through the card and
     *      it reads as part of the same surface.
     *   2. A border that fades toward the sides rather than a hard outline.
     *   3. A wide, soft shadow that suggests ambient light rather than a
     *      panel lifted off the page.
     */
    background:
        linear-gradient(
            160deg,
            color-mix(in srgb, var(--dfun-card-fill) 88%, transparent) 0%,
            color-mix(in srgb, var(--dfun-card-fill) 64%, transparent) 100%
        );
    border: 1px solid transparent;
    background-clip: padding-box;
    box-shadow:
        0 18px 44px -22px var(--dfun-card-shadow-tint),
        0 2px 10px -6px color-mix(in srgb, var(--dfun-card-shadow-tint) 60%, transparent),
        inset 0 1px 0 color-mix(in srgb, var(--dfun-card-edge) 70%, transparent);
    backdrop-filter: blur(20px) saturate(1.15);
    -webkit-backdrop-filter: blur(20px) saturate(1.15);

    /*
     * Edge highlight drawn as a masked gradient rather than a border, so it
     * can fade out at the corners instead of tracing a closed rectangle.
     */
    position: relative;
    pointer-events: auto;   /* re-enable hover on cards only */
    transition:
        box-shadow 0.35s var(--mz-ease),
        transform 0.35s var(--mz-ease),
        background 0.35s var(--mz-ease);
}

.dfun-card::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: linear-gradient(
        150deg,
        color-mix(in srgb, var(--dfun-card-edge) 90%, transparent) 0%,
        color-mix(in srgb, var(--dfun-card-edge) 28%, transparent) 38%,
        transparent 62%
    );
    -webkit-mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    pointer-events: none;
}

.dfun-card:hover {
    box-shadow:
        0 24px 56px -24px color-mix(in srgb, var(--dfun-primary) 34%, transparent),
        inset 0 1px 0 color-mix(in srgb, var(--dfun-card-edge) 95%, transparent);
    transform: translateY(-4px) scale(1.015);
}

.dfun-card-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
}

.dfun-card-icon {
    width: 46px;
    height: 46px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.35rem;
    line-height: 1;
border-radius: 12px;
    background: color-mix(in srgb, var(--dfun-card-edge) 40%, transparent);
    border: 1px solid color-mix(in srgb, var(--dfun-card-edge) 50%, transparent);
}

/* Small square glyph carrying the channel's own brand colour */
.dfun-chip {
width: 46px;
    height: 46px;
    flex-shrink: 0;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.02em;
    color: #fff;

    /*
     * A fully-saturated brand swatch is the loudest thing on the card and it
     * fights the glass around it — the row ends up looking like a strip of
     * stickers. Dropping opacity lets the card's own tint show through, so
     * the chip reads as a tinted tile rather than a pasted logo.
     */
    background: color-mix(in srgb, var(--chip, var(--dfun-primary)) var(--dfun-chip-alpha), transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--chip, var(--dfun-primary)) 42%, transparent);
}

.dfun-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.22rem 0.55rem;
    border-radius: var(--mz-radius-pill);
    background: var(--dfun-positive-bg);
    color: var(--dfun-positive);
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1.5;
    white-space: nowrap;
}

.dfun-card-name {
    font-family: 'Poppins', sans-serif;
    font-size: 0.975rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--dfun-text);
    margin: 0 0 0.15rem;
}

.dfun-card-cat {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--dfun-text-3);
    margin: 0;
}

.dfun-card-foot {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid color-mix(in srgb, var(--dfun-card-edge) 40%, transparent);
}

.dfun-card-foot-label {
    font-size: 0.65rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--dfun-text-3);
    margin: 0 0 0.1rem;
}

.dfun-card-foot-value {
    font-family: 'Poppins', sans-serif;
    font-size: 1.1rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.2;
    color: var(--dfun-text);
    margin: 0;
}

/* ── 5. Responsive ────────────────────────────────────────── */

/* Tablet: flatten the perspective, pull the funnel back so it stays framed. */
@media (max-width: 1199.98px) {
    .dfun-stage {
        perspective: 900px;
        bottom: 0;
    }
    .dfun-hero {
        --dfun-r1-y: -13vh; --dfun-r1-z: -110px; --dfun-r1-rot: -5deg;
        --dfun-r2-y: -6.5vh; --dfun-r2-z: -10px; --dfun-r2-rot: 5deg;
        --dfun-r3-y: 0vh;   --dfun-r3-z: 40px;   --dfun-r3-rot: -3deg;
        --dfun-r4-y: 6.5vh; --dfun-r4-z: 90px;   --dfun-r4-rot: 3deg;
    }
}

/* Mobile: scale down rather than restructure. Cards get smaller, the
   stage shortens, and the copy moves up out of the funnel's way. */
@media (max-width: 767.98px) {
    .dfun-hero {
        padding-top: 6rem;
        min-height: 100svh;
        --dfun-r1-y: -9vh;  --dfun-r1-z: -60px; --dfun-r1-rot: -4deg;
        --dfun-r2-y: -4.5vh; --dfun-r2-z: 0px;  --dfun-r2-rot: 4deg;
        --dfun-r3-y: 0vh;   --dfun-r3-z: 30px;  --dfun-r3-rot: -2deg;
        --dfun-r4-y: 4.5vh; --dfun-r4-z: 60px;  --dfun-r4-rot: 2deg;
    }

    .dfun-copy {
        padding: 0 1.125rem;
    }

    .dfun-badge {
        font-size: 0.7rem;
        padding: 0.3rem 0.7rem;
        margin-bottom: 1rem;
    }

    .dfun-title {
        font-size: clamp(2rem, 8.5vw, 2.6rem);
        letter-spacing: -0.03em;
        margin-bottom: 1rem;
    }

    .dfun-sub {
        font-size: 0.9375rem;
        margin-bottom: 1.5rem;
    }

    .dfun-ctas {
        gap: 0.625rem;
    }

    .dfun-btn {
        flex: 1 1 auto;
        justify-content: center;
        padding: 0.85rem 1.25rem;
        font-size: 0.9rem;
    }

    .dfun-proof {
        gap: 0.5rem 0.875rem;
        margin-top: 1.25rem;
    }

    .dfun-proof-item {
        font-size: 0.75rem;
    }

    /* The proof row wraps to two lines on narrow screens, which would strand a
       divider at the start of the second line. Drop the dividers here. */
    .dfun-proof-sep {
        display: none;
    }

    .dfun-stage {
        perspective: 640px;
        height: 50vh;
        min-height: 280px;
        bottom: 0;
    }

    .dfun-row {
        bottom: 20%;
    }

    .dfun-card {
        width: 196px;
        padding: 0.8rem;
        gap: 0.6rem;
        border-radius: 12px;
    }

    /* Fewer blur layers on touch hardware — backdrop-filter is the single
       most expensive thing on this hero. */
    .dfun-card {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: var(--dfun-card-bg);
    }

    .dfun-card-icon,
    .dfun-chip {
        width: 34px;
        height: 34px;
        font-size: 0.6rem;
        border-radius: 9px;
    }

    .dfun-card-icon {
        font-size: 1rem;
    }

    .dfun-tag {
        font-size: 0.62rem;
        padding: 0.15rem 0.4rem;
    }

    .dfun-card-name {
        font-size: 0.85rem;
    }

    .dfun-card-cat {
        font-size: 0.64rem;
    }

    .dfun-card-foot {
        padding-top: 0.55rem;
    }

    .dfun-card-foot-label {
        font-size: 0.58rem;
    }

    .dfun-card-foot-value {
        font-size: 0.95rem;
    }

/* Snappier on small screens so the loop doesn't drag */
    .dfun-row-1, .dfun-row-2, .dfun-row-3, .dfun-row-4 {
        animation-duration: 28s;
    }

    .dfun-row {
        gap: 0.75rem;
    }

    .dfun-hero::after {
        height: 16vh;
    }
}

/* Hide the 4th row on mobile — three rows read as depth, four read as noise,
   and it removes a third of the compositing cost. */
@media (max-width: 767.98px) {
    .dfun-stage .dfun-row:nth-child(4) {
        display: none;
    }
}

/* Coarse pointers: no hover lift, no parallax (both are mouse affordances). */
@media (hover: none) {
    .dfun-card:hover {
        transform: none;
        /* Restore the resting glass, not the old opaque panel shadow. */
        box-shadow:
            0 18px 44px -22px var(--dfun-card-shadow-tint),
            0 2px 10px -6px color-mix(in srgb, var(--dfun-card-shadow-tint) 60%, transparent),
            inset 0 1px 0 color-mix(in srgb, var(--dfun-card-edge) 70%, transparent);
    }
    .dfun-btn-primary:hover,
    .dfun-btn-ghost:hover {
        transform: none;
    }
}

/* ── 6. Reduced motion ────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    .dfun-row,
    .dfun-stage-inner,
    .dfun-btn,
    .dfun-card,
    .dfun-btn-primary::before {
        animation: none !important;
        transition: none !important;
    }

    /* Park the rows at a readable offset so the content is still visible
       instead of collapsing into an unreadable stack at translateX(0). */
    .dfun-row {
        transform: translateX(-50%) rotateX(8deg);
    }

    .dfun-stage-inner {
        transform: translateX(-50%);
    }

    .dfun-badge .dfun-pulse {
        animation: none;
    }
}

/* ── 7. Navbar tie-in ─────────────────────────────────────── */

/*
 * The shared navbar is transparent over the landing hero, which works, but
 * a hard edge where the navbar meets the hero's grid background looks
 * unfinished next to the reference design.
 *
 * `:has()` scopes this to pages that actually contain the funnel, so the
 * navbar is untouched on every other page. Where `:has()` is unsupported the
 * navbar simply keeps its existing transparent background — that is the
 * current behaviour, so the fallback is correct rather than broken.
 */
body:has(.dfun-hero) .mz-navbar:not(.mz-navbar-scrolled) {
    background: var(--mz-header-bg, linear-gradient(
        to bottom,
        var(--dfun-bg) 0%,
        color-mix(in srgb, var(--dfun-bg) 82%, transparent) 55%,
        transparent 100%
    ));
    padding-bottom: 1.75rem;
}

/* Give the hero a little more breathing room under the taller navbar. */
body:has(.dfun-hero) .dfun-hero {
    padding-top: 7.5rem;
}

@media (max-width: 767.98px) {
    body:has(.dfun-hero) .mz-navbar:not(.mz-navbar-scrolled) {
        padding-bottom: 1.5rem;
    }
    body:has(.dfun-hero) .dfun-hero {
        padding-top: 6.5rem;
    }
}

/* ── 8. Short viewports ──────────────────────────────────────
   The funnel's geometry is in vh while the hero copy is roughly a fixed
   height, so vertical space is the binding constraint — width breakpoints
   cannot catch it. Measured collisions before these rules: 1280x720, 
   1180x820, 1024x768, 900x700, 844x390 and 320x568.

   The fix is to stop bottom-anchoring the stage. Bottom-anchoring means the
   stage grows upward as it gets taller, and on a short viewport it grows
   straight through the sub-copy and the CTA row. Anchoring to `top: 100%`
   instead pins the funnel's leading edge to the hero's bottom boundary, so
   it can only ever extend *downward* — into the overlap zone with the next
   section — and collision with in-flow copy becomes structurally
   impossible rather than a matter of tuning numbers.

   `min-height` is dropped deliberately. It used to force a 240–520px floor
   that the stage could not shrink below, which is what pushed cards up into
   the copy on small screens no matter how small the height rule made it.

   Placed after the width breakpoints on purpose — landscape phones match
   both, and the height constraint is the one that must win.
   ------------------------------------------------------------------ */
@media (max-height: 880px) {
    .dfun-stage {
        top: 100%;
        bottom: auto;
        height: clamp(200px, 30vh, 360px);
        min-height: 0;
    }

    /* Copy needs proportionally more of a short hero, so the scrim grows. */
    .dfun-hero::before {
        height: 58%;
    }
}

@media (max-height: 700px) {
    .dfun-stage {
        height: clamp(170px, 26vh, 260px);
    }
    .dfun-hero::before {
        height: 64%;
    }
}

/* Landscape phones and other very short windows: there is no vertical room
   for both the copy and a decorative funnel, so the funnel steps aside. */
@media (max-height: 580px) {
    .dfun-stage {
        display: none;
    }
    .dfun-hero {
        padding-top: 5.5rem;
    }
    .dfun-proof {
        display: none;
    }
}

/* ── 9. Print ─────────────────────────────────────────────── */
@media print {
    .dfun-stage {
        display: none;
    }
    .dfun-hero {
        min-height: auto;
    }
}