/* ==========================================================================
   Text Ads landing page — page-scoped styles
   Spec: text-ads.md  ·  Figma frame 9965:5606 ("Ad Format (Text)"), 1920px

   Every value in this file is taken from the Figma node data, not eyeballed.
   The artboard is 1920 wide with content inset 200px, so the content column
   is 1520px. Type and spacing tokens below use `clamp(min, Npx/19.2 vw, max)`
   where `max` is the literal design px — at a 1920 viewport the page renders
   at 1:1 with the artboard, and scales down smoothly from there.

   SCOPING RULE (text-ads.md §2.1): every selector is nested under
   .text-ads-page / .text-ads-body. Nothing here may reach .th-header,
   .sticky-wrapper or .th-menu-wrapper — the shared header is used verbatim
   and is already #1E1E1E, so it needs no overrides.

   This sheet loads AFTER assets/css/style.css so its rules win.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */
:root {
  /* Colour — read off the Figma nodes */
  --c-page:        #070C18;
  --c-surface:     #1E1E1E;
  --c-surface-alt: #121212;
  --c-accent:      #E9FD45;
  --c-blue:        #3B82F6;
  --c-blue-deep:   #0050D4;
  --c-grey:        #9CA3AF;
  --c-grey-alt:    #6B7280;
  --c-light-gray:  #D2D2D2;
  --c-white:       #FFFFFF;
  --c-light-bg:    #F9FAFB;
  --c-green:       #128446;

  --c-hairline: rgba(156, 163, 175, 0.28);

  --font-heading: 'Be Vietnam Pro', sans-serif;
  --font-body:    'Inter', sans-serif;
  --font-script:  'Caveat', cursive;

  /* Type — max = literal design px */
  --fs-stat:    clamp(48px, 5vw,     96px);  /* stat numbers, Black    */
  --fs-cta:     clamp(40px, 3.333vw, 64px);  /* S10 panel heading      */
  --fs-section: clamp(28px, 2.5vw,   48px);  /* every section heading  */
  --fs-marquee: clamp(18px, 1.875vw, 36px);  /* marquee items          */
  --fs-32:      clamp(22px, 1.667vw, 32px);  /* hero H1, row titles, FAQ q */
  --fs-24:      clamp(17px, 1.25vw,  24px);  /* card titles, buttons, labels */
  --fs-21:      clamp(16px, 1.094vw, 21px);  /* body copy              */
  --fs-20:      clamp(15px, 1.042vw, 20px);  /* hero buttons, pill num */
  --fs-18:      clamp(15px, 0.938vw, 18px);  /* S7 card copy           */
  --fs-14:      clamp(12px, 0.729vw, 14px);  /* S8 card copy, pill label */

  /* Tracking scales with size — from the Figma `tracking-[]` values */
  --tr-stat:    -0.48px;
  --tr-cta:     -0.32px;
  --tr-section: -0.24px;
  --tr-32:      -0.16px;
  --tr-24:      -0.12px;
  --tr-21:      -0.105px;
  --tr-20:      -0.1px;
  --tr-14:      -0.07px;

  /* Radius */
  --r-card:  20px;
  --r-panel: clamp(28px, 4.167vw, 80px);  /* S10 panel, r-80 */
  --r-faq:   clamp(28px, 3.75vw,  72px);  /* FAQ row, r-72   */
  --r-pill:  34px;

  /* Layout — content column 1520, wide column 1680 (S3 1679, S9 120..1800) */
  --container:      1520px;
  --container-wide: 1680px;
  --gutter:         24px;

  /* Section rhythm: the artboard leaves ~250px between blocks, so half of
     that on each side of a section boundary. */
  --space-section: clamp(48px, 6.5vw, 125px);

  --dur-fast:   150ms;
  --dur-normal: 300ms;
  --ease-out:   cubic-bezier(0.16, 1, 0.3, 1);
}

/* --------------------------------------------------------------------------
   2. Page base
   -------------------------------------------------------------------------- */
body.text-ads-body {
  background-color: var(--c-page);
  color: var(--c-grey);
  font-family: var(--font-body);
  overflow-x: hidden;
}

.text-ads-page {
  position: relative;
  background-color: var(--c-page);
  color: var(--c-grey);
  font-family: var(--font-body);
}

.text-ads-page *,
.text-ads-page *::before,
.text-ads-page *::after { box-sizing: border-box; }

/* Zero-specificity so `.ta-bg { max-width: none }` and any full-bleed plate
   can beat it. As `.text-ads-page img` this rule is (0,1,1) and silently
   squeezed every background wider than its container back to 100%. */
:where(.text-ads-page img) { max-width: 100%; height: auto; }

/* Reset through :where() so it carries ZERO specificity — a plain
   `.text-ads-page h1 {margin:0}` would be (0,1,1) and would beat every
   single-class rule below it, including the hero's `margin-inline: auto`. */
.text-ads-page :where(h1, h2, h3, h4, p) { margin: 0; }

.text-ads-page :where(h1, h2, h3, h4) {
  font-family: var(--font-heading);
  font-weight: 600;
  color: var(--c-white);
  line-height: normal;
}

.text-ads-page a { text-decoration: none; }

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */
.ta-container {
  position: relative;
  z-index: 2;
  width: 100%;
  /* 1520 is the artboard's CONTENT width (content sits at x 200..1720 with no
     inner padding), so the gutter is added on top rather than eaten out of it. */
  max-width: calc(var(--container) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}
/* The wide column is a CONTENT width in the artboard: S3's grid is 1679 wide
   and S9's step band spans 120..1800, both with no inner padding. Adding the
   gutter to max-width keeps the content box at 1680 instead of 1680 minus two
   gutters — without it the S3 tablet renders ~40px short of its 1004px. */
.ta-container--wide { max-width: calc(var(--container-wide) + 2 * var(--gutter)); }

.ta-section {
  position: relative;
  padding-block: var(--space-section);
}

/* --------------------------------------------------------------------------
   4. Shared type patterns
   -------------------------------------------------------------------------- */

/* Section headings are two-tone: white lead + lime emphasis */
.ta-heading {
  font-family: var(--font-heading);
  font-size: var(--fs-section);
  font-weight: 600;
  letter-spacing: var(--tr-section);
  color: var(--c-white);
}
.ta-heading em { font-style: normal; color: var(--c-accent); }

.ta-body {
  font-family: var(--font-body);
  font-size: var(--fs-21);
  font-weight: 400;
  line-height: 1.762;   /* 37/21 */
  color: var(--c-grey);
}

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */
.ta-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-family: var(--font-heading);
  font-weight: 500;
  line-height: 1;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out),
              background-color var(--dur-normal) var(--ease-out),
              color var(--dur-normal) var(--ease-out);
}
.ta-btn:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 3px; }

/* Hero pair — px-24 py-20 r-30, 20px Medium */
.ta-btn--hero {
  font-size: var(--fs-20);
  letter-spacing: var(--tr-20);
  padding: 20px 24px;
  border-radius: 30px;
}
.ta-btn--fill {
  color: var(--c-white);
  background-color: var(--c-blue);
}
/* No glow on hover — asked for directly. The lift stays; it was the blue
   drop shadow under it that went. */
.ta-btn--fill:hover,
.ta-btn--fill:focus-visible {
  color: var(--c-white);
  transform: translateY(-2px);
}
.ta-btn--outline {
  color: var(--c-blue);
  background-color: var(--c-page);
  border-color: var(--c-blue);
}
.ta-btn--outline:hover,
.ta-btn--outline:focus-visible {
  color: var(--c-white);
  background-color: var(--c-blue);
  transform: translateY(-2px);
}

.ta-btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  align-items: center;
}

/* Section-level CTA rows (S5 "Run Text Ads Now", S8 "Advertise With Text Ad").
   Centred under the grid they close, with the section's own rhythm above.

   TABLET AND PHONE ONLY. Hidden from 1200 up — the same breakpoint the rest of
   this page splits desktop on. On a wide screen the hero's own CTA row and the
   S10 panel are both within reach of these sections, so a third button between
   them is repetition; on a narrow one those two are thousands of pixels of
   scrolling apart and the reader needs a way out where they are.

   From 1200 up the row is CLIPPED rather than `display: none`d. It must not
   hold space or be seen — but on those widths the section itself is the button
   (see the CTA hit areas below), and a click target made of mousemove and
   click handlers is reachable by pointer only. Clipped, the link stays in the
   tab order and in the accessibility tree, and `:focus-within` brings the row
   back on screen the moment it is tabbed to, so the keyboard has the same way
   out the mouse has. */
.ta-section-cta {
  justify-content: center;
  margin-top: clamp(36px, 4.17vw, 80px);
}
@media (min-width: 1200px) {
  .ta-section-cta {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .ta-section-cta:focus-within {
    position: static;
    width: auto;
    height: auto;
    margin-top: clamp(36px, 4.17vw, 80px);
    overflow: visible;
    clip-path: none;
    white-space: normal;
  }
}

/* --------------------------------------------------------------------------
   CTA HIT AREAS — a section that IS the button (desktop only)

   Two blocks carry `data-ta-cta` + `data-ta-cta-href` in the markup: S5's
   benefits column and S8's verticals section. On a fine pointer at 1200 and up
   the pointer is replaced inside them by a dot and a lagging pill reading the
   CTA's own words, and a click anywhere opens the register page in a new tab
   (main.js initSectionCta). Touch and narrow screens never see any of it —
   they get the button in .ta-section-cta instead.

   The dot and the pill are appended to <body>, OUTSIDE #smooth-content: a
   transform on that wrapper makes it the containing block for anything fixed
   inside it, which would make these two scroll away with the page. Same reason
   smoother.js lifts `.scroll-top` out.
   -------------------------------------------------------------------------- */
.ta-cta-hit { cursor: none; }

.ta-cta-dot,
.ta-cta-pill {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-normal) var(--ease-out);
}
.ta-cta-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--c-accent);
}
.ta-cta-pill {
  z-index: 9998;
  padding: 9px 18px;
  border-radius: var(--r-pill);
  background-color: var(--c-blue);
  color: var(--c-white);
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 15px;
  letter-spacing: 0.02em;
  white-space: nowrap;
  box-shadow: 0 14px 34px -14px rgba(59, 130, 246, 0.9);
}
.ta-cta-dot.is-on,
.ta-cta-pill.is-on { opacity: 1; }

/* Nothing here exists for a coarse pointer, and the class is only ever added
   by JS that has already checked — this is the belt to that braces. */
@media (pointer: coarse), (max-width: 1199px) {
  .ta-cta-hit { cursor: auto; }
  .ta-cta-dot,
  .ta-cta-pill { display: none; }
}

/* --------------------------------------------------------------------------
   6. Atmosphere layers
   -------------------------------------------------------------------------- */
.ta-bg {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  user-select: none;
}

/* MUST stay two classes deep. The sitewide sheet carries
   `img:not([draggable]) { max-width: 100% }` at specificity (0,1,1), which
   beats a bare `.ta-bg` (0,1,0) no matter what order the sheets load in —
   and silently squeezes every full-bleed plate back to its container width.
   `.text-ads-page .ta-bg` is (0,2,0) and wins. */
.text-ads-page .ta-bg { max-width: none; }

/* The wide aurora/glow plates are authored far wider than the artboard and
   sit centred on it; `mix-blend-mode: plus-lighter` is what Figma uses. */
.ta-bg--blend { mix-blend-mode: plus-lighter; }

/* --------------------------------------------------------------------------
   6b. Aurora wave band

   One piece of artwork (Frame 2132, 1920x920 — authored at artboard width)
   is the aurora for every band section. It is a wave with a crest, so it is
   pinned full-bleed at 100vw and flipped vertically where the design puts
   the band at the top of a section instead of the bottom.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   S1 — Hero
   -------------------------------------------------------------------------- */
/* `overflow: visible` on purpose: the ad card flies OUT of this section and
   down into the S3 tablet. Only the grid plate needed the clip and it now
   carries its own (.ta-hero__gridclip) — the blooms are 882 tall against an
   891 hero and never spilled. */
.ta-hero {
  position: relative;
  overflow: visible;
  padding-top: clamp(48px, 9.84vw, 189px);   /* card top = 189 */
  padding-bottom: clamp(40px, 5.4vw, 103px); /* buttons end 779, ribbon 882 */
}

.ta-hero__gridclip {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
  pointer-events: none;
}

/* The pointer-driven light (assets/text-ads/js/hero-shader.js).

   z-index 1 puts it between the grid plate (0) and .ta-hero__inner (30), and
   `plus-lighter` is the same additive-light reasoning the two blooms use: the
   grid has to read straight THROUGH this, not be covered by it. Because the
   canvas is its own stacking context the blend reaches the plate and the page
   ground beneath, and nothing here disturbs the blooms — they are siblings at
   z-index 0 and are composited before it.

   No clip wrapper: a canvas cannot overflow its own box, and .ta-hero is
   deliberately `overflow: visible` for the card flight.

   The element is authored in the markup rather than injected so it costs no
   layout shift, and the script REMOVES it when it cannot run (no WebGL, or
   prefers-reduced-motion) — the hero's authored backdrop is the fallback. */
.ta-hero__shader {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  /* TEMPORARILY DISABLED — not removed. The canvas, its CSS and
     hero-shader.js all stay exactly as they are; this one line switches the
     pointer light off. Delete it to switch it back on, nothing else.

     The script parks itself as a result: it only animates while its
     IntersectionObserver reports the canvas on screen, and a `display: none`
     element never intersects, so the loop sleeps instead of burning frames on
     something nobody can see. */
  display: none;
  pointer-events: none;
  mix-blend-mode: plus-lighter;
}

/* UPPER layer, over the grid plate — see the S4 band for the full reasoning.
   The plate and both blooms all took `z-index: 0` from `.ta-bg`, so paint order
   fell back to DOM order and the grid, last in the markup, sat ON TOP of the
   blue light and greyed it down. The artboard has the grid reading straight
   THROUGH the bloom, which is additive light, not a cover — hence
   `plus-lighter`. `.ta-hero` is `position: relative` with no z-index and so is
   not a stacking context, which is what lets the blend reach the plate. */
.text-ads-page .ta-hero__glow {
  top: 0;
  /* HALF THE VIEWPORT, full height. The box and the light are two different
     things now: the canvas is 50vw x 100% so the shader has room to throw a
     long ray, and the MASK (below) is what holds the lit part to the
     artboard's own reach. Sizing the box to the artboard instead put the
     canvas edge right where the light was still faintly on, which is the
     vertical line that showed. */
  width: 50vw;
  height: 100%;
  z-index: 1;
  mix-blend-mode: plus-lighter;
  /* Fitted against the artboard's own edge profile — additive light at full
     strength overshoots it by ~15%. `top: 0` and the native 882 height are
     already what the artboard has: the bloom's bright lobe lands at y355 in the
     design and y360 here. */
  opacity: 0.87;
}
.ta-hero__glow--left  { left: 0; }
.ta-hero__glow--right { right: 0; }

/* The exported blooms stay as the FALLBACK. side-rays.js clears these the
   moment it has a live context and a linked program, so they are what a
   visitor sees only when WebGL is unavailable — the same picture the page
   shipped with before the shader replaced it. */
/* THE MASK IS WHAT SETS THE RANGE. The shader throws its rays from a light
   placed far off-canvas — that is what makes them read as rays rather than a
   spot — but it also means distance barely varies across the box, so no
   falloff value shapes the field into the artboard's edge bloom. Measured:
   the Figma glow drops 28 -> 1 across its width, the raw shader 17 -> 8.

   So the artboard's own bloom shapes it. rays-mask-left/right.webp are built
   from Frame 2154/2155 — the Figma sources the .webp blooms came from — with
   two changes:

     - the star specks are median-filtered out. As a mask they were holes, and
       a hole in a mask shows the RAY colour, so every star came through as a
       blue dot instead of the design's own point of light. The stars belong
       to the artwork, not to the light's shape.
     - the inner 28% is ramped to a true zero. The source bloom still carries
       a pixel or two of alpha at its inner edge, and against `plus-lighter`
       on a dark ground that is enough to draw a faint vertical line where the
       mask stops. It now reaches 0 well before anything can see an edge.

   Sized to the artboard's 29.2vw and pinned to its own edge, so the LIGHT
   keeps the design's reach while the canvas around it is simply transparent.

   Scoped to .ta-rays-live (added by side-rays.js once a context and program
   both exist) so the no-WebGL fallback is never masked by itself. */
.text-ads-page .ta-hero__glow--left.ta-rays-live,
.text-ads-page .ta-stats__glow--left.ta-rays-live {
  -webkit-mask-image: url('../images/rays-mask-left.webp');
          mask-image: url('../images/rays-mask-left.webp');
  -webkit-mask-position: left top;
          mask-position: left top;
}
.text-ads-page .ta-hero__glow--right.ta-rays-live,
.text-ads-page .ta-stats__glow--right.ta-rays-live {
  -webkit-mask-image: url('../images/rays-mask-right.webp');
          mask-image: url('../images/rays-mask-right.webp');
  -webkit-mask-position: right top;
          mask-position: right top;
}
.text-ads-page .ta-hero__glow.ta-rays-live,
.text-ads-page .ta-stats__glow.ta-rays-live {
  -webkit-mask-size: clamp(240px, 29.2vw, 560px) 100%;
          mask-size: clamp(240px, 29.2vw, 560px) 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

/* STAR SPECKS — stars-left/right.webp, lifted out of the Figma glow frames.
   One element carries both, each pinned to its own edge at the artboard's
   width, sitting above the rays (z-index 2) and OUTSIDE their mask.

   They have to be their own layer: in the rays mask a speck is a hole, and a
   hole shows the RAY colour, so every star came through as a blue dot instead
   of the artwork's own point of light. The mask has them filtered out; this
   puts them back at the colour the design gives them.

   No blend mode. They are already points of light, and `plus-lighter` on a
   1px speck sitting over a bright bloom just clips it to white. */
.text-ads-page .ta-stars {
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  pointer-events: none;
  background-image: url('../images/stars-left.webp'), url('../images/stars-right.webp');
  background-position: left top, right top;
  background-size: clamp(240px, 29.2vw, 560px) 100%;
  background-repeat: no-repeat, no-repeat;
}

/* Anchored to their own edge at the artboard's 29.2vw, NOT stretched across
   the full box the canvas now occupies — a no-WebGL visitor gets the original
   pair of edge blooms back, exactly as they were framed. */
.text-ads-page .ta-hero__glow--left,
.text-ads-page .ta-stats__glow--left  { background: url('../images/glow-left.webp')  left top / clamp(240px, 29.2vw, 560px) 100% no-repeat; }
.text-ads-page .ta-hero__glow--right,
.text-ads-page .ta-stats__glow--right { background: url('../images/glow-right.webp') right top / clamp(240px, 29.2vw, 560px) 100% no-repeat; }

/* A canvas is replaced content: without this it keeps a baseline and sits a
   few px above where the <img> did. */
.text-ads-page .ta-hero__glow,
.text-ads-page .ta-stats__glow { display: block; }

/* BACK layer. grid-bg is 1919x922, pinned to the artboard top at full artboard
   width. `z-index: 0` is explicit so DOM order can never decide this again. */
.text-ads-page .ta-hero__grid {
  z-index: 0;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  /* NO opacity. Measured one grid line at a time against the render — the
     artboard's ruling peaks 7.7 levels over the surrounding dark, and the plate
     at `opacity: .32` managed 2. Even at full strength this export only reaches
     6.2, because rasterising the Figma ruling to WebP spread each hairline over
     ~3px and softened it; anything less than 1 and the grid stops reading at
     all across the centre of the band. */
}

/* 30, not 2, so the flying card clears everything it passes over on the way
   down — the marquee ribbon sits at 3 and each section's container at 2. It
   stays UNDER the site header (41) and the format switcher (40), which is
   what the reference does. Nothing else in the hero overlaps a later section,
   so raising the whole inner block costs nothing. */
/* `isolation: isolate` is load-bearing, not decoration. The two ray canvases
   are now full-bleed and blend `plus-lighter`, and a blending element pulls
   everything painted after it INSIDE THE SAME isolated group into its blend —
   a z-index of 30 does not save you, because z-index makes a stacking context
   and blending needs an isolated GROUP, which is a different thing. Without
   this line the copy and the CTA row are blended straight out of the hero:
   measured, the title's white glyphs go from 210 to 23. Isolating the content
   gives the blend a floor it cannot reach past, and changes no paint order. */
.ta-hero__inner { position: relative; z-index: 30; isolation: isolate; }

/* The element the flight writes to. Scaling from the top-left is what makes
   the tween land the card's box exactly on the tablet's card slot: the
   measured delta is corner-to-corner. */
.ta-hero__flyer {
  position: relative;
  /* Above the hero's own copy. GSAP leaves a transform on the headline and the
     CTA row after their reveal, and a transformed element paints in the
     positioned layer — so with `z-index: auto` the headline, which comes LATER
     in the markup, was drawing straight through the card as it flew past. */
  z-index: 5;
  transform-origin: top left;
  will-change: transform;
}

/* The tablet's own copy of the card is only there for no-JS, reduced motion
   and narrow layouts. Once the flight is wired the flying card lands on that
   exact spot, so the static one would show through underneath it. */
.ta-has-flight .ta-device .ta-adcard { visibility: hidden; }

/* --- Ad preview card (design node 10240:7028) ---------------------------- */
.ta-adcard {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: clamp(9px, 0.689vw, 13.229px);
  width: clamp(280px, 21.36vw, 410.104px);
  padding: clamp(18px, 1.378vw, 26.458px);
  background-color: var(--c-white);
  border-radius: clamp(18px, 1.378vw, 26.458px);
}

.ta-adcard__title {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: clamp(19px, 1.447vw, 27.781px);
  line-height: 1.2;
  color: var(--c-blue);
}

.ta-adcard__meta {
  display: flex;
  align-items: center;
  gap: clamp(5px, 0.413vw, 7.938px);
}

/* Outlined green chip — NOT a filled badge */
.ta-adcard__badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: clamp(2px, 0.138vw, 2.646px) clamp(4px, 0.345vw, 6.615px);
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: clamp(9px, 0.689vw, 13.229px);
  line-height: 1.2;
  color: var(--c-green);
  border: 1px solid var(--c-green);
  border-radius: clamp(4px, 0.276vw, 5.292px);
}

.ta-adcard__url {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: clamp(12px, 0.965vw, 18.521px);
  color: var(--c-green);
}

.ta-adcard__desc {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: clamp(14px, 1.102vw, 21.167px);
  line-height: 1.35;
  color: var(--c-grey-alt);
}

/* Figma stacks four blurred rings behind the card (blur 1.98 / 19.8 / 39.7
   and a 105.8 fill). Layered shadows reproduce that halo in one paint. */
.ta-hero__card-wrap {
  position: relative;
  width: fit-content;
  margin-inline: auto;
}

/* The card's bloom, and ONLY the card's. It used to be a four-stop
   `filter: drop-shadow` stack on the wrapper, which a filter applies to the
   whole subtree — so the handwritten note picked up a blue haze the artboard
   does not give it (measured 30 against the design's 15 at the same point).
   `box-shadow` follows the same rounded rect and leaves the note alone.

   RE-MEASURED against the design render. Stepping out from the card's left
   edge at its vertical middle, the reference reads

        px    12   24   48   72  120
        ref   86   73   50   36   23
        was   46   37   23   17   14

   — the halo was roughly half as bright AND falling off far faster, so the
   card sat flat on the section instead of glowing into it. Three stops now:
   the tight rim, the artboard's sigma-40 core, and a wide low-alpha wash that
   carries the last of it out past 100px, which is what the two-stop version
   was missing. */
/* Every alpha is multiplied by `--ta-glow`, which initCardFlight() runs from
   1 to 0 in the LEAD-IN before the card moves — the bloom is the hero's, not
   the tablet's, so it is put out before the flight rather than dragged along
   and left glowing through the device's own screen. The variable is authored
   HERE, with a `1` fallback, so the hero renders at full bloom with no JS,
   under reduced motion, and on the very first paint. */
.ta-hero__card-wrap .ta-adcard {
  box-shadow:
    0 0 14px rgba(150, 200, 255, calc(0.96 * var(--ta-glow, 1))),
    0 0 80px 9px rgba(13, 92, 255, var(--ta-glow, 1)),
    0 0 170px 12px rgba(13, 92, 255, calc(0.68 * var(--ta-glow, 1)));
}

/* --- Handwritten annotation (Caveat 24px white, node 10242:7090) --------- */
/* Design node 10242:7090 — text at x1311/y248, arrow at x1252/y304. The card
   is centred and 410 wide, so its right edge is 1165: the text sits 146px
   past it and the arrow hangs 59px back and 56px down from the text. */
.ta-hero__note {
  position: absolute;
  z-index: 3;
  left: calc(100% + clamp(24px, 7.6vw, 146px));
  top: clamp(24px, 3.07vw, 59px);
  width: max-content;
  font-family: var(--font-script);
  font-weight: 400;
  font-size: var(--fs-24);
  line-height: 1.25;
  color: var(--c-white);
}
/* The arrow is TWO nodes in the file — the curve (10242:7091, box x1252/y304,
   76.043 x 75.367) and a separate arrowhead (10242:7092, box x1244/y351.06,
   13.999 x 15.419) — and both carry a `rotate(-70.35deg) scaleY(-1)`. The
   asset that shipped was the curve's raw vector with NEITHER: unrotated, so it
   read as a vertical hook instead of a shallow sweep, and with no head on it.
   `hero-arrow.svg` is now both nodes composed at their artboard offsets, so
   the whole thing is one 84.043 x 75.367 image anchored 67px left of the note
   and 56px down. */
.ta-hero__note img {
  position: absolute;
  left: clamp(-67px, -3.49vw, -28px);
  top: clamp(30px, 2.92vw, 56px);
  width: clamp(42px, 4.377vw, 84px);
}

/* --- Headline block (w-963, gap-36, centred) ---------------------------- */
.ta-hero__copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(20px, 1.875vw, 36px);
  width: min(963px, 100%);
  margin: clamp(32px, 3.7vw, 71px) auto 0;
  text-align: center;
}

.ta-hero__title {
  font-family: var(--font-heading);
  font-weight: 500;              /* Medium, not Bold */
  font-size: var(--fs-32);
  letter-spacing: var(--tr-32);
  color: var(--c-light-gray);    /* #D2D2D2, not white */
  max-width: 703px;
}

.ta-hero__sub {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-21);
  line-height: 1.35;
  color: var(--c-grey);
}

.ta-hero__cta { justify-content: center; }

/* --------------------------------------------------------------------------
   S2 — Marquee ribbon

   The Figma band is a 2140px lime bar rotated -3deg and bled past both
   edges of the artboard, with a blurred lime plate behind it.
   -------------------------------------------------------------------------- */
/* The bar is deliberately wider than the viewport and rotated, so its
   bounding box sticks out on all four sides. `overflow-x: clip` (not hidden)
   trims the ends without creating a scroll container — hidden would also clip
   the rotated bar vertically and slice the text off.

   The vertical padding is load-bearing, not decoration. A rotated element
   keeps its UNROTATED height in flow (117px here), but paints 229px tall —
   so without this the band bleeds ~56px up into the hero and ~56px down into
   S3, and every section below it sits 112px too high. 112px is exactly the
   rotation spread: 112vw * sin(3deg). With the padding the wrap's layout box
   and the band's painted box are the same rectangle, which is what makes the
   artboard's 103px / 121px gaps land correctly. */
.ta-marquee-wrap {
  position: relative;
  z-index: 3;
  overflow-x: clip;
  overflow-y: visible;
  padding-block: clamp(10px, 2.93vw, 56px);
}

/* Rotating a full-bleed bar pulls its corners inside the viewport; over-wide
   plus a negative margin keeps the ends off-screen. The glow lives INSIDE
   this rotated box so the bloom tilts with the ribbon instead of sitting
   level under it. */
.ta-marquee-rot {
  position: relative;
  width: 112vw;
  margin-inline: -6vw;
  rotate: -3deg;
}

.ta-marquee {
  position: relative;
  z-index: 1;
  background-color: var(--c-accent);
  overflow: hidden;
  /* Layered lime bloom on top of the plate's wider falloff. The artboard is
     brightest immediately against the band and still reads ~60px out, which a
     single shadow cannot cover — sampled against the render, one 30px core
     plus two wider passes lands within a few levels of the design. */
  box-shadow:
    0 0 30px rgba(233, 253, 69, 0.55),
    0 0 70px rgba(233, 253, 69, 0.38),
    0 0 150px rgba(233, 253, 69, 0.22);
}

/* The artboard blooms lime light out of both long edges. It sits OUTSIDE
   .ta-marquee (which clips) but INSIDE .ta-marquee-rot, so it spills past the
   band while still following its angle. */
.text-ads-page .ta-marquee__glow {
  position: absolute;
  z-index: 0;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  width: 116%;
  /* .text-ads-page img caps at 100% — without this the plate is squeezed to
     the wrapper width and the bloom never reaches past the ribbon ends. */
  max-width: none;
  height: auto;
  /* No mix-blend-mode: .ta-marquee-wrap has its own stacking context, so
     plus-lighter would blend against that context's transparent backdrop
     rather than the page, and the bloom disappears. The plate already carries
     its own alpha falloff. */
  opacity: 1;
  pointer-events: none;
}

.ta-marquee__track {
  --ta-marquee-gap: clamp(24px, 2.969vw, 57px);
  display: flex;
  width: max-content;
  will-change: transform;
  /* Duration is overwritten by main.js so the ribbon always travels at a
     fixed px/s regardless of viewport. This value is the no-JS fallback. */
  animation: ta-marquee-scroll 21s linear infinite;
}
.ta-marquee:hover .ta-marquee__track { animation-play-state: paused; }

/* One rhythm for the whole ribbon: the SAME gap sits between every phrase and
   every star, and the group carries a trailing copy of that gap as padding so
   the seam where copy two follows copy one measures the same as any internal
   gap. Padding on both ends (the old `padding: y x`) doubled up at the seam —
   140px there against 57px everywhere else, which read as a break in the flow.
   Keeping the trailing space as padding (not a gap on the track) also keeps
   the loop seamless: the -50% keyframe travels exactly one group width, and
   that width now includes the trailing gap. */
.ta-marquee__group {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  gap: var(--ta-marquee-gap);
  padding-block: clamp(18px, 2.083vw, 40px);
  padding-inline: 0 var(--ta-marquee-gap);
}

.ta-marquee__item {
  flex: 0 0 auto;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: var(--fs-marquee);
  line-height: 1.028;
  color: var(--c-surface);
  white-space: nowrap;
}

.ta-marquee__star {
  flex: 0 0 auto;
  width: clamp(15px, 1.406vw, 27px);
  height: auto;
}

@keyframes ta-marquee-scroll {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* --------------------------------------------------------------------------
   S3 — What are Display Text Ads?  (w-1679, gap-130)
   -------------------------------------------------------------------------- */
/* The artboard puts 121px between the ribbon's painted bottom edge and the
   S3 grid (band ends 1104, grid starts 1225) — slightly tighter than the
   default section rhythm, because the ribbon already carries its own
   breathing room in .ta-marquee-wrap's padding. */
.ta-what {
  overflow: hidden;
  padding-top: clamp(36px, 5.47vw, 105px);
  /* S3's aurora — Frame 2132, the wave plate, 1920x920 at artboard width.
     Fitted against the render over every part of the band the tablet and the
     copy do not cover: scale 1.00, plate BOTTOM on the section's bottom edge,
     rmse 2.7 levels over ~250 samples. `100% auto` keeps that 1920:920 ratio
     at any width, so the crest lands in the same place relative to the copy;
     `bottom` anchoring is what the fit found and is also what keeps the band
     meeting S4's top edge with no seam.

     It is a background rather than an <img> so it cannot enter the z-index
     conversation at all — backgrounds paint behind the element's own content
     by definition. The old plate (aurora-wave, 1920x1035) was a different
     piece of artwork cropped to 55% with object-fit, which is why its crest
     never lined up with the artboard's. */
  background-image: url('../images/wave-what.webp');
  background-repeat: no-repeat;
  background-position: center bottom;
  background-size: 100% auto;
}

.ta-what__grid {
  display: grid;
  grid-template-columns: minmax(0, 545fr) minmax(0, 1004fr);
  gap: clamp(32px, 6.77vw, 130px);
  align-items: center;
}

.ta-what__copy {
  display: flex;
  flex-direction: column;
  gap: clamp(18px, 1.875vw, 36px);
}

.ta-what__title {
  font-family: var(--font-heading);
  font-weight: 500;               /* Medium */
  font-size: var(--fs-32);
  letter-spacing: var(--tr-32);
  color: var(--c-white);
}
.ta-what__title em { font-style: normal; color: var(--c-accent); }

/* The highlighted clause is BLUE in the design, not lime. */
.ta-mark {
  color: var(--c-blue);
  font-weight: 500;
}

/* --- Tablet mockup (node 10264:8678, 1004x670) -------------------------- */
.ta-device {
  position: relative;
  width: 100%;
  aspect-ratio: 1004 / 670;
  background-color: var(--c-grey);
  border-radius: clamp(20px, 3.02vw, 58px);
  padding: clamp(3px, 0.365vw, 7px);
}
.ta-device__screen {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: clamp(17px, 2.604vw, 50px);
  background-image: linear-gradient(to bottom, var(--c-surface-alt), var(--c-surface));
  overflow: hidden;
}
.ta-device__notch {
  position: absolute;
  top: 0;
  left: 43.33%;             /* 435/1004 */
  width: 13.35%;            /* 134/1004 */
  height: auto;
}

/* Content skeleton — the Figma bars, expressed as % of the 990x656 screen */
.ta-device__bar {
  /* Each bar is placed by absolute % of the 990x656 screen, taken from the
     Figma node tops — a flex column cannot reproduce them because the
     design's vertical gaps are irregular. */
  position: absolute;
  left: 4.65%;              /* x 46 / 990   */
  width: 62.42%;            /* 618 / 990    */
  background-color: var(--c-surface);
  border-radius: 6px;
}
.ta-device__bar--short { width: 31.62%; }   /* 313 / 990 */
.ta-device__bar--block { border-radius: 8px; }

.ta-device .ta-adcard {
  position: absolute;
  left: 68.48%;             /* x 678 / 990   */
  top: 12.04%;              /* y 79 / 656    */
  width: 28.05%;            /* 277.72 / 990  */
  gap: 0.9%;
  padding: 1.8%;
  border-radius: clamp(8px, 0.933vw, 17.917px);
}
.ta-device .ta-adcard__title { font-size: clamp(11px, 0.98vw, 18.813px); }
.ta-device .ta-adcard__url   { font-size: clamp(8px, 0.653vw, 12.542px); }
.ta-device .ta-adcard__desc  { font-size: clamp(9px, 0.747vw, 14.334px); }
.ta-device .ta-adcard__badge { font-size: clamp(6px, 0.467vw, 8.959px); }

/* --------------------------------------------------------------------------
   Stacked panels (S4 over S5/S6) — see initSectionStack() in scroll.js

   `.ta-stacked` is set by that function, so these only apply when the effect
   is actually running — i.e. at >= 1200 only. Below that, under reduced
   motion, or with no GSAP, the sections stay exactly as they were: nothing
   pins, nothing overlaps, and the grounds and rungs below are never set.

   The grounds are --c-page, which is the colour that was already showing
   THROUGH these transparent sections — so nothing looks different until two
   of them overlap, at which point the upper one has something to cover with.

   TWO PAIRS, NOT ONE CHAIN. S4 travels over a pinned S3, and S11 (the FAQ)
   travels over a pinned S10 (the steps) — see initSectionStack. S5/S6 travels
   over NOTHING: it comes up from under S4's bottom edge as an ordinary
   section. Its ground and its rung are kept anyway — they cost nothing, they
   are what the section already looked like, and they keep paint order
   following document order.
   -------------------------------------------------------------------------- */
.ta-stacked .ta-what  { z-index: 1; background-color: var(--c-page); }

/* 31, not 2, and the gap is deliberate. The flown ad card lands in S3's tablet
   but it never leaves the hero — it is inside `.ta-hero__inner`, which is
   `isolation: isolate` at rung 30, so the whole card sits at 30 whatever its
   own z-index says. Rung 1 is what puts S3 UNDER it, which is why the card is
   visible in the tablet at all; the same arithmetic then had S4 sliding
   UNDERNEATH the card, which read as the card floating over the next section.
   Clearing 30 is the only rung that satisfies both. */
.ta-stacked .ta-stats { z-index: 31; background-color: var(--c-page); }
.ta-stacked .ta-band  { z-index: 32; background-color: var(--c-page); }

/* S10 holds, S11 travels over it. The rung goes on `.ta-faq-section` itself
   and NOT on the `.pin-spacer` GSAP wraps it in: GSAP copies the pinned
   element's computed z-index onto that spacer when it builds it, so writing it
   here covers the section both while it is in flow inside the spacer and while
   it is pinned out of it. Targeting `.pin-spacer` directly would also hit S3's
   spacer, which needs the opposite rung. */
.ta-stacked .ta-steps        { z-index: 33; background-color: var(--c-page); }
.ta-stacked .ta-faq-section  { z-index: 34; background-color: var(--c-page); }

/* --------------------------------------------------------------------------
   S4 — Stats band (block h-920, stats at top-383, gap-64)
   -------------------------------------------------------------------------- */
/* The artboard gives this band a fixed 920px height with the figures sitting
   near its middle. Here it is a full screen instead, with the figures centred
   in it — the padding stays as a floor so short viewports never crush them. */
.ta-stats {
  position: relative;
  overflow: hidden;
  /* Seats the band against the sections above and below. The band is
     full-bleed, so its side edges — and this shadow with them — sit off
     screen; only the horizontal edges read. */
  box-shadow: 0 0 90px rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  /* svh tracks the visible viewport on mobile, where 100vh includes the area
     under the browser chrome and would overflow. */
  min-height: 100svh;
  padding-block: clamp(48px, 9.5vw, 182px);
}

/* BACK layer. The grid plate fades out down its own height, and the artboard
   lines that fade up with the band's bottom edge — plate bottom on band bottom,
   which is what `bottom: 0` at the plate's native 1919 gives. Sampled row by
   row against the render, grid contrast tracks the design within 0.02 the whole
   way down.

   A BACKGROUND, like S3's plate, not an <img>: backgrounds paint behind the
   element's own content by definition, so the plate cannot enter the z-index
   conversation with the two blooms at all. As an <img> it took `z-index: 0`
   from `.ta-bg` exactly as the blooms did, paint order fell back to DOM order,
   and the plate — last in the markup — covered both of them. That is what
   flattened the blue light out of this band, and it needed an explicit
   `z-index` to hold it off. Nothing to hold off now.

   It is on a PSEUDO-ELEMENT rather than on `.ta-stats` itself because the
   plate parallaxes (scroll.js initStatsWave): a transform belongs to the box
   it moves, and transforming the section would take the figures with it. The
   `--ta-wave-y` the transform reads is the only thing the tween writes.

   Geometry is the <img>'s, resolved: `width: 100vw` on a SQUARE 1919x1919
   plate is a 100vw box, so the height is stated rather than inherited from an
   intrinsic ratio, and `cover` replaces `object-fit: cover` unchanged.

   The plate is square, so on a landscape screen — where the band's 100svh is
   shorter than the viewport is wide — it overhangs the band and the crop is
   inert. On a phone the band is TALLER than it is wide and the plate covered
   only its bottom 46% (390 of 844 at 390x844), leaving the top half of the
   section on the flat page colour. `min-height` floors it at the band's own
   height and `cover` crops the sides instead of squashing the wave.

   Deliberately not a media query: the floor only bites when the band is taller
   than the viewport is wide, which is the exact condition that produced the
   gap — phones, and a tablet held in portrait. */
.text-ads-page .ta-stats::before {
  content: '';
  position: absolute;
  z-index: 0;
  pointer-events: none;
  bottom: 0;
  left: 50%;
  width: 100vw;
  height: 100vw;
  min-height: 100%;
  background-image: url('../images/wave-stats.webp');
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  /* -50% is the centring the <img> carried in CSS; --ta-wave-y is the
     parallax, and 0% until the tween arms so a no-JS render is the plate at
     its rest position rather than 6% off it. */
  transform: translate(-50%, var(--ta-wave-y, 0%));
}

/* UPPER layer, over the grid plate. In the artboard the grid lines stay fully
   visible THROUGH the blue bloom, so the bloom adds light rather than covering
   the plate — `plus-lighter`, the same operator Figma uses. `.ta-stats` is
   `position: relative` with no z-index and so is NOT a stacking context, which
   is what lets the blend reach the plate and the page colour underneath it.

   Vertical placement is top-anchored, not centred: fitting the exported plates
   against the design's own edge profiles (least squares over the band's 920
   rows, with the grid's gradient carried as a second term) puts BOTH blooms'
   top edge on the band's top edge — left at the band's own height, right
   stretched to 1.25x so its second, brighter lobe lands at 54% of the band the
   way the artboard has it. Heights are percentages so the composition holds on
   a band that is 100vh rather than the artboard's fixed 920. */
.text-ads-page .ta-stats__glow {
  z-index: 1;
  top: 0;
  width: 50vw;
  mix-blend-mode: plus-lighter;
}
.text-ads-page .ta-stats__glow--left  { left: 0;  height: 100%; opacity: 0.85; }
.text-ads-page .ta-stats__glow--right { right: 0; height: 125%; opacity: 0.65; }

/* The artboard pins these individually — stat 1 at x200 (flush left of the
   content column), stat 2 centred on the page centre line, stat 3 at x1354 so
   its right edge meets the column. `space-between` cannot express that: it
   distributes the slack evenly and lands stat 2 ~50px left of centre. A
   three-track grid with per-item justify-self puts all three exactly. */
.ta-stats__grid {
  /* Same reason as .ta-hero__inner — the band's rays are full-bleed and would
     otherwise blend the figures away. */
  position: relative;
  z-index: 2;
  isolation: isolate;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: start;
  gap: clamp(24px, 3vw, 56px);
}
.ta-stat:nth-child(1) { justify-self: start; }
.ta-stat:nth-child(2) { justify-self: center; }
.ta-stat:nth-child(3) { justify-self: end; }

.ta-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(14px, 3.333vw, 64px);
  text-align: center;
}

.ta-stat__num {
  font-family: var(--font-heading);
  font-weight: 900;                /* Black */
  font-size: var(--fs-stat);
  line-height: 1;
  letter-spacing: var(--tr-stat);
  white-space: nowrap;
  /* Fixed-width figures, so a digit swap mid-count cannot change the string
     width by a pixel or two and twitch the block. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  display: block;
  position: relative;
}

/* The ghost carries the FINAL value and is what gives the box its width, so
   the box never resizes while the count runs — which is what stops the label
   underneath from sliding. The live layer floats on top of it. */
.ta-stat__num-ghost { visibility: hidden; }
.ta-stat__num-live {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
}
/* Design order: lime, blue, white */
.ta-stat:nth-child(1) .ta-stat__num { color: var(--c-accent); }
.ta-stat:nth-child(2) .ta-stat__num { color: var(--c-blue); }
.ta-stat:nth-child(3) .ta-stat__num { color: var(--c-white); }

.ta-stat__label {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: var(--fs-24);
  letter-spacing: var(--tr-24);
  color: var(--c-grey);
}

/* --------------------------------------------------------------------------
   S5 + S6 — Benefits of Displaying Text Ads / A Simple Breakdown
             (one .ta-band section, gap-80, rows px-40 py-36 r-20)
   -------------------------------------------------------------------------- */
/* The artboard runs ONE aurora plate across both blocks, so they are ONE
   section. Figma node 10065:5694 "Mobile Vector 360 1" is 4096x1035 sitting
   at x=-981 y=3367 inside the 1920 frame — i.e. it is 2.13x the artboard
   width and hangs off BOTH edges, which is why cropping a 100vw copy per
   section never matched: the design only ever shows the middle 47% of the
   artwork, magnified. Every number below is that node divided by 19.2.

     4096 / 19.2 = 213.3333vw   width
    -981 / 19.2 =  -51.0938vw   left
     426 / 19.2 =   22.1875vw   top, measured from the section's border box
                                (section top 2941 -> plate top 3367)

   Height is left to `auto` so the 4096:1035 aspect is what fixes it — a
   second explicit length here would fight the width and skew the crest. */
.ta-band {
  overflow: hidden;
  /* Row 4 down to the S6 heading — see .ta-how below, and the S5 viewport
     floor that subtracts it. */
  --s5-how-gap: clamp(76px, 12.4479vw, 239px);
}

/* aurora-band.webp is NOT the raw node — it is the 2021x1035 slice of it
   (plate x 880..2901) that both bands actually show, cut at 1:1. Shipping the
   full 4096 and letting the browser scale it to 213vw meant a 2.13x upscale
   of a downsampled file, which went visibly soft; the slice renders pixel for
   pixel at 1920 and is one file instead of two.

     display width  2021 / 19.2 = 105.2604vw
     left          -101 / 19.2 =  -5.2604vw   (the slice starts 101px before
                                               the visible window, and by
                                               coincidence BOTH bands need the
                                               same 101 — see .ta-steps__waveclip)
     top             426 / 19.2 =  22.1875vw  from the section's border box */
.text-ads-page .ta-band__wave {
  top: 22.1875vw;
  left: -5.2604%;
  width: 105.2604%;
  height: auto;
}

.ta-benefits__head { margin-bottom: clamp(27px, 3.4375vw, 66px); }

/* S5 gets a screen of its own. The wave (scroll.js initBandMotion) now starts
   the moment the row block enters the viewport and the crest takes 2.3s to
   run all four rows, so the block wants a viewport-sized stage rather than
   half a screen shared with whatever is scrolling past above or below it.

   "One screen" is measured section-top to S6, not on the block alone: the
   band's own padding and the gap down to the S6 heading are part of that
   first screenful, so both come out of the floor. Land on the S5/S6 boundary
   from the top of the section and you have travelled exactly 100vh.

   The slack sits BELOW row 4, not around the content: no flex centring here,
   deliberately. The aurora plate is positioned off the SECTION (see
   .ta-band__wave above) so it does NOT travel with the content — centring
   pushed the rows ~150px down into the bright part of the crest, where the
   grey row copy stops being readable against the lime. Top-aligned, every row
   keeps the exact position on the plate the artboard gives it and only the
   empty space below grows.

   Desktop only, on the SAME 1200 breakpoint as the wave itself: below that
   the rows stack into a title over a full-width paragraph, the block is
   already about as tall as a phone screen, and this floor would only add dead
   space. `min-height`, not `height`, so a short viewport (or a zoomed-in
   reader) grows the block instead of clipping it. */
@media (min-width: 1200px) {
  .ta-benefits {
    min-height: calc(100vh - var(--space-section) - var(--s5-how-gap));
  }
}

/* Row 4's box bottom to the S6 heading's box top is 247px on the artboard.
   It used to be two lots of --space-section (250) either side of a section
   boundary; merging the sections means one explicit gap owns it. Held in a
   variable because the S5 viewport floor above has to subtract this same gap
   to land the S6 heading on the one-screen mark. */
.ta-how { margin-top: var(--s5-how-gap); }

/* Rows are w-1600 against a 1520 column: the 40px padding hangs outside.
   The transparent border is load-bearing: it is what the ::after ring sits
   on, and removing it would shrink every row by 2px. */

/* THE BLEED MAY ONLY SPEND ROOM THE CONTAINER IS NOT ALREADY USING.

   It used to be `clamp(-40px, -2.083vw, 0px)` on its own, which asks for the
   artboard's 40px break-out and tapers it by viewport width — but the taper
   is against the SCREEN while the room to bleed into is whatever the screen
   has left over after the container, and those two part company as soon as
   the container stops being able to reach its 1520+2x24 max. Below that the
   container is simply the screen, there is no room outside it at all, and the
   bleed was spending the gutter and then the edge: measured at rest, the rows
   ran 6px past the viewport at 1440 and 4px at 1366 — clipped, so no
   scrollbar, just the ring's rounded corner sliced off at the screen edge.
   1600 was 1px over.

   Deriving it from the leftover instead keeps the artboard's 40px wherever
   there is genuinely 40px to spare (1920 is unchanged), tapers it through the
   1600s, and reaches 0 exactly when the container starts touching the screen.
   The 20px is a scrollbar allowance — `100vw` counts the scrollbar and
   `innerWidth` does not, and 15px is what Chrome takes on Windows. */
.ta-benefit-row {
  --ta-row-bleed: min(
    40px,
    2.083vw,
    max(0px, (100vw - 20px - var(--container) - 2 * var(--gutter)) / 2)
  );

  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(16px, 2vw, 40px);
  margin-inline: calc(-1 * var(--ta-row-bleed));
  padding: clamp(18px, 1.875vw, 36px) clamp(16px, 2.083vw, 40px);
  border: 1px solid transparent;
  border-radius: var(--r-card);
  transition: background-color var(--dur-normal) var(--ease-out);
}

/* The rows start hidden and are raised by the wave's first beat
   (scroll.js initBandMotion). Two gates, and both matter:

   .ta-wave-armed is set on <html> by that function only once it is actually
   going to run — without GSAP, or under prefers-reduced-motion, it returns
   first, the class never lands and the rows render at full opacity. Failing
   visible is the point: an opacity authored straight onto .ta-benefit-row
   would leave the block permanently blank the moment anything upstream of
   the animation broke.

   The media query is the second gate. The wave is desktop-only: below 1200
   the row stacks into a title over a full-width paragraph (see the max-width
   900 block near the end of this file) and a 200px throw would drag two
   lines of wrapped copy off the right edge. JS builds the motion inside a
   matchMedia branch on the SAME breakpoint, so the two can never disagree. */
@media (min-width: 1200px) {
  .ta-wave-armed .ta-benefit-row {
    opacity: 0;
    transform: translate(200px, 0px);
  }
}

/* Hover fill + background blur, read off the hover VARIANT in the file
   (`Property 1=1 Hover`, node 10062:5661) rather than sampled from a render:

     fills   GRADIENT_LINEAR, opacity 0.4, #000000 -> #1E1E1E,
             handles (0,.5) -> (1,.5)  = a flat left-to-right 90deg
     effects BACKGROUND_BLUR radius 10
     stroke  0.5px INSIDE, #3B82F6 -> #E9FD45  (see ::after)

   Figma's blur radius is TWICE the CSS one — checked against a node in this
   same file whose LAYER_BLUR radius is 60 and whose generated code is
   `blur-[30px]` — so radius 10 is `blur(5px)`, not 10.

   It lives on a pseudo-element because a gradient cannot be transitioned as a
   `background-image`, and because `backdrop-filter` needs its own box to clip
   the blur to the row's radius. Opacity carries the fade; the blur is only
   attached on hover so four rows are not each holding a compositor layer at
   rest. */
.ta-benefit-row::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-card);
  background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.4) 0%, rgba(30, 30, 30, 0.4) 100%);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-normal) var(--ease-out);
}
.ta-benefit-row:hover::before,
.ta-benefit-row:focus-within::before {
  opacity: 1;
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
}

/* The 1px gradient hairline, blue at the left running through to lime at the
   right. It has to be a masked pseudo-element rather than a second
   background layer: the two-layer `background-clip: padding-box, border-box`
   trick needs an OPAQUE fill on top to hide everything but the ring, and the
   fill above is deliberately translucent. `inset: -1px` puts the ring on the
   border box; the mask xor punches out the middle. */
.ta-benefit-row::after {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: var(--r-card);
  padding: 1px;
  /* The stroke, straight off the hover variant: `#3B82F6 -> #E9FD45`, the raw
     brand blue into the raw brand lime.

     Angle and stops are the file's gradient handles converted to pixel space:
     start (0.2044, 0) and end (0.9353, 1) on a 1600x132 box is an axis of
     (1169.4, 132)px, i.e. 96.44deg. The stops are then read off a 2x render of
     the variant itself: the ring is flat blue out to x=337 and flat lime from
     x=1484, which on this angle's gradient line is 21.3% and 92.4%. (The
     handles alone predict 20.2/93.6 — the render is the arbiter.)

     FULL ALPHA — see the ring-brightness note above .ta-vcard::after. The 0.5
     that was here halved the stroke to model Figma's half-pixel coverage, and
     against this page's near-black it read as a grey line rather than a lit
     edge. Geometry is untouched; only the colour is at strength now. */
  background: linear-gradient(96.44deg,
    var(--c-blue) 21.3%,
    var(--c-accent) 92.4%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-normal) var(--ease-out);
}
.ta-benefit-row:hover::after,
.ta-benefit-row:focus-within::after { opacity: 1; }
.ta-benefit-row:hover .ta-benefit-row__title,
.ta-benefit-row:focus-within .ta-benefit-row__title { color: var(--c-blue); }
.ta-benefit-row:hover .ta-benefit-row__desc,
.ta-benefit-row:focus-within .ta-benefit-row__desc { color: var(--c-white); }

/* Positioned so it paints ABOVE ::before. Both are in the same z-index-0
   layer, where paint order is tree order, and ::before is the row's first
   child — without this the hover fill would sit on top of the icon and title. */
.ta-benefit-row__head {
  position: relative;
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.25vw, 24px);
  flex: 0 0 auto;
}

.ta-benefit-row__icon {
  flex: 0 0 auto;
  width: clamp(34px, 2.604vw, 50px);
  height: clamp(34px, 2.604vw, 50px);
}

.ta-benefit-row__title {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--fs-32);
  letter-spacing: var(--tr-32);
  color: var(--c-light-gray);
  transition: color var(--dur-normal) var(--ease-out);
}

.ta-benefit-row__desc {
  position: relative;              /* same reason as __head */
  flex: 0 0 auto;
  width: min(640px, 48%);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-21);
  line-height: 1.429;              /* 30/21 */
  color: var(--c-grey);
  text-align: right;
  transition: color var(--dur-normal) var(--ease-out);
}

/* --------------------------------------------------------------------------
   S6 — A Simple Breakdown of How Text Ads Work
   -------------------------------------------------------------------------- */
.ta-how__head { margin-bottom: clamp(20px, 4.1146vw, 79px); }

/* Artboard: stepper column 200..766, panel 767..1747. The panel is 980 wide
   and deliberately runs 27px PAST the 1720 content edge, so the columns are
   fixed widths that overflow .ta-container rather than fr units that would
   divide the 1520 column between them. .ta-band clips the overhang. */
.ta-how__grid {
  display: grid;
  grid-template-columns: clamp(180px, 29.4792vw, 566px) clamp(280px, 51.0417vw, 980px);
  gap: 0;
  /* The stepper hangs off the BOTTOM of the panel on the artboard (last item
     ends 15px above the panel's bottom edge), not on its centre line. */
  align-items: end;
}

/* Plain text list on a hairline rail — no cards in the design. */
/* The rail sits at x=164 on the artboard — 36px OUTSIDE the 200 content
   inset — with the labels back on the inset at x=201. A negative margin
   pulls the whole block out and the padding puts the text back. */
.ta-stepper {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(22px, 3.4896vw, 67px);
  /* THE RAIL LIVES INSIDE THE COLUMN.

     The artboard hangs this block 36px LEFT of the container so the labels
     line up with the content edge and the rail sits out in the gutter. That
     only holds at 1920, where there are 200px of gutter to hang into: the
     container stops growing at 1568, so from there down the gutter is 24px
     and the rail — points and all — went off the left edge of the screen.
     Floored to keep it on screen it still sat outside the column, which is
     the state in the screenshot: heading at the content edge, bar to the left
     of everything.

     So the whole block starts AT the content edge now. `padding-left` is what
     the labels are indented by; the rail is half a point in from the edge, so
     the points' left rims touch the column exactly where the heading above
     them starts and nothing pokes out of the container at any width. */
  margin-left: 0;
  --ta-rail-gap: clamp(20px, 1.9271vw, 37px);
  padding-left: var(--ta-rail-gap);
  padding-bottom: clamp(8px, 0.7813vw, 15px);

  /* Rail and point sizes, measured off the prototype recording rather than
     guessed. That capture is 1280 wide against the 1920 artboard and its step
     pitch is 64px to our 96, so it is exactly 1:1.5 — every number below is a
     measurement times that.

       rail      2.53px in the capture  ->  3.8  -> 4px   (the blue fill reads
                 2.67 there, but bright-on-dark blooms in video; the grey
                 track is the honest sample and they are one width)
       point    10.0px                  -> 15px
       inactive  ~6.4px                 -> 9px, i.e. 0.6 of the point

     The 1px rail this replaced was invented, and against it the 9px dot read
     as a bead on a thread instead of a station on a line. */
  --ta-rail-w: 4px;
  --ta-point: 15px;
}
/* The rail is two lines on the same column: a grey track that is always the
   full height, and a blue fill scaled down from the top over it. Both are
   inset by 14px at the top and by the stepper's own bottom padding + 14px, so
   the column runs from the FIRST point's centre to the LAST one's — the fill
   reaching 100% means "arrived at the last point", not "ran past the block". */
.ta-stepper::before,
.ta-stepper__fill {
  content: '';
  position: absolute;
  /* Half a point in from the block's left edge, so the rail's centre line is
     the points' centre line and the widest thing on it — a point — starts at
     0 rather than straddling it. */
  left: calc(var(--ta-point) / 2 - var(--ta-rail-w) / 2);
  top: 14px;
  bottom: calc(clamp(8px, 0.7813vw, 15px) + 14px);
  width: var(--ta-rail-w);
  border-radius: var(--ta-rail-w);
}
.ta-stepper::before { background: var(--c-hairline); }

/* scaleY rather than height: the fill creeps for 15s at a time and a
   transform keeps that on the compositor instead of laying out the column 60
   times a second. scroll.js (initStepperAutoplay) is its only writer. */
.ta-stepper__fill {
  background: var(--c-blue);
  transform: scaleY(0);
  transform-origin: top center;
  pointer-events: none;
}

.ta-stepper__item {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-24);
  letter-spacing: var(--tr-24);
  line-height: 1.2;
  color: var(--c-light-gray);
  cursor: pointer;
  transition: color var(--dur-normal) var(--ease-out);
}

/* EVERY point sits on the rail, not just the active one — the prototype
   recording shows four of them, and the fill needs something to arrive AT for
   the highlight to read as "this step is done". The label carries almost none
   of the state there (all four stay legible white; the reached ones are a
   shade brighter), so the point is what marks position: grey and small at
   rest, full-size blue once the fill reaches it.

   CENTRED ON THE RAIL, not offset from it. The point used to be placed by its
   LEFT EDGE (`-padding - 6px`), which left its centre 2px off the rail's — a
   1px rail hid that, a 4px one does not. It is now pinned to the rail's own
   centre line — back out of the label indent, then in by half a point, the
   same axis .ta-stepper__fill sits on — and translated back by half of itself
   in both axes, so any change to --ta-rail-w or --ta-point keeps the two
   concentric. */
.ta-stepper__item::before {
  content: '';
  position: absolute;
  left: calc(-1 * var(--ta-rail-gap) + var(--ta-point) / 2);
  top: 50%;
  translate: -50% -50%;
  scale: 0.6;
  width: var(--ta-point);
  height: var(--ta-point);
  border-radius: 50%;
  background: var(--c-grey-alt);
  transition: background-color var(--dur-normal) var(--ease-out),
              box-shadow var(--dur-normal) var(--ease-out),
              scale var(--dur-normal) var(--ease-out);
}
.ta-stepper__item:hover { color: var(--c-white); }

/* Two states, and they accumulate — the run is one-way, so a point the fill
   has passed stays lit rather than handing the highlight on. `is-done` is
   every point reached so far (scroll.js writes it); `is-active` is the one
   the fill is standing on right now (main.js writes it, off the same index)
   and adds the halo. */
.ta-stepper__item.is-done,
.ta-stepper__item.is-active { color: var(--c-white); }
.ta-stepper__item.is-done::before,
.ta-stepper__item.is-active::before {
  background: var(--c-blue);
  scale: 1;
}
.ta-stepper__item.is-active::before {
  box-shadow: 0 0 0 5px rgba(59, 130, 246, 0.16), 0 0 14px rgba(59, 130, 246, 0.55);
}
.ta-stepper__item:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 4px; }

.ta-how__panel {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 980 / 550;
  border: 1px solid var(--c-hairline);
  border-radius: var(--r-card);
  overflow: hidden;
  background-color: var(--c-surface);
}
.ta-how__panel img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--r-card);
}

/* --------------------------------------------------------------------------
   S7 — Benefits That Make Display Text Ads Valuable
        (gap-130, grid gap-20 / row-gap-46, card p-40 r-20, NO fill)
   -------------------------------------------------------------------------- */
.ta-value__head { margin-bottom: clamp(36px, 6.77vw, 130px); }

.ta-value-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: clamp(12px, 1.042vw, 20px);
  row-gap: clamp(28px, 2.396vw, 46px);
}

.ta-vcard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 2.292vw, 44px);
  padding: clamp(20px, 2.083vw, 40px);
  border: 0.5px solid transparent;
  border-radius: var(--r-card);
  transition: background-image var(--dur-normal) var(--ease-out);
}



/* Hover FILL, sampled straight off the design's hovered card rather than
   guessed: a NEUTRAL greyscale ramp, black in the top-left corner running to
   --c-surface in the bottom-right. Sampled values climb ~5 per 110px across
   and ~3 per 120px down, which is a 119deg vector, and they are grey
   (R=G=B) the whole way — so the navy tint and the white radial sheen that
   used to be here were both wrong. The ring is on ::after below. */
.ta-vcard:hover,
.ta-vcard:focus-within {
  background-image: linear-gradient(119deg, #000000 0%, var(--c-surface) 100%);
}

/* The ring is a masked pseudo-element, NOT a `background-clip` layer, for one
   concrete reason: Chrome resolves the card's 0.5px border to a full pixel on
   some edges and to ZERO on others. With the two-layer trick that rendered a
   lit LEFT edge and no RIGHT edge at all — the exact inverse of the design,
   and the pale line down the left is what read as "washed out". A 1px
   pseudo-element renders all four edges identically. */
.ta-vcard::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-card);
  padding: 1px;
  /* RING BRIGHTNESS — ONE RULE FOR THE WHOLE PAGE.

     Every hover ring on this page runs between the two raw brand tokens at
     FULL strength, and `.ta-vert`'s always-on edge
     (`linear-gradient(90deg, var(--c-blue), var(--c-accent))`) is the
     reference all of them are matched to. Nothing samples a render for these
     any more and nothing carries alpha:

       - .ta-benefit-row::after  blue -> lime, 96.44deg  (S5)
       - .ta-vcard::after        lime -> blue, 90deg     (S7, this one)
       - .ta-post::after         lime -> blue, 90deg     (S9)

     Only the COLOUR is shared — each keeps its own angle and stops, which are
     the design's geometry.

     This card's ramp was the worst of the three: eight stops sampled off a
     render, which composited the design's half-pixel stroke down to a
     grey-green (113,119,93) into a near-navy (49,69,96) — the lime and the
     blue were both gone. Its position-0 stop was also fully TRANSPARENT, so
     the straight left edge showed the card's own black fill through and read
     as an unfinished border. Both are replaced by the two tokens, corner to
     corner, with no transparent stop. */
  background: linear-gradient(90deg,
    var(--c-accent) 0%,
    var(--c-blue) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-normal) var(--ease-out);
}
.ta-vcard:hover::after,
.ta-vcard:focus-within::after { opacity: 1; }
.ta-vcard:hover .ta-vcard__title,
.ta-vcard:focus-within .ta-vcard__title { color: var(--c-blue); }
.ta-vcard:hover .ta-vcard__desc,
.ta-vcard:focus-within .ta-vcard__desc { color: var(--c-white); }

.ta-vcard__head {
  display: flex;
  align-items: center;
  gap: clamp(14px, 1.458vw, 28px);
}
.ta-vcard__icon {
  flex: 0 0 auto;
  width: clamp(40px, 3.125vw, 60px);
  height: clamp(40px, 3.125vw, 60px);
}
.ta-vcard__title {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--fs-24);
  letter-spacing: var(--tr-24);
  line-height: 1.25;
  color: var(--c-light-gray);
  transition: color var(--dur-normal) var(--ease-out);
}

.ta-vcard__desc {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-18);
  line-height: 1.45;
  color: var(--c-grey);
  transition: color var(--dur-normal) var(--ease-out);
}

/* --------------------------------------------------------------------------
   S8 — Verticals (gap-80, grid gap-60, card bordered, copy always visible)
   -------------------------------------------------------------------------- */
.ta-vert__head { margin-bottom: clamp(32px, 4.167vw, 80px); }

.ta-vert-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(20px, 3.125vw, 60px);
}

/* The card's 1px border is a GRADIENT, not a hairline: `#3B82F6` on the left
   edge running to `#E9FD45` on the right, flat 90deg across the whole card.
   Read off the artboard's own border pixels — at x+30 / x+230 / x+440 of a
   466px card the top border is (70,137,234) / (144,190,157) / (223,245,78),
   which is that ramp to within one level at every point. It used to be
   `--c-hairline` (#9CA3AF at 28%), which is the colour of the line UNDER the
   thumbnail, not of the card's edge.

   Two background layers do it: the fill is clipped to the padding box, the
   gradient to the border box, so the 1px transparent border shows the gradient
   through. That works here — unlike the S5 hover ring — because this card's
   fill is opaque. */
.ta-vert {
  display: flex;
  flex-direction: column;
  border: 1px solid transparent;
  border-radius: var(--r-card);
  background-image:
    linear-gradient(var(--c-surface), var(--c-surface)),
    linear-gradient(90deg, var(--c-blue) 0%, var(--c-accent) 100%);
  background-origin: border-box;
  background-clip: padding-box, border-box;
  overflow: hidden;
  transition: transform var(--dur-normal) var(--ease-out);
}
.ta-vert:hover { transform: translateY(-4px); }

/* The "X.XXM / Daily Imp." pill is baked into the supplied artwork —
   never re-create it in markup or it doubles up.

   The 1px `#9CA3AF` under the thumbnail is the artboard's own: the image frame
   carries a full-strength hairline on its bottom edge, and it reads clearly
   against the body's near-black. */
.ta-vert__img {
  display: block;
  width: 100%;
  aspect-ratio: 700 / 206;
  object-fit: cover;
  border-bottom: 1px solid var(--c-grey);
}

/* Padding 18 / gap 11 look wrong against the artboard's 24 / 21 and are not.
   Every text layer in the file is `text-box-trim: trim-both` with a
   `cap alphabetic` edge, so the artboard's 24 is measured to the CAP of the
   title, not to its line box — and a CSS line box carries ~6px of leading
   above the cap at 24px and ~3.4px at 14px. Subtracting that leading is what
   lands the ink where the artboard has it: title cap 24px below the body's
   top edge, description lines starting 62 and 80 below it, body 114 tall.
   With the artboard's raw numbers the body renders 139 tall and the whole card
   is 26px too deep. (CSS `text-box: trim-both cap alphabetic` would express
   this directly, but it is Chrome-only today.) */
.ta-vert__body {
  display: flex;
  flex-direction: column;
  gap: clamp(7px, 0.573vw, 11px);
  padding: clamp(12px, 0.938vw, 18px);
  background-image: linear-gradient(to right, var(--c-surface), #000000);
  flex: 1 0 auto;
  justify-content: center;
}

.ta-vert__name {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-24);
  letter-spacing: var(--tr-24);
  color: var(--c-white);
}

.ta-vert__desc {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: var(--fs-14);
  /* `leading-[normal]` in the file, which for Be Vietnam Pro at 14px is 18px —
     the artboard's two description lines sit exactly 18px apart. The 1.45 that
     was here spread them to 20.3. */
  line-height: normal;
  color: var(--c-grey);
}

/* --------------------------------------------------------------------------
   S9 — Set Up Your Text Ad Campaign in 4 Simple Steps

   Every length below is the Figma node divided by 19.2, so the section
   renders 1:1 against the artboard at 1920 and scales from there. Column
   geometry (artboard):

     column pitch 414, card 280 wide  ->  4 tracks + 134 gap over the 1520
                                          content column
     tile 128 square, centred on the column   (276 vs column 200: inset 76,
                                               and (280-128)/2 = 76)
     node 36 square, centred                  ((280-36)/2 = 122, and
                                               322-200 = 122)
   -------------------------------------------------------------------------- */
/* Deliberately NOT `overflow: hidden`. The plate is 1035 tall against a
   ~991 section, and on the artboard it runs on past the section edge and
   fades out behind the top of S10 — clipping it put a hard horizontal cut
   across the page. Nothing here overhangs to the RIGHT (the wave's
   -5.2604vw + 105.2604vw ends at exactly 100vw, the connector at 1660 of
   1920), and negative-x overflow does not scroll, so letting it bleed costs
   no horizontal scrollbar. */

/* The S9 aurora is the S5/S6 plate MIRRORED — Figma node 10151:5935 wraps it
   in `rotate(180deg) scaleY(-1)`, which composes to a plain horizontal flip.
   Placed at x -1296 y 6765 (section top 6604), 4096x1035. */
/* Sized to the plate exactly (1035 / 2021 * 105.2604% of the section width
   = 53.906%, i.e. a 1.8551 ratio), so clipping sideways costs nothing
   vertically. */
.ta-steps__waveclip {
  position: absolute;
  z-index: 0;
  top: 8.3854vw;
  left: 0;
  right: 0;
  aspect-ratio: 1.8551;
  overflow: hidden;
  pointer-events: none;
}
/* The plate is a BACKGROUND now, not an <img> — same box, same art, same
   position, one less element and one less request shape in the markup.

   It lives on ::before rather than on .ta-steps__waveclip itself for one
   reason: the wrapper carries `data-speed`, so ScrollSmoother owns its
   transform, and the mirror has to be a transform (there is no
   `background-flip`). Putting the two on separate elements means neither can
   overwrite the other.

   Every number is the <img>'s, unchanged. Percentages, not vw: `vw` counts
   the scrollbar but the section does not, so a vw-sized plate lands ~15px
   wide of the viewport on a real desktop and drags the visible window
   off-centre. Against the wrapper these are exact. `aspect-ratio` is the
   file's own 2021x1035, which is what `height: auto` used to give it, and
   `100% 100%` then fills that box without distorting anything. */
.ta-steps__waveclip::before {
  content: '';
  position: absolute;
  top: 0;
  left: -5.2604%;
  width: 105.2604%;
  aspect-ratio: 2021 / 1035;
  background: url('../images/aurora-band.webp') center / 100% 100% no-repeat;
  transform: scaleX(-1);
}

/* Heading box bottom (6790) to tile top (7012). */
.ta-steps__head { margin-bottom: clamp(48px, 11.5625vw, 222px); }

/* S10 gets a screen of its own, with the panel centred in it.

   The artboard's 304px card-bottom (7416) to panel-top (7720) gap used to be
   a `margin-top` here. It is now the CENTRING's job: half of a viewport minus
   the panel is 291px at 1920x1080 and 263px at 1440x900 — the artboard figure
   to within a few pixels — so the margin is gone rather than stacking on top
   of it, which would have left ~570px of dead space above the panel and as
   much again below.

   min-height, not height: a viewport shorter than the panel plus its own
   breathing room grows the block instead of clipping it. Phones take none of
   this — see the max-width 767 block, where S10 is 80svh with the panel
   bottom-aligned under the arrow zone. */
.ta-cta {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
}

.ta-steps__flow { position: relative; }

/* x -60..1660 against a content column at 200..1720, expressed as a share of
   that column so it tracks the container instead of the viewport:
     left  (-60 - 200) / 1520 = -17.105%
     width  1720 / 1520        = 113.158%
   The SVG is 1724.47x54.7688 and the artboard box is 1720x51.807, i.e. the
   art bleeds ~1.5px past the box on three sides — `height: auto` keeps that
   ratio, and the top is the bled edge (7170.5) rather than the box (7172). */
.text-ads-page .ta-steps__path {
  position: absolute;
  z-index: 0;
  top: 8.2552vw;
  left: -17.105%;
  width: 113.158%;
  /* The sitewide `img:not([draggable]) { max-width: 100% }` clamps this back
     to the 1520 column and swallows the whole 200px overhang — same trap the
     .ta-bg plates hit. */
  max-width: none;
  height: auto;
  pointer-events: none;
  user-select: none;
}

.ta-steps__row {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(16px, 6.9792vw, 134px);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Fixed height so the three pieces can be pinned at their artboard offsets
   (tile 0, node 190, card 264, card bottom 404) instead of being stacked by
   flow — the node has to land ON the connector, which is positioned against
   the flow block, so neither can drift with content height. */
.ta-step {
  position: relative;
  height: clamp(220px, 21.0417vw, 404px);
}

.ta-step__tile {
  position: absolute;
  top: 0;
  left: 50%;
  translate: -50% 0;
  width: clamp(64px, 6.6667vw, 128px);
  height: auto;
}

/* The exported node SVG is 42px for a 36px component — Figma renders it at
   `inset: -8.33%` because the 3px stroke is centred on the circle and spills
   outside. Sizing the img at 42 and offsetting by the same 3px puts the
   36px component itself on the artboard's coordinates. */
.ta-step__node {
  position: absolute;
  top: clamp(107px, 9.9479vw, 191px);
  left: 50%;
  translate: -50% 0;
  width: clamp(24px, 2.1875vw, 42px);
  height: auto;
}
/* Node 1 sits 4px higher than the other three on the artboard. */
.ta-step:first-child .ta-step__node { top: clamp(105px, 9.7396vw, 187px); }

.ta-step__card {
  position: absolute;
  top: clamp(150px, 13.75vw, 264px);
  left: 0;
  right: 0;
  height: clamp(112px, 7.2917vw, 140px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Figma trims its text boxes to the cap height, browsers do not, so the
     nominal gap-24 lands ~8px wide here. 18 reproduces the reference's
     26px ink-to-ink gap. */
  gap: clamp(10px, 0.9375vw, 18px);
  padding-inline: clamp(16px, 2.0833vw, 40px);
  background-color: rgba(7, 12, 24, 0.6);
  /* Transparent, so the box keeps its 280x140 while ::after paints the real
     stroke on the border box. */
  border: 1px solid transparent;
  border-radius: var(--r-card);
  text-align: center;
}

/* The stroke is a GRADIENT, lime into WHITE, and it restarts on every card —
   not the flat `--c-accent` that was here. Sampled along one card's top border
   at 0 / 25 / 50 / 75 / 100% of its width: (232,252,68) / (238,252,120) /
   (243,252,160) / (248,254,202) / (254,254,254), which is `#E9FD45 -> #FFFFFF`
   interpolated to within two levels at every point, and all four cards read
   the same ramp at the same fractions.

   A masked ::after rather than the two-layer `background-clip` trick, because
   this card's fill is `rgba(7,12,24,.6)` — the aurora has to keep showing
   through it. */
.ta-step__card::after {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: var(--r-card);
  padding: 1px;
  background: linear-gradient(90deg, var(--c-accent) 0%, #FFFFFF 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

.ta-step__num {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-21);
  letter-spacing: var(--tr-21);
  /* Figma trims this box to the cap height; 1.1 left ~4px of slack above the
     glyphs and pulled the whole centred stack 4px up. */
  line-height: 1;
  color: var(--c-accent);
}

/* The artboard's cards are `whitespace-nowrap` — the copy is allowed to run
   past the 200px content box (280 minus the 40px padding) rather than rewrap,
   and every line break in the design is an explicit one. Without this,
   "Register & Add Funds" breaks into three lines and card 3 breaks a line
   early. */
.ta-step__label {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-20);
  /* 25px cap-to-cap on the reference's two-line cards. */
  line-height: 1.25;
  color: var(--c-white);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   S10 — CTA panel (w-1520 h-497, r-80, 1.5px lime border, 71.25deg gradient)
   -------------------------------------------------------------------------- */
.ta-cta__panel {
  position: relative;
  overflow: hidden;
  min-height: clamp(300px, 25.9vw, 497px);
  /* Per-side, not the uniform 78.5 this started with. Measured off the
     reference render at 1920, where the panel is exactly 1520x497:
       title cap-top   y 78   -> 62 top
       button row bottom y 437 -> 60 bottom  (497 - 437)
       ghost button right x 1459 -> 60 right (1520 - 1459)
       title/sub left  x 82   -> 78.5 left, unchanged
     The left inset really is the odd one out — the copy sits further in than
     the buttons hang out. Every vw figure is that cap over 1920. */
  padding:
    clamp(24px, 3.23vw, 62px)
    clamp(22px, 3.125vw, 60px)
    clamp(22px, 3.125vw, 60px)
    /* Left floor matches the right one so the asymmetry is a DESKTOP fact
       only — below ~540px both sides sit at 22px and the stacked buttons stay
       centred in the panel. */
    clamp(22px, 4.09vw, 78.5px);
  border: 1.5px solid transparent;
  border-radius: var(--r-panel);
  /* RING. Solved off the reference rather than eyeballed. Sampling its border
     pixels right around the perimeter and searching for the axis that makes
     those samples a consistent function of position lands on 90deg — the ring
     runs straight across, left to right.

     The give-away is in the reference itself: its left edge is one flat sage
     for its whole height and its right edge one flat lime for its whole
     height. Only a horizontal axis does that. The 118deg this replaced tilted
     far enough to smear the blue band down both sides, and it also topped out
     at 187 where the reference reaches 240 — which is what left the whole
     outline looking greyed-out next to the design.

     Stops are the binned means of those samples, and reproduce the reference's
     own border to a mean 10/255 per channel. The residual is real and not
     worth chasing with more stops: at the same x the reference's TOP edge is
     brighter than its BOTTOM edge over the left half only (187 vs 87 at
     x=200) and they agree over the right half, which is a corner glow, not
     something any single linear gradient can say. */
  background-image:
    /* Lime bloom in the bottom-left corner (Figma's imgEllipse164, a 1145x351
       ellipse hung off the corner). Sampled off the reference it peaks around
       rgb(88,97,37) at the corner and is half gone 330px across but only
       110px up — a wide, shallow wash, which is why it is an ellipse and not
       a circle. It rides on the fill layer rather than a pseudo-element so
       `overflow: hidden` cannot eat it. */
    radial-gradient(46% 52% at 1% 101%,
      rgba(233, 253, 69, 0.46) 0%,
      rgba(226, 246, 82, 0.26) 40%,
      rgba(150, 200, 110, 0.08) 70%,
      transparent 90%),
    linear-gradient(71.25deg, #070C18 1.362%, #121212 98.075%),
    /* CORNER ACCENTS — the part of the reference's ring that is not the linear
       gradient. At the same x its TOP edge is warmer and brighter than its
       BOTTOM edge over the left third only (187 vs 87 at x=200), and the two
       agree from the middle rightwards. Two washes anchored to the left
       corners say exactly that; the alpha each one needs was read straight
       off the difference between the reference and the ring alone:

         top    x120 .50  x240 .59  x360 .31  x480 .15  x600 .00
         bottom x120 .44  x300 .53  x480 .42  x660 .30  x840 .08

       They are only 90px tall so they die out before the side edges, which
       already match the ring exactly and must not be touched.

       Both sit BELOW the opaque fill above, so they are visible in the 1.5px
       ring and nowhere else. */
    radial-gradient(620px 90px at 0% 0%,
      rgba(233, 245, 150, 0.55) 30%,
      rgba(233, 245, 150, 0.5) 48%,
      rgba(233, 245, 150, 0.18) 70%,
      transparent 98%),
    radial-gradient(880px 90px at 0% 100%,
      rgba(28, 71, 134, 0.5) 0%,
      rgba(28, 71, 134, 0.48) 55%,
      rgba(28, 71, 134, 0.22) 80%,
      transparent 100%),
    linear-gradient(90deg,
      rgb(164, 182, 156) 0%,
      rgb(136, 162, 140) 6%,
      rgb(135, 161, 140) 12%,
      rgb(120, 148, 143) 20%,
      rgb(90, 124, 144) 28%,
      rgb(62, 99, 147) 36%,
      rgb(41, 83, 147) 42%,
      rgb(77, 112, 148) 50%,
      rgb(128, 155, 149) 58%,
      rgb(180, 197, 151) 66%,
      rgb(222, 237, 152) 74%,
      rgb(227, 240, 154) 82%,
      rgb(226, 238, 164) 100%);
  background-origin: padding-box, padding-box, border-box, border-box, border-box;
  background-clip: padding-box, padding-box, border-box, border-box, border-box;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(28px, 4vw, 60px);
}


.ta-cta__title {
  position: relative;
  z-index: 2;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--fs-cta);
  letter-spacing: var(--tr-cta);
  /* 1.25, not 1.2: the reference sets the two lines 80px apart on a 64px
     face (cap-top y78 -> y158), which is 64 x 1.25. At 1.2 the second line
     rode 4px high and the whole title sat wrong against the panel. */
  line-height: 1.25;
  color: var(--c-white);
  max-width: 1011px;
}
.ta-cta__title em { display: block; font-style: normal; color: var(--c-accent); }

.ta-cta__foot {
  position: relative;
  z-index: 2;
  display: flex;
  /* CENTRED against the buttons, not bottom-aligned. In the reference the
     sub-line's optical centre (y404) and the pill's centre (y403) are the
     same line; `flex-end` instead hung the text off the pill's baseline and
     left it 14px low. */
  align-items: center;
  justify-content: space-between;
  gap: clamp(20px, 3vw, 48px);
  flex-wrap: wrap;
}

.ta-cta__sub {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-24);
  color: var(--c-light-bg);
}

/* Primary CTA carries two blurred blobs (blue + lime) behind the label */
.ta-cta__btn {
  position: relative;
  overflow: hidden;
  /* THE CORNERS WERE LEAKING THE BLOBS.

     `overflow: hidden` + `border-radius` is a MAIN-THREAD clip. The blobs
     below animate `transform`, so Chrome hands them to the compositor —
     and a composited child inside a rounded `overflow` ancestor gets
     clipped to that ancestor's RECTANGLE, corners and all. The pill's own
     background covers the middle, so what showed was the four corner
     cut-outs lit up with blurred blob colour instead of the card behind:
     measured off the reported screenshot, the card reads (12,15,21) away
     from the button and all four cut-outs read (41-60, 62-93, 70-96) —
     four to six times too bright, in blob colour. A square glow around a
     round button.

     `clip-path` is the same rounded rect stated as a clip the COMPOSITOR
     honours, so the blobs are cut to the pill on both paths. Shape is
     unchanged: diffing the button with and against this rule moves only
     the 1px antialiased edge, never the silhouette. */
  clip-path: inset(0 round var(--r-pill));
  padding: clamp(16px, 1.25vw, 21px) clamp(22px, 1.667vw, 32px);
  border-radius: var(--r-pill);
  font-size: var(--fs-24);
  letter-spacing: var(--tr-24);
  background-color: var(--c-blue-deep);
  color: var(--c-white);
}
/* Two blurred blobs inside the clipped button — lime on the left, blue on the
   right — which is what produces the artboard's lime-into-blue wash. They sit
   in a motion layer in Figma, so they drift here rather than sitting still. */
/* Both blobs are 220x219 at blur 30 in Figma (nodes 10286:6528 / 6530), and
   the recording shows them travelling rather than sitting still: tracking the
   lime centroid frame by frame, it swings across roughly 42% of the button's
   width and back, dwelling at the left and crossing quickly. The old ±34px
   drift kept the lime pinned to the left third. */
.ta-cta__btn::before,
.ta-cta__btn::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 220px;
  aspect-ratio: 220 / 219;
  border-radius: 50%;
  filter: blur(30px);
  pointer-events: none;
  /* No `will-change: transform` here. It pinned each blob to a permanent
     compositor layer — the layer whose rectangular clip produced the
     corner glow — and it bought nothing: the transform animation is
     composited while it runs either way. A standing layer hint on two
     decorative blurs is exactly the use the property warns against. */
}
.ta-cta__btn::before {
  background: var(--c-accent);
  left: -70px;
  animation: ta-cta-blob-a 9s cubic-bezier(0.65, 0, 0.35, 1) infinite alternate;
}
.ta-cta__btn::after {
  background: var(--c-blue);
  right: -70px;
  animation: ta-cta-blob-b 9s cubic-bezier(0.65, 0, 0.35, 1) infinite alternate;
}
.ta-cta__btn > * { position: relative; z-index: 2; }

/* The label is white at rest and has to stay white under the cursor. It did
   not: the sitewide `a:hover { color: var(--title-color) }` in
   assets/css/style.css outspecifies a bare class — an element plus a
   pseudo-class scores over one class name, and loading this sheet later does
   not change that — so hovering the CTA turned it dark. Naming the hover
   state here matches the specificity and takes it back. --ghost already does
   the same thing a few rules down. */
.ta-cta__btn:hover,
.ta-cta__btn:focus-visible { color: var(--c-white); }

/* `clip-path` clips the element's WHOLE rendering, and `.ta-btn`'s focus
   ring is an `outline` sitting 3px OUTSIDE the box — so the clip above
   erases it. Not a guess: with the ring forced on, the clip took it from
   1368 painted pixels to 0. Dropping the clip for the one state that
   draws a ring keeps the keyboard focus indicator intact. `overflow:
   hidden` still holds the corners on the main thread while focused, and
   focus is a transient keyboard-only state. */
.ta-cta__btn:focus-visible { clip-path: none; }

@keyframes ta-cta-blob-a {
  from { transform: translate(0, -50%)     scale(1); }
  to   { transform: translate(150px, -50%) scale(1.1); }
}
@keyframes ta-cta-blob-b {
  from { transform: translate(0, -50%)      scale(1.1); }
  to   { transform: translate(-150px, -50%) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .ta-cta__btn::before,
  .ta-cta__btn::after { animation: none; }
}

.ta-cta__btn--ghost {
  padding: clamp(16px, 1.094vw, 21px) clamp(22px, 1.667vw, 32px);
  border-radius: var(--r-pill);
  font-size: var(--fs-24);
  letter-spacing: var(--tr-24);
  color: var(--c-blue);
  background: transparent;
  border: 1px solid var(--c-blue);
}
.ta-cta__btn--ghost img { width: clamp(16px, 1.146vw, 22px); height: auto; }
.ta-cta__btn--ghost:hover { color: var(--c-white); background-color: var(--c-blue); }
.ta-cta__btn--ghost:hover img { filter: brightness(0) invert(1); }

.text-ads-page /* Dashboard, converging fan lines and cursor, positioned as % of the
   artboard's 1520x497 panel (dashboard x911.45 w608.19, fan x814.39 y45.2
   w705.8, cursor x1296.43 y220.3 — all from the Figma nodes). */
.ta-cta__visual {
  position: absolute;
  z-index: 1;
  inset: 0;
  pointer-events: none;
}
.text-ads-page .ta-cta__dash {
  position: absolute;
  left: 59.96%;
  top: 0;
  width: 40.01%;
  max-width: none;
  /* NO border. The screenshot already carries a 1px blue frame of its own,
     and a CSS one on top drew a hard rectangle across the panel — the
     reference has no edge there at all, the image simply stops existing. */

  /* The screenshot is LIT BY THE CURSOR. That is what the reference's fade
     actually is — not a soft edge, a cone of light thrown from the cursor at
     the bottom of the fan, widening upward across the dashboard. Everything
     outside the cone is dark, which is why the bottom corners are gone while
     the top-right is fully there.

     Solved rather than guessed. Rendering the dash unmasked, and the fan on
     its own, gives the reference's true per-pixel alpha once the rays are
     subtracted:

              u=.06  .19   .31   .44   .56   .69   .81   .94
       v=.08   0.24  0.43  0.69  0.94  1.04  1.04  0.91  1.05
       v=.42   0.07  0.32  0.51  0.70  0.77  0.82  0.79  0.06
       v=.75   0.01  0.00  0.05  0.72  0.87  0.86  0.02  0.00
       v=.92   0.01  0.00  0.00  0.00  0.99  0.55  0.00  0.00

     A wedge that narrows to a column at the bottom and fans out at the top.
     Two linear gradients cannot say that (best fit rmse 0.185); a cone from
     the cursor plus a distance falloff lands at 0.122, and the apex it solves
     to — 62% across, 108% down, just under the dash's bottom edge — is the
     cursor's own position.

       conic  = the cone's angular span
       radial = the light running out with distance
       intersect multiplies the two, which is the physical thing: angle AND
       reach both have to allow it. */
  -webkit-mask-image:
    conic-gradient(from -90deg at 62% 108%,
      transparent 15deg, #000 55deg, #000 130deg, transparent 145deg),
    radial-gradient(100% 300% at 62% 108%, #000 20%, transparent 140%);
          mask-image:
    conic-gradient(from -90deg at 62% 108%,
      transparent 15deg, #000 55deg, #000 130deg, transparent 145deg),
    radial-gradient(100% 300% at 62% 108%, #000 20%, transparent 140%);
  /* Without mask-composite the first layer stands alone — the cone, no
     falloff. Degraded, never broken. */
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}
.text-ads-page .ta-cta__fan {
  position: absolute;
  left: 53.58%;
  top: 9.09%;
  width: 46.42%;
  height: 53.88%;
  max-width: none;
}
.text-ads-page .ta-cta__cursor {
  position: absolute;
  left: 85.29%;
  top: 44.32%;
  width: 2.19%;
  max-width: none;
  border: 1px solid var(--c-blue-deep);
}

/* --------------------------------------------------------------------------
   S11 — FAQ

   The artboard does NOT stack the eight rows: node 9981:9454 is a 950px
   column, `h-[920px] overflow-y-auto pt-[210px] gap-[40px]`, sitting at
   right:180 — a fixed WINDOW the questions travel through. Built here as a
   full-height section that scroll.js pins, with the list driven through the
   window on scroll progress and released once the last row lands.

     card    #1E1E1E, 1px #9CA3AF, r-72, px-50 py-60, question 32px #9CA3AF
     toggle  120px circle, 60px to the right of the card
     hover   ring, plus and question all go lime
     open    card and circle invert to white; question #1E1E1E, answer #9CA3AF
   -------------------------------------------------------------------------- */
.ta-faq-section {
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  min-height: 100svh;
  /* NO padding. The window below is the full screen, and the breathing room
     lives INSIDE it — as list padding, which the travel carries past the top
     edge on the way in and leaves under the last row on the way out. A
     padding here would just shrink the window. */
  padding-block: 0;
  /* The artboard's question column is `h-[920px]` with `pt-[210px]`
     (node 9981:9454). That 210 is the lead-in, and the heading takes the same
     value on top so it starts on the first card's line. */
  --ta-faq-lead: clamp(48px, 10.9375vw, 210px);
}

/* Covers the whole section. With the section pinned this reads as a fixed
   backdrop, which is what the design does — the questions move, the aurora
   does not. */
.ta-faq__waveclip {
  position: absolute;
  z-index: 0;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
/* Widened just enough that the plate's own 2021:1035 ratio makes it as tall
   as a 100vh section (1080 / 1035 * 2021 = 2109 = 109.84% of 1920) — cover
   would have stretched the wave instead. Still the same slice, so the crest
   sits where the artboard puts it. */
.text-ads-page .ta-faq__wave {
  position: absolute;
  top: 0;
  left: -4.92%;
  width: 109.84%;
  min-height: 100%;
  height: auto;
  max-width: none;
}

.ta-faq__layout {
  display: grid;
  grid-template-columns: minmax(0, 494fr) minmax(0, 950fr);
  gap: clamp(32px, 4vw, 76px);
  align-items: start;
}

/* No `position: sticky` any more — the whole section is pinned while the
   questions run, so the heading holds still on its own. Sticky on top of a
   pin fights the pin's transform and drifts. */
.ta-faq__heading em { display: block; }

/* The same lead-in the list carries, so the heading's cap and the first card's
   top edge start on one line instead of the heading floating above them. */
.ta-faq__heading { padding-top: var(--ta-faq-lead); }

/* The window is the FULL screen. It used to be sized by scroll.js to a whole
   number of rows against a padded section; with the section's padding gone the
   window is simply the viewport, and the list's own padding does the spacing.
   `svh` so mobile browser chrome cannot push it past the fold. */
.ta-faq__viewport {
  position: relative;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

.ta-faq {
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2.083vw, 40px);
  /* Lead-in and lead-out. The list TRAVELS through the window, so the top
     padding is what you see before the first card arrives and the bottom
     padding is what is left under the last one — not a permanent squeeze on
     how many rows fit. */
  padding-block: var(--ta-faq-lead);
  will-change: transform;
}

.ta-faq__item {
  display: flex;
  align-items: flex-start;
  gap: clamp(14px, 3.125vw, 60px);
}

/* The card owns the fill, ring and radius — not the question button — because
   the answer opens INSIDE it and the whole card inverts to white. */
.ta-faq__card {
  flex: 1 1 auto;
  min-width: 0;
  padding: clamp(22px, 3.125vw, 60px) clamp(24px, 2.604vw, 50px);
  background-color: var(--c-surface);
  /* Solid --c-grey, not --c-hairline: the artboard's card is
     `border border-[#9ca3af]` and the reference samples [153,165,179] on the
     edge — at 28% alpha the ring all but disappeared. */
  border: 1px solid var(--c-grey);
  border-radius: var(--r-faq);
  transition: background-color var(--dur-normal) var(--ease-out),
              border-color var(--dur-normal) var(--ease-out);
}

.ta-faq__q {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-32);
  letter-spacing: var(--tr-32);
  line-height: 1.25;
  color: var(--c-grey);
  transition: color var(--dur-normal) var(--ease-out);
}
.ta-faq__q:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 6px; }

/* Hover — sampled off the reference: the ring, the plus and the question all
   go lime while the fill stays #1E1E1E. */
.ta-faq__item:not(.is-open):hover .ta-faq__card,
.ta-faq__item:not(.is-open):focus-within .ta-faq__card { border-color: var(--c-accent); }
.ta-faq__item:not(.is-open):hover .ta-faq__q,
.ta-faq__item:not(.is-open):focus-within .ta-faq__q { color: var(--c-accent); }
.ta-faq__item:not(.is-open):hover .ta-faq__toggle,
.ta-faq__item:not(.is-open):focus-within .ta-faq__toggle { color: var(--c-accent); border-color: var(--c-accent); }

/* Open — the card and the circle invert. */
.ta-faq__item.is-open .ta-faq__card {
  background-color: var(--c-white);
  border-color: var(--c-white);
}
.ta-faq__item.is-open .ta-faq__q { color: var(--c-surface); }
.ta-faq__item.is-open .ta-faq__toggle {
  background-color: var(--c-white);
  border-color: var(--c-white);
  color: var(--c-surface);
}

/* 120px circle with the plus drawn as two bars, so one `color` drives the
   whole thing and the rotate to an X is free. */
.ta-faq__toggle {
  position: relative;
  flex: 0 0 auto;
  width: clamp(48px, 6.25vw, 120px);
  height: clamp(48px, 6.25vw, 120px);
  padding: 0;
  border: 1px solid var(--c-grey);
  border-radius: 50%;
  background-color: var(--c-surface);
  color: var(--c-grey);
  cursor: pointer;
  transition: background-color var(--dur-normal) var(--ease-out),
              border-color var(--dur-normal) var(--ease-out),
              color var(--dur-normal) var(--ease-out),
              transform var(--dur-normal) var(--ease-out);
}
.ta-faq__icon {
  position: absolute;
  inset: 0;
  display: block;
}
.ta-faq__icon::before,
.ta-faq__icon::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  background-color: currentColor;
  translate: -50% -50%;
}
.ta-faq__icon::before { width: 44%; height: 1.5px; }
.ta-faq__icon::after  { width: 1.5px; height: 44%; }
.ta-faq__item.is-open .ta-faq__toggle { transform: rotate(45deg); }
.ta-faq__toggle:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 4px; }

/* Not in the Figma frame — every row ships collapsed there — so it inherits
   the card's own inset and simply grows inside it. */
.ta-faq__a {
  height: 0;
  overflow: hidden;
  transition: height var(--dur-normal) var(--ease-out);
}
.ta-faq__a-inner {
  padding-top: clamp(14px, 1.875vw, 36px);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-21);
  line-height: 1.68;
  color: var(--c-grey);
}

/* --------------------------------------------------------------------------
   S12 — Blog / resources
   -------------------------------------------------------------------------- */
/* Heading box is the artboard's 900 (200..1100) and the paragraph still ends on
   the artboard's right edge, 1740 — 20px PAST the content column, which the
   negative margin buys back so the fr split resolves exactly.

   The paragraph column is 580 rather than the artboard's 509, and the copy is
   RIGHT-aligned rather than left: a DELIBERATE departure, asked for directly.
   580 is picked from the line widths — the three lines measure 547 / 552 / 380,
   so the box has to clear 552 to keep them three, and stay under ~620 or
   "better," pulls up onto line two. 580 sits in the middle of that window with
   ~5% of slack for font-rendering variance. Do not "restore" this to the
   artboard's 509/left/four-lines. */
.ta-blog__head {
  display: grid;
  grid-template-columns: minmax(0, 900fr) minmax(0, 580fr);
  gap: clamp(24px, 3.125vw, 60px);
  margin-right: clamp(-20px, -1.042vw, 0px);
  align-items: start;
  margin-bottom: clamp(32px, 3.5vw, 68px);
}
.ta-blog__head .ta-body { text-align: right; }
.ta-blog__heading em { display: block; }

.ta-blog__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(16px, 1.5vw, 28px);
}

.ta-post {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 0.99vw, 19px);
  padding: clamp(12px, 1.042vw, 20px);
  border-radius: var(--r-card);
}

/* Hover fill: the same `#000000 -> #1E1E1E` 90deg ramp the S5 rows and S7
   cards use, at full opacity — the card goes DARKER than the page, not lighter.
   Sampled across the reference's hovered card the interior climbs 6 -> 26 left
   to right and is neutral (R=G=B) the whole way; the `rgba(255,255,255,.03)`
   that was here lifted it to 14/19/31 and kept the page's navy cast.

   On a pseudo-element because a gradient cannot be transitioned as a
   `background-image`. */
.ta-post::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-card);
  background-image: linear-gradient(90deg, #000000 0%, var(--c-surface) 100%);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-normal) var(--ease-out);
}

/* Hover ring — 1px masked, lime into blue left to right (the same direction
   as the S7 cards, the opposite of the S5 rows). Full alpha, per the
   ring-brightness note above .ta-vcard::after; the 50% that stood in for the
   file's 0.5px stroke is gone. */
.ta-post::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-card);
  padding: 1px;
  background: linear-gradient(90deg,
    var(--c-accent) 0%,
    var(--c-blue) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-normal) var(--ease-out);
}
.ta-post:hover::before,
.ta-post:focus-within::before,
.ta-post:hover::after,
.ta-post:focus-within::after { opacity: 1; }

/* Positioned so they paint ABOVE ::before — see the S5 row for the same
   tree-order reason. */
.ta-post__thumb {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
}
/* No hover zoom on the thumbnail. The artboard's hover state changes the
   card's fill, its ring and the title colour, and leaves the image alone. */
.ta-post__thumb img {
  display: block;
  width: 100%;
  aspect-ratio: 850 / 553;
  object-fit: cover;
}

.ta-post__title {
  position: relative;              /* same reason as __thumb */
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--fs-24);
  line-height: 1.542;              /* 37/24 */
  color: var(--c-grey);
  transition: color var(--dur-normal) var(--ease-out);
}
/* LIME on hover, not white. Sampled off the reference, the hovered title's lit
   pixels average (218,235,102) — that is #E9FD45 antialiased against the card's
   near-black fill. White would average (255,255,255). */
.ta-post:hover .ta-post__title,
.ta-post:focus-within .ta-post__title { color: var(--c-accent); }

/* This page has no footer (text-ads.md §2.2) — S12 needs its own bottom
   breathing room or the last card sits flush against the viewport edge. */
.ta-blog { padding-bottom: clamp(72px, 7vw, 134px); }

/* Reveals are driven entirely by gsap.fromTo() in scroll.js — there is no
   opacity:0 pre-state class here on purpose. If GSAP fails to load, every
   section stays visible instead of the page rendering blank. */

/* --------------------------------------------------------------------------
   Responsive — breakpoints 1199 / 900 / 767 / 374
   -------------------------------------------------------------------------- */
@media (max-width: 1199px) {
  .ta-hero__note { display: none; }

  .ta-what__grid,
  .ta-how__grid { grid-template-columns: minmax(0, 1fr); }
  .ta-how__panel { order: -1; }

  /* The S5+S6 plate is placed at the artboard's literal offsets, which are
     vw-derived and therefore aspect-locked. Once the band stacks, the section
     grows to roughly 3x its own width while the plate does not, so the
     artboard geometry stops meaning anything — it lands as a thin strip
     across the first benefit row. Below this breakpoint it goes back to a
     proportion of the section, keeping the same share of it (23% down, 57%
     tall) that the artboard gives it at 1920. */
  .text-ads-page .ta-band__wave {
    top: 23%;
    left: 50%;
    width: 200%;
    height: 57%;
    object-fit: cover;
    transform: translateX(-50%);
  }
  .ta-steps__waveclip {
    top: 12%;
    aspect-ratio: auto;
    height: 70%;
  }
  /* Same box the <img> had here: twice the width, centred, cropped to the
     clip's height. `cover` is the background spelling of `object-fit: cover`,
     and the explicit `aspect-ratio: auto` releases the desktop rule's 2021:1035
     so `height` can own it. */
  .ta-steps__waveclip::before {
    top: 0;
    left: 50%;
    width: 200%;
    height: 100%;
    aspect-ratio: auto;
    background-size: cover;
    transform: translateX(-50%) scaleX(-1);
  }

  /* S9 unwinds from artboard-pinned absolutes to normal flow: the connector
     is a single horizontal run and cannot survive a wrapped grid. */
  .ta-steps__path { display: none; }
  .ta-steps__row { gap: clamp(24px, 3vw, 48px); }
  .ta-step {
    height: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
  }
  .ta-step__tile,
  .ta-step__node,
  .ta-step__card {
    position: static;
    translate: none;
  }
  .ta-step__card {
    width: 100%;
    height: auto;
    min-height: 112px;
    padding-block: 22px;
  }
  .ta-step__label { white-space: normal; }

  /* Stacked, the stepper has no panel to hang off, and the rail has nothing
     to sit outside of. */
  .ta-how__grid { align-items: stretch; }

  /* HIDDEN FROM TABLET DOWN. The stepper (`User Visits a Website` / `Ad Grabs
     Attention` / ...) is a desktop affordance: it hangs beside the panel and
     reads as a legend for it. Once the two columns stack it is four
     unexplained buttons sitting under the thing they were labelling, so the
     artboard's S5 is heading -> panel -> rule and nothing else.

     This used to stop at phones (a `display: none` in the 767 block, now
     folded in here) while tablets got a re-laid-out rail — `margin-left: 0;
     padding-bottom: 0`, both of which only existed to make the stacked
     version presentable. Hiding the whole thing one breakpoint earlier makes
     those moot.

     THE SEQUENCE IS UNAFFECTED. initStepperAutoplay() in scroll.js drives the
     PANEL through window.taSetActiveStep() and reads no geometry at all — it
     only writes classes and a scaleY — so a hidden rail cannot change what it
     does. Checked either side of the breakpoint with S5 on screen: 1240 with
     the rail up and 1024 with it hidden report the same step state. What is
     lost here is the rail, not the steps. Hidden rather than removed so the
     markup stays one document across breakpoints. */
  .ta-stepper { display: none; }

  .ta-faq__layout,
  .ta-blog__head { grid-template-columns: minmax(0, 1fr); margin-right: 0; }
  /* Ragged-left copy under a left-aligned heading reads as a mistake once the
     two stack. */
  .ta-blog__head .ta-body { text-align: left; }

  /* THE WINDOW IS A DESKTOP DEVICE. Below 1200 S11 goes back to plain flow:
     full-width column, all eight rows stacked, one ordinary scroll.

     It used to keep the desktop treatment here — section fixed to one screen,
     window clipped, list driven through it by a pin. That works on a wide
     screen, where the heading sits BESIDE the window and the hold reads as a
     designed pause. Stacked it does not: the heading takes the top of the
     screen, so the rows travel through whatever is left (a 707px window at
     390x844) and the reader is pinned in place for the 346px of travel before
     the page moves on. On touch that reads as the page having frozen, and the
     `svh` window fights the address bar showing and hiding.

     EVERY DECLARATION BELOW IS A RELEASE, not a new layout — each one undoes a
     base rule that only exists to build that window. They come in pairs with
     the base rules around line 2388, so read them together.

     scroll.js gates the pin to the same 1200 (initFaqScroll). The two must
     move together: a released pin over a still-clipped 100svh window would
     hide every row past the first screenful, and a live pin over a released
     window would scroll a section that no longer overflows. */
  .ta-faq-section {
    /* Base sets `min-height: 100svh` + a centring flex column to hold exactly
       one screen. In flow the section is as tall as its rows. */
    display: block;
    min-height: 0;
    /* Base zeroes this because the breathing room lived INSIDE the window, as
       list padding the travel carried past the edges. With no travel that
       padding is just a hole (see `.ta-faq` below), so the space comes back
       out here as ordinary section padding, on the same scale as its
       neighbours. */
    padding-block: clamp(48px, 9vw, 96px);
    /* Nothing overflows any more, and `hidden` on a plain-flow section would
       clip the FAQ card's focus ring at the column edge. */
    overflow: visible;
  }
  .ta-faq__viewport {
    /* The window's whole job was to clip. Both go. */
    height: auto;
    min-height: 0;
    overflow: visible;
  }
  .ta-faq {
    /* The lead-in/lead-out. At 390 that is 48px top and bottom of empty
       column — invisible on desktop because the travel started with the
       first card below the top edge and ended with the last above the
       bottom one. Nothing travels now, so it is just a gap on top of the
       section padding above. */
    padding-block: 0;
    /* No transform is written below 1200, so the layer hint is pure cost. */
    will-change: auto;
  }
  /* Same lead-in, same reason — it existed to start the heading's cap on the
     first card's line inside the window. Stacked, the heading is above the
     list and this is a gap under the section's own padding. */
  .ta-faq__heading { padding-top: 0; }
  /* THE PLATE MUST NOT BE MEASURED FROM THE SECTION.

     `.ta-faq__waveclip` is `inset: 0`, so its height IS the section's height
     — and in flow the section's height is its CONTENT. Sizing the plate
     against that box fed the accordion straight into the backdrop: opening
     one row at 390x844 took the section 1143 -> 1349, the plate went with it
     1143 -> 1349, and `cover` re-cropped at the new scale. The whole wave
     jumped every time a question opened.

     So the plate is sized from the VIEWPORT and hung off the section's
     BOTTOM edge instead. `svh` is the small-viewport height — the one unit
     that is defined not to move when the address bar shows or hides — and
     nothing about it depends on how many answers are open. A row opening now
     adds dark space ABOVE a plate that has not moved.

     NO EDGE TO HIDE, so nothing fades it. The plate is RGBA and carries its
     own falloff: down the slice `cover` shows, alpha runs 0 at a tenth, 177
     at half, a 202 peak at seven tenths, then back to 7 by 95% and 0 at the
     foot. It is a soft band that ends in nothing at BOTH ends, so wherever
     it is hung it meets the page in transparency. Bottom-anchored, the peak
     lands about 250px above the section's foot — the artboard's reading,
     dark overhead and the glow low — and the last of it dissolves into
     #070C18 on its own. */
  .text-ads-page .ta-faq__wave {
    top: auto;
    bottom: 0;
    height: 100vh;
    height: 100svh;
    min-height: 0;
    /* Element aspect is far narrower than the plate's 1.953, so `cover`
       scales to fit the HEIGHT exactly and crops the sides — the whole
       top-to-bottom falloff survives, which is the part that matters. */
    object-fit: cover;
    object-position: bottom center;
  }

  .ta-value-grid,
  .ta-vert-grid,
  .ta-blog__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .ta-benefit-row { gap: 20px; }
  .ta-benefit-row__desc { width: 52%; }
}

@media (max-width: 900px) {
  /* Two-up below 900. The connector only makes sense as one horizontal run,
     so it goes; each step then stacks tile -> node -> card in normal flow and
     the fixed height that pinned them to the artboard is released. */
  .ta-steps__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .ta-benefit-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
  }
  .ta-benefit-row__desc { width: 100%; text-align: left; }

  /* ------------------------------------------------------------------
     S10 VISUAL — a backdrop on desktop, the card's HEAD on mobile.

     This used to be `display: none` below 900, which left the phone card
     as three lines of type over flat black. The composition still works
     at this width, it just cannot sit BEHIND the copy: at 339px the
     dashboard would run under the title and the fan under the buttons.

     So it comes out of the absolute layer and becomes the panel's first
     block instead — image on top, copy under it — which is the same
     arrangement the other CTA cards use on phones.

     GEOMETRY. Nothing here is re-eyeballed; it is the desktop composition
     re-based on its own bounding box. Against the 1520x497 panel the
     Figma nodes are

       dash    x 911.45  y 0      w 608.19  h 223
       fan     x 814.39  y 45.2   w 705.8   h 267.8
       cursor  x 1296.43 y 220.3  w 34

     so the box they occupy is 608.19 wide (the dashboard's width, which
     the fan's right edge shares to within half a pixel) by 313 tall (the
     dashboard's top down to the fan's tail). Fitting THAT box to the card
     width makes every offset below a plain percentage of it, and the
     dashboard fills the card edge to edge the way the reference does.

     The fan is the one thing wider than the box — it hangs 16% off the
     left — and the panel's own `overflow: hidden` takes that off. No loss:
     the fan's gradient is already at ~0 opacity over that stretch, which
     is why the rays fade out before the card's left edge rather than
     stopping at it.
     ------------------------------------------------------------------ */
  .text-ads-page .ta-cta__visual {
    position: relative;
    inset: auto;
    /* Full-bleed to the card's edges. The panel's radius and
       `overflow: hidden` do the clipping, exactly as for the desktop
       backdrop, so the image reads as part of the card and not as a
       picture dropped inside its padding. */
    width: calc(100% + var(--cta-pad-x) * 2);
    margin-inline: calc(var(--cta-pad-x) * -1);
    /* The panel is a `space-between` column; without this the flex box
       squeezes the visual to give the copy its height. */
    flex: 0 0 auto;
    aspect-ratio: 608.19 / 313;
  }
  /* 223/313. Width 100% + this height is the dashboard's own 608.19x223
     ratio, so the screenshot is scaled, never stretched. Its cursor-cone
     mask is anchored in its own box and rides along untouched. */
  .text-ads-page .ta-cta__dash {
    left: 0;
    top: 0;
    width: 100%;
    height: 71.246%;
  }
  /* (814.39 - 911.45)/608.19, 45.2/313, 705.8/608.19, 267.8/313 */
  .text-ads-page .ta-cta__fan {
    left: -15.959%;
    top: 14.441%;
    width: 116.049%;
    height: 85.559%;
  }
  /* (1296.43 - 911.45)/608.19, 220.3/313, 34/608.19 — the point the rays
     converge on, which is the whole reason the fan is there. */
  .text-ads-page .ta-cta__cursor {
    left: 63.298%;
    top: 70.383%;
    width: 5.591%;
  }

  .ta-cta__panel {
    /* ONE inset both sides below 900, so the bleed above is a single
       figure. Above 900 the panel keeps the artboard's asymmetric insets
       (the copy sits further in than the buttons hang out). */
    --cta-pad-x: clamp(22px, 3.125vw, 60px);
    padding-inline: var(--cta-pad-x);
    /* The visual IS the card's top edge now — nothing sits above it. */
    padding-top: 0;
  }

  /* `--fs-cta` is flat 40px for every width under 1200 (its 3.333vw term
     does not clear the floor until then), which is right for a PHONE — 40
     is the mobile artboard's own face — and too small once the card is a
     tablet's 716px wide. There the title sat at the same size it takes at
     390 while the buttons ran the full width under it, and against the
     dashboard now heading the card it read like a caption.

     5.5vw does not clear 40 until 727px, so this is a NO-OP on phones —
     the signed-off 40px stands, and the title still breaks over three
     lines at 390 exactly as before. It only bites across the tablet band:
     42px at 767, 49px at 900. The 50 cap is never reached inside the
     query and is there so the expression cannot run away.

     The step back to 40 at 901 is deliberate: that is where the panel
     stops being a stacked card and the title moves BESIDE the dashboard
     into a 3:1 panel, which is the layout 40px was drawn for. */
  .ta-cta__title { font-size: clamp(40px, 5.5vw, 50px); }
}

@media (max-width: 767px) {
  :root { --gutter: 18px; }

  /* ONE INSET FOR THE WHOLE PAGE ON PHONES.

     The mobile artboard gives several sections their own, wider inset — 62/603
     for S2's copy, 65/603 for S5/S6/S7, 56/452 for S9 — and each was
     reproduced faithfully as `padding-inline` on that section's container.
     Faithful, and wrong on a real phone: at 390px those land at 39.8 / 42.1 /
     48.4px against the page gutter's 18, so scrolling the page walked the
     left edge in and out four times over — heading, card, heading, card, none
     of them lining up with the one above.

     Every container now sits on `--gutter` and nothing overrides it, so one
     vertical line runs the length of the page and the content is as wide as
     the reference. The artboard shares are kept in the comments below rather
     than in the rules: they are still what the DESIGN says, they are just not
     what the page does any more. */
  .ta-what > .ta-container,
  .ta-band > .ta-container,
  .ta-steps > .ta-container,
  .ta-container:has(.ta-value-grid) { padding-inline: var(--gutter); }

  /* ========================================================================
     S1 HERO — built to the MOBILE artboard, not to a shrunk desktop

     Every number below is read off the mobile design export (603px wide) and
     written as a share of that artboard, so it scales with the viewport the
     way the rest of the page does. `x/603` becomes `x*0.166vw`; the comments
     carry the raw artboard value so the next person can re-measure.

     The mobile composition is NOT the desktop one reflowed:
       - the note sits ABOVE the card, not to its right
       - the card is 57% of the screen, not ~72%
       - the headline and the sub have a NARROWER measure than the container,
         which is what gives the artboard its 3-line / 3-line wrap
       - the two CTAs stay SIDE BY SIDE, at content width
     ======================================================================== */

  /* Card top sits 266/603 down the artboard. The note is out of flow
     (absolute), so that space has to be padding. */
  .ta-hero {
    padding-top: 44vw;
    padding-bottom: 12vw;
  }

  /* The note is BACK. It is hidden from 1199 down by the block above, which
     was right while it had nowhere to go — on the artboard it has a place of
     its own, over the card rather than beside it. */
  .ta-hero__note {
    display: block;
    left: 58.4%;                      /* text starts 201/344 into the card */
    top: auto;
    bottom: calc(100% + 11vw);        /* 67/603 of clear air under it      */
    font-size: 3.65vw;                /* 22/603                            */
    line-height: 1.3;
  }

  /* THE ARROW IS ITS OWN ASSET HERE, not the desktop one rotated.

     `hero-arrow.svg` is composed for the DESKTOP placement — note to the
     right of the card, head pointing down-LEFT — and it is 84x75 (aspect
     1.12). The mobile artboard's arrow is a different draw: a long shallow
     sweep that leaves the text under its first line and drops almost
     straight DOWN into the card's corner, 54x42 (1.29). Measured leftmost
     white pixel per row on the artboard:

         y180 x328   y192 x290   y204 x280   y216 x276   y222 x274 (head)

     Rotating the desktop asset got the head pointing the right way but kept
     the wrong curvature. `hero-arrow-mobile.png` (41x32, aspect 1.28) is the
     artboard's own vector, so it is used directly and nothing is rotated.

     Painted on `::before` rather than swapped on the <img>, because CSS
     cannot change an image's `src` — the desktop <img> is simply hidden at
     this breakpoint and the markup is untouched. */
  .ta-hero__note img { display: none; }
  .ta-hero__note::before {
    content: '';
    position: absolute;
    left: -9.3vw;                     /* 56/603 back from the text         */
    top: 3.48vw;                      /* 21/603 down from its first line   */
    width: 8.96vw;                    /* 54/603                            */
    aspect-ratio: 41 / 32;
    background: url("../images/hero-arrow-mobile.png") center / contain no-repeat;
    pointer-events: none;
  }

  /* 344/603. The wrapper is `width: fit-content` by default, which lets the
     card's own type decide — on the artboard the card is a MINIATURE and its
     type is sized to the box, not the other way round. */
  /* 344/603 of the ARTBOARD — but this resolves against `.ta-container`,
     which has already taken the 18px gutter off each side, so the share is
     restated against 603-2*18*(603/402) = the container's own width. */
  .ta-hero__card-wrap { width: 62.6%; }

  /* THE HERO'S CARD IS THE PHONE'S CARD, SMALLER — same proportions, not just
     the same parts.

     initCardFlight() flies this card into the phone and scales it UNIFORMLY to
     the slot's WIDTH, so the two only land on top of each other if they have
     the same aspect ratio. They did not: every inside measurement here was a
     share of the ARTBOARD (21/603 padding, 22/603 title) while the phone's
     card is sized to its own box, which left this one at h/w 0.693 against
     the slot's 0.583. Scaled to the slot's 316px width it arrived 34px too
     tall with every line in the wrong place — the double image in the
     hand-off.

     So each number below is now the phone card's own share of ITS width,
     re-expressed against this card: the two are 0.6707 of each other at 390,
     so `device vw x 0.6707`. Same relative padding, same relative type, same
     wraps, therefore the same aspect — and the flight lands exactly.

     (The ratio drifts ~1.5% across phone widths because this card's width
     comes off a container with a fixed 18px gutter while the phone's is pure
     vw. That is a pixel or two of height at the hand-off, and the crossfade
     at the end of the flight covers it.) */
  .ta-hero__card-wrap .ta-adcard {
    width: 100%;
    padding: 4.44vw;                  /* phone card's 7.2% of its own box   */
    /* ZERO, like the phone card's. Its `gap: 5.3%` is a percentage ROW gap
       against an auto height, which resolves to 0 — the rows there are spaced
       by their line boxes alone, and the reference render is that. A real gap
       here would have made this card proportionally taller than the one it
       lands on. */
    gap: 0;
    border-radius: 2.01vw;
  }
  .ta-hero__card-wrap .ta-adcard__title {
    font-size: 3.02vw;                /* 4.5vw x 0.6707                     */
    line-height: 1.3;
  }
  .ta-hero__card-wrap .ta-adcard__url,
  .ta-hero__card-wrap .ta-adcard__badge { font-size: 2.01vw; }   /* 3 x .67 */
  .ta-hero__card-wrap .ta-adcard__desc {
    font-size: 2.35vw;                /* 3.5vw x 0.6707                     */
    line-height: 1.5;
  }

  /* The middle row is the one thing that would NOT scale with the card.
     `.ta-adcard__url` inherits a 26px line-height from the sitewide stylesheet
     and the chip's padding, border and radius are absolute px, so the row
     measured the same 26px in a 212px card and in a 316px one — 12.3% of one
     box against 8.2% of the other, and that difference alone was most of the
     34px the flight used to land wrong by.

     Unitless line-height and `em` padding tie the whole row to its own type,
     which is already proportional; only the flex gap has to be restated per
     card because it does not inherit from the type. */
  .ta-hero__card-wrap .ta-adcard__url,
  .ta-device .ta-adcard__url { line-height: 1.2; }
  .ta-hero__card-wrap .ta-adcard__badge,
  .ta-device .ta-adcard__badge {
    padding: 0.171em 0.342em;         /* 2px / 4px at the phone card's 11.7 */
    border-width: 0.085em;            /* 1px at 11.7                        */
    border-radius: 0.45em;
    line-height: 1.2;
  }
  .ta-hero__card-wrap .ta-adcard__meta { gap: 0.86vw; }
  .ta-device .ta-adcard__meta { gap: 1.28vw; }

  /* The measure, not the container, is what wraps the artboard's headline
     onto three lines — the container's own 18px gutter would give it the
     width for two. */
  /* Type is measured off the artboard's INK, not guessed from the desktop
     ramp: the headline's ascender-to-descender band is 34/603 and its line
     pitch 45, the sub's band 20 with a pitch of 32.5. Written as vw so the
     ratio to the measure above holds at every phone width — which is what
     keeps the artboard's line breaks ("Display Text Ads Built" / "to Meet
     Every Campaign's" / "Demands"). */
  .ta-hero__title {
    max-width: 82%;                   /* 449/603 of the screen             */
    margin-inline: auto;
    font-size: 5.55vw;                /* 34/603 + the ink-to-em allowance  */
    line-height: 1.32;                /* 45/34                             */
  }
  .ta-hero__sub {
    max-width: 83%;                   /* 455/603 of the screen             */
    margin-inline: auto;
    font-size: 3.32vw;                /* 20/603                            */
    line-height: 1.63;                /* 32.5/20                           */
  }

  /* The artboard's vertical rhythm, which is NOT one even gap: 98px of air
     under the card, 26 between headline and sub, then 67 before the CTAs
     (at a 402 screen). One flex `gap` cannot say that, so the row that needs
     more takes it as a margin. */
  .ta-hero__copy {
    margin-top: 20.4vw;               /* 123/603 under the card            */
    gap: 3.7vw;                       /* 22/603 headline -> sub            */
  }
  .ta-hero__cta { margin-top: 11.2vw; }  /* 67/603, on top of that gap     */

  /* SIDE BY SIDE, at content width — the artboard's row is 67% of the screen
     with both pills on one line. Stacking them full-width was a mobile
     default, not the design. */
  .ta-hero__cta {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    width: 100%;
    gap: 2.2vw;                       /* 13/603                            */
  }
  .ta-hero__cta .ta-btn {
    width: auto;
    flex: 0 0 auto;
    font-size: 3.3vw;                 /* 20/603                            */
    padding: 3vw 4vw;
    border-radius: 999px;
  }

  /* At `100% auto` the wave keeps its 1920:920 ratio, which on a 390 viewport
     is a 187px strip under a 670px section — a fifth of the presence it has on
     the artboard, where it fills the band. Sizing by HEIGHT instead lets it
     crop at the sides and hold roughly the artboard's proportion. */
  .ta-what { background-size: auto 42%; }

  /* ========================================================================
     S2 — the artboard's device is a PORTRAIT PHONE, not the desktop tablet

     This is the section that does NOT reflow. On the artboard the tablet is
     replaced by a phone standing on end, 572/603 of the screen wide, and it
     is CLIPPED by the section's bottom edge rather than ending in a bezel —
     the ad card inside is then nearly the full width of the screen instead of
     a thumbnail in the corner.

     The text block also has its own inset: 62/603 either side, wider than the
     page gutter, which is what holds the paragraph to the artboard's 9 lines.
     ======================================================================== */
  .ta-what { overflow: hidden; }

  /* No padding under it: the phone runs INTO the section's bottom edge and
     `overflow: hidden` cuts it there, which is the artboard's flat crop. */
  .ta-what { padding-top: 31.3vw; padding-bottom: 0; }   /* heading at 1401 */
  .ta-what__grid { gap: 14.7vw; }                        /* 59/402 copy->phone */
  .ta-what__copy { gap: 5.2vw; }                         /* 32/603 head->para  */
  .ta-what__title { font-size: 5.55vw; line-height: 1.25; }
  .ta-what__copy .ta-body {
    font-size: 3.62vw;                                   /* holds the 9 lines */
    line-height: 1.57;                                   /* 33/21             */
  }

  /* Breaks out of the page gutter to the artboard's 15/603,
     then stands on end. `height` rather than `aspect-ratio` because the
     visible box is a CROP: the phone continues past the section, which
     `overflow: hidden` above cuts — the same flat edge the artboard has. */
  .ta-device {
    width: 94.9vw;                                       /* 572/603           */
    margin-inline: calc(50% - 47.45vw);
    aspect-ratio: auto;
    height: 127.7vw;                                     /* 770/603, cropped  */
    border-radius: 8vw 8vw 0 0;
    padding: 1.5vw 1.5vw 0;
  }
  .ta-device__screen { border-radius: 6.8vw 6.8vw 0 0; }
  .ta-device__notch {
    left: 40.4%;                                         /* 231/572           */
    width: 21%;                                          /* 120/572           */
  }

  /* The skeleton runs the width of the PHONE's screen.

     Its 4.65% / 62.42% is the desktop TABLET's geometry: there the ad card
     sits in the right third (left 68.48%) and the bars fill the two-thirds
     beside it. On the phone the card is centred and 88.3% wide, so bars still
     cut at 62% stopped two-thirds of the way across with a third of the
     screen blank down the right — the mock read as a broken page rather than
     a loading one.

     Same inset as the card, and the short variant keeps its relationship to
     the long one: 313/618 = 50.6% of 88.3. */
  .ta-device__bar {
    left: 5.7%;                                          /* 41/572 of screen  */
    width: 88.3%;                                        /* 488/572 of screen */
  }
  .ta-device__bar--short { width: 44.7%; }               /* 50.6% of the long */

  /* Nearly the full width of the screen, and low — 299/770 down it. */
  /* Percentages here resolve against the SCREEN, which the bezel has already
     taken 1.5vw off each side of — not against the device box. */
  .ta-device .ta-adcard {
    left: 5.7%;                                          /* 41/572 of screen  */
    top: 35.5%;                                          /* 299/770 of screen */
    width: 88.3%;                                        /* 488/572 of screen */
    padding: 7.2%;                                       /* 35/488            */
    gap: 5.3%;                                           /* 26/488 — the card's
                              rows are far apart on the artboard; the desktop
                              tablet's 0.9% collapsed it to two-thirds height */
    border-radius: 3vw;
  }
  /* The card's type, sized off the REFERENCE render rather than off the
     artboard's ink. Measured as shares of the phone's SCREEN width, which is
     the one dimension both the reference crop (350px) and the live page
     (358px at 390) have in common:

       title  cap 11/350  = 3.14%  ->  ~16px
       desc   line pitch 20/350 = 5.71%  ->  13.6px x 1.5 = 20.4px pitch
       url    ink 8/350   = 2.29%  ->  ~12px

     The old 3.05/2.5/3vw came out ~30% under that and fitted the whole title
     on ONE line. The title's final size is set by the reference's WRAP rather
     than by that cap measurement, which a JPEG's antialiasing rounds down:
     the reference breaks after "with", and "Grow Your Business with Smart"
     only overflows the card's 255px column from 4.4vw up. 4.5vw is that
     threshold plus a margin, and every size here is a share of the viewport,
     so the break holds on any phone width. */
  .ta-device .ta-adcard__title { font-size: 4.5vw; line-height: 1.3; }
  .ta-device .ta-adcard__url,
  .ta-device .ta-adcard__badge { font-size: 3vw; }
  .ta-device .ta-adcard__desc  { font-size: 3.5vw; line-height: 1.5; }

  /* The blooms keep a 220px floor so they still read as light rather than a
     smear, which on a 375 viewport is 59% of the width EACH — they overlap
     across the middle, and now that they add light instead of covering the
     plate that overlap doubles up. Dialling both back keeps the band's centre
     dark, which is the whole point of an edge bloom. */
  .text-ads-page .ta-stats__glow--left  { opacity: 0.6; }
  .text-ads-page .ta-stats__glow--right { opacity: 0.45; }

  /* .ta-stats__grid is a three-track grid on desktop, so the old
     `flex-direction: column` here was a no-op — collapse the tracks instead
     and drop the per-item justify-self that spreads them across the row. */
  /* S3 — the artboard spreads the three figures right down the band: 322/603
     between one figure's cap and the next, which is 127px of clear air at 402
     against the 40 that was here. The band is `100svh` and the grid is centred
     in it, so the gap is the only thing that has to be said. */
  .ta-stats__grid {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    gap: 31.6vw;                      /* 127/402                            */
  }
  .ta-stat       { gap: 4.15vw; }     /* 25/603 figure -> label             */
  .ta-stat__num  { font-size: 12.3vw; }  /* cap 52/603 -> ~74/603 em        */
  .ta-stat__label{ font-size: 3.2vw; }   /* 19/603                          */
  .ta-stat:nth-child(1),
  .ta-stat:nth-child(2),
  .ta-stat:nth-child(3) { justify-self: center; }

  .ta-value-grid,
  .ta-vert-grid,
  .ta-blog__grid,
  .ta-steps__row { grid-template-columns: minmax(0, 1fr); }

  /* ========================================================================
     S4 — on the artboard these are CARDS, not rows

     The gradient ring is ALWAYS ON here. On desktop it is a hover state; a
     phone has no hover, so on the artboard every card carries its ring and
     the section reads as a stack of four bordered blocks rather than four
     runs of text. Same ring, same colours — only `opacity` changes.

     The cards sit on the artboard's own inset (65/603 either side) and are
     25/603 apart. Measured off the border boxes themselves: tops at 4230 /
     4485 / 4740 / 4962, all x 65..542.
     ======================================================================== */
  .ta-benefit-rows { display: flex; flex-direction: column; gap: 4.15vw; }  /* 25/603 */

  .ta-benefit-row {
    margin-inline: 0;
    padding: 6.97vw 4.3vw;                               /* 42/603, 26/603  */
    gap: 1.66vw;                                         /* 10/603          */
    border-radius: var(--r-card);
  }
  .ta-benefit-row::after { opacity: 1; }

  /* ...and so is the FILL. The hover state's ground — the artboard's
     `Property 1=1 Hover` gradient, #000000 -> #1E1E1E at 0.4, over a 5px
     backdrop blur — is what gives each card its body against the aurora. A
     phone has no hover to reveal it, so on the artboard it is simply there.
     Same declaration as `:hover`, not an approximation of it.

     The blur was deliberately left off at rest on desktop so four rows would
     not each hold a compositor layer for nothing; here they do, which is the
     cost of the card having a ground at all. */
  .ta-benefit-row::before {
    opacity: 1;
    -webkit-backdrop-filter: blur(5px);
    backdrop-filter: blur(5px);
  }

  /* The head row keeps icon + title on one line, the description drops under
     it — that part the stacked layout already had right. */
  .ta-benefit-row__head { gap: 2.8vw; }
  .ta-benefit-row__icon { width: 9.8vw; height: 9.8vw; }     /* 59/603      */
  .ta-benefit-row__title { font-size: 4.5vw; }               /* 27/603      */
  .ta-benefit-row__desc  { font-size: 3.32vw; line-height: 1.65; }  /* 20/603, pitch 33 */

  .ta-benefits__head { margin-bottom: 8vw; }

  /* Every section heading on the artboard is the same size as the hero's:
     line pitch 45/603 with a 27..34 ink band, whatever the section. The class
     is `.ta-heading` — the `.ta-section__heading` this used to name does not
     exist in the markup, so the rule was inert and the headings stayed on the
     desktop ramp (28px against the artboard's 22). */
  .ta-heading { font-size: 5.55vw; line-height: 1.32; }

  /* ========================================================================
     S6 — the value cards take S4's treatment

     Same story as the benefit rows: on the artboard every card carries the
     gradient ring, always on, because a phone has no hover. Border boxes at
     x 67..540 with tops 6102 / 6348 / 6621 / 6867 / 7140 / 7413 — a 37/603
     gap, wider than S4's 25.
     ======================================================================== */
  /* ------------------------------------------------------------------
     S5 — the panel breaks out, and the artboard rules a line under it

     The panel is NOT on the card inset: its box runs x 42..570 where the
     cards run 65..540, so it is 528/603 wide and hangs 23/603 outside the
     container on each side. Below it the artboard rules a hairline across
     the container's own width, which is what separates S5 from S6 — there
     is no such divider on desktop.

     The artwork keeps its own aspect. The artboard's panel is a wider crop
     of the same composition (2.58 against the asset's 1.78); forcing that
     with `object-fit: cover` would cut the chips off the bottom, so the
     width matches and the height follows the file.
     ------------------------------------------------------------------ */
  /* The artboard rings the panel in BLUE, 2px, not the `--c-hairline` grey it
     wears on desktop. Sampled off all four edges of the ring — left x 28..29,
     right x 377..379, top y 137..139, bottom y 335..336 — the fill reads
     rgb(40,146,223) on every one of them, which is neither `--c-blue`
     (#3B82F6, more indigo) nor any existing token, so it is stated here. */
  /* Flush with the column, not broken out of it. The artboard hangs this
     panel 23/603 outside its section's 65/603 inset; with every section on
     the page gutter now, that break-out lands within a pixel or two of the
     gutter anyway — near-flush reads as a mistake, actually flush reads as
     the design. */
  .ta-how__panel {
    width: 100%;
    margin-inline: 0;
    border: 2px solid #2892DF;
  }
  .ta-how {
    padding-bottom: 12vw;
    border-bottom: 1px solid var(--c-hairline);
  }
  /* ...and the band stops AT that rule. Every .ta-section carries
     --space-section top and bottom, so a section boundary is normally two of
     them — invisible in the middle of a scroll, but this boundary is DRAWN:
     the reader saw the line and then 48px of nothing before S7's own 48px,
     which reads as the next section starting with a hole in it. Zeroing the
     band's bottom leaves S7's padding to own the gap on its own — 55px from
     the rule to the heading's ink, which is what the reference shows. The
     12vw above the rule is untouched. */
  .ta-band { padding-bottom: 0; }

  .ta-value-grid { gap: 6.14vw; }                        /* 37/603          */

  .ta-vcard {
    padding: 4.48vw 4.3vw;                               /* 27/603, 26/603  */
    gap: 2.2vw;
    border: 0;
  }
  .ta-vcard::after { opacity: 1; }
  .ta-vcard__head  { gap: 2.8vw; }
  .ta-vcard__icon  { width: 9.8vw; height: 9.8vw; }      /* 59/603          */
  .ta-vcard__title { font-size: 4.3vw; line-height: 1.3; }
  .ta-vcard__desc  { font-size: 3vw; line-height: 1.6; } /* pitch 29/603    */

  /* ========================================================================
     S12 — the CTA IS a screen, with the panel centred in it

     `100svh` for the block (`80vh` first, as the fallback for anything without
     svh) and the panel 60% of that, centred: 20% of clear screen above it and
     20% below. The top fifth is what the steps' connector arrowhead ends in —
     it reads as pointing INTO the panel rather than butting against it — and
     the bottom fifth is what stops the panel sitting on the FAQ's edge.

     `margin-top` goes to 0 because that gap is the block's own top fifth now.
     Left as it was, the artboard's ~120px and this would stack into a hole.

     `height: 60%` on the panel resolves against the block's definite height,
     so the panel is 60svh without either being restated, and `min-height: 0`
     is what stops the desktop rule's 100vh floor beating this `height`. */
  .ta-cta {
    /* MIN-height, not height. A fixed screen was fine while the panel fitted
       inside its 60% share, and it is not on a short phone: at 320x568 the
       panel's own content measures 517px against the 341px that share gives
       it, and the panel is `overflow: hidden`, so the sub-line and BOTH
       buttons were cut off below its bottom edge. A floor lets the block grow
       to whatever the panel needs instead of clipping it; on a tall phone the
       panel is still 60svh and nothing about this screen changes. */
    min-height: 80vh;
    min-height: 100svh;
    height: auto;
    margin-top: 0;
    /* Only bites when the panel outgrows the screen — there the centring has
       no leftover height to spend, and this is what keeps the panel off the
       steps above and the FAQ below. Inside the fifths everywhere else. */
    padding-block: 24px;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  /* The section ENDS with that screen. S9+S10 share one `.ta-section`, so its
     padding-bottom used to sit under the CTA's own bottom fifth — 169 + 48 =
     217px between the panel and the FAQ, against the 169 the centring is
     already there to give. The block owns the gap; the section adds nothing
     after it. */
  .ta-steps { padding-bottom: 0; }
  .ta-cta__panel {
    /* 60svh is the FLOOR the artboard's proportion becomes, not the height:
       the panel takes exactly that while its content fits (every tall phone),
       and grows past it rather than hiding the foot when it does not.
       `flex: 0 0 auto` is what stops the centring flexbox shrinking it back
       down to the block's own height. */
    height: auto;
    /* 48vh is 60% of the block's own 80vh fallback, so a browser without svh
       keeps the same proportion it always had. */
    min-height: 48vh;
    min-height: 60svh;
    flex: 0 0 auto;
    /* 18, not the 22 the desktop clamps bottom out at: at the reference's
       40px face "Can Really Do?" measures 297px against a 293px column and
       broke onto a fourth line. Four pixels of padding is what keeps the
       artboard's three-line title, and 18 is the page gutter, so the panel's
       inside edge lines up with everything else on the screen.

       Set through the variable, not on `padding-inline` directly, because
       the visual's full-bleed margin above reads the same figure — the two
       cannot drift apart. */
    --cta-pad-x: 18px;
    padding-inline: var(--cta-pad-x);
    display: flex;
    flex-direction: column;
    /* Title at the top, sub + buttons at the bottom — the artboard's gap
       between them is the panel's leftover height, not a fixed lump. */
    justify-content: space-between;
  }

  .ta-cta__foot { flex-direction: column; align-items: stretch; }
  .ta-cta__buttons { flex-direction: column; align-items: stretch; }
  .ta-cta__buttons .ta-btn { width: 100%; }

  .ta-faq__item { gap: 12px; align-items: center; }

  /* THE CARD WAS PADDED TWICE. `.ta-faq__card` insets its contents (22/24)
     and the question button was adding its own 20/24 inside that, so on a
     390px screen the title had 181px of a 279px card to live in — every
     question wrapped to three lines and the collapsed card stood 196px tall.
     The button's padding is gone (the card's is the inset, as on desktop) and
     the card's is trimmed, which hands the title 241px.

     The face comes down with it. --fs-32 bottoms out at 22px, which is sized
     for the desktop card's 700px column, not for this one; 17/1.35 fits the
     shorter questions on two lines and reads as a list rather than as five
     stacked paragraphs. Letter-spacing goes back to normal — the -0.16px in
     the token is a correction for 32px type. */
  .ta-faq__card { padding: 16px 18px; }
  .ta-faq__q {
    padding: 0;
    font-size: 17px;
    line-height: 1.35;
    letter-spacing: normal;
  }
  .ta-faq__a-inner { font-size: 15px; line-height: 1.6; }
}

/* ==========================================================================
   S9 — THE FLOW TURNS 90 DEGREES BELOW THE DESKTOP BREAKPOINT

   Two compositions ship for this section and only two: the artboard's
   horizontal run (tiles above, dashed thread through the nodes, cards below)
   and the phone's vertical one (tile | node | card, thread running down). The
   768..1199 band used to get NEITHER — it fell back to four boxed columns
   with the tile and the node stacked INSIDE the card's ring and no connector
   at all, which is a composition the design does not contain. At 1024 that is
   four 217px columns; at 768 it wraps to a 2x2 of them.

   The horizontal run cannot come down here — four cards at desktop's 269px
   need 1200px before gutters — so the vertical one goes up instead, and this
   block now owns every width under the desktop breakpoint. It is the phone
   block's own rules, moved out of it unchanged apart from the unit below, so
   phones render exactly what they rendered before.

   Measured off the mobile design (crop 452 wide), as shares of the artboard:

     tile     x  56..126   (70)      node centres  x 163..186 (23), c 174.5
     card     x 211..397  (186)      node centres  y 92 / 325 / 560 / 793

   — i.e. a row pitch of 233.5 and a content run of 56..397 (341) inside a
   12.4% inset. The column tracks below are those five spans expressed
   against the row's own width, with the two gaps as empty tracks rather
   than one `column-gap`, because they are NOT equal (37 and 25).
   ========================================================================== */
@media (max-width: 1199px) {
  /* ONE NUMBER DRIVES EVERY VERTICAL MEASURE. The horizontal spans are
     percentages of the row and take care of themselves; the row pitch, and
     the thread's length and thickness with it, were `vw` — fine on a phone,
     where the viewport IS the composition, and useless on a tablet, where
     51.66vw is a 619px row at 1199. Naming the basis lets the tablet cap it
     without any of the ratios moving. On phones it resolves to 100vw, so
     every value below is the literal `51.66vw` / `206.64vw` / `6.56vw` /
     `4vw 3vw` that was here before. */
  .ta-steps__flow { --ta-flow-unit: 100vw; }

  .ta-steps__row {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .ta-step {
    height: calc(var(--ta-flow-unit) * 0.5166);   /* 233.5/452 node-to-node */
    display: grid;
    grid-template-columns: 20.5% 10.9% 6.7% 7.3% 54.6%;
    align-items: center;
    gap: 0;
  }
  .ta-step__tile { grid-column: 1; width: 100%; height: auto; }
  .ta-step__node { grid-column: 3; width: 100%; height: auto; }
  /* `position: relative` is load-bearing: the 1199 fallback above sets the
     three pieces `static` to unwind the artboard's absolutes, and the card's
     ring is an `inset: -1px` ::after — with the card static it resolved
     against `.ta-step` instead and drew a border around the WHOLE ROW
     (304x210 rather than the card's 165x116). */
  .ta-step__card {
    position: relative;
    /* ...and `relative` HONOURS the desktop rule's artboard `top`, which
       `static` was quietly discarding — without this the card hangs 90px
       below its own node. */
    top: auto;
    left: auto;
    grid-column: 5;
    width: auto;
    min-height: 0;
    padding: calc(var(--ta-flow-unit) * 0.04) calc(var(--ta-flow-unit) * 0.03);
    text-align: center;
  }

  /* THE THREAD. `step-path.svg` is a horizontal run (1724.47 x 54.7688) with
     `preserveAspectRatio="none"`, so it stretches to whatever box it is given
     — a box of length x thickness, turned 90 degrees, is the vertical thread.
     Rotating about the top-left corner maps the image's +x onto the page's
     +y, so the arrowhead that ends the run now points DOWN, the gradient runs
     top to bottom, and — the part worth keeping — initStepsPath()'s reveal
     needs no change at all: its `inset(0 100% 0 0) -> inset(0 0 0 0)` clip is
     in the image's own coordinates, which after the turn wipes downward.

     The length is a known multiple of the row pitch (4 x the pitch) because
     the pitch comes off the same unit, so it can be stated in CSS without
     measuring the flow. Thickness holds the SVG's own 3.176% ratio so the
     wave is not squashed. `translateY(-50%)` runs in the rotated frame, which
     is what centres the thread on the node column. */
  /* `.text-ads-page` and not a bare class: the desktop rule is written at
     that specificity and would otherwise keep its own top/left/width/height
     (and its `max-width: none`, which is still needed here). It also has to
     out-rank the `display: none` the 1199 fallback still carries. */
  .text-ads-page .ta-steps__path {
    display: block;
    top: 0;
    left: 34.75%;                     /* node centre within the row         */
    width: calc(var(--ta-flow-unit) * 2.0664);   /* 4 x the row pitch       */
    height: calc(var(--ta-flow-unit) * 0.0656);  /* 54.7688/1724.47 of it   */
    transform-origin: 0 0;
    transform: rotate(90deg) translateY(-50%);
  }
}

/* --------------------------------------------------------------------------
   Tablet only — the same composition, held to a width it reads at

   A phone hands this layout the whole screen because the screen is about as
   wide as the composition wants to be. A tablet does not: left to run full
   width the row's percentage tracks stretch with it, and at 1199 that is a
   233px tile beside a 620px card holding two lines of type.

   So the flow is capped and centred, and the unit is set from the same pair
   rather than from the viewport. 575 / 500 holds the phone build's own
   row-to-viewport ratio (339 of 390) and lands the composition at roughly
   1.5x the phone: a 297px row pitch, a 102px tile against its 128px natural
   size, and a 273px card — within 4px of what the card measures on desktop.
   -------------------------------------------------------------------------- */
@media (min-width: 768px) and (max-width: 1199px) {
  .ta-steps__flow {
    --ta-flow-unit: 575px;
    max-width: 500px;
    margin-inline: auto;
  }
}

@media (max-width: 374px) {
  :root { --gutter: 14px; }
}

/* --------------------------------------------------------------------------
   Short phones — the CTA panel, sized to the screen it actually has

   The rule above lets the panel GROW rather than clip, which is the fix; this
   is what stops it having to grow much. On an SE (320x568, and ~460 of that
   once the browser chrome is out) the panel's content is 517px: a 40px title
   over five lines is 250 of it, and the stacked buttons another 143. That
   fits, but only by filling the screen edge to edge and pushing the block
   past a single screenful.

   So on a screen this short the panel takes the mobile artboard's proportions
   instead of its absolute figures — 40px is the 603-wide artboard's face, and
   these screens are half that wide. The title comes down to ~7.6vw (24px at
   320, 28px at 375), which is four lines instead of five, and the paddings,
   the gap and the buttons come in with it. Measured at 320x568 that takes the
   panel from 517px to ~360px, back inside the 60% share the design gives it.

   Height-gated, not width-gated: a 390x844 phone is the SAME width as one of
   these and has 280px more to spend, so it must keep the full-size panel.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) and (max-height: 700px) {
  .ta-cta__panel {
    padding-block: 20px;
    gap: 18px;
  }
  .ta-cta__title {
    font-size: clamp(24px, 7.6vw, 30px);
    /* Tighter than the 1.25 the artboard sets, because at four lines of a
       smaller face the looser leading is what puts the block over. */
    line-height: 1.2;
  }
  .ta-cta__sub { font-size: 15px; }
  .ta-cta__foot { gap: 14px; }
  .ta-cta__buttons { gap: 10px; }
  .ta-cta__buttons .ta-btn {
    padding-block: 13px;
    font-size: 15px;
  }
}

/* --------------------------------------------------------------------------
   Reduced motion
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .ta-marquee__track { animation: none; transform: none; }
  .text-ads-page *,
  .text-ads-page *::before,
  .text-ads-page *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* --------------------------------------------------------------------------
   Floating ad-format switcher (Figma 10471:10835-10849)

   Card, from the node: `w-140 p-24 gap-16 rounded-10`, fill
   `linear-gradient(#070C18, #1E1E1E)`, 1px `rgba(233,253,69,0.2)`, 46px icon
   over an 18px `#9CA3AF` label.

   Collapsed, all five sit on ONE spot under the artboard's rotations
   (native 0, banner +15, push -15, popunder -30, interstitial +30) with
   native face up. Expanded, they deal out into a column. Both states are the
   same elements — only `translate`/`rotate` change, so the transition is one
   compositor-friendly property pair and no layout is recalculated.
   -------------------------------------------------------------------------- */
.ta-formats {
  position: fixed;
  z-index: 40;
  right: clamp(16px, 2.083vw, 40px);
  bottom: clamp(16px, 2.083vw, 40px);
  display: flex;
  align-items: flex-end;
  /* 89, not 20. On the artboard the annotation's text ends at x182 and the
     card starts at x271 — the arrow needs that room to curve INTO the stack
     instead of touching it. Measured off the reference: with 20 the arrow's
     tip sat 6px from the card, against the artboard's 59. */
  gap: clamp(32px, 4.635vw, 89px);
  pointer-events: none;
  /* Held back until 15s after the page has finished loading — main.js adds
     `is-ready`. Hidden with visibility rather than `display: none` so the fan
     keeps its box and its cards are already laid out (and measured) by the
     time it fades up; the delayed `visibility` transition is what keeps it
     non-interactive for the whole fade. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  transition:
    opacity 500ms var(--ease-out),
    transform 500ms var(--ease-out),
    visibility 0s linear 500ms;
}
.ta-formats.is-ready {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}

/* Back to the resting state while S11 is on screen — three classes, so it
   out-ranks `.is-ready`, and the same transition so it leaves the way it
   arrived. main.js owns the class; the note on initFormats() has the
   hit-testing that made it necessary. */
.ta-formats.is-ready.is-standing-down {
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  transition:
    opacity 500ms var(--ease-out),
    transform 500ms var(--ease-out),
    visibility 0s linear 500ms;
}

/* AND IT STOPS TAKING CLICKS ON THE FIRST FRAME, not on the last one. The
   `visibility` step above is delayed by the length of the fade so the box does
   not vanish mid-transition — which also left the toggle hit-testable for the
   whole 500ms it was fading out. Hit-testing the circles at the pin's opening
   frame caught exactly that: one circle still returning
   `button.ta-formats__toggle` at 1680, 1440 and 1400. Dropping pointer-events
   here closes the window without touching how it looks going away. */
.ta-formats.is-ready.is-standing-down > * { pointer-events: none; }

.ta-formats > * { pointer-events: auto; }

/* The Caveat annotation and its arrow, from nodes 10472:10850 / 10472:10855. */
.ta-formats__note {
  position: relative;
  z-index: 12;   /* above the fan, so the arrow is not painted under a card */
  margin: 0 0 8px;
  font-family: 'Caveat', cursive;
  font-weight: 400;
  font-size: clamp(18px, 1.25vw, 24px);
  line-height: 1.15;
  color: var(--c-white);
  white-space: nowrap;
  pointer-events: none;
}
/* The artboard puts the arrow up and to the RIGHT of the annotation
   (node 10472:10855 at x+70, y-17 against the text), curving into the stack —
   not stacked under it.

   Anchored from the note's LEFT rather than its right: the artboard's offset
   is measured from the text's start, and anchoring to the right edge made the
   arrow's position depend on how wide the two lines happen to render. Measured
   against the reference, the ink then lands at note-left +67 and note-top +19,
   against +52 / -39 before — the arrow used to float too high and too far
   left, which is what closed the gap to the stack. */
.ta-formats__arrow {
  position: absolute;
  left: clamp(48px, 3.49vw, 67px);
  bottom: calc(100% - clamp(12px, 0.99vw, 19px));
  width: clamp(38px, 2.78vw, 53px);
  height: auto;
}

/* The whole stack is the button when collapsed; expanded it only has to be
   reachable for closing, so it sits behind the cards. */
.ta-formats__toggle {
  position: absolute;
  right: 0;
  bottom: 0;
  width: var(--ta-format-w);
  height: var(--ta-format-h);
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: none;
  cursor: pointer;
}
.ta-formats__toggle-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.ta-formats__toggle:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 4px; }

.ta-formats {
  --ta-format-w: clamp(96px, 7.292vw, 140px);
  --ta-format-h: clamp(96px, 6.875vw, 132px);
  --ta-format-gap: clamp(10px, 0.938vw, 18px);
}

.ta-formats__list {
  position: relative;
  width: var(--ta-format-w);
  height: var(--ta-format-h);
  margin: 0;
  padding: 0;
  list-style: none;
  transition: height var(--dur-slow, 520ms) var(--ease-out);
}

/* Collapsed: every card on the same spot, fanned. */
.ta-formats__item {
  position: absolute;
  right: 0;
  bottom: 0;
  width: var(--ta-format-w);
  height: var(--ta-format-h);
  /* Deepest card paints first so "native" (index 0) ends up on top. */
  z-index: calc(10 - var(--i));
  transform: rotate(var(--rot, 0deg));
  transition: transform var(--dur-slow, 520ms) var(--ease-out);
}
/* `--rot` and `--i` come down per card from includes/ad-formats.php, and
   `--ta-format-count` from the <aside>. The angles used to be nth-child rules
   and the column height a literal `5 *`, which meant the stylesheet had to be
   edited every time the list changed length — and the list changes length by
   design now, since each page drops its own format out of it. */

/* Expanded: unrotate and deal upward. The list grows to the column's height
   so the fixed box still measures correctly for hit-testing. */
.ta-formats.is-open .ta-formats__list {
  height: calc(var(--ta-format-count, 5) * var(--ta-format-h)
             + (var(--ta-format-count, 5) - 1) * var(--ta-format-gap));
}
/* The column grows UPWARD from the anchored corner, in the registry's own
   order top to bottom — so card 0 travels furthest and the last card stays
   put, whatever the list's length happens to be on this page. */
.ta-formats.is-open .ta-formats__item {
  transform: translateY(calc(-1 * (var(--ta-format-count, 5) - 1 - var(--i)) * (var(--ta-format-h) + var(--ta-format-gap))));
  /* Each card leaves a beat after the one below it, so they deal out rather
     than move as a block. */
  transition-delay: calc((var(--ta-format-count, 5) - 1 - var(--i)) * 45ms);
}

.ta-formats__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 0.833vw, 16px);
  width: 100%;
  height: 100%;
  padding: clamp(14px, 1.25vw, 24px);
  border: 1px solid rgba(233, 253, 69, 0.2);
  border-radius: 10px;
  background-image: linear-gradient(180deg, #070C18 0%, #1E1E1E 100%);
  text-align: center;
  transition: background-color var(--dur-normal) var(--ease-out),
              border-color var(--dur-normal) var(--ease-out),
              color var(--dur-normal) var(--ease-out);
}

/* Masked, not an <img>: the exported icons are solid #E9FD45 paths and the
   hover state needs them dark on a lime card. */
.ta-formats__icon {
  width: clamp(32px, 2.396vw, 46px);
  height: clamp(32px, 2.396vw, 46px);
  background-color: var(--c-accent);
  /* mask-image comes from the element's inline style — see the note in the
     markup. Only the geometry is set here, and NOT via the `mask` shorthand,
     which would reset that image back to none. */
  -webkit-mask-position: center;
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-position: center;
  mask-size: contain;
  mask-repeat: no-repeat;
  transition: background-color var(--dur-normal) var(--ease-out);
}
.ta-formats__label {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: clamp(14px, 0.938vw, 18px);
  line-height: 1;
  color: var(--c-grey);
}

/* Hover: the card fills lime and the icon and label go dark against it. */
.ta-formats__card:hover,
.ta-formats__card:focus-visible {
  background-image: none;
  background-color: var(--c-accent);
  border-color: var(--c-accent);
}
.ta-formats__card:hover .ta-formats__icon,
.ta-formats__card:focus-visible .ta-formats__icon { background-color: var(--c-surface); }
.ta-formats__card:hover .ta-formats__label,
.ta-formats__card:focus-visible .ta-formats__label { color: var(--c-surface); }
.ta-formats__card:focus-visible { outline: 2px solid var(--c-white); outline-offset: 3px; }

/* Collapsed, the stack is a picture of a button, not five links: the list has
   to be pointer-transparent so the click lands on .ta-formats__toggle
   underneath it. Open, the cards take the pointer back and the toggle sits
   behind the face-up card; closing is outside-click or Escape. */
.ta-formats:not(.is-open) .ta-formats__list,
.ta-formats:not(.is-open) .ta-formats__item { pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .ta-formats__item,
  .ta-formats__list { transition-duration: 1ms; }
}

/* Kept on phones — collapsed the stack is only one card, and at the clamp
   minimums the open column is 5x96 + 4x10 = 520px, which clears a 844-tall
   phone. The annotation is what does not fit next to it, so that goes
   instead. Only a genuinely short screen loses the whole thing. */
@media (max-width: 767px) {
  .ta-formats__note { display: none; }
}
@media (max-height: 560px) {
  .ta-formats { display: none; }
}

/* --------------------------------------------------------------------------
   Site header dock — hide on the way down, slide back on the way up

   THE ONE EXCEPTION TO THE SCOPING RULE AT THE TOP OF THIS FILE. That rule
   keeps this sheet off .th-header so the shared bar renders identically here
   to everywhere else, and these three rules do not touch how it LOOKS — no
   colour, type, spacing or size — only where it sits and when it is on
   screen. The bar's own fill still comes from assets/css/style.css.

   Gated behind `.ta-header-dock`, which assets/text-ads/js/header.js adds
   after it has lifted the header out of #smooth-content and put that height
   back as padding. Without the script none of this applies and the header
   stays exactly as the shared markup renders it, which is what a no-JS
   visitor gets.

   `translate3d` and not `top`: the bar is fixed over a page that pins three
   sections, so the move has to stay on the compositor and out of layout.
   450ms is longer than the page's --dur-normal on purpose — this is a 101px
   travel arriving from off-screen, and at 300ms it snaps in rather than
   sliding. --ease-out (the same out-expo the rest of the page uses) puts
   almost all of that travel in the first third, so it reads as fast.
   -------------------------------------------------------------------------- */
.th-header.ta-header-dock {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  will-change: transform;
  transition: transform 450ms var(--ease-out);
}

.th-header.ta-header-dock.is-hidden {
  transform: translate3d(0, -100%, 0);
}

@media (prefers-reduced-motion: reduce) {
  .th-header.ta-header-dock { transition-duration: 1ms; }
}
