/* ==========================================================================
   SIGMA SCANNER — hero.css
   §2 Hero. The page's highest-priority section.

   Loaded after the three foundation files. Writes into the same @layer
   names, so cascade order is guaranteed regardless of link order.

   ---------------------------------------------------------------------------
   THE ENTRANCE STATE MACHINE
   ---------------------------------------------------------------------------
   <html data-hero="…">

     (absent)   default. Everything visible. This is what ships in the HTML,
                what a crawler sees, and what renders with JS disabled.
     pending    set by a 12-line inline script in <head>, BEFORE first paint,
                and only when motion is wanted. Hides the animated elements
                so there is no flash before GSAP takes over.
     playing    the master timeline is running.
     done       timeline finished; idle loops continue.
     static     FAILSAFE. The inline script flips pending→static after
                1400ms if nothing has taken over. So even if the GSAP CDN
                hangs forever, the hero is fully visible 1.4s after paint.

   The failsafe is the whole reason `pending` is allowed to hide content at
   all. Without it this file would violate the no-JS contract.
   ---------------------------------------------------------------------------
   ========================================================================== */

@layer layout {

  .hero {
    position: relative;
    isolation: isolate;
    overflow: clip;
    padding-block: calc(var(--sp-9) + var(--nav-h)) var(--sp-10);
    /* NEVER a bare 100vh — inside an iframe that is the frame's viewport,
       not the device's, and 100dvh is unreliable in the same context. */
    min-block-size: min(100svh, 940px);
    display: flex;
    align-items: center;
  }

  .hero__inner {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    inline-size: 100%;
  }
}


/* ==========================================================================
   ATMOSPHERE

   Strengthened from the first pass, which sat at 5–7% opacity and read as
   flat white at a normal viewing distance — indistinguishable from "no
   background" on anything but a colour-calibrated monitor. This version is
   still layered light rather than a single gradient card behind the copy —
   which is what would actually contradict the system's ban on mesh blobs —
   but it is now visible at a glance, matching the depth in the GreenSigma
   reference (soft green corner glow, faint grain, a lit top edge).
   ========================================================================== */
@layer components {

  .hero__bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    overflow: clip;
  }

  /* Base tint — the whole hero sits on a faint green-white gradient rather
     than pure white, which is what makes the wash above it read as light
     falling on a surface instead of a shape floating on a blank page. */
  .hero__bg::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, var(--signal-050) 0%, var(--paper) 46%);
  }

  /* Ambient wash — two lobes, brighter and wider than the original pass,
     positioned top-centre and top-right so the composition has an
     identifiable light source rather than an even haze. */
  .hero__wash {
    position: absolute;
    inset-block-start: -26%;
    inset-inline-start: 50%;
    translate: -50% 0;
    inline-size: min(1680px, 165%);
    aspect-ratio: 16 / 10;
    background:
      radial-gradient(42% 46% at 38% 30%, rgb(16 160 80 / .20) 0%, rgb(16 160 80 / 0) 66%),
      radial-gradient(46% 50% at 76% 14%, rgb(34 197 94 / .16) 0%, rgb(34 197 94 / 0) 68%),
      radial-gradient(60% 55% at 50% 8%,  rgb(7 94 49 / .10)  0%, rgb(7 94 49 / 0)  74%);
  }

  /* A second, lower wash gives the panel something to sit "in" rather than
     "on" — matching the reference's soft glow directly behind the product
     mockup. Faded almost entirely by the time it reaches the fold. */
  .hero__wash--lower {
    position: absolute;
    inset-block-end: -18%;
    inset-inline-start: 50%;
    translate: -50% 0;
    inline-size: min(1400px, 130%);
    aspect-ratio: 16 / 9;
    background: radial-gradient(50% 60% at 50% 100%, rgb(16 160 80 / .12) 0%, rgb(16 160 80 / 0) 70%);
  }

  /* Dot grid — texture, not decoration, now at a legible-but-quiet 6%. */
  .hero__grid {
    position: absolute;
    inset: 0;
    background-image: radial-gradient(circle at 1px 1px, var(--ink-900) 1px, transparent 0);
    background-size: 30px 30px;
    opacity: .06;
    mask-image: radial-gradient(74% 62% at 50% 36%, #000 0%, transparent 80%);
    -webkit-mask-image: radial-gradient(74% 62% at 50% 36%, #000 0%, transparent 80%);
  }

  /* Pointer spotlight. Position is written to two custom properties by
     hero.js — the element itself never re-lays-out, only its own paint. */
  .hero__spot {
    position: absolute;
    inset: 0;
    opacity: 0;
    background: radial-gradient(
      480px 480px at var(--spot-x, 50%) var(--spot-y, 30%),
      rgb(16 160 80 / .12) 0%,
      rgb(16 160 80 / 0) 62%
    );
    transition: opacity 600ms var(--ease-out-quad);
  }
  .hero__spot.is-lit { opacity: 1; }

  /* Top edge light — a thin, bright hairline exactly where a light source
     would graze the page. Cheap (one gradient, ~4px tall) and it is the
     single highest-leverage line for making the section read as "lit"
     rather than "flat". */
  .hero__edge {
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    block-size: 320px;
    background: linear-gradient(180deg, rgb(16 160 80 / .10) 0%, rgb(16 160 80 / 0) 100%);
  }

  .hero__particles {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    opacity: 0;
    transition: opacity 1200ms var(--ease-out-quad);
  }
  .hero__particles.is-on { opacity: 1; }

  /* ------------------------------------------------------------------
     PREMIUM ATMOSPHERE — additive layer, sits above the existing wash/
     grid/spot/particles and below the copy (z-index 0, same as .hero__bg
     itself). Everything here is decorative and capped low in opacity —
     the brief for this pass was "alive, never distracting". */

  /* Mesh gradient — three soft colour fields drifting slowly. Deliberately
     NOT a hard-edged blob: each stop fades to transparent well before its
     circle's edge, so the three overlap into one continuous field rather
     than three visible discs. */
  .hero__mesh {
    position: absolute;
    inset: -10%;
    background:
      radial-gradient(38% 42% at 14% 18%, rgb(16 160 80 / .10) 0%, rgb(16 160 80 / 0) 70%),
      radial-gradient(34% 38% at 88% 12%, rgb(34 197 94 / .09) 0%, rgb(34 197 94 / 0) 72%),
      radial-gradient(40% 44% at 66% 92%, rgb(11 122 62 / .08) 0%, rgb(11 122 62 / 0) 74%);
    filter: blur(6px);
    animation: hero-mesh-drift 26s ease-in-out infinite;
  }

  /* Noise — a tiled SVG fractal-noise data URI rather than a raster file,
     so it costs zero requests. Blended at 2.5%, purely to break up the
     flat gradient banding a print of this composition would otherwise show. */
  .hero__noise {
    position: absolute;
    inset: 0;
    opacity: .025;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
    background-size: 180px 180px;
  }

  /* Extremely subtle stock-chart lines, sitting far back in the wash. */
  .hero__chartlines {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    color: var(--signal-600);
    opacity: .07;
  }
  .hero__chartlines path {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
  }

  /* Floating blurred glow orbs — larger, softer, slower than the mesh
     above; the two together are what makes the field read as depth
     rather than a single flat gradient. */
  .hero__glow {
    position: absolute;
    border-radius: 50%;
    filter: blur(60px);
    opacity: .5;
    will-change: transform;
  }
  .hero__glow--a {
    inline-size: min(420px, 46vw);
    aspect-ratio: 1;
    inset-block-start: 6%;
    inset-inline-start: -6%;
    background: radial-gradient(circle, rgb(16 160 80 / .22) 0%, rgb(16 160 80 / 0) 72%);
    animation: hero-glow-a 18s ease-in-out infinite;
  }
  .hero__glow--b {
    inline-size: min(360px, 38vw);
    aspect-ratio: 1;
    inset-block-end: 2%;
    inset-inline-end: -4%;
    background: radial-gradient(circle, rgb(34 197 94 / .18) 0%, rgb(34 197 94 / 0) 72%);
    animation: hero-glow-b 22s ease-in-out infinite;
  }

  /* Soft glowing particles — a handful of static-positioned motes, each
     with its own drift + twinkle. Distinct from .hero__particles (the
     canvas field, which is pointer-reactive); these are cheap, ambient,
     and never touched by JS. */
  .hero__motes i {
    position: absolute;
    border-radius: 50%;
    background: radial-gradient(circle, rgb(16 160 80 / .5) 0%, rgb(16 160 80 / 0) 74%);
    animation: hero-mote-drift 12s ease-in-out infinite;
  }
  .hero__motes i:nth-child(1) { inline-size: 6px; block-size: 6px; inset-block-start: 18%; inset-inline-start: 22%; animation-duration: 13s; animation-delay: 0s; }
  .hero__motes i:nth-child(2) { inline-size: 4px; block-size: 4px; inset-block-start: 64%; inset-inline-start: 12%; animation-duration: 16s; animation-delay: 1.4s; }
  .hero__motes i:nth-child(3) { inline-size: 5px; block-size: 5px; inset-block-start: 30%; inset-inline-start: 82%; animation-duration: 14.5s; animation-delay: .6s; }
  .hero__motes i:nth-child(4) { inline-size: 3px; block-size: 3px; inset-block-start: 78%; inset-inline-start: 68%; animation-duration: 18s; animation-delay: 2.2s; }
  .hero__motes i:nth-child(5) { inline-size: 5px; block-size: 5px; inset-block-start: 10%; inset-inline-start: 58%; animation-duration: 15s; animation-delay: .9s; }

  @keyframes hero-mesh-drift {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50%      { transform: translate(1.5%, -2%) scale(1.03); }
  }
  @keyframes hero-glow-a {
    0%, 100% { transform: translate(0, 0); }
    50%      { transform: translate(3%, 4%); }
  }
  @keyframes hero-glow-b {
    0%, 100% { transform: translate(0, 0); }
    50%      { transform: translate(-4%, -3%); }
  }
  @keyframes hero-mote-drift {
    0%, 100% { opacity: .25; transform: translateY(0); }
    50%      { opacity: .7;  transform: translateY(-14px); }
  }

  @media (prefers-reduced-motion: reduce) {
    .hero__mesh, .hero__glow, .hero__motes i { animation: none; }
  }

  /* Same disclosure the existing glass surfaces already follow. */
  @media (prefers-reduced-transparency: reduce) {
    .hero__noise { display: none; }
  }
}


/* ==========================================================================
   COPY
   ========================================================================== */
@layer components {

  /* ---------- BADGE ------------------------------------------------------
     Replaces the naked letterspaced eyebrow. A bordered pill has a shape,
     and shape is what stops the top of the composition reading as loose
     fragments hovering above the headline. */
  .hero__badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    margin-block-end: var(--sp-6);
    padding: 7px 16px 7px 12px;
    border-radius: var(--r-full);
    border: 1px solid var(--signal-200);
    background: var(--glass-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    box-shadow: var(--inset-top, inset 0 1px 0 rgb(255 255 255 / .8)), 0 1px 2px rgb(7 26 18 / .04),
                0 0 0 1px rgb(16 160 80 / .04);
    color: var(--signal-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    max-inline-size: none;
  }
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .hero__badge { background: var(--signal-050); }
  }
  .hero__badge-dot {
    inline-size: 6px; block-size: 6px; flex: none;
    border-radius: var(--r-full);
    background: var(--signal-500);
    box-shadow: 0 0 0 3px rgb(16 160 80 / .16);
    animation: hero-badge-pulse 2.6s ease-in-out infinite;
  }
  @keyframes hero-badge-pulse {
    0%, 100% { box-shadow: 0 0 0 3px rgb(16 160 80 / .16); }
    50%      { box-shadow: 0 0 0 5px rgb(16 160 80 / .1); }
  }
  @media (prefers-reduced-motion: reduce) {
    .hero__badge-dot { animation: none; }
  }

  /* ---------- TITLE ------------------------------------------------------
     Materially smaller than the first pass, which topped out at 96px and
     bullied everything beneath it into looking like footnotes. Capping at
     60px keeps it commanding while leaving the sub-copy, the buttons and
     the product panel enough presence to belong to the same composition. */
  .hero__title {
    font-size: clamp(2.25rem, 1.35rem + 3.6vw, 4.1rem);
    line-height: 1.02;
    letter-spacing: -0.034em;
    color: var(--ink-900);
    margin-block-end: var(--sp-5);
    max-inline-size: 15ch;
  }

  /* The second line carries the brand green.

     This reverses the "green is earned" restraint of the original design
     system, and it is the right call for THIS brand: the product's own
     login screen already sets its headline emphasis in green, and matching
     it makes the marketing page and the app read as one product. Data
     semantics survive because gain/loss use --data-* tokens, which are a
     different hue family from --signal-*. */
  .hero__accent {
    background: linear-gradient(100deg, var(--signal-700) 0%, var(--signal-500) 55%, var(--signal-600) 100%);
    background-size: 200% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: hero-accent-sheen 7s ease-in-out infinite;

    /* DESCENDER FIX — not cosmetic padding, load-bearing.

       background-clip:text paints the gradient ONLY inside this element's
       own background box. The title's tight line-height (1.02) makes that
       box shorter than the font's natural content area — measured: 36.72px
       box vs 45px of real ascent+descent at 36px — so descenders (g, y, p,
       q, j) hang ~0.115em below the box's bottom edge. Because `color` is
       transparent, the part of a glyph with no gradient behind it renders
       as NOTHING rather than as ink: the tail of the g in "single" simply
       disappeared. The non-gradient first line never showed this, which is
       what makes the bug look like a clipping issue when it isn't one.

       Padding grows the background box past the descenders; the matching
       negative margin keeps this line's layout position, .hero__line's
       height and the gap to .hero__rule exactly as they were. Deliberately
       kept below .hero__line's own .22em bottom padding so the
       overflow:hidden reveal clip still never reaches it. */
    padding-block-end: .18em;
    margin-block-end: -.18em;
  }
  @keyframes hero-accent-sheen {
    0%, 100% { background-position: 0% 50%; }
    50%      { background-position: 100% 50%; }
  }
  @media (prefers-reduced-motion: reduce) {
    .hero__accent { animation: none; }
  }
  @supports not (background-clip: text) {
    .hero__accent { background: none; color: var(--signal-600); }
  }

  /* The accent bar under the headline — carried over from the login screen,
     where it does the same job of terminating the headline block. */
  .hero__rule {
    display: block;
    inline-size: 56px;
    block-size: 4px;
    border-radius: var(--r-full);
    background: var(--signal-600);
    margin-block-end: var(--sp-6);
  }

  /* Per-line mask reveal. The outer span clips; the inner one travels.
     Splitting is authored in the markup rather than computed at runtime —
     a two-line headline does not need a splitter library, and hand-authored
     line breaks are the only way to guarantee the break lands where the
     designer intended at every width. */
  /* padding-block-end clears descenders (g, y, p, q, j) from the
     overflow:hidden clip below — .06em wasn't enough room and was
     shaving the tail off "single" at every size; .22em is the standard
     safe margin for a sans-serif descender at this line-height. */
  .hero__line { display: block; overflow: hidden; padding-block-end: .22em; }
  .hero__line > span { display: block; will-change: transform; }

  .hero__lead {
    font-size: clamp(1rem, .95rem + .35vw, 1.1875rem);
    line-height: 1.6;
    color: var(--ink-500);
    max-inline-size: 54ch;
    margin-block-end: var(--sp-4);
    text-wrap: pretty;
  }

  /* Promoted out of the lead paragraph into its own line. Buried inside the
     body copy it read as a trailing afterthought; on its own it is the
     sharpest sentence in the section. */
  .hero__claim {
    font-size: clamp(1rem, .95rem + .3vw, 1.125rem);
    font-weight: var(--fw-semibold);
    color: var(--ink-800);
    letter-spacing: -0.015em;
    margin-block-end: var(--sp-8);
    max-inline-size: none;
  }

  .hero__actions {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    inline-size: 100%;
    max-inline-size: 22rem;
    margin-block-end: var(--sp-5);
  }

  /* ---------- HERO CTAS ---------------------------------------------
     Scoped additions on top of the shared .ss-btn system — never edits
     .ss-btn itself, so nav, footer and every other CTA on the site is
     untouched. The lift is done with shadow, not transform: the primary
     CTA is also .ss-magnetic, and cursor.js drives that element's own
     `x`/`y` transform every pointermove via gsap.quickTo — a CSS
     `:hover { transform }` on the same element would fight it and lose
     almost every frame. Shadow and border-color have no such owner. */
  .hero__cta {
    position: relative;
    overflow: hidden;
    transition: box-shadow 320ms var(--ease-out-quad),
                border-color 320ms var(--ease-out-quad),
                background-color 320ms var(--ease-out-quad);
  }
  .hero__cta--primary {
    box-shadow: var(--shadow-sm), 0 0 0 0 rgb(16 160 80 / 0);
  }
  @media (hover: hover) and (pointer: fine) {
    .hero__cta--primary:hover {
      box-shadow: var(--shadow-md), 0 14px 36px -8px rgb(11 122 62 / .45);
    }
  }
  .hero__cta--ghost {
    background: var(--paper);
  }
  @media (hover: hover) and (pointer: fine) {
    .hero__cta--ghost:hover {
      border-color: var(--signal-300);
      box-shadow: var(--shadow-sm), 0 10px 28px -10px rgb(16 160 80 / .22);
    }
  }
  /* Sheen sweep — its own pseudo-element transform, independent of the
     button's own (magnetic-owned) transform, so the two never collide. */
  .hero__cta::after {
    content: "";
    position: absolute;
    inset-block: 0;
    inline-size: 60%;
    inset-inline-start: -70%;
    background: linear-gradient(100deg, transparent 0%, rgb(255 255 255 / .35) 50%, transparent 100%);
    transform: skewX(-20deg);
    pointer-events: none;
  }
  @media (hover: hover) and (pointer: fine) {
    .hero__cta:hover::after {
      inset-inline-start: 130%;
      transition: inset-inline-start 650ms var(--ease-out-quad);
    }
    .hero__cta::after { transition: inset-inline-start 0ms; }
  }
  @media (prefers-reduced-motion: reduce) {
    .hero__cta::after { display: none; }
  }

  /* ---------- TRUST BAR ---------------------------------------------
     A single glass strip rather than three loose bullet points — reads as
     one credential, not three afterthoughts. */
  .hero__micro {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: var(--sp-2) var(--sp-5);
    margin-block-end: var(--sp-2);
    padding: 10px var(--sp-5);
    border-radius: var(--r-full);
    background: var(--glass-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    border: 1px solid rgb(255 255 255 / .7);
    box-shadow: var(--inset-top, inset 0 1px 0 rgb(255 255 255 / .8)), var(--shadow-sm);
    font-size: var(--fs-xs);
    color: var(--ink-500);
    max-inline-size: none;
  }
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .hero__micro { background: var(--paper); }
  }
  .hero__micro span { display: inline-flex; align-items: center; gap: 6px; font-weight: var(--fw-medium); }
  .hero__micro .ss-icon {
    inline-size: 13px; block-size: 13px; flex: none;
    color: var(--signal-600);
  }

  .hero__disclaimer {
    margin-block-start: var(--sp-6);
    font-size: var(--fs-xs);
    color: var(--ink-400);
    max-inline-size: 62ch;
    text-align: center;
  }

  /* ---------- PILLARS ----------------------------------------------------
     Four capabilities, mirroring the pattern already on the login screen.
     Compositionally this is the load-bearing addition: it bridges the copy
     block and the product panel, which previously sat in the same column
     with ~96px of nothing between them and read as two unrelated objects.

     Hairline rules top and bottom, no filled container — a tinted box here
     would compete with the browser panel directly beneath it. */
  .hero__pillars {
    list-style: none;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-3);
    inline-size: 100%;
    max-inline-size: 640px;
    margin-block-start: var(--sp-8);
    padding: 0;
    border-block: 0;
  }
  /* Glass feature cards, replacing the plain hairline-ruled strip. Each
     tile is its own small surface — matching the glass budget used by the
     stage's floating cards, but a shade lighter since there are four of
     them on screen at once rather than two. */
  .hero__pillars li {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    text-align: center;
    padding: var(--sp-4) var(--sp-3);
    border-radius: var(--r-lg);
    background: var(--glass-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    border: 1px solid rgb(255 255 255 / .7);
    box-shadow: var(--inset-top, inset 0 1px 0 rgb(255 255 255 / .8)), var(--shadow-sm);
    transition: box-shadow var(--dur-base) var(--ease-out-quad),
                border-color var(--dur-base) var(--ease-out-quad),
                background-color var(--dur-base) var(--ease-out-quad);
  }
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .hero__pillars li { background: var(--paper); }
  }
  @media (hover: hover) and (pointer: fine) {
    .hero__pillars li:hover {
      border-color: var(--signal-200);
      box-shadow: var(--inset-top, inset 0 1px 0 rgb(255 255 255 / .8)), var(--shadow-md),
                  0 0 0 1px rgb(16 160 80 / .06);
    }
    .hero__pillars li:hover .ss-icon { color: var(--signal-500); }
  }
  .hero__pillars .ss-icon {
    inline-size: var(--icon-md);
    block-size: var(--icon-md);
    color: var(--signal-600);
    margin-block-end: var(--sp-1);
    transition: color var(--dur-base) var(--ease-out-quad);
  }
  .hero__pillars b {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--ink-900);
    letter-spacing: -0.01em;
  }
  .hero__pillars span {
    font-size: var(--fs-xs);
    color: var(--ink-400);
  }
}


/* ==========================================================================
   STAGE — the product shot and its reflection
   ========================================================================== */
@layer components {

  /* ---------- ROW / COPY / STAGEWRAP -----------------------------------
     New wrapper layer around the existing copy block and .hero__stage.
     Mobile/tablet: identical to the old behaviour — a single centred
     column, copy above stage. ≥1024px: splits into the two-column
     layout, text left, stage right. .hero__stage itself is never
     targeted by any rule in this block — only its new parent. */
  .hero__row {
    display: flex;
    flex-direction: column;
    align-items: center;
    inline-size: 100%;
  }
  .hero__copy {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    inline-size: 100%;
  }
  .hero__stagewrap {
    position: relative;
    inline-size: 100%;
  }

  .hero__stage {
    position: relative;
    inline-size: 100%;
    margin-block-start: var(--sp-9);
    /* 3D context for the pointer tilt. 2200px is deliberately long: a short
       perspective exaggerates rotation into the "tilted mockup" look this
       design explicitly rejects. */
    perspective: 2200px;
  }

  /* ---------- PRODUCT SHOT ---------------------------------------------
     A single art-directed export (assets/hero.png) rather than a coded
     mockup. object-fit:contain, not cover — the source already includes
     its own drop shadow and platform, so cropping it is never safe. */
  .hero__shot {
    display: block;
    inline-size: 100%;
    block-size: auto;
    max-inline-size: 620px;
    margin-inline: auto;
    position: relative;
    z-index: 2;
    transform-style: preserve-3d;
    will-change: transform;
    filter: drop-shadow(0 32px 64px rgb(7 26 18 / .16));
  }

  /* ---------- REFLECTION ------------------------------------------------
     Grounding, not a mirror. If it reads AS a reflection it is too strong. */
  .hero__reflection {
    position: absolute;
    inset-block-start: 100%;
    inset-inline: 6%;
    block-size: 76px;
    z-index: 1;
    border-radius: 0 0 var(--r-2xl) var(--r-2xl);
    background: linear-gradient(180deg, rgb(7 26 18 / .085) 0%, rgb(7 26 18 / 0) 100%);
    filter: blur(12px);
    pointer-events: none;
  }
}


/* ==========================================================================
   FLOATING LAYER — premium fact cards docked around the product shot.
   The brief's "floating information cards", kept honest: every card states
   a fact already established elsewhere on this page (the trust strip, the
   pillars) rather than an invented number. No Trustpilot score, no
   download count, no user count — this project's standing rule (see the
   testimonials section's own comment on this) is that no unverifiable
   third-party number ever appears.
   ========================================================================== */
@layer components {

  /* ---------- ORBIT CARDS ----------------------------------------------
     Three, docked to the shot's corners rather than scattered — this
     file's own standing rule elsewhere is "docked, not scattered". Hidden
     below the two-column breakpoint, where there is no spare margin around
     the shot for them to sit in without overlapping it. */
  .hero__orbit {
    display: none;
    position: absolute;
    z-index: 4;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-lg);
    background: var(--glass-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    border: 1px solid rgb(255 255 255 / .7);
    box-shadow: var(--inset-top, inset 0 1px 0 rgb(255 255 255 / .8)), var(--shadow-lg),
                0 0 32px -12px rgb(16 160 80 / .35);
    white-space: nowrap;
    animation: hero-orbit-bob 5.5s ease-in-out infinite;
  }
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .hero__orbit { background: rgb(255 255 255 / .97); }
  }
  .hero__orbit .ss-icon {
    inline-size: var(--icon-md); block-size: var(--icon-md);
    color: var(--signal-600); flex: none;
  }
  .hero__orbit b {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--ink-900);
    letter-spacing: -0.01em;
  }
  .hero__orbit span {
    display: block;
    font-size: 11px;
    color: var(--ink-400);
  }
  .hero__orbit--a { animation-delay: 0s; }
  .hero__orbit--b { animation-delay: 1.4s; }
  .hero__orbit--c { animation-delay: .7s; }

  @keyframes hero-orbit-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-9px); }
  }
  @media (prefers-reduced-motion: reduce) {
    .hero__orbit { animation: none; }
  }
}


/* ==========================================================================
   SCROLL CUE
   ========================================================================== */
@layer components {

  .hero__scroll {
    position: relative;
    z-index: 2;
    display: none;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-2);
    margin-block-start: var(--sp-10);
    font-size: var(--fs-eyebrow);
    letter-spacing: var(--ls-eyebrow);
    text-transform: uppercase;
    font-weight: var(--fw-semibold);
    color: var(--ink-300);
    text-decoration: none;
  }
  .hero__scroll:hover { color: var(--ink-500); }

  .hero__track {
    position: relative;
    inline-size: 1px;
    block-size: 40px;
    background: linear-gradient(180deg, var(--ink-200), transparent);
    overflow: hidden;
  }
  .hero__track i {
    position: absolute;
    inset-inline-start: -1px;
    inline-size: 3px; block-size: 9px;
    border-radius: var(--r-full);
    background: var(--signal-500);
    translate: 0 -12px;
  }
}


/* ==========================================================================
   ENTRANCE FROM-STATES

   ---------------------------------------------------------------------------
   ONE OWNER PER PROPERTY
   ---------------------------------------------------------------------------
   Every rule below is scoped to [data-hero="pending"] and nothing else.

   `pending` is the only state in which CSS hides anything. The moment
   js/hero.js takes over it applies the identical from-states with
   gsap.set() and flips to `playing`, where CSS has no opinion at all.

   Two owners on one transform is the classic source of "the headline
   stops halfway": GSAP initialises `yPercent` by reading the computed
   transform, which the stylesheet is simultaneously asserting, and the
   two compose instead of one replacing the other.

   Consequently NO base component rule may set opacity:0 on anything that
   exists in the shipped HTML — otherwise a visitor with JavaScript
   disabled (data-hero never set) gets an invisible element forever.
   The only exceptions are elements JS creates or owns outright: the
   scan sweep, the transfer pills, the particle canvas, the spotlight.
   ---------------------------------------------------------------------------
   ========================================================================== */

:root[data-hero="pending"] .hero__line > span { transform: translateY(108%); }

:root[data-hero="pending"] .hero__rule { transform: scaleX(0); }

:root[data-hero="pending"] .hero__badge,
:root[data-hero="pending"] .hero__lead,
:root[data-hero="pending"] .hero__claim,
:root[data-hero="pending"] .hero__pillars li,
:root[data-hero="pending"] .hero__actions > *,
:root[data-hero="pending"] .hero__micro,
:root[data-hero="pending"] .hero__disclaimer,
:root[data-hero="pending"] .hero__scroll,
:root[data-hero="pending"] .hero__orbit,
:root[data-hero="pending"] .hero__reflection { opacity: 0; }

:root[data-hero="pending"] .hero__shot {
  opacity: 0;
  transform: translateX(120px) scale(.965);
}


/* ==========================================================================
   RESPONSIVE
   Mobile is the design. Desktop is the same design with more room — the
   only structural change is the panel split at lg.

   ---------------------------------------------------------------------------
   WHY @layer components AND NOT @layer layout
   ---------------------------------------------------------------------------
   Cascade layers outrank both specificity AND source order. The declared
   order in style.css is:

       @layer reset, tokens, base, layout, components, utilities;

   Almost every hero base rule lives in `components`. Putting these
   breakpoint overrides in `layout` — which sorts EARLIER — means the base
   rule wins at every viewport and the media queries silently do nothing.
   Devtools shows them matching; they just lose the cascade.

   This cost a debugging round trip. Responsive overrides must live in the
   same layer as, or a later layer than, the rules they override.
   ---------------------------------------------------------------------------
   ========================================================================== */
@layer components {

  /* ---------- ≥ 640 ---------------------------------------------------- */
  @media (min-width: 640px) {
    .hero__actions {
      flex-direction: row;
      justify-content: center;
      max-inline-size: none;
      gap: var(--sp-4);
    }
  }

  /* ---------- ≥ 768 · orbit cards get room to breathe ------------------- */
  @media (min-width: 768px) {
    .hero { padding-block-end: var(--sp-11); }
    /* Was --sp-12 (96px). The pillar strip now sits between the copy and
       the panel, so the panel no longer needs a large gap to feel separate
       — the strip is doing that job. */
    .hero__stage { margin-block-start: var(--sp-10); }
    .hero__scroll { display: inline-flex; margin-block-start: var(--sp-9); }

    /* 2×2 → 4-up, still stacked vertically per item exactly as on the
       login screen. Two columns made the strip 254px tall, which cost more
       height than the structure it bought back; four columns brings it to
       ~110px and keeps the icon-above-label rhythm intact. */
    .hero__pillars {
      grid-template-columns: repeat(4, minmax(0, 1fr));
      max-inline-size: 780px;
      margin-block-start: var(--sp-9);
      padding-block: var(--sp-6);
      gap: var(--sp-4);
    }
  }

  @media (min-width: 1024px) {
    .hero { min-block-size: min(100svh, 1000px); }

    /* ---- the two-column split ----
       The one other structural change in the whole section, alongside the
       stage's own rank/chart split at 900px. Copy left, stage right — a
       fixed-basis flex rather than a percentage grid, so both sides can
       shrink together on the narrower end of this breakpoint (1024–1180px)
       without either column overflowing. .hero__stage is never targeted
       here; only its new .hero__row/.hero__copy/.hero__stagewrap parents. */
    .hero__row {
      flex-direction: row;
      /* Top-aligned, not centred: the copy column runs materially taller
         than the stage (the pillar cards alone add ~280px), and centring
         two panels that different in height reads as a layout mistake —
         a wide, unexplained gap above and below the shorter one. Starting
         both at the same top edge is the safer, more conventional read. */
      align-items: flex-start;
      justify-content: space-between;
      gap: clamp(2rem, 3vw, 4rem);
    }
    .hero__copy {
      flex: 1 1 420px;
      max-inline-size: 480px;
      align-items: flex-start;
      text-align: left;
    }
    .hero__stagewrap {
      flex: 1 1 520px;
      min-inline-size: 0;
    }

    /* Smaller than the stacked layout's headline, and NOT capped by ch —
       this column is ~420–480px wide, not the full container, and the
       longest authored line ("before a single rupee.") is 22 characters.
       The ch cap that works for the full-width stacked layout would force
       a second, unauthored wrap inside this narrower rail; sizing by the
       column's own width instead keeps each authored line on one line. */
    .hero__title { font-size: clamp(1.875rem, 1.4rem + 1.2vw, 2.75rem); max-inline-size: none; }
    .hero__lead { font-size: clamp(1rem, .92rem + .3vw, 1.125rem); max-inline-size: 40ch; }
    .hero__claim { max-inline-size: 34ch; }

    .hero__actions { justify-content: flex-start; max-inline-size: none; }
    .hero__micro { justify-content: flex-start; }
    /* Back to 2-up, and narrower — .hero__pillars now lives outside
       .hero__row entirely (a sibling below it, not inside .hero__copy), so
       it centres on the page via .hero__inner's own align-items:center,
       the same way .hero__disclaimer and .hero__scroll already do. No
       align-self/margin trick needed here as a result. */
    .hero__pillars { grid-template-columns: repeat(2, minmax(0, 1fr)); max-inline-size: 420px; }

    .hero__reflection { block-size: 92px; inset-inline: 10%; }

    /* ---- orbit cards appear ----
       Three, docked to three different corners of the shot so none of
       them collide: upper-left (overlapping the shot itself, matching
       the reference), outer right-mid, and upper-right above the
       corner. Overhang capped at -8%/-11%, which stays inside the
       gutter at every viewport this breakpoint covers. */
    .hero__orbit { display: flex; }
    .hero__orbit--a { inset-block-start: -6%;  inset-inline-start: -8%; }
    .hero__orbit--b { inset-block-start: 42%;  inset-inline-end: -9%; }
    .hero__orbit--c { inset-block-start: -9%;  inset-inline-end: 6%; }
  }

  @media (min-width: 1024px) and (max-width: 1180px) {
    /* The narrow end of the split — trims the orbit cards' overhang so
       they never reach the .hero clip boundary on a 1024px laptop. */
    .hero__orbit--a { inset-inline-start: -3%; }
    .hero__orbit--b { inset-inline-end: -3%; }
    .hero__orbit--c { inset-inline-end: 2%; }
  }

  @media (min-width: 1280px) {
    .hero__copy { max-inline-size: 540px; }
    .hero__title { font-size: clamp(2.25rem, 1.7rem + 1.1vw, 3.1rem); }
  }

  /* ---------- SHORT VIEWPORTS ------------------------------------------
     1366×768 laptops. The primary CTA must never require scrolling. */
  @media (min-width: 1024px) and (max-height: 820px) {
    .hero { min-block-size: 0; padding-block: calc(var(--sp-9) + var(--nav-h)) var(--sp-10); }
    .hero__stage { margin-block-start: var(--sp-9); }
    .hero__scroll { margin-block-start: var(--sp-7); }
  }

  /* ---------- COARSE POINTER -------------------------------------------
     No tilt, no spotlight, no magnetic pull — none of them have an input
     device. The entrance timeline still runs. */
  @media (pointer: coarse) {
    .hero__spot { display: none; }
  }

  /* ---------- REDUCED MOTION -------------------------------------------
     T1: the end state IS the design. The composition was built to work as
     a still — that is the test the animation had to pass.

     The inline <head> script never sets data-hero="pending" when reduce is
     on, so nothing is hidden in the first place. These rules finish the
     job: bars filled, candles up, chips placed, no idle loops. */
  @media (prefers-reduced-motion: reduce) {
    .hero__particles { display: none; }
    .hero__spot { display: none; }

    .hero__orbit { opacity: 1 !important; transform: none !important; }
    .hero__shot { opacity: 1 !important; transform: none !important; }
  }
}


/* ==========================================================================
   FAILSAFE / STATIC / DONE

   No rules needed. Because `pending` is the only state that hides anything
   and no base rule sets opacity:0 on shipped markup, every other state —
   absent, static, playing, done — is already the finished composition.

   That is the test: delete every line of JavaScript on this page and the
   hero is complete. It is also why the 1400ms failsafe in <head> is only
   ever a nicety rather than the thing standing between the visitor and a
   blank screen.
   ========================================================================== */
