/* ==========================================================================
   Bowtie marketing site — design system
   --------------------------------------------------------------------------
   Tokens are extracted from the live production site and are canonical; see
   www/CLAUDE.md -> "Design system". Reference them by variable, never inline a
   hex value.

   Mobile-first: base styles target a 390px phone, and every @media block widens
   from there. Elementor's own breakpoints are the reference for parity —
   mobile <=767, tablet <=1024, laptop <=1366 — so the same layout switches
   happen at the same widths the live site switches at.
   ========================================================================== */

@import url("/assets/fonts/fonts.css");

:root {
  /* Brand ---------------------------------------------------------------- */
  /* The accent is a teal->blue RAMP, not a single teal. Solid fills use the
     deeper --teal; text and iconography use the lighter --teal-bright, which is
     also the gradient's first stop. Every value below was read off the live DOM
     with getComputedStyle, not eyeballed. */
  --teal: #2dd4bf;         /* solid fills: primary button, progress bar track */
  --teal-bright: #5eead4;  /* accent text, gradient start */
  --teal-tint: rgba(45, 212, 191, 0.16); /* teal at low alpha, badge/pill fills */
  --blue: #49beff;         /* accent text, progress bar fill */
  --blue-solid: #4194ff;   /* solid fills and borders */
  --blue-grad: #60a5fa;    /* gradient end */
  --on-accent: #052824;    /* text ON a solid teal fill; near-black green */
  /* The one non-accent label colour on the site. Live reserves it for the
     eyebrow of a section that names a problem, so the reader can tell at a
     glance which band is the diagnosis and which is the answer. Not decorative:
     using the accent ramp there loses that distinction. */
  --amber: #fbbf24;

  /* The accent gradient. 90deg for eyebrows, stat figures and card titles;
     135deg for the large hero heading highlight. */
  --grad-accent: linear-gradient(90deg, var(--teal-bright) 0%, var(--blue-grad) 100%);
  --grad-accent-diag: linear-gradient(135deg, var(--teal-bright) 0%, var(--blue-grad) 100%);

  /* Surfaces. All measured: bg rgb(6,8,12), card rgb(14,22,38). The card is
     distinctly BLUE-shifted, not neutral grey. */
  --bg: #06080c;
  --surface: #0e1626;
  --surface-alt: #131d30;
  --surface-subtle: rgba(255, 255, 255, 0.03);
  --surface-raised: #131d30;
  --border: rgba(255, 255, 255, 0.08);
  --border-bright: #2f3f58;

  /* Text ----------------------------------------------------------------- */
  --text: #ecf1f8;
  --muted: #a2b0c5;
  --muted-deep: #6e7c92;

  /* Hover for solid-teal fills. The live site defines no :hover on its primary
     button (Elementor left the default), so this is ours; it is deliberately a
     small step off --teal so it reads as feedback, not as a different colour. */
  --teal-deep: #26bdac;
  /* Negative / error only, never decoration. Not part of the live marketing
     palette, which has no red: it exists for form validation and the "before"
     column of the comparison table, where red carries meaning. */
  --coral: #ff6b6b;

  /* Type. Hellix is declared first and activates the moment its files land in
     /assets/fonts — no other edit needed. It is NOT currently licensed, so
     everything falls through to Inter today. See www/todo.md. Always keep an
     explicit fallback: bare `font-family: Hellix` resolves to the browser's
     default SERIF, which is a visible defect in a sans-serif design. */
  --font-display: "Hellix", "Inter", system-ui, sans-serif;
  --font-body: "Inter", system-ui, sans-serif;
  --font-ui: "Roboto", "Inter", system-ui, sans-serif;
  --font-alt: "Manrope", "Inter", system-ui, sans-serif;

  /* Body steps are fluid; the two heading steps are not.
     Live sets its headings per breakpoint from fixed values, and a clamp cannot
     reproduce that: it interpolates continuously, so it lands between live's
     steps everywhere except at the exact endpoints. Measured on live, h1/h2 are
     34/30 on mobile, 44/40 on tablet and 50/44 from 1025px up, so those two are
     stepped to match in the RESPONSIVE TIERS block below.

     That applies to the small end of the scale too. Measured across 390 / 1000 /
     1200 / 1390, live holds 14, 16 and 18 flat at every width and steps only 24
     (down to 18 below 1025) along with h1 and h2. The fluid versions of the first
     three were a sixth of a step short on a phone, which is where most of the
     copy on the site is read. */
  --step--1: 0.875rem;  /* 14px, flat */
  --step-0: 1rem;       /* 16px, flat */
  --step-1: 1.125rem;   /* 18px, flat */
  --step-2: 18px;   /* 24px from 1025 up — stepped, see RESPONSIVE TIERS */
  --step-3: 30px;   /* h2 — stepped, see RESPONSIVE TIERS */
  --step-4: 34px;   /* h1 — stepped, see RESPONSIVE TIERS */

  /* Vertical rhythm and the page gutter are also stepped rather than fluid, for
     the same reason. See RESPONSIVE TIERS for the measured values. */
  --sec-pad: 72px;
  --sec-pad-loose: 72px;
  --hero-pad-top: 48px;
  --hero-pad-bottom: 58px;
  --gutter: 32px;
  /* Gap between a section head and the content under it. Live nearly halves it
     below 1025px, which is most of why our narrow widths read airier than
     live's even after the section padding matched. */
  --head-gap: 44px;

  /* Space --------------------------------------------------------------- */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4rem;
  --sp-9: 6rem;

  /* Measured off the live site: 8px on buttons and inputs, 18px on cards,
     20px on the large panels. */
  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 18px;
  --radius-xl: 20px;
  --radius-pill: 999px;

  /* 1280px measured: the live content column sits at x=80 in a 1440 viewport. */
  --container: 1280px;
  --header-h: 69px; /* measured from the live sticky header */

  /* Motion. Timings match the live site's transitions. */
  --t-fast: 0.15s;
  --t-mid: 0.3s;
  --t-slow: 0.4s;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);

  /* The live site uses NO box-shadow on any marketing surface — cards are
     separated by border and background alone. These are reserved for true
     overlays (modal, dropdown, mobile nav), which have no live counterpart
     because they only exist on interaction. Do not put them back on cards. */
  --shadow-lg: 0 24px 60px rgba(0, 0, 0, 0.55);
  --glow-teal: 0 0 0 1px rgba(45, 212, 191, 0.35), 0 8px 32px rgba(45, 212, 191, 0.12);
}

/* ==========================================================================
   RESPONSIVE TIERS
   --------------------------------------------------------------------------
   Live is an Elementor site with four breakpoints, and it re-sets heading size,
   section padding and the page gutter at each one. Those are not smooth curves:
   section padding goes 72 -> 72 -> 40 -> 120 as the viewport widens, which no
   clamp() can express, and rebuilding it as fluid type made every window
   narrower than 1367px noticeably airier than live. Values below are measured,
   not derived.

     tier      viewport      section  loose  hero      h1  h2  gutter  head gap
     mobile    <= 767        72       72     48 / 58   34  30  32      44
     tablet    768 - 1024    72       72     48 / 54   44  40  32      44
     laptop    1025 - 1366   40       40     40 / 40   50  44  32      80
     desktop   >= 1367       120      140    64 / 64   50  44  80      80

   `--step-2` steps too, but on its own schedule: 18px below 1025 and 24px above,
   with no tablet value of its own.

   The 40px laptop band really is tighter than the tablet band above and the
   desktop band below it. It reads like a value someone typed into one field and
   never revisited, but it is what visitors on a 13" laptop actually see, so it
   is reproduced. The defaults in :root are the mobile tier, so this cascade is
   min-width only.

   1025px is also where three components change shape, not just size: the stat
   row, the "how it works" flow and the problem callout. Those overrides sit with
   their own component rules further down, under `max-width: 1024px`, since they
   are layout rather than tokens.
   ========================================================================== */

@media (min-width: 768px) {
  :root {
    --step-3: 40px;
    --step-4: 44px;
    --hero-pad-bottom: 54px;
  }
}

@media (min-width: 1025px) {
  :root {
    --step-2: 24px;
    --step-3: 44px;
    --step-4: 50px;
    --sec-pad: 40px;
    --sec-pad-loose: 40px;
    --hero-pad-top: 40px;
    --hero-pad-bottom: 40px;
    /* 64px, which is the gutter live's Elementor containers put between every
       top-level row inside a section at this tier, not just under the head. The
       80px this used to carry was measured off one section that also has a
       trailing paragraph, and it made every other section on the site taller
       than live by the difference. Sections that live sets tighter override it
       with .section--head-tight. */
    --head-gap: 64px;
  }
}

@media (min-width: 1367px) {
  :root {
    --sec-pad: 120px;
    --sec-pad-loose: 140px;
    --hero-pad-top: 64px;
    --hero-pad-bottom: 64px;
    /* Below this width live drops the 64px container margin and pads the page
       by 32px instead; above it the column caps at 1280 and the 16px padding
       plus the auto margin reproduce live's 80px inset at 1440. */
    --gutter: var(--sp-4);
  }
}

/* Accent gradient text: eyebrows, hero heading highlights, stat figures and
   feature-card titles on the live site all use this, never a flat colour.
   -webkit-text-fill-color is the part that actually reveals the gradient;
   `color` stays set so a browser without background-clip:text still renders. */
.grad-text {
  background-image: var(--grad-accent);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: var(--teal-bright);
}
/* background-image, never the `background` shorthand: the shorthand resets every
   background-* longhand, including background-clip, so a modifier written as
   `background: ...` silently drops the clip back to border-box and the gradient
   paints as a solid block behind invisible text. */
.grad-text--diag { background-image: var(--grad-accent-diag); }

@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .grad-text { background: none; -webkit-text-fill-color: currentColor; }
}

/* ==========================================================================
   Base
   ========================================================================== */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  /* Offset in-page anchor targets so the sticky header never covers them. */
  scroll-padding-top: calc(var(--header-h) + var(--sp-4));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--muted);
  font-family: var(--font-body);
  font-size: var(--step-0);
  /* 16px/28.8px on the live site — a 1.8 ratio, looser than a typical 1.65. */
  line-height: 1.8;
  /* Belt and braces against a stray wide child causing a horizontal scrollbar. */
  overflow-x: hidden;
}

/* Headings are Inter on the live site, NOT the display face. Hellix is specced
   only on a handful of non-heading elements there, so pointing headings at
   --font-display would silently restyle every heading the day the Hellix files
   land. Keep them on --font-body; see www/todo.md. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-body);
  font-weight: 700;
  /* The live headings are set solid (line-height == font-size) with no tracking
     adjustment. Both are visible at large sizes; do not "improve" them. */
  line-height: 1;
  letter-spacing: normal;
  /* Display headings (h1/h2) are pure white on the live site; h3 and below sit
     on --text, a hair cooler. Live is not perfectly consistent about this (two
     of the seven h2s on /for-growth/ are --text), but #fff is the clear intent
     and the majority, so it is what we standardise on. */
  color: #fff;
  margin: 0 0 var(--sp-4);
  /* Deliberately NOT text-wrap: balance. Balancing is the nicer default, but it
     re-breaks every multi-line heading: the closing CTA came out 608/591 where
     live greedily fills 724/475. Same height, visibly different shape. Fidelity
     wins; live's measures are tuned to greedy wrapping. */
}

/* The hero h1 is the one heading set at 600 rather than 700. */
h1 { font-size: var(--step-4); font-weight: 600; }
h2 { font-size: var(--step-3); }
/* Smaller headings carry the live 1.2 leading, which only the large sizes drop. */
h3, h4, h5, h6 { line-height: 1.2; color: var(--text); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }

p { margin: 0 0 var(--sp-4); }
p:last-child { margin-bottom: 0; }

/* Lists used as layout containers. `list-style: none` removes the marker but
   leaves the UA's 40px inline-start padding and 1em block margin in place, so a
   grid built on a <ul> sits off the content column and every card in it comes
   out narrower than its live counterpart. Reset them in one place, early enough
   that a component rule below can still set its own margin. */
.cards,
.carousel__track,
.flow,
.tiers,
.tier ul,
.compare__col ul,
.platforms,
.ep-list {
  margin: 0;
  padding: 0;
}

a {
  color: var(--teal);
  text-decoration: none;
  transition: color var(--t-fast) var(--ease);
}
a:hover { color: var(--teal-deep); }

/* Every image fluid — never a fixed px width (www/CLAUDE.md). */
img, svg, video {
  max-width: 100%;
  height: auto;
  display: block;
}

ul, ol { margin: 0 0 var(--sp-4); padding-left: 1.25rem; }

code, pre { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 0.9em; }
pre {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--sp-4);
  /* Wide code scrolls inside its own box; the page body must never scroll sideways. */
  overflow-x: auto;
}

hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-7) 0; }

/* A single, consistent focus ring. Never remove it without replacing it —
   the live site's tab controls are unreachable by keyboard precisely because
   they were built without focus affordances. */
:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Visually hidden but available to screen readers and skip links. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -100px;
  z-index: 200;
  background: var(--teal);
  color: #04121a;
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius);
  font-weight: 600;
  transition: top var(--t-fast) var(--ease);
}
.skip-link:focus { top: var(--sp-3); }

/* ==========================================================================
   Layout
   ========================================================================== */

/* --container is the width of the *content* column, so the gutter is added on
   top of it. Live puts 16px of page padding outside a 1280px column; capping
   max-width at 1280 with border-box padding would give a 1248px column and
   every grid inside it would come out narrow. */
.container {
  width: 100%;
  max-width: calc(var(--container) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Section rhythm comes from --sec-pad, which is stepped per tier in RESPONSIVE
   TIERS above. Inside a section the separating is done by the head: 20px
   between eyebrow, heading and intro, then a single 80px step down to whatever
   the section is actually showing. */
.section { padding-block: var(--sec-pad); }

/* A couple of sections on the live site run 140px instead of 120px at desktop.
   It is an Elementor one-off rather than a system, but the extra 20px is
   visible; below 1367px live collapses the distinction and so do we. */
.section--loose { padding-block: var(--sec-pad-loose); }

/* The two bands directly under the home hero. Live pins these to fixed numbers
   rather than the tier scale: the hero already ends in 80px of its own, so the
   run below it gets roughly half the usual air above and two thirds below. */
.section--snug { padding-block: 56px 80px; }

/* 96, against the 72 every other stacked band runs on. Live gives the closing
   band on /our-data/ a quarter more air once the page stacks, which is the last
   thing above the footer and the only place the extra reads as deliberate. At
   desktop it is back on the section rhythm, so this is a stacked-tier rule. */
@media (max-width: 1024px) {
  .section--cta-tall { padding-block: 96px; }
}
/* The snug pair only agree at desktop. On a phone live pulls the logo wall in to
   32 top and bottom and puts the band under it back on the section rhythm, so
   the shared token has to be undone on both. */
/* The logo wall pulls in to 32/32 the moment the row stops being a desktop row,
   which is 72px earlier than the snug default. The band under it only leaves the
   snug rhythm on a phone. */
@media (max-width: 1024px) {
  .section--snug { padding-block: var(--sec-pad); }
}
/* After the phone rule above, not before it: both selectors are one class deep,
   so the logo wall only keeps its own padding if it wins on source order. */
@media (max-width: 1024px) {
  .section--logos { padding-block: 32px; }
}
/* The snug pair splits again at the laptop tier: the band under the logo wall
   rejoins the section rhythm at 40/40 while the wall itself holds 56/80 across
   both wide tiers. Live set the two separately and only one of them carries a
   laptop override, so the exclusion is the rule rather than an exception to it.
   Worth 56px on the tallest section of the home page. */
@media (min-width: 1025px) and (max-width: 1366px) {
  .section--snug:not(.section--logos) { padding-block: var(--sec-pad); }
}

.section__head {
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* 760px, measured off the live section heads. Not a round rem value, but
     using 45rem here let long h2s wrap a word earlier than they do on live.
     Live does not hold it everywhere though: a head whose h2 carries its own
     line break is given the room that break was written against, so the measure
     is a property rather than a constant. */
  max-width: var(--head-w, 760px);
  margin-inline: auto;
  margin-bottom: var(--head-gap);
  text-align: center;
}
/* Headings that carry their own line break need the full measure, or the break
   lands in a different place than the one the copy was written for. */
.section__head--wide { --head-w: none; }
/* The intermediate measure live uses where a two-line h2 would otherwise run to
   three: wide enough for the written break, still narrow enough to read as a
   head rather than a band. */
.section__head--roomy { --head-w: 840px; }
/* The narrow end of the same set, used where live wants a two-line h2 out of
   copy that would otherwise sit on one long line. */
.section__head--narrow { --head-w: 720px; }
/* Tighter than the base, and only where the content below carries its own band:
   the pricing table brings 60px of padding of its own, so live butts the head
   almost straight onto it rather than paying for the air twice. This one holds
   at 20 on every tier; the two below it do not. */
.section--head-snug { --head-gap: 20px; }

/* The other two head gutters exist only from 1025 up. Below that live flattens
   every section on the site back to the 44px base regardless of what the section
   is showing, so a page that reads correctly at desktop was coming out 36-40px
   taller per band at tablet and phone. Gating the modifiers is what keeps the
   narrow tiers on live's rhythm; overriding them inside the narrow media query
   would work too but leaves two numbers to keep in step. */
@media (min-width: 1025px) {
  /* Live tightens the gutter under the head in the sections that follow it with
     a card grid and then a closing line, so the three rows read as one unit. */
  .section--head-tight { --head-gap: 40px; }
  /* The widest gutter live uses under a head, reserved for the sections whose
     content is a diagram rather than a grid: the extra step separates the claim
     from the thing that illustrates it instead of stacking them. */
  .section--head-loose { --head-gap: 80px; }
}

/* One section on the site tightens its row gutter at the laptop tier and nowhere
   else: /for-sales/ runs its solution band on 64 above 1366 and on 40 between
   1025 and 1366, while every other band on every page holds its gutter across
   both tiers. It is a per-element override rather than a rule, so it gets a
   modifier rather than a change to the base. Two rows of gutter, so 48px of the
   page depends on it. */
@media (min-width: 1025px) and (max-width: 1366px) {
  .section--laptop-tight { --head-gap: 40px; }
  .section--laptop-tight .stat-strip { margin-top: 40px; }
}

/* One step above the base, and only at the narrow tiers: the single band live
   gives a little more room than the flat 44 once the modifiers above go away. */
@media (max-width: 1024px) {
  .section--head-mid { --head-gap: 48px; }
}

/* Phone only. Live pulls the head 12px closer in the bands whose content is a
   horizontal run that has just stacked into a column. Once the row is a column
   the head reads as the first item in the stack rather than as a separate block,
   and the base gutter opens a hole above it. Matched by content rather than by a
   modifier class so a page that gains one of these bands inherits the rhythm. */
@media (max-width: 767px) {
  .section:has(.stats),
  .section:has(.carousel),
  .section:has(.steprail) { --head-gap: 32px; }
}

/* The single line live closes a problem band with. It is the section's
   conclusion, set solid and white at a size between the body and the h2, not a
   caption: rendered as muted body copy the band loses the beat it was built
   around. It takes the same gutter above it as the head takes below. */
.section__closer {
  max-width: 840px;
  margin: var(--head-gap) auto 0;
  font-size: 26px;
  font-weight: 700;
  line-height: 1;
  color: #fff;
  text-align: center;
}
/* Live steps this line rather than scaling it fluidly: 24 on a phone, 32 through
   the tablet tier where the closer has the whole measure to itself, back down to
   26 on a desktop. It is set solid at every step, so each number is also exactly
   the line's height and a wrong one costs the band a whole line. */
@media (max-width: 1024px) {
  .section__closer { font-size: 32px; }
}
@media (max-width: 767px) {
  .section__closer { font-size: 24px; }
}
.section__head > * { margin: 0; }
/* :not(.eyebrow) matters: the eyebrow is a <p> too, and this rule outranks
   .eyebrow on specificity, so without it every eyebrow silently renders at the
   intro paragraph's size and colour. */
.section__head p:not(.eyebrow) {
  color: var(--muted);
  font-size: var(--step-0);
  line-height: 1.5;
  /* 762px, not 48rem. The 6px difference is not cosmetic: the intro paragraphs
     on this site run long enough that 768px fits one extra line's worth of text,
     so a wide measure silently drops a line and shortens the section head. */
  max-width: 762px;
  margin-inline: auto;
  /* Live's paragraph widgets carry a 0.9em bottom margin that the flex gap does
     not replace, so the last line of an intro sits ~14px further from the
     content below it than a bare gap would put it. */
  margin-bottom: 14.4px;
}

/* Live sizes each section head's measure by hand rather than from one scale, so
   the sections that differ get a modifier instead of moving the default. Both
   must sit AFTER the rules they override: --sm-intro ties with
   `.section__head p:not(.eyebrow)` on specificity exactly, so only source order
   decides, and placed above it the modifier silently does nothing.
     --sm       760px head, live's testimonials measure
     --sm-intro 620px intro column, live's "More pipeline. Same team size." */
/* 760, not 550: at 550 the heading still lands on two lines, so the section
   height is unchanged and the error is invisible in a measurement, but it breaks
   after a different word than live and the carousel below it reads as wider than
   the head it belongs to. */
.section__head--sm { max-width: 760px; }
.section__head--sm-intro p:not(.eyebrow) { max-width: 620px; }
/* The home ICP band is the one intro live sets a step above body copy. The
   leading follows at 1.5, so it buys taller lines rather than a new rhythm. */
.section__head--intro-lg p:not(.eyebrow) { font-size: 18px; }

/* Live eyebrows are an icon and gradient text on one centered row: Inter 600 at
   16px set solid, sentence case, no tracking. The icon is a sibling of the text
   rather than a child, which is why the gradient clip sits on the span. Live
   clips the same gradient on the widget box instead, which renders identically
   because that box shrink-wraps the text.

   16px, not 14: set at 14 the label reads as a caption rather than as the
   section's first line, and it is the one size that shows up on every page. The
   box stays 24px tall either way, so correcting it moves no section height. */
.eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  /* The live eyebrow sits in a 24px-tall box regardless of whether it carries an
     icon, so every section head below it starts 24px down. Our text is 16px and
     our icon 16px, which left each head 8px short and cascaded into the section
     height. Centring inside the box keeps the glyph and the text on one baseline. */
  min-height: 24px;
  margin: 0 0 var(--sp-4);
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
}
.section__head .eyebrow { margin-bottom: 0; }

/* `width: auto` so the markup's own width/height attributes decide: most eyebrow
   glyphs are 16px square, but a few live sections use a taller one (28x32 on
   "Built for growth teams") and forcing every icon to 16px shortens those rows
   by 8px. flex: none keeps them from being squeezed by a long label. */
/* auto/auto, so each icon draws at its intrinsic size: the set is a mix of 16px
   glyphs and 28x32 numerals and live sizes none of them. Note this beats the
   width/height attributes on the <img> (a presentation hint loses to any CSS
   rule), so a one-off size needs the modifier below, not an attribute. */
.eyebrow__icon { flex: none; width: auto; height: auto; }
/* The one place live draws a 28x32 numeral small enough to stay inside the 24px
   label row instead of pushing it to 32. */
.eyebrow__icon--sm { width: 16px; }

.eyebrow > span {
  background-image: var(--grad-accent);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: var(--blue);
}

@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .eyebrow > span { background: none; -webkit-text-fill-color: currentColor; }
}

/* Some eyebrows are a flat teal rather than the ramp: the ones that label a
   column inside a section, where a second gradient next to the heading's would
   read as two competing accents. */
.eyebrow--flat > span {
  background: none;
  -webkit-text-fill-color: currentColor;
  color: var(--teal-bright);
}
.eyebrow--start { justify-content: flex-start; text-align: left; }

/* The logo-wall label ("Trusted by...") shares the eyebrow's metrics but is a
   quiet tertiary grey on the live site, not the accent gradient. It also carries
   no icon and never will, so it sits in its own 16px line box rather than the
   24px one the iconed eyebrows share, and it holds a wider gutter below. */
.eyebrow--muted {
  min-height: 0;
  margin-bottom: var(--sp-6);
}
.eyebrow--muted > span {
  background: none;
  -webkit-text-fill-color: currentColor;
  color: var(--muted-deep);
}

/* The problem-band eyebrow. Flat amber rather than the accent ramp, so the
   section that names the problem is visibly not one of the sections that answer
   it. See --amber. */
.eyebrow--warn > span {
  background: none;
  -webkit-text-fill-color: currentColor;
  color: var(--amber);
}

/* ==========================================================================
   Buttons
   ========================================================================== */

/* Measured off the live header CTA: 8px radius (NOT a pill), 11px/14px padding,
   Inter 500 at 14px, solid #2dd4bf with near-black-green #052824 text. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 11px 14px;
  /* No border on the base button: live's filled buttons are borderless 36px
     boxes, and a transparent 1px border made every one of ours 38px. Only the
     ghost variant draws a border, and it is 38px on live for that reason. */
  border: 0;
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 500;
  /* Set solid, like the headings: live measures 14px/14px, and the 1.2 we had
     made every button 3px taller than its live counterpart. */
  line-height: 1;
  cursor: pointer;
  text-align: center;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
/* Thumb-sized tap targets, but only where the pointer is actually a thumb, so
   the desktop button keeps the live site's exact height. */
@media (pointer: coarse) {
  .btn { min-height: 44px; }
}
.btn:active { transform: translateY(1px); }

.btn--primary { background: var(--teal); color: var(--on-accent); }
.btn--primary:hover { background: var(--teal-deep); color: var(--on-accent); }

/* --text, not --muted: the live secondary button is as legible as the primary,
   and only the border distinguishes them. */
/* The 3% white wash is live's, not transparent: it lifts the ghost button just
   off the page background so the border is not doing all the work. */
/* Shared padding with .btn, so the 1px border lands outside it and the ghost
   measures 38x(+2) against the primary's 36. That is what live does: it never
   compensates the border, and .btn-row's flex-start alignment is what keeps the
   mismatched pair sitting on the same top edge. Tightening the padding to
   equalise the heights reads better in isolation but is 2px off live on every
   row that carries a ghost button. */
.btn--ghost {
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  border: 1px solid var(--border);
}
.btn--ghost:hover { background: var(--surface-subtle); border-color: var(--border-bright); color: var(--text); }

.btn--link { padding-inline: 0; background: none; color: var(--teal-bright); }
.btn--link:hover { color: var(--blue); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  /* flex-start, not the default stretch: the filled button is 36px and the ghost
     38px (its border), and live lets them differ rather than equalising them. */
  align-items: flex-start;
  gap: var(--sp-3);
}
/* Buttons keep their intrinsic width at every viewport and wrap only when the
   pair genuinely does not fit. Live never stacks them: at 390 the two hero CTAs
   are 137 and 144 wide with a 12px gap, which fits the 350px measure with room
   to spare. Forcing 100% below 480 cost 48px of hero height on five pages. */
.btn-row > .btn { flex: 0 0 auto; }

/* The hero rows are the exception: live never lets those two wrap. Below the
   width where the pair fits at its natural size the buttons shrink in
   proportion and their labels take a second line inside the button instead of
   the second button dropping to a row of its own. On the heroes that carry a
   pair beside a product shot that happens inside the laptop tier, where the copy
   column is at its narrowest, and it is worth 34px of hero height: a wrapped row
   stands 86 where live's shrunken pair stands 52. Nowhere else does the pair
   come close to its container, so this changes nothing below 1025. */
.hero .btn-row { flex-wrap: nowrap; }
.hero .btn-row > .btn { flex: 0 1 auto; min-width: 0; }

/* ==========================================================================
   Header / navigation
   --------------------------------------------------------------------------
   One header in the DOM, not the live site's two (desktop + mobile duplicates).
   Sticky is pure CSS; the live site used a JS sticky plugin for the same effect.
   ========================================================================== */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  /* Same hue as --bg at 0.85, so at the top of the page the header is
     indistinguishable from live's transparent one; the alpha only shows once
     content scrolls under it. No border-bottom: live has none, and the hairline
     was the one visible difference between our sticky header and their static
     one. */
  background: rgba(6, 8, 12, 0.85);
  backdrop-filter: blur(12px);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: var(--header-h);
}

.site-header__logo img { width: auto; height: 24px; }

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  background: none;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  color: var(--text);
  cursor: pointer;
}
.nav-toggle svg { width: 20px; height: 20px; }
.nav-toggle__close { display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle__open { display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle__close { display: block; }

/* --- Mobile nav: a panel below the header, toggled by .nav-toggle --------- */
.site-nav {
  position: fixed;
  inset: var(--header-h) 0 0;
  background: var(--bg);
  padding: var(--sp-5);
  overflow-y: auto;
  /* Hidden via visibility (not display) so the open/close transition can run.
     visibility is a DISCRETE property: `transition: visibility <dur>` flips it at
     the midpoint, which leaves the panel unfocusable for half the animation and
     silently breaks the focus move on open. So it is stepped explicitly instead --
     delayed to the end when closing (below) and instant when opening (next rule). */
  visibility: hidden;
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity var(--t-mid) var(--ease), transform var(--t-mid) var(--ease),
              visibility 0s linear var(--t-mid);
}
.site-nav[data-open="true"] {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition: opacity var(--t-mid) var(--ease), transform var(--t-mid) var(--ease),
              visibility 0s;
}

.site-nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.site-nav__link,
.site-nav__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  width: 100%;
  min-height: 48px;
  padding: var(--sp-3) 0;
  background: none;
  border: 0;
  color: var(--text);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
}
.site-nav__link:hover,
.site-nav__summary:hover { color: var(--teal); }

.site-nav__caret {
  width: 10px; height: 10px;
  flex: none;
  transition: transform var(--t-fast) var(--ease);
}
[aria-expanded="true"] > .site-nav__caret { transform: rotate(180deg); }

.site-nav__sub {
  list-style: none;
  margin: 0 0 var(--sp-3);
  padding: 0 0 0 var(--sp-4);
  border-left: 1px solid var(--border);
  display: none;
}
.site-nav__group[data-open="true"] .site-nav__sub { display: block; }

.site-nav__sub a {
  display: block;
  min-height: 44px;
  padding: var(--sp-2) 0;
  color: var(--muted);
  font-weight: 400;
}
.site-nav__sub a:hover { color: var(--text); }

/* Hidden with the CTA below the desktop breakpoint: on mobile the drawer is the
   whole navigation, and a bare "Log in" beside the burger reads as clutter. */
.site-header__actions { display: none; }
.site-header__cta { display: none; }
/* Padded like a button rather than set as bare text: live renders "Log in" as a
   borderless, transparent-background button sharing the demo button's box
   metrics, which is what puts only 5px between the two boxes while leaving 33px
   of visual space between the two labels. */
.site-header__login {
  display: inline-flex;
  align-items: center;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  color: var(--muted);
  white-space: nowrap;
}
.site-header__login:hover { color: var(--text); }

/* --- Desktop nav: horizontal bar with hover/focus dropdowns -------------- */
@media (min-width: 1025px) {
  .nav-toggle { display: none; }
  .site-header__actions { display: flex; align-items: center; gap: 5px; }
  .site-header__cta { display: inline-flex; }

  .site-nav {
    position: static;
    inset: auto;
    padding: 0;
    overflow: visible;
    visibility: visible;
    opacity: 1;
    transform: none;
    background: none;
    margin-inline-start: auto;
  }

  .site-nav__list { flex-direction: row; align-items: center; gap: var(--sp-5); }

  .site-nav__link,
  .site-nav__summary { min-height: 0; padding: var(--sp-2) 0; font-size: var(--step-0); }

  .site-nav__group { position: relative; }

  .site-nav__sub {
    display: block;
    position: absolute;
    top: calc(100% + var(--sp-3));
    left: 50%;
    translate: -50% 0;
    min-width: 220px;
    margin: 0;
    padding: var(--sp-2);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    /* Closed by default; opened on hover or on focus-within so the dropdown is
       reachable by keyboard — the live site's was not. */
    visibility: hidden;
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease),
                visibility var(--t-fast);
  }
  .site-nav__group:hover .site-nav__sub,
  .site-nav__group:focus-within .site-nav__sub,
  .site-nav__group[data-open="true"] .site-nav__sub {
    visibility: visible;
    opacity: 1;
    transform: translateY(0);
  }

  .site-nav__sub a {
    min-height: 0;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius);
  }
  .site-nav__sub a:hover { background: var(--surface-raised); }
}

/* ==========================================================================
   Hero
   ========================================================================== */

/* The hero is the one section that is NOT on the --sec-pad rhythm: live gives it
   its own pair of values at every tier (64/64 desktop, 40/40 laptop, 48/54
   tablet, 48/58 mobile), so the h1 sits close under the header rather than
   floating. The asymmetry on the two narrow tiers is live's. */
.hero { padding-block: var(--hero-pad-top) var(--hero-pad-bottom); text-align: center; }
/* 550px, not a wide measure: live sets the whole hero stack in a 550px column,
   which is what makes the h1 break after "outbound." rather than mid-phrase. At
   48rem the same two lines fit but break in a different place, and the hero is
   the one block where the break is part of the composition.

   The stack is a 16px-gap flex column so each block's spacing comes from one
   place; the members below zero their own margins into that gap. */
.hero__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  /* calc, not a bare 550px: this element also carries .container, whose
     padding-inline is inside the border box, so a flat 550px yields a 518px
     text column and the h1 breaks a word earlier than live's. */
  max-width: calc(550px + 2 * var(--sp-4));
  margin-inline: auto;
}
/* The gutter doubles below 1367, and because the cap above is written against
   --sp-4 rather than against the gutter that is actually being paid, the text
   column comes out 32px narrower through the laptop tier than at desktop. Live
   holds 550 across both. It shows on exactly one page, /for-revops/, whose
   standfirst runs a step larger than its siblings' and so tips onto a fourth
   line at 518. Scoped to 1025 up: the two narrow tiers were measured against the
   present cap and match live there. */
@media (min-width: 1025px) {
  .hero__inner { max-width: calc(550px + 2 * var(--gutter)); }
}
.hero__inner > * { margin: 0; width: 100%; }
/* Live pushes the h1 18px below the eyebrow's gap, the only place on the page
   with more than the standard 16px between two stacked blocks. */
.hero__inner h1 { margin-top: 18px; }
/* Legal / utility pages: same hero, less vertical drama, since the body copy is
   the point and nothing below the fold is a conversion element. Their copy is
   prose rather than a composed headline, so it keeps the wide measure. */
.hero--compact { padding-block: var(--sp-7) var(--sp-5); }
.hero--compact .hero__inner { max-width: 48rem; }
.hero--compact h1 { font-size: var(--step-3); margin-top: 0; }
/* Deliberately small and narrow: the live hero sets its subhead at 14px/21px in
   a 550px measure, so the h1 carries the section on its own and the paragraph
   reads as a caption under it. Scaling this up unbalances the whole hero. */
.hero__sub {
  color: var(--muted);
  font-size: var(--step--1);
  line-height: 1.5;
  max-width: 550px;
  margin-inline: auto;
  /* The 0.9em trailing margin live's paragraph widgets carry, on top of the
     hero stack's 16px gap. */
  margin-bottom: 14.4px;
}
/* /for-revops/ is the one hero live sets at the body size rather than a step
   down. The leading is the same 1.5, so it buys a taller line box, not a
   different rhythm. */
.hero--sub-lg .hero__sub { font-size: var(--step-0); }
.hero .btn-row { justify-content: center; }

.hero__stage {
  margin-top: var(--sp-8);
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
}

/* --------------------------------------------------------------------------
   Split hero (home only)
   --------------------------------------------------------------------------
   Live's home page does not centre its hero: copy sits in a 337px column on the
   left and the product stage fills the 923px beside it, vertically centred
   against the copy. Rebuilt centred, the same h1 sets on one line instead of
   two and the product shot lands under the fold, which is a different page.

   The column ratio is live's, expressed in fr so it scales the way Elementor's
   percentage widths do: 337.1 / 20 / 922.9 on a 1280px container.

   The copy column runs on margins rather than a flex gap because live's spacing
   is not one rhythm: 18px under the eyebrow, 16 under the h1, 44 above the
   buttons, 50 above the stat strip. A gap plus four overrides is the same thing
   spelled less clearly. */
/* The home hero does not sit on --hero-pad-top/bottom: live gives it 128/80 of
   its own, which is what puts the product stage's top edge just under the
   header rather than level with the h1.

   Every product page uses this same split, but live composes each one on its own
   geometry rather than from a shared scale: the column ratio, the block padding,
   the standfirst size and the vertical alignment are all per page. So the
   machinery is here and the numbers live in the per-page blocks below, as custom
   property values. The defaults are the home page's. */
.hero--split {
  padding-block: var(--hero-split-pt, 128px) var(--hero-split-pb, 80px);

  /* Every split hero on the site is the same two-column machine: the copy column
     asks for an even half of the row and is capped, and the image column takes
     whatever is left. The cap is the only thing that varies per page, so it is
     the only thing the page blocks below set.

     Written as a ratio of two fr units the columns track each other all the way
     down, which is not what live does: there the copy stops growing at its cap
     and the image absorbs the rest, so the two are only in the authored
     proportion at the widths where the cap binds. Below that they are even. The
     difference is worth 156px of hero height on /pricing/ at 1100.

     Home's cap is a percentage rather than a length, so it binds at every width
     and the columns stay in proportion; the min() below resolves to it either
     way, which is why one formula covers both kinds of page. */
  --hero-split-c1: 26.34%;
  --hero-split-cols:
    min(var(--hero-split-c1), calc((100% - var(--hero-split-gap, 20px)) / 2)) 1fr;
  text-align: left;
}

.hero__split {
  display: grid;
  grid-template-columns: var(--hero-split-cols);
  gap: var(--hero-split-gap, 20px);
  align-items: var(--hero-split-align, center);
}

/* Flex, not a block, purely to stop margin collapsing: live's 14.4px paragraph
   trailing margin and the 44px above the buttons are additive there, and in a
   block box the larger would swallow the smaller and cost 14px. */
.hero__col { display: flex; flex-direction: column; }
.hero__col > * { margin: 0; }
.hero__col h1 { margin-top: 18px; }
.hero--split .hero__sub {
  /* 18px/27px on the home page, against the 14px the centred heroes use: that
     paragraph carries four lines beside the product shot rather than sitting as
     a caption under a headline. The product pages each pick their own size. */
  margin-top: 16px;
  margin-bottom: 14.4px;
  max-width: none;
  font-size: var(--hero-split-sub-fs, var(--step-1));
  line-height: 1.5;
}
.hero--split .btn-row {
  justify-content: flex-start;
  margin-top: var(--hero-split-btn-mt, 44px);
  margin-bottom: var(--hero-split-btn-mb, 14px);
}
/* Live closes the copy column on /our-data/ with a 50px spacer, the same one it
   puts between the home buttons and the proof strip. At desktop the image column
   is the taller of the two so it costs nothing; once the columns stack it is
   50px of real hero height, which is why it has to be here and not tier-scoped. */
.hero--split-data .btn-row { margin-bottom: calc(var(--hero-split-btn-mb, 14px) + 50px); }

/* Live's stage is a flat PNG on the page background, with the panel chrome
   painted into the image. The border and shadow the centred heroes give their
   stage would frame a picture that already has a frame. */
.hero--split .hero__stage {
  margin: 0;
  border: 0;
  border-radius: 0;
  overflow: visible;
  box-shadow: none;
  text-align: center;
}
/* Centred in the right column, which on several pages is wider than the shot
   itself. Where live sizes the image rather than letting it fill, the page block
   below sets --hero-split-img-w to live's pixel width; max-width keeps it from
   overflowing once the column is narrower than that. */
.hero--split .hero__stage img {
  display: block;
  width: var(--hero-split-img-w, auto);
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}

/* --------------------------------------------------------------------------
   Per-page split-hero geometry
   --------------------------------------------------------------------------
   All measured off live at 1390px, where the container is 1280px wide, so a cap
   below that binds at every width down to the stacking breakpoint and a wider
   one only binds above the width where half the row exceeds it.

   These vary far more than a design system would: 64/64, 70/70, 120/120 and
   128/80 block padding across four sibling pages, and three different standfirst
   sizes. That is what live does, and matching it is the point. */
.hero--split-sales {
  --hero-split-c1: 592px;
  --hero-split-sub-fs: var(--step--1);
  /* The shot is 520px square inside a 668px column, not a fill. */
  --hero-split-img-w: 520px;
}
/* Once the split stacks, live lets the same shot fill the column and upscale
   past its natural 520. It is the only hero here whose image is narrower than
   the stacked column, so it is the only one where the fixed width shows: at 600
   the column is 560 and the hero comes up 40px short of live's. */
@media (max-width: 767px) {
  .hero--split-sales { --hero-split-img-w: 100%; }
}
.hero--split-aiq {
  --hero-split-pt: 70px;
  --hero-split-pb: 70px;
  --hero-split-c1: 455px;
  --hero-split-sub-fs: var(--step-0);
  --hero-split-btn-mt: 32px;
  --hero-split-btn-mb: 0px;
  --hero-split-img-w: 741px;
}
.hero--split-data {
  --hero-split-pt: 64px;
  --hero-split-pb: 64px;
  /* No column cap of its own: this hero is the home page's, to the pixel. */
  /* 44/14, which is the home page's pair rather than the 32/0 its two siblings
     take. The narrow-tier block below resets every split hero to 24/14, so this
     only applies from 1025 up. At desktop the image column is the taller of the
     two and the difference is invisible; through the laptop tier the copy column
     is what sets the hero's height and it is worth 26px. */
  --hero-split-btn-mt: 44px;
  --hero-split-btn-mb: 14px;
  --hero-split-img-w: 741px;
  /* Copy is 534px against a 568px shot; live tops them out rather than centring,
     so the eyebrow lines up with the top edge of the image. */
  --hero-split-align: start;
}
.hero--split-pricing {
  --hero-split-pt: 120px;
  --hero-split-pb: 120px;
  --hero-split-c1: 550px;
  --hero-split-btn-mt: 32px;
  --hero-split-btn-mb: 0px;
  --hero-split-img-w: 100%;
}
.hero--split-marketing {
  --hero-split-pt: 64px;
  --hero-split-pb: 64px;
  /* The only even split on the site, and the only one with no gutter between the
     columns: the image is bled to the copy. The cap is wider than half the row
     at every width the page reaches, so the two columns are always even; it is
     set rather than omitted because it is live's, and a wider container would
     start to bind it. */
  --hero-split-c1: 700px;
  --hero-split-gap: 0px;
  --hero-split-sub-fs: var(--step--1);
  --hero-split-btn-mt: 16px;
  --hero-split-btn-mb: 0px;
  --hero-split-img-w: 100%;
  --hero-split-align: start;
}
/* 96% of its column at every width, not just the narrow tiers: live's heading
   widget measures that everywhere on this page. It is what tips the headline
   onto a fourth line at 1100, where the column is 518 and 96% of it is 497, and
   it is worth a whole 50px line of hero height there. See live-quirks.md. */
.hero--split-marketing .hero__col h1 { width: 96%; }

/* Elementor has a laptop tier between the tablet stack and the desktop layout,
   and live uses it: between 1025 and 1366 every section drops to 40/40 (which
   --sec-pad already does here) and the split heroes drop off their per-page
   values onto one of two pairs. The four whose hero ends on a product shot keep
   an asymmetric 80/40; the two that end on copy go flat 40/40, which is the same
   pair the centred heroes get from --hero-pad-top/bottom. Without this the
   heroes render their desktop padding a tier early and each page stands 48 to
   120px taller than live's between those two widths. */
@media (min-width: 1025px) and (max-width: 1366px) {
  .hero--split { --hero-split-pt: 80px; --hero-split-pb: 40px; }
  .hero--split-marketing,
  .hero--split-data { --hero-split-pt: 40px; --hero-split-pb: 40px; }
}

/* Live lifts the eyebrow out of the copy column on /for-marketing/ and sets it
   in a band of its own above the hero, full width, with no bottom padding of its
   own so the hero's 64px is the whole gap under it. */
/* 85 = live's 10px band padding + a 50px spacer widget + the 25px row gap. It
   goes on the band, not on the eyebrow: the eyebrow's own 24px min-height is a
   border-box floor, so padding-top there is absorbed by it and the row collapses
   to its 16px content. */
.hero-eyebrow-band { padding-block: 85px 10px; }
.hero-eyebrow-band .eyebrow { margin: 0; }
/* The band is one of the few places live grows its padding on a phone rather
   than shrinking it, and it grows with the viewport rather than sitting at a
   size: live sets 6% on the inner container, which is itself inset 6% a side, so
   the band's own padding is 5.28% of the window. That is 20.6 on a 390 phone and
   31.7 on a 600 tablet, and a fixed value picked at one of those widths is wrong
   at the other by the difference. The 75 is the spacer and row gap as above. */
@media (max-width: 767px) {
  .hero-eyebrow-band { padding-block: calc(75px + 5.28%) 5.28%; }
}

/* The three-up proof strip under the buttons. Each column is a figure row, a
   3px progress track and a wrapping label; live runs all three tracks at 75%,
   so the bar is a design element rather than a reading of the number above it. */
.hero__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  list-style: none;
  margin: 50px 0 0;
  padding: 0;
}
.hstat { display: flex; flex-direction: column; gap: 10px; }
.hstat__figure {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  margin: 0;
}
.hstat__figure img { flex: none; }
.hstat__n {
  color: var(--text);
  font-size: 30px;
  font-weight: 700;
  /* 32px, matching the figure's min-height, rather than the font's own 30: on a
     single line the two are the same and nothing shows, but the home hero's
     first figure wraps to two lines through the laptop tier, where a 30px lead
     puts the row at 60 against live's 64. */
  line-height: 32px;
  letter-spacing: -0.02em;
}
.hstat__bar {
  display: block;
  height: 3px;
  border-radius: 2px;
  /* 16% of the accent, not a grey: the empty quarter of the track still reads as
     part of the same element as the fill. */
  background: rgb(45 212 191 / 0.16);
  overflow: hidden;
}
.hstat__bar > span {
  display: block;
  height: 100%;
  border-radius: 2px;
  background-image: linear-gradient(90deg, #5eead4 0%, #60a5fa 100%);
}
.hstat__label {
  color: var(--text);
  font-size: var(--step-0);
  font-weight: 600;
  line-height: 1.5;
}
/* The second line some strips carry under the label: same size, regular weight,
   muted, and no gap of its own because it reads as the end of the same
   sentence. */
.hstat__caption {
  color: var(--muted);
  font-size: var(--step-0);
  line-height: 1.5;
}
.hstat__label + .hstat__caption { margin-top: -10px; }

/* The same three-up proof strip used outside a hero: /for-sales/ closes its
   solution section with one. Live sets it 240px per column, centred at 760px
   rather than filling the 1280px container, so it reads as a footnote to the
   cards above it and not as a band of its own. */
.stat-strip {
  display: grid;
  /* Fractional columns under a 760px cap rather than three fixed 240s: live
     composes this as a flex row that shrinks, so between 768 and the 760px cap
     the columns come in under 240 and the third label wraps. Fixed tracks do not
     shrink, and that one wrapped line is 24px of the band's height. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  max-width: 760px;
  gap: 20px;
  list-style: none;
  /* 64px, the gutter live puts between every top-level row inside this section. */
  margin: var(--sp-8) auto 0;
  padding: 0;
}
/* Live opens an extra 32 above the strip through the tablet tier only, on top of
   the 44 the section is already spacing its rows with, and drops back to the
   plain row gutter on a phone. */
@media (max-width: 1024px) {
  .stat-strip { margin-top: 76px; }
}
@media (max-width: 767px) {
  .stat-strip { grid-template-columns: 1fr; margin-top: var(--head-gap); }
}

/* ==========================================================================
   Showcase band
   --------------------------------------------------------------------------
   A visual paired with a short claim, used for the "See it working" band on
   /ai-qualification/ and /our-data/. Live composes the two differently enough
   that the column ratio, gutter and inset are per instance, so they sit on the
   modifiers below rather than in the base.
   ========================================================================== */

.showcase {
  display: grid;
  grid-template-columns: var(--showcase-cols, 1fr 1fr);
  gap: var(--showcase-gap, 80px);
  align-items: center;
  padding: var(--showcase-pad, 0);
}
.showcase__media { margin: 0; }
.showcase__media img,
.showcase__media video {
  display: block;
  width: 100%;
  height: auto;
}
/* 20px between all three blocks, against the 16 the section heads use: this
   copy sits beside a visual rather than over a grid, so it carries more air.
   Centred against the visual, not top-aligned: the copy is shorter than the
   media in both instances, and live hangs it on the media's optical middle. */
.showcase__body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 20px;
}
.showcase__body > * { margin: 0; }
/* Elementor's own paragraph margin, which live carries at every width: the copy
   column's last widget is 38.4 tall against a 24px line, so the column stands
   14.4 higher than its text does. It only shows where the copy is the taller of
   the two columns. */
.showcase__body p:not(.eyebrow):last-child { margin-bottom: 14.4px; }
/* 1.5, not the page default: this paragraph is a one-line coda under a 44px
   heading, and the looser body leading reads as a gap rather than as a line. */
.showcase__body p:not(.eyebrow) { color: var(--muted); font-size: var(--step-0); line-height: 1.5; }

/* /ai-qualification/: inset inside the container and padded on top of the
   section's own 140, which stops the visual reading as full-bleed. It is a
   panel on live, not a bare pair of columns: same surface, border and radius as
   a card, which is what holds the video and the claim together as one object. */
.showcase--inset {
  /* Live's two-column idiom rather than a fixed ratio: even columns that fill
     until their own caps bind, a 16px floor under the gutter, and each column
     hugging its outside edge so the leftover opens between them. At 1100 the
     panel's 938px of content splits 461/461 with the 16 in the middle; at 1300
     both caps bind and the 68px left over becomes the gutter. Written as
     538fr/532fr the two columns tracked each other all the way down, so the
     media came out 46px narrow at 1100, and on a 16:9 frame that is 26px of
     panel height. */
  --showcase-cols: minmax(0, 1fr) minmax(0, 1fr);
  --showcase-gap: 16px;
  /* 48 horizontal, not 49: the border sits inside the padding here, so an extra
     pixel a side narrows the content box to 1180 and the two fixed columns lose
     the last pixel each. The media is 16:9, so that pixel costs half a pixel of
     panel height, which is the whole band's delta against live. */
  --showcase-pad: 56px 48px;
  /* Stretch rather than the component's centring: live hangs the video off the
     top of the panel and centres the copy against it. It only shows at the
     widths where the copy is the taller of the two, which is the narrow end of
     the laptop tier. */
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.showcase--inset .showcase__media { max-width: 538px; justify-self: start; align-self: start; }
.showcase--inset .showcase__body { max-width: 532px; justify-self: end; }
/* 600 rather than the site's 700: live drops a weight for the heading inside
   this panel so it does not outweigh the section heads on the same page. */
.showcase--inset h2 { font-weight: 600; }
/* The box is live's video frame, so it is 16:9 whatever the still inside it is.
   Cover rather than contain: a letterboxed still would put two bands of panel
   surface where live has none. */
.showcase--inset .showcase__media img,
.showcase--inset .showcase__media video {
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
/* /our-data/: no inset, and the gutter is the leftover rather than a set value,
   because live sizes both columns and lets space-between do the rest. */
/* Same shape as the feature rows: two capped columns, a floor under the gutter,
   and the leftover opening between them once both caps fit. The media column is
   the one that never gives (live holds it at 462 all the way down to the stack),
   so only the copy column shrinks, and it does so against a 620 cap that only
   binds above 1162. */
.showcase--data {
  --showcase-cols: 462px minmax(0, 1fr);
  --showcase-gap: 80px;
}
.showcase--data .showcase__body { max-width: 620px; margin-left: auto; }

@media (max-width: 1024px) {
  .showcase {
    --showcase-cols: 1fr;
    --showcase-gap: 40px;
    --showcase-pad: 0;
  }
  /* The cap and the right-hand hug are both properties of the two-column row. */
  .showcase--data .showcase__body { max-width: none; margin-left: 0; }
  /* The panel survives the stack on /ai-qualification/. It is the object the
     band is composed of, so live keeps its surface and border and only trades
     the desktop inset for a smaller one; dropping it to zero widens the media
     by the padding on both sides, and the media is 16:9, so every pixel of
     padding costs a bit over half a pixel of band height. */
  .showcase--inset {
    --showcase-pad: 28px;
    --showcase-gap: 24px;
  }
  /* Claim above the visual, as live stacks it: the heading is what the video is
     evidence for, and behind the video it reads as a caption. */
  .showcase--inset .showcase__body { grid-row: 1; }
  /* The caps and the outside-edge hug are both properties of the two-column
     row; stacked, each block takes the panel's full width. */
  .showcase--inset .showcase__media,
  .showcase--inset .showcase__body { max-width: none; justify-self: stretch; }
}
/* 16, not 28: through the tablet tier live gives the media the panel's width
   almost entirely, and the inset only has to keep the border off the frame. */
@media (min-width: 48rem) and (max-width: 1024px) {
  .showcase--inset { --showcase-pad: 16px; }
}

@media (max-width: 1024px) {
  /* 44, the gutter live puts between every top-level row in a section; the
     unpadded band falls back on it once the two columns stack rather than
     keeping the desktop pair's own gutter. */
  .showcase--data { --showcase-gap: 44px; }
  /* Claim above the screenshot, as live stacks it. */
  .showcase--data .showcase__body { grid-row: 1; }
  /* This screenshot stops at its natural 770 instead of filling the column, so
     from about 850px up live's band stops growing with the viewport while a
     stretched one keeps going: at 1000 the difference is 108px of image. */
  .showcase--data .showcase__media img {
    width: auto;
    max-width: 100%;
    margin-inline: auto;
  }
  /* Elementor's paragraph margins, top and bottom, which this coda carries on
     live and which the desktop row hides inside a taller column. */
  .showcase--data .showcase__body p:not(.eyebrow) {
    margin-top: 16px;
    margin-bottom: 14.4px;
  }
}

/* ==========================================================================
   Feature rows
   --------------------------------------------------------------------------
   A claim paired with a screenshot, repeated down the page with the image
   swapping sides each time. Distinct from .showcase, which is a single band: the
   alternation is the point here, and it is what keeps three consecutive
   image-and-copy rows from reading as a list.

   No card chrome. Each screenshot already carries its own frame, so a panel
   around it would be a border inside a border. The row's height comes from the
   image, which is why nothing here sets one.
   ========================================================================== */

.featurerows {
  list-style: none;
  display: grid;
  gap: 80px;
  margin: 0;
  padding: 0;
}

.featurerow {
  display: grid;
  grid-template-columns: 1fr;
  gap: 40px;
}
/* Two even columns capped at 556, gutter 25, and whatever is left over opens
   between them: live runs this as a space-between flex row whose columns shrink
   below their cap once 556 + 25 + 556 stops fitting, which is at 1137. So the
   leftover is the gutter above that width and the gutter is a flat 25 below it,
   where the columns give way instead. Written as a fixed 556/556 pair the images
   overflow the container by 100px at 1100; written as two plain fr columns they
   never stop growing and the desktop row loses its gutter. */
@media (min-width: 1025px) {
  .featurerow {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 25px;
  }
  .featurerow__media,
  .featurerow__body { max-width: 556px; width: 100%; }
  /* The cap only reads as live's gutter if each column sits against its own
     outside edge; centred, the leftover would split either side of the pair. */
  .featurerow__media { justify-self: start; }
  .featurerow__body { justify-self: end; }
  .featurerow:nth-child(even) .featurerow__media { justify-self: end; }
  .featurerow:nth-child(even) .featurerow__body { justify-self: start; }
  /* The swap is positional rather than a class on the markup, so the copy stays
     in document order and the alternation can never disagree with the source. */
  .featurerow:nth-child(even) .featurerow__media { grid-column: 2; grid-row: 1; }
  .featurerow:nth-child(even) .featurerow__body { grid-column: 1; grid-row: 1; }
}

.featurerow__media { margin: 0; }
.featurerow__media img {
  display: block;
  width: 100%;
  height: auto;
}

.featurerow__body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 25px;
}
.featurerow__body > * { margin: 0; }
/* The eyebrow's 24px box exists so section heads all start the same distance
   down whether or not the label carries an icon. These labels never do, and the
   column is optically centred against the image, so the extra 8px would hang the
   whole copy block 4px high. Set solid, it is the 16px row live draws. */
.featurerow__body .eyebrow { min-height: 0; }
.featurerow__body h3 { font-size: var(--step-2); font-weight: 700; line-height: 1; }
.featurerow__body p:not(.eyebrow) { color: var(--muted); font-size: var(--step-0); line-height: 1.5; }

/* Stacked, live puts the copy above the image and composes the row on a
   different set of numbers than the desktop one: the gutter closes from 168 to
   25, the copy block's own leading changes tier at 767, and the paragraph keeps
   the trailing margin the desktop column drops. Each of these is a height, so
   all of them are in the band's total. */
@media (max-width: 1024px) {
  .featurerow { gap: 25px; }
  /* Copy first. Live stacks with column-reverse against a source order of
     image-then-copy, so the claim leads and the screenshot supports it; taking
     the source order literally puts three unexplained screenshots down the page. */
  .featurerow__body { grid-row: 1; }
  /* The 24px label box the desktop column drops. Stacked, the copy is no longer
     optically centred against the image, so the box costs nothing and live keeps
     it: the label sits on the same 24px line as every other eyebrow. */
  .featurerow__body .eyebrow { min-height: 24px; }
  /* Elementor's default paragraph margin, which survives here because the copy
     is a stack of separate widgets rather than one flow. 14.4 = 0.9em. */
  .featurerow__body p:not(.eyebrow) { margin-bottom: 14.4px; }
}
@media (min-width: 48rem) and (max-width: 1024px) {
  .featurerow__body { gap: 25px; }
  /* 33.5, not 32: live's heading is a 32px inline box inside a widget whose own
     line-height is 24, and the line box is the union of the two rather than the
     larger of them. It is 1.5px per line of heading, at the top of a band that
     repeats three times. */
  .featurerow__body h3 { font-size: 32px; line-height: 33.5px; }
}
@media (max-width: 767px) {
  /* 8, against 25 a tier up: on a phone the three blocks read as one paragraph
     of copy, so live tightens them to the same rhythm the eyebrow already sets. */
  .featurerow__body { gap: 8px; }
  /* Set on the 24px line rather than solid: at 18px the heading fits inside the
     label's own line box, so the row is 24 whether it wraps or not. */
  .featurerow__body h3 { font-size: 18px; line-height: 24px; }
}

/* Below 1025px the two columns stack: the copy column has nowhere near enough
   room for a 337px measure beside a 769px image. */
@media (max-width: 1024px) {
  /* Live drops the 550px measure below 1025 and lets the centred hero run the
     full container, so the h1 and standfirst rewrap against the viewport rather
     than against a fixed column. Holding the cap made 768 and 1000 render
     identically to each other, which is not what either tier looks like on
     live: the same headline is two lines at 768 and one at 1000. */
  .hero__inner,
  .hero__sub { max-width: none; }

  /* Live's narrow-tier hero stack is not the desktop stack scaled down: the
     heading loses 2px of its lead-in, the standfirst gains a 16px step of its
     own on top of the column gap, and the button row grows 24px above and 14
     below. Together they are 52px, which is the whole gap between our hero and
     live's on every centred page at both narrow tiers. */
  .hero__inner h1 { margin-top: 16px; }
  .hero__inner .hero__sub { margin-top: 16px; }
  .hero__inner .btn-row { margin-top: 24px; margin-bottom: 14px; }

  /* The split heroes stop being a split, and they stop paying the per-page
     block padding set above. Live gives every one of them a flat 48/24 here, so
     the product shot sits just under the copy rather than floating inside a
     120px band sized for a two-column composition that no longer exists. */
  .hero--split {
    --hero-split-pt: 48px;
    --hero-split-pb: 24px;
    --hero-split-btn-mt: 24px;
    --hero-split-btn-mb: 14px;
  }
  /* The two whose hero ends on copy rather than on a bled image keep the
     section's own bottom padding instead of the flat 24. */
  .hero--split-marketing,
  .hero--split-data { --hero-split-pb: var(--hero-pad-bottom); }

  .hero__split { grid-template-columns: 1fr; gap: 44px; }

  /* The copy column's internal rhythm below 1025 is 18 above the heading, 14
     above the standfirst and 24 above the buttons, with 14 under them. Above
     1025 the per-page blocks set their own; these are the values live uses on
     every split page at both narrow tiers. */
  .hero--split .hero__col h1 { margin-top: 18px; }
  .hero--split .hero__sub { margin-top: 14px; }

  /* /for-marketing/ runs the other rhythm live uses, the one the centred heroes
     are on: a 16px column gap with a 16px lead-in on top of it, which reads as
     16 above the heading and 32 above the standfirst. */
  .hero--split-marketing { --hero-split-btn-mt: 40px; }
  /* 96%, which is what live's heading widget measures against its column at
     every width here. It is worth a whole line of a 44px headline at 768 and a
     line of a 34px one at 390, so it is a height difference, not a nicety. */
  .hero--split-marketing .hero__col h1 { margin-top: 16px; }
  .hero--split-marketing .hero__sub { margin-top: 32px; }
  /* No gutter between the stacked copy and image here: the 40px under the button
     row is already the whole separation live draws, and the 44 the other splits
     take would double it. */
  .hero--split-marketing .hero__split { gap: 0; }

}

/* The home hero's proof strip stops being three-up at phone widths: live gives
   each stat the full measure and stacks them, so the labels read on one or two
   lines instead of breaking after every second word in a 110px column. */
@media (max-width: 767px) {
  /* 82 rather than the 50px spacer live uses at desktop: once the strip stacks,
     live puts another 32 between the spacer and the first stat. */
  .hero__stats { grid-template-columns: 1fr; gap: 20px; margin-top: 82px; }
}

/* Two pages set their h1 a step below the tablet heading scale: 40px where the
   rest of the site is on 44. Both are heroes whose headline already runs long,
   and live gives the step back at phone widths, so it is scoped to the tier. */
@media (min-width: 768px) and (max-width: 1024px) {
  .hero--split-pricing h1,
  .hero--split-aiq h1 { font-size: var(--step-3); }
}

/* /for-marketing/ is the one split live does not collapse at tablet. Its columns
   are even, so 1fr 1fr still leaves a readable measure on both sides at 768 and
   the image stays beside the copy; it stacks with the rest at phone widths. */
@media (min-width: 768px) and (max-width: 1024px) {
  .hero--split-marketing .hero__split {
    grid-template-columns: 1fr 1fr;
    gap: 0;
  }
}

/* Live insets the hero container by 20px at phone widths where every other
   section on the page is inset 32. That is 24px of extra measure, which is a
   whole word per line in a 34px headline, so the h1 breaks where live's breaks
   instead of a word early. */
@media (max-width: 767px) {
  .hero > .container { padding-inline: 20px; }
}

/* ==========================================================================
   Logo marquee
   --------------------------------------------------------------------------
   The live site used a Swiper carousel with autoplay_speed:0 and speed:5000,
   which is a continuous scroll rather than a stepped carousel. A CSS keyframe
   marquee reproduces it exactly and drops the Swiper dependency.

   The track is duplicated in the HTML (aria-hidden on the copy) so translating
   by -50% loops seamlessly.
   ========================================================================== */

.marquee {
  position: relative;
  overflow: hidden;
  /* Fade the edges so logos enter and leave instead of popping. */
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

.marquee__track {
  display: flex;
  width: max-content;
  gap: var(--sp-6);
  animation: marquee 40s linear infinite;
}
.marquee:hover .marquee__track { animation-play-state: paused; }

.marquee__item {
  flex: none;
  /* Live's slide width: 106 on a phone, 125 from tablet up. */
  width: 106px;
  display: grid;
  place-items: center;
}
/* Natural size and full opacity, which is what live does at every width: the
   logos are already drawn to a common optical weight, so scaling them to a
   shared box makes the wide wordmarks tower over the square marks and dimming
   them a third loses the point of a customer wall. Capping the height on a
   phone shrank the tallest mark from 41 to 34 and took the whole band with it. */
.marquee__item img {
  width: auto;
  height: auto;
  max-width: 100%;
  object-fit: contain;
}

@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@media (min-width: 768px) {
  .marquee__track { gap: 40px; } /* the live desktop image_spacing_custom */
  .marquee__item { width: 125px; } /* the live desktop slide width */
}

/* ==========================================================================
   ICP loop — the four-step "qualification that thinks" sequence
   ========================================================================== */

/* No card chrome. Live runs these four as bare rows against the page: a small
   glyph on the outer edge, a fixed 500px column of copy, and the screenshot
   taking whatever width is left. The band gets a deep lead-in of its own on top
   of the section head's, so the sequence reads as a chapter rather than a list. */
.steps {
  display: flex;
  flex-direction: column;
  gap: 25px;
  padding-top: 120px;
}

.step {
  display: flex;
  gap: 25px;
  padding-block: 52px;
}
/* The first row butts straight onto the band's lead-in rather than paying for
   the air twice. */
.steps > .step:first-child { padding-top: 0; }
/* Odd rows put the screenshot on the left, which walks the eye down the page in
   a zigzag rather than a column. The glyph rides the outer edge either way. */
.step:nth-child(odd) { flex-direction: row-reverse; }

/* 60px down from the top of the row: the copy sits against the middle of the
   screenshot beside it, not its top edge. */
.step__icon {
  flex: none;
  /* Declared rather than left to the glyph's intrinsic size, because the
     compensating margin below subtracts it. */
  width: 28px;
  height: 32px;
  margin-top: 60px;
}
/* Live's icon column is not the glyph's own width: it is 4% of whatever is left
   after the 500px copy column and the row's gaps, with the screenshot taking the
   other 96%. That makes the column 19.4px at 1100 and 29.1px at 1390 where our
   glyph is a flat 28, so at the narrow end of the laptop tier we take 8.6px out
   of the screenshot and the two width-driven rows each land 5.6px short. The
   negative margin hands that width back and lets the glyph overrun into the 25px
   gap, which keeps the icon at its authored size rather than squashing it to 19
   the way live does. The margin goes on whichever side faces the copy, so the
   glyph stays pinned to the row's outer edge in both directions, and the first
   row subtracts its own 120px of body inset as well. See live-quirks.md. */
.step__icon { margin-right: calc(0.04 * (100% - 550px) - 28px); }
.step:nth-child(odd) .step__icon {
  margin-right: 0;
  margin-left: calc(0.04 * (100% - 550px) - 28px);
}
.steps > .step:first-child .step__icon { margin-left: calc(0.04 * (100% - 670px) - 28px); }

.step__body {
  flex: none;
  width: 500px;
  margin-top: 60px;
  /* Optical, not structural: the label's line box sits 4px higher than the
     glyph's without it. */
  padding-top: 4px;
}
/* The first row inset on all four sides, which is what pulls its screenshot in
   narrower than the other three. An Elementor one-off rather than a system, but
   the narrower opening image is visible. */
.steps > .step:first-child .step__body { margin: 60px; }

.step__body .eyebrow { margin-bottom: var(--sp-2); }
/* Flat 24px rather than the heading scale: live holds this title at 24/28.8 at
   every width, so stepping it down with the rest costs two lines per row once
   the copy wraps against a phone measure. */
.step__body h3 {
  margin: 0;
  font-size: 24px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--text);
}
/* 20/1/20, the 41px block live draws. */
.step__body hr {
  margin-block: 20px;
  border: 0;
  border-top: 1px solid var(--border);
}
/* 1.5, not the page's 1.8: this copy is a caption to the screenshot beside it
   and live sets it tighter than running body text. The 14.4px under it is live's
   too, and has to be restored explicitly because the global p:last-child rule
   would otherwise zero it. */
.step__body p { margin-bottom: 14.4px; line-height: 1.5; color: var(--muted); }

/* 204px, which is live's own offset: it bottom-aligns the button with the
   screenshot in the last row rather than letting it trail the copy. The number
   is fixed rather than an auto-margin because the row is not a flex column, and
   because live carries the same 204 down to a phone. */
.step__cta { margin: 204px 0 0; }
/* The one button live pads 12/20 rather than the site-wide 11/14, so it draws a
   40x173 box where the ghost buttons everywhere else are 38. */
.step__cta .btn { padding: 12px 20px; }

.step__figure { flex: 1; min-width: 0; margin: 0; }
/* max-width rather than width, so the screenshot fills a column narrower than
   itself but never upscales past its natural 770. Live stops at 501px tall from
   the moment the stacked column clears 770 (it is 108px of extra height per row
   at 1000 otherwise), and the same cap is what holds the desktop row at 700. */
.step__figure img { display: block; max-width: 100%; height: auto; }

/* Below the tier the three-column row stops fitting, so the rows stack: the
   screenshot on top, the copy under it, and the glyph back in the text flow. */
@media (max-width: 1024px) {
  .steps { padding-top: 40px; }
  /* Live keeps the glyph's 60px lead-in and its 60px trail once the row stacks,
     pays 24px under each row, and lets the list gap carry the rest. */
  /* gap 0, not the row's 25: stacked, live spaces these three purely with the
     glyph's and the body's own margins, so leaving the flex gap in place adds it
     twice per row and costs 50px on every step. */
  .step,
  .step:nth-child(odd) { flex-direction: column; gap: 0; padding-block: 0 24px; }
  .step__icon,
  .step__body { width: auto; }
  /* The icon column's laptop-tier width compensation goes with the row: stacked,
     the glyph is back in the text flow and there is no screenshot beside it to
     hand the width to. */
  .step__icon,
  .step:nth-child(odd) .step__icon,
  .steps > .step:first-child .step__icon { margin-inline: 0; }
  .step__icon { margin-bottom: 60px; }
  /* The copy stays above the screenshot here, which is the opposite of the
     desktop zigzag: stacked, live reads label then copy then evidence. */
  .step__figure { order: 2; }
  .step__body { margin-block: 25px; }
  /* The first row's 60px inset survives the stack on its bottom edge only: its
     glyph trail already supplies the 60 above. */
  .steps > .step:first-child .step__body { margin: 0 0 60px; }
}

/* ==========================================================================
   Persona tabs
   --------------------------------------------------------------------------
   Four cards acting as the tablist, and one panel below them. Rebuilt as real
   ARIA tabs: the live site used <div>s with a click listener, no role, no
   tabindex, no aria-selected, so keyboard and screen-reader users could not
   reach three of the four panels. See www/todo.md.

   The only thing marking the selected card on live is a glow image faded in over
   it. Border, fill, glyph and label are identical on all four, so that image is
   carrying the entire selected state.
   ========================================================================== */

/* 12px of dead space below the panel. Live closes the block with an empty HTML
   widget holding the tab script, and the flex gap above that widget is real
   height the section is composed around. */
.tabs { padding-bottom: 12px; }

.tabs__list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  margin-bottom: 12px;
}

.tabs__tab {
  position: relative;
  display: flex;
  flex-direction: column;
  /* The glyph rides the top edge and the label the bottom one. The card height is
     fixed, so whatever is left over falls between them rather than under them. */
  justify-content: space-between;
  min-height: 178px;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
/* Painted over the card rather than under it, which is what live does: the image
   carries its own alpha, so the glyph and label read through the wash. */
.tabs__tab::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: url("/assets/img/tab-glow.png") 100% 50% / cover no-repeat;
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease);
  pointer-events: none;
}
.tabs__tab[aria-selected="true"]::before { opacity: 1; }

.tabs__icon { flex: none; width: 48px; height: 48px; }
/* 18px flat, not --step-2: the tab label is one of the headings live keeps at 18
   on desktop, where the shared step goes to 24 from the 1025 tier up. */
.tabs__tab h3 {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--muted);
}

.tabs__panel[hidden] { display: none; }
.tabs__panel {
  animation: fade-in var(--t-slow) var(--ease);
}

.persona {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: 56px 48px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
/* Two fixed columns with the slack left between them, rather than a fluid split:
   live sets both to 512 and lets space-between hold the ~158px that is over. */
.persona__intro,
.persona__points { flex: 0 1 512px; }

.persona__intro { display: flex; flex-direction: column; gap: 14px; }
/* The column's own 14px gap is the whole separation live draws here, so the
   eyebrow's sitewide bottom margin has to come off or the label sits 16px too
   far from the title. Invisible at desktop, where the points column is the
   taller of the two, and 16px of real panel height once they stack. */
.persona__intro .eyebrow { margin-bottom: 0; }
.persona__intro h3 {
  margin: 0;
  font-size: 36px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: normal;
  color: var(--text);
}
/* 10px of its own above the button, on top of the column's 14px gap. */
.persona__cta { margin: 10px 0 0; }
/* The brighter border and the roomier padding are this button's own: it is the
   one secondary button live sets at 40px rather than 38. */
.persona__cta .btn { padding: 12px 20px; border-color: var(--border-bright); }

.persona__points { display: flex; flex-direction: column; gap: var(--sp-5); }
.persona__point { padding-bottom: var(--sp-2); border-bottom: 1px solid var(--border); }
.persona__point h4 {
  margin: 0 0 12px;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: normal;
  color: var(--text);
}
/* The 14.4px is live's paragraph-widget margin. It sits inside the rule rather
   than between rows, so the copy hangs off the line above the next row's title. */
.persona__point p {
  margin: 0 0 14.4px;
  font-size: var(--step-0);
  line-height: 1.5;
  color: var(--muted);
}

/* Below the desktop tier the four cards stop fitting on one row and the panel's
   two 512px columns stop fitting side by side, so both fall to a stack. */
@media (max-width: 1024px) {
  .tabs__list { grid-template-columns: repeat(2, 1fr); }
  .persona { flex-direction: column; gap: 44px; padding: 40px; }
  .persona__intro,
  .persona__points { flex: none; }
  .persona__intro h3 { font-size: 32px; }
}
/* Two cards to a row all the way down, and the card keeps its 178px box: live
   never falls to a single column, and dropping the fixed height there collapsed
   the four cards from 368px of list to 546. */
@media (max-width: 767px) {
  .persona { padding: 28px; }
  .persona__intro h3 { font-size: 28px; }
}

/* ==========================================================================
   Testimonial + stats
   ========================================================================== */

.quote {
  display: grid;
  gap: var(--sp-6);
  /* <figure> carries a UA margin of 16px 40px. On a card component that is 32px
     of section height and 80px of measure nobody asked for. */
  margin: 0;
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.quote blockquote {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.02em;
}
.quote p { color: var(--muted); }

.quote__who { display: flex; align-items: center; gap: var(--sp-3); }
.quote__who img { width: 40px; height: 40px; border-radius: 50%; flex: none; }
.quote__who strong { display: block; font-weight: 600; }
.quote__who span { color: var(--muted-deep); font-size: var(--step--1); }
.quote__logo { max-height: 28px; width: auto; opacity: 0.8; }

/* Outcome stats. Every value here is measured off the live cards: 24px padding,
   18px radius, #0e1626 fill, and a figure row of [direction arrow][gradient
   numeral][unit glyph] above a two-tier label. The figure is NOT flat teal —
   it carries the accent gradient, and the arrow and unit are gradient SVGs so
   the ramp reads continuously across the whole row. */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  /* 25px is the gutter the live site uses between every top-level row of
     cards, which puts each of the three stat cards at exactly 410px. */
  gap: 25px;
  margin-top: var(--sp-6);
}
/* Live drops to one full-width stat per row below 1025px rather than letting
   three narrow cards ride down together, and auto-fit would keep all three at
   936px because they still clear the 240px minimum. The card's own internals do
   not change, only how many sit on a row. */
@media (max-width: 1024px) {
  .stats { grid-template-columns: 1fr; }
}
/* /our-data/ runs the same card at three hand-set widths instead of an even
   three-up, and centres the short ones against the tall one rather than
   stretching them. The widths still total the container (330 + 570 + 330 + two
   25px gutters = 1280), so it reads as a row rather than as a broken grid. */
.stats--stagger {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  --stat-label-fs: 14px;
}
/* /for-revops/ runs the label a step up, at 18px. The row is still 24px tall
   either way, so the size only moves where the long labels break. */
.stats--label-lg { --stat-label-fs: 18px; }
.stats--stagger .stat { flex: none; width: var(--stat-w, 330px); }
.stats--stagger .stat--wide { --stat-w: 570px; }
/* Between the tablet stack and the desktop row live drops the hand-set widths
   and runs the three as an even three-up on whatever the container is, which is
   what keeps the band on one row at 1100 where 330 + 570 + 330 no longer fits.
   Held at the fixed widths the third card wraps to a second row and the band
   stands 210px over live's. The phrase card takes the same 32px figure it takes
   on a phone, and for the same reason: at 44 it needs a fourth line once it is a
   third of the row rather than a half. */
@media (min-width: 1025px) and (max-width: 1366px) {
  .stats--stagger .stat { flex: 1 1 0; width: auto; }
  .stats--stagger .stat--wide .stat__n { font-size: 32px; line-height: 33.5px; }
}
@media (max-width: 1024px) {
  .stats--stagger .stat { width: 100%; }
  /* The card that carries a phrase where the others carry a figure drops to 32
     once the row stacks. At 44 the phrase takes a fourth line on a phone and the
     card runs 75px over live's; the figure cards keep their 44 either way. */
  .stats--stagger .stat--wide .stat__n {
    font-size: 32px;
    /* 33.5, the same union of a 32px inline box and the widget's own 24px line
       that live's headings sit on. Three forced lines make it 100.5. */
    line-height: 33.5px;
  }
}
.stat {
  padding: 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  text-align: center;
}
/* Two classes so it outranks the global `p:last-child { margin-bottom: 0 }`,
   which is more specific than a single class and would otherwise flatten the
   one card that carries a figure and no label under it. */
.stat .stat__figure {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-bottom: 12px;
}
.stat__icon { width: 24px; height: 24px; flex: none; }
.stat__n {
  font-family: var(--font-body);
  font-size: 44px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.024em;

  background-image: var(--grad-accent);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: var(--teal-bright);
}
/* The label is two tiers on the live site: a bold white lead, then the
   qualifier in muted grey underneath. Collapsing them into one grey sentence
   loses the emphasis the design is built around. */
.stat__label {
  display: block;
  color: var(--text);
  /* Per row, not per site: the outcome cards set this at 16 and the data
     numbers at 14. The row height is 24 either way, so the size only decides
     where the longer labels wrap, which is exactly why live varies it. */
  font-size: var(--stat-label-fs, 16px);
  font-weight: 600;
  /* 24px, not the 1.2 the text itself is set on. Live nests a 19.2px inline box
     inside a 24px line box, so the label occupies 24px and the caption below it
     starts 5px lower than a tight leading would put it. */
  line-height: 24px;
  letter-spacing: -0.02em;
}
.stat__sub {
  display: block;
  color: var(--muted);
  font-size: var(--step--1);
  font-weight: 400;
  line-height: 1.5;
  /* Butted straight against the label, with the same 0.9em trailing margin every
     paragraph on the site carries. The card's visual bottom padding is 24 + 14.4,
     not 24, which is why it looks bottom-heavy next to a naive rebuild. */
  margin-bottom: 14.4px;
}

@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .stat__n { background: none; -webkit-text-fill-color: currentColor; }
}

@media (min-width: 900px) {
  .quote { grid-template-columns: 1.3fr 1fr; align-items: center; gap: var(--sp-8); padding: var(--sp-7); }
}

/* --------------------------------------------------------------------------
   The home page's proof band
   --------------------------------------------------------------------------
   Live ships a different component here to the one the other four pages use:
   one quote card beside a column of three figures, rather than the six-quote
   carousel. Same idea, different construction, so it rides on modifiers instead
   of reshaping .quote for everybody. These rules sit after the 900px block
   above deliberately, so they outrank its grid on source order.
   -------------------------------------------------------------------------- */

.testimonial-band { display: flex; gap: 20px; }
/* 798 + 20 + 462 is exactly the 1280 content column. Fixed rather than
   fractional because the quote's line breaks are composed against 700px of
   text measure, and a fraction moves them. */
.testimonial-band .quote { flex: 0 1 798px; }
.testimonial-band .stat-list { flex: 0 1 462px; }

.quote--feature {
  display: flex;
  flex-direction: column;
  /* Explicit, because the 900px rule above centres the carousel card's two
     columns and centring here would shrink every row to its own text. */
  align-items: stretch;
  gap: 18px;
  padding: 56px 48px;
}
/* 24px: live opens the column with an empty container that contributes nothing
   but still costs a gap, plus 6px of its own above the quote. Folded into one
   margin rather than reproducing the empty element. */
.quote--feature blockquote {
  margin-top: 24px;
  font-family: var(--font-body);
  font-size: 24px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: normal;
  color: var(--text);
}
.quote--feature p {
  margin: 0 0 14.4px;
  font-size: var(--step-0);
  line-height: 1.5;
  color: var(--muted);
}
/* The first paragraph carries 10px of its own on top of the column gap. */
.quote--feature p:first-of-type { margin-top: 10px; }

.quote--feature .quote__who {
  justify-content: space-between;
  align-items: stretch;
  gap: 16px;
}
/* 200px fixed: the name column is not sized by its content on live, so letting
   it shrink to the text would pull the row's two halves together. */
.quote__id { display: flex; flex: none; gap: 14px; width: 200px; }
/* Not the round 40px avatar the carousel uses: live hangs a 33.5x44 portrait
   here, top-aligned against a 48px-tall name block. */
.quote--feature .quote__who .quote__id img {
  flex: none;
  align-self: flex-start;
  width: 33.5px;
  height: 44px;
  border-radius: 0;
}
.quote__id > div { display: flex; flex-direction: column; }
.quote--feature .quote__who .quote__id strong,
.quote--feature .quote__who .quote__id span {
  display: block;
  font-size: 16px;
  /* 24px line boxes around 16px text, as everywhere else live stacks two label
     tiers: the pair occupies 48px, which is what the row is composed around. */
  line-height: 24px;
  letter-spacing: normal;
}
.quote--feature .quote__who .quote__id strong { font-weight: 600; color: var(--text); }
.quote--feature .quote__who .quote__id span { font-weight: 400; color: var(--muted); }
.quote--feature .quote__logo {
  flex: none;
  align-self: center;
  width: 162px;
  height: 36px;
  max-height: none;
  opacity: 1;
}

/* The figures beside the quote card are not the bordered .stat card the other
   pages use: no chrome, left aligned, a flat numeral rather than the accent
   gradient, and a progress track under every row. */
.stat-list {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 16px;
}
.stat-list__item { display: flex; flex-direction: column; gap: 10px; width: 382px; }
.stat-list__figure { display: flex; align-items: center; gap: 8px; margin: 0; }
.stat-list__figure img { flex: none; }
.stat-list__figure .stat__n {
  background-image: none;
  -webkit-text-fill-color: currentColor;
  color: var(--text);
}
/* The fill is left at zero width because that is what live renders: the bar
   animates when it scrolls into view and the animation never runs. Reproduced
   rather than "fixed" so the band matches. See www/todo.md. */
.stat-list__bar { height: 3px; border-radius: 2px; background: rgba(45, 212, 191, 0.16); }
.stat-list__bar > span { display: block; width: 0; height: 100%; border-radius: 2px; background: var(--blue); }
.stat-list__label { display: flex; flex-direction: column; margin: 0; }
.stat-list__label > span {
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  letter-spacing: normal;
  color: var(--muted);
}
.stat-list__label > span:first-child { font-weight: 600; color: var(--text); }

/* The two columns stop fitting side by side below the desktop tier, the same
   place the stat row and the persona panel fall to a stack. */
@media (max-width: 1024px) {
  /* 40, not the desktop 20: stacked, live doubles the separation between the
     quote card and the figures under it. */
  .testimonial-band { flex-direction: column; gap: 40px; }
  /* The 798 and 462 bases above are widths only. Once the band is a column they
     become heights and pad both blocks out by whatever their content is short,
     which was 139px on the quote and 109 on the figures. */
  .testimonial-band .quote,
  .testimonial-band .stat-list { flex: none; }
  .quote--feature { padding: 40px; }
  .stat-list { align-items: stretch; }
  .stat-list__item { width: 100%; }
}
/* 28 on a phone, which is 12 less than the tablet card. The measure it leaves is
   what the quote's line breaks are composed against, so the padding is a height
   difference as much as a width one. */
@media (max-width: 767px) {
  .quote--feature { padding: 28px; }
}
@media (max-width: 600px) {
  .quote--feature .quote__who { flex-direction: column; align-items: flex-start; }
}

/* ==========================================================================
   Closing CTA
   ========================================================================== */

/* The closing CTA sits directly on the page background on the live site: no
   panel, no border, no glow, and one button rather than two. The band treatment
   we had put a second bordered surface immediately above the footer's, which
   read as a stack of unrelated boxes. */
.cta-band {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  /* 760px is the centred content column the live site uses for section heads
     and the closing CTA alike. */
  max-width: 760px;
  margin-inline: auto;
  text-align: center;
}
/* Stretch, not the flex default of shrink-to-fit: otherwise each line box is
   only as wide as its text and the centring comes from the flex container
   rather than the text, which changes where a long line wraps. */
.cta-band > * { margin: 0; align-self: stretch; }
/* :not(.eyebrow), as in .section__head and .flow__col: the eyebrow is a <p>, and
   this rule outranks .eyebrow on specificity. */
/* margin-bottom for the same reason as .section__head p: live's paragraph widget
   carries a 0.9em trailing margin on top of the container gap. */
/* 16px, not the 14.4px (0.9em) every other paragraph on the site carries: the
   live CTA paragraph is the one widget with an explicit margin override, and
   the 1.6px difference is the whole gap between our CTA section and theirs. */
.cta-band p:not(.eyebrow) { color: var(--muted); font-size: var(--step-0); line-height: 1.5; margin-bottom: 16px; }
.cta-band .btn-row { justify-content: center; }
/* Live's closing CTA does not run one gap: /ai-qualification/ is a uniform 20
   while /our-data/ and the blog run 22 down to the heading and 36 down to the
   buttons. It is authored per page rather than derived, so it rides on a
   modifier instead of moving the default. */
.cta-band--roomy { gap: 22px; }
.cta-band--roomy .btn-row { margin-top: 14px; }
/* The home closing CTA is the one live sets at the h1 size rather than the h2
   size, and it hangs the heading and the intro on measures of their own rather
   than on the band's. The two measures are what decide where each of them
   breaks, so they are the whole difference between this band and the others. */
.cta-band--lg h2 { font-size: var(--step-4); max-width: 680px; margin-inline: auto; }
.cta-band--lg p:not(.eyebrow) { max-width: 550px; margin-inline: auto; margin-bottom: 14.4px; }
/* Live steps this heading rather than scaling it fluidly: 50 on a desktop, 42
   through the tablet tier, 30 on a phone. The fluid ramp overshot by 4 at 390
   and by 2 at 768, which is a whole line each time the copy is three lines
   deep. The band it sits in is also padded above the section rhythm at every
   width below the desktop tier. */
@media (max-width: 1024px) {
  .section--cta-lg { padding-block: 96px; }
  .cta-band--lg h2 { font-size: 42px; }
}
@media (max-width: 767px) {
  .cta-band--lg h2 { font-size: 30px; }
}

/* ==========================================================================
   Footer
   ========================================================================== */

/* No margin-top: the closing CTA section already carries the page's bottom
   rhythm, and adding more here opened a gap live does not have. */
/* The footer runs on two sets of numbers, and the switch is at 1025 rather than
   at the 768 the rest of the page turns on: through 1024 live gives it 56/30 of
   padding and wraps the link columns, and only above that does it open out to
   80/32 and set them in a row. */
.site-footer {
  border-top: 1px solid var(--border);
  padding-block: 56px 30px;
  color: var(--muted);
  font-size: var(--step--1);
}

/* Wrapped, not stacked and not an auto-fit grid. Live gives the brand the whole
   width and then sets each link column at 40% of it with no gutter, so two sit
   side by side and the third drops to a line of its own with the same left edge.
   An auto-fit grid puts all three on one line at 600 and all three on separate
   lines at 390, neither of which is live's. */
.site-footer__cols {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin-bottom: 40px;
}
.site-footer__brand { flex: 0 0 100%; }
.site-footer__cols > div:not(.site-footer__brand) { flex: 0 0 40%; }
/* Inline, against the sitewide block reset: live's wordmark sits on a text
   baseline, so the box around it is the image plus the line's descender rather
   than the image alone. Worth 1.8px, and only at the wrapped tier, where the
   brand is a row of the footer's height instead of a cell in a taller one. */
.site-footer__brand {
  flex: 0 0 100%;
  /* The wordmark sits on a text line rather than in a block of its own, so the
     box around it is decided by that line's metrics and not by the image. Live
     runs it at 16 on 24 in the OS UI face, which lands the box at 25.8: the
     image is centred on the x-height, so it hangs 7.8px under the baseline
     while the line's own strut only reaches 6. Both the size and the face are
     load-bearing here (Inter's taller x-height lifts the image and loses a
     third of a pixel), which is why they are set rather than inherited. */
  font-size: 16px;
  line-height: 24px;
  font-family: -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
}
/* Inline-block on a middle baseline, against the sitewide block reset. As a
   block the box would be the image alone; live's is the line around it. */
.site-footer__brand img {
  display: inline-block;
  vertical-align: middle;
  width: auto;
  height: 24px;
}

/* 12px set solid, uppercase, and near-white: these read as labels above the
   links rather than as headings in their own right. */
.site-footer h2 {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--text);
  margin: 0 0 20px;
}
/* 12px of gap on top of the 21px line box gives live's 33px link pitch. */
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.site-footer li { line-height: 21px; }
.site-footer a { color: var(--muted); }
.site-footer a:hover { color: var(--teal); }

/* The padding-bottom is live's empty second row: the block is a column of two
   items with a 12px gap (25 above 1024), and the second one has no content, so
   what it actually contributes to the footer is that gap and nothing else. */
.site-footer__bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-top: 28px;
  padding-bottom: 12px;
  border-top: 1px solid var(--border);
  color: var(--muted-deep);
}
/* 24px line box around 12px text, as live has it. Set solid, the copyright sits
   12px higher and the whole footer comes out short. */
.site-footer__bottom p { margin: 0; font-size: 12px; font-weight: 600; line-height: 24px; }
.site-footer__bottom a {
  color: inherit;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.5;
}

/* Fixed column widths rather than fractions. Live gives the brand 320px and each
   link column 166px, left-aligned, which leaves real space at the right edge.
   `1fr` columns would spread them to fill it and put the link columns in visibly
   different places than live's. */
@media (min-width: 64.0625rem) {
  .site-footer { padding-block: 80px 32px; }
  .site-footer__cols {
    display: grid;
    grid-template-columns: 320px 166px 166px 166px;
    justify-content: start;
    gap: 25px;
    margin-bottom: 48px;
  }
  .site-footer__brand { flex: none; }
  .site-footer__brand img { display: block; }
  .site-footer__bottom { padding-bottom: 25px; }
}

/* ==========================================================================
   Demo modal — the primary conversion path
   --------------------------------------------------------------------------
   Five steps: CRM -> role -> source -> contact details -> summary, then a
   confirmation. Behaviour lives in site.js. Sized to be usable at 390px,
   because a 5-step form in a dialog is the easiest thing to break on a phone.
   ========================================================================== */

.modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  /* End-aligned on mobile: a bottom sheet is reachable with a thumb. */
  place-items: end center;
  padding: 0;
  background: rgba(2, 4, 8, 0.7);
  backdrop-filter: blur(4px);
  opacity: 0;
  visibility: hidden;
  /* Stepped, not transitioned: see the .site-nav note. A plain
     `transition: visibility <dur>` flips visibility halfway through, so the
     dialog is still unfocusable when open() tries to move focus into it, and
     the modal opens with focus stranded on <body> behind the overlay. */
  transition: opacity var(--t-mid) var(--ease), visibility 0s linear var(--t-mid);
}
.modal[data-open="true"] {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--t-mid) var(--ease), visibility 0s;
}

.modal__dialog {
  position: relative;
  width: 100%;
  max-width: 560px;
  max-height: 92vh;
  overflow-y: auto;
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-lg);
  transform: translateY(16px);
  transition: transform var(--t-mid) var(--ease);
}
.modal[data-open="true"] .modal__dialog { transform: none; }

.modal__close {
  position: absolute;
  top: var(--sp-4);
  right: var(--sp-4);
  width: 40px; height: 40px;
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--muted);
  cursor: pointer;
  transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.modal__close:hover { color: var(--text); border-color: var(--border-bright); }
.modal__close svg { width: 14px; height: 14px; }

.modal__progress {
  height: 3px;
  background: var(--border);
  border-radius: var(--radius-pill);
  overflow: hidden;
  margin-bottom: var(--sp-6);
}
.modal__bar {
  height: 100%;
  width: 20%;
  background: var(--teal);
  border-radius: inherit;
  transition: width var(--t-mid) var(--ease);
}

.modal h2 { font-size: var(--step-2); margin-bottom: var(--sp-2); }
.modal__sub { color: var(--muted-deep); font-size: var(--step--1); margin-bottom: var(--sp-5); }

.step-pane[hidden] { display: none; }
.step-pane { animation: fade-in var(--t-mid) var(--ease); }

/* Selectable option cards. One column on a phone so labels never truncate. */
.opts { display: grid; gap: var(--sp-3); margin-bottom: var(--sp-6); }
@media (min-width: 420px) { .opts { grid-template-columns: 1fr 1fr; } }

.opt {
  display: flex;
  align-items: center;
  min-height: 52px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.opt:hover { border-color: var(--border-bright); }
.opt[aria-pressed="true"] {
  border-color: var(--teal);
  background: rgba(78, 205, 196, 0.1);
  box-shadow: var(--glow-teal);
}

.field { margin-bottom: var(--sp-4); }
.field label {
  display: block;
  margin-bottom: var(--sp-2);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--muted);
}
.field input {
  width: 100%;
  min-height: 48px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  /* 16px minimum stops iOS Safari zooming the viewport on focus. */
  font: inherit;
  font-size: 16px;
  transition: border-color var(--t-fast) var(--ease);
}
.field input:focus { border-color: var(--teal); outline: none; box-shadow: var(--glow-teal); }
.field[data-error="true"] input { border-color: var(--coral); }

.field__error {
  display: none;
  margin-top: var(--sp-2);
  color: var(--coral);
  font-size: var(--step--1);
}
.field[data-error="true"] .field__error { display: block; }

.field-row { display: grid; gap: var(--sp-4); }
@media (min-width: 480px) { .field-row { grid-template-columns: 1fr 1fr; } }

.modal__actions {
  display: flex;
  flex-direction: row-reverse;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}
.modal__actions .btn { flex: 1 1 auto; }
.modal__actions .btn--back { flex: 0 0 auto; }

.summary { list-style: none; margin: 0 0 var(--sp-5); padding: 0; display: grid; gap: var(--sp-2); }
.summary li {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--step--1);
}
.summary dt, .summary__k { color: var(--muted); }
.summary__v { font-weight: 600; text-align: right; }

.modal__done { text-align: center; padding-block: var(--sp-5); }
.modal__done .modal__tick {
  width: 56px; height: 56px;
  margin: 0 auto var(--sp-5);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(78, 205, 196, 0.12);
  border: 1px solid var(--teal);
  color: var(--teal);
}

@media (min-width: 640px) {
  .modal { place-items: center; padding: var(--sp-5); }
  .modal__dialog { border-radius: var(--radius-lg); padding: var(--sp-7) var(--sp-6) var(--sp-6); }
}

/* Locks background scroll while the modal is open (set by site.js). */
body[data-modal-open="true"] { overflow: hidden; }

/* ==========================================================================
   Consent banner
   ========================================================================== */

.consent {
  position: fixed;
  inset: auto 0 0;
  z-index: 190;
  padding: var(--sp-5);
  background: var(--surface);
  border-top: 1px solid var(--border-bright);
  box-shadow: var(--shadow-lg);
  transform: translateY(110%);
  transition: transform var(--t-slow) var(--ease);
}
.consent[data-open="true"] { transform: none; }

.consent__inner {
  max-width: var(--container);
  margin-inline: auto;
  display: grid;
  gap: var(--sp-4);
}
.consent p { color: var(--muted); font-size: var(--step--1); margin: 0; }
.consent__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.consent__actions .btn { flex: 1 1 auto; font-size: var(--step--1); padding-inline: var(--sp-4); }

.consent__prefs { display: grid; gap: var(--sp-3); margin-top: var(--sp-2); }
.consent__prefs[hidden] { display: none; }
.consent__row {
  display: flex;
  align-items: start;
  gap: var(--sp-3);
  padding: var(--sp-3);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.consent__row input { margin-top: 4px; width: 18px; height: 18px; flex: none; accent-color: var(--teal); }
.consent__row strong { display: block; font-size: var(--step--1); }
.consent__row span { color: var(--muted-deep); font-size: var(--step--1); }

@media (min-width: 900px) {
  .consent__inner { grid-template-columns: 1fr auto; align-items: center; }
  .consent__actions { flex-wrap: nowrap; }
  .consent__actions .btn { flex: 0 0 auto; }
  .consent__prefs { grid-column: 1 / -1; grid-template-columns: repeat(3, 1fr); }
}

/* ==========================================================================
   Blog
   ========================================================================== */

/* --------------------------------------------------------------------------
   The index
   --------------------------------------------------------------------------
   Live's blog index is three distinct blocks, not one card grid: a hero, one
   featured post laid out as a wide image beside its own headline, and a labelled
   grid of small cards under a "Load More". None of the three shares the card
   treatment used elsewhere on the site, so this section is self-contained.

   It also runs its own section rhythm. Measured on live:

     tier             hero      featured  grid    closing CTA
     <= 767           48 / 58   72        72      96
     768 - 1024       48 / 58   72        72      96
     1025 - 1366      40 / 40   40        40      40
     >= 1367          64 / 64   80        52      120

   The 1025-1366 row is the site's own --sec-pad, and the two narrow rows match
   it for the featured and grid blocks, so only the >=1367 values and the CTA's
   96 are spelled out below. */

/* The blog hero is set one step larger than the product-page heroes and packed
   tighter: live gives it a 16px eyebrow and a 16/24 standfirst against 14px and
   14/21 elsewhere, and only 18px between the eyebrow and the h1 where the rest
   of the site takes 34. Three lines of copy carrying a whole section, rather
   than a caption under a composed headline. */
.hero--blog .eyebrow { font-size: var(--step-0); }
/* The blog hero packs its eyebrow, heading and standfirst tighter than the rest
   of the site's heroes, which carry 18px above the h1 on top of the 16px column
   gap. Live runs a flat 16 below 1025 and 18 above, so the extra is scoped to the
   wide layout and the shared 18 is zeroed out everywhere else. */
.hero--blog .hero__inner h1 { margin-top: 0; }
@media (min-width: 1025px) {
  .hero--blog .hero__inner h1 { margin-top: 2px; }
}
/* Two classes, so this outranks .hero__inner > * { margin: 0 } and the standfirst
   keeps the 0.9em trailing margin live's paragraph widget carries. Without it the
   whole page below the hero sits 14.4px high. */
.hero--blog .hero__sub { font-size: var(--step-0); line-height: 1.5; margin-bottom: 14.4px; }

.section--feature,
.section--posts { padding-block: var(--sec-pad); }

@media (min-width: 1367px) {
  .section--feature { padding-block: 80px; }
  .section--posts { padding-block: 52px; }
}

/* The blog's closing CTA is the one place the shared CTA band does not take the
   site's section padding: live gives it 96 on both narrow tiers where every
   other section takes 72. */
@media (max-width: 1024px) {
  .section--blog-cta { padding-block: 96px; }
}

/* The band inside it is spaced differently too. Live's blog CTA carries the
   spacing on the widgets rather than as a column gap: 22px above the heading and
   above the paragraph, 36 above the button, and the paragraph keeps the 0.9em
   trailing margin it has everywhere else rather than the 16px the shared band
   overrides it to. Scoped to the blog, because live's product-page CTAs really do
   run a uniform 20px gap and changing .cta-band would move all of them. */
.section--blog-cta .cta-band { gap: 22px; }
.section--blog-cta .cta-band p:not(.eyebrow) { margin-bottom: 14.4px; }
/* 22 from the gap plus 14 here: live's button sits 36px under the paragraph. */
.section--blog-cta .cta-band .btn-row { margin-top: 14px; }

/* The featured post ------------------------------------------------------- */

/* No card chrome at all: live sets this straight on the page background, and the
   only rounded edge is the image's own. Giving it a panel would make the newest
   post read as an ad above the grid rather than as the lead story. */
.post-feature__link { display: grid; gap: 44px; color: inherit; }

.post-feature__media img {
  display: block;
  width: 100%;
  /* Fixed height rather than an aspect ratio: live's image is 476x292 in the
     two-column layout and 936x292 stacked, so it crops rather than reflows and
     the text beside it never moves. */
  height: 292px;
  object-fit: cover;
  border-radius: 16px;
}

/* 16 here and 20 from 1025 up: once the image stops sitting beside the text the
   block is a single narrow column, and live tightens it. */
.post-feature__body { display: flex; flex-direction: column; gap: 16px; }

/* Categories left, date right, on one 24px row. The margin reset matters: this
   is a <p>, and the global paragraph margin would stack on top of the column
   gap and push the headline 16px down. */
.post-feature__meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  /* 8, not the 16 the rest of the site uses for an inline gap: at 390 the two
     halves plus 16 come to 331 against a 326 column, and the date drops to a
     second line. Live's 8 is what keeps it on one row. */
  gap: 8px;
  margin: 0;
  font-size: var(--step-0);
  line-height: 24px;
}
/* Flat --blue here, where the small cards below run the same text as gradient.
   That inconsistency is live's, and it is visible: the featured categories read
   as a lighter blue than the grid's teal-to-blue ramp. */
.post-feature__cats { color: var(--blue); font-weight: 600; }
.post-feature__meta time { color: var(--muted); }

.post-feature__title { color: var(--text); font-size: var(--step-3); line-height: 1; margin: 0; }
/* Two classes deep, because this paragraph is the last child of its column and
   the global `p:last-child { margin-bottom: 0 }` would otherwise win and eat the
   trailing margin below. */
.post-feature__body .post-feature__excerpt {
  color: var(--muted);
  /* 24px, against the 1.8 the body sets: this paragraph sits beside a 292px
     image and the looser leading costs it a third line. */
  line-height: 24px;
  /* The 0.9em trailing margin live's paragraph widgets carry. */
  margin: 0 0 14.4px;
}

@media (min-width: 1025px) {
  /* 476px fixed, not a fraction: the image keeps its size from 1025 up and the
     text column absorbs the difference, which is why the headline breaks in the
     same place at 1200 and at 1440. */
  .post-feature__link {
    grid-template-columns: 476px minmax(0, 1fr);
    gap: 80px;
    align-items: start;
  }
  .post-feature__body { gap: 20px; }
}

@media (max-width: 767px) {
  .post-feature__media img { height: 196px; }
}

/* The grid ---------------------------------------------------------------- */

.posts__head {
  color: var(--text);
  font-size: 30px;
  line-height: 1;
  /* 44 below 1025 and 24 above. The heading gets less room on the wide layout,
     not more, which is backwards but is what live does. */
  margin: 0 0 44px;
}
/* 28 on tablet against 30 on mobile: live steps this heading down before it
   steps back up, so it cannot come from one scale. */
@media (min-width: 768px) { .posts__head { font-size: 28px; } }
@media (min-width: 1025px) { .posts__head { font-size: 36px; margin-bottom: 24px; } }

/* Three columns only from 1367px. Between 768 and 1366 live holds two, so the
   card keeps a readable measure on a laptop instead of squeezing to a third of
   1136px. */
/* grid-auto-rows: 1fr, so every row is as tall as the tallest card on the page
   rather than as tall as the tallest card in its own row. Live does the same,
   and the effect is visible: without it a row of two-line titles sits noticeably
   shorter than the row under it and the grid reads as ragged. */
.post-list { display: grid; grid-template-columns: 1fr; grid-auto-rows: 1fr; gap: 16px; }
@media (min-width: 768px) { .post-list { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1367px) { .post-list { grid-template-columns: repeat(3, 1fr); } }

.post-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 16px;
  transition: border-color var(--t-fast) var(--ease);
}
.post-card:hover { border-color: var(--border-bright); }

/* One anchor for the whole tile, so the card is a single hit target and the
   "Read more" is a styled span rather than a nested link. space-between is what
   holds that button on the baseline across a row of cards whose titles wrap to
   different depths; the 28px gap is the minimum, not the actual. */
.post-card__link {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 28px;
  height: 100%;
  color: inherit;
}

.post-card__media img {
  display: block;
  width: 100%;
  height: 200px;
  object-fit: cover;
  border-radius: 14px;
}

.post-card__body { display: flex; flex-direction: column; gap: 8px; }

/* Between 768 and 1024 live insets the image and the text to 90% of the card and
   centres them, while "Read more" keeps the full width. It reads as an Elementor
   tablet setting rather than a considered one, but it is what live renders and it
   is not cosmetic: the narrower measure is why live's longest card titles wrap to
   four lines at 1000px where ours would fit three, which sets the height of every
   row in the grid. */
@media (min-width: 768px) and (max-width: 1024px) {
  .post-card__media,
  .post-card__body { width: 90%; align-self: center; }
}

/* Comma-separated inline text, not chips. Live links each name to a category
   archive; we have no category pages, so they render as plain text. Logged in
   www/todo.md. */
.post-card__cats {
  background-image: var(--grad-accent);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: var(--teal-bright);
  font-size: 15px;
  font-weight: 600;
  line-height: 22.5px;
  margin: 0;
}

/* 500, not the 700 every other heading on the site carries: the card title is
   competing with two others in a row and live deliberately sets it lighter. */
.post-card__title {
  color: var(--text);
  font-size: 24px;
  font-weight: 500;
  line-height: 1.1;
  margin: 0;
}

.post-card__more {
  display: block;
  background: var(--surface-subtle);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 14px;
  font-weight: 500;
  line-height: 14px;
  padding: 11px 14px;
  text-align: center;
  transition: background-color var(--t-fast) var(--ease);
}
.post-card:hover .post-card__more { background: rgb(255 255 255 / 0.06); }

/* Load More --------------------------------------------------------------- */

/* Hidden until site.js takes over. Without JS the grid renders every post and
   there is nothing left to load, which is also the better answer for a crawler. */
.post-list__more { display: flex; justify-content: center; margin-top: 30px; }
/* The #333 label is live's own value and it is very nearly invisible against the
   page background. Reproduced rather than corrected, because the aesthetic is
   meant to match live; it is logged in www/todo.md as a live bug to fix in one
   place once the two are the same. */
.btn-more {
  background: var(--surface-subtle);
  border: 1px solid var(--border-bright);
  border-radius: 18px;
  color: #333;
  cursor: pointer;
  font: inherit;
  font-size: 12.8px;
  font-weight: 500;
  line-height: 1;
  padding: 12px 16px;
}
.btn-more:hover { background: rgb(255 255 255 / 0.06); }

/* The scale-on-hover is decoration, not information. */
@media (prefers-reduced-motion: reduce) {
  .post-card,
  .post-card__more,
  .btn-more { transition: none; }
}

/* The post's featured image, above the body. Fixed aspect so the reserved box
   matches the rendered one and the article text does not jump when it loads. */
.article__hero {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius);
  margin-block: var(--sp-6);
}

/* Long-form article body. */
.prose { max-width: 44rem; margin-inline: auto; }
.prose h2 { margin-top: var(--sp-7); }
.prose h3 { margin-top: var(--sp-6); }
.prose p, .prose li { color: #d7dce6; }
.prose blockquote {
  margin: var(--sp-6) 0;
  padding-left: var(--sp-5);
  border-left: 2px solid var(--teal);
  color: var(--muted);
  font-size: var(--step-1);
}
.prose img { border-radius: var(--radius); margin-block: var(--sp-6); }
.prose ul,
.prose ol { padding-left: 1.35rem; margin-block: var(--sp-4); }
.prose li { margin-block: 0.35rem; }
.prose li > ul,
.prose li > ol { margin-block: 0.35rem; }
.prose a { color: var(--teal); }

/* Legal pages are wider than an article: the privacy policy carries multi-column
   tables that are unreadable inside a 44rem measure. */
.prose--legal { max-width: 62rem; }
.prose--legal h2 { font-size: var(--step-2); }
.prose--legal h3 { font-size: var(--step-1); }

/* Tables scroll inside their own wrapper so a wide legal table can never make
   the page body scroll sideways on a phone. build.py's source pages wrap every
   <table> in .table-wrap. */
.table-wrap {
  overflow-x: auto;
  margin-block: var(--sp-6);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  -webkit-overflow-scrolling: touch;
}
.prose table {
  width: 100%;
  min-width: 44rem;
  border-collapse: collapse;
  font-size: var(--step--1);
}
.prose th,
.prose td {
  padding: var(--sp-3) var(--sp-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
  color: #d7dce6;
}
.prose th {
  background: var(--surface-alt);
  color: var(--text);
  font-weight: 600;
  white-space: nowrap;
}
.prose tbody tr:last-child td { border-bottom: 0; }

.article__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  color: var(--muted-deep);
  font-size: var(--step--1);
  margin-bottom: var(--sp-7);
}

/* ==========================================================================
   Carousel (partials/testimonials.html)
   --------------------------------------------------------------------------
   Live drives this with Swiper. Here it is a scroll-snap track: the browser
   owns the scrolling, the momentum and the touch handling, and site.js only
   paints the dots and runs the timer. That keeps drag, trackpad, keyboard and
   screen-reader behaviour native, and it degrades to a plain scrollable row if
   the script never runs.

   Slide geometry is live's: 3 per view above 1024px, 1 below, 10px between.
   Live sets a fixed 420px slide on a 1280px track, which is the same thing as
   thirds once the two gaps are removed, so this is expressed as a fraction and
   holds at any container width.
   ========================================================================== */

.carousel { position: relative; }

/* /for-revops/ only. Live nests the track one container deeper there, and that
   container carries 10px of padding on every side. The slides are sized off the
   track, so the inset is not just 20px of band height: it recomposes the three
   visible cards at 413.3 rather than 420. See partials/testimonials-revops.html. */
.section--carousel-inset .carousel { padding: 10px; }
/* Below the two-up tier that inset stops being 10px and starts tracking the
   container: live sets 6% a side, which is 19.6 on a 390 phone and 32.2 at 600.
   Held at 10 the band comes in 19 short on the phone and 44 short at 600, and
   the error grows with the viewport rather than staying put. */
@media (max-width: 767px) {
  .section--carousel-inset .carousel { padding: 6%; }
}

.carousel__track {
  list-style: none;
  display: flex;
  gap: 10px;
  margin: 0;
  padding: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  /* The scrollbar is the one part of native scrolling we do not want: it would
     sit under the cards where live has nothing, and the dots already say the
     row scrolls. */
  scrollbar-width: none;
}
.carousel__track::-webkit-scrollbar { display: none; }
/* Keyboard scrolling of the track is a real focus target, so it needs a real
   focus ring; :focus-visible keeps it off mouse users. */
.carousel__track:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 4px;
}

.carousel__slide {
  flex: 0 0 100%;
  scroll-snap-align: start;
  /* Stretch, so every card takes the height of the tallest one in the row —
     this is what Swiper's equal-height mode was doing, and what makes the
     attribution blocks line up across the three visible cards. */
  display: flex;
}
@media (min-width: 64rem) {
  .carousel__slide { flex-basis: calc((100% - 20px) / 3); }
}

.carousel__dots {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-top: 32px;
}
.carousel__dot {
  width: 20px;
  height: 5px;
  padding: 0;
  border: 0;
  border-radius: 18px;
  background: var(--teal);
  opacity: 0.2;
  cursor: pointer;
  transition: width 200ms ease, opacity 200ms ease;
}
/* The active dot is a 81px bar rather than a filled circle. It reads as a
   progress indicator, which is why live can get away with six of them. */
.carousel__dot[aria-current="true"] { width: 81px; opacity: 1; }
.carousel__dot:focus-visible { outline: 2px solid var(--teal); outline-offset: 4px; opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .carousel__dot { transition: none; }
}

/* ---------------------------------------------------------- testimonial card */

.tcard {
  display: flex;
  flex: 1;
  flex-direction: column;
  /* space-between with a 12px floor: the cards are all as tall as the tallest,
     and live lets the slack fall into the two gaps rather than pooling it under
     the attribution. A card with a two-line role therefore sits at 12px gaps
     and its shorter neighbours open up to match. */
  justify-content: space-between;
  gap: 12px;
  margin: 0;
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  /* Every card on the live site is 18px, including this one. */
  border-radius: var(--radius-lg);
}

/* Tag and company on one 24px row. Both are 14px; only the weight and the
   colour separate them, so the pair reads as a single label. */
.tcard__meta {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 24px;
}
.tcard__tag {
  color: var(--text);
  font-size: var(--step--1);
  font-weight: 600;
  line-height: 24px;
  letter-spacing: normal;
}
/* 14px line box, not the 24 the tag beside it sits on. On one line that costs
   nothing, because the row is centred and the tag sets its height. It only shows
   at phone widths, where the pair wraps: live's second row is 14 tall, so the
   whole meta block is 44 rather than the 54 two 24px rows would make. */
.tcard__org {
  color: var(--muted);
  font-size: var(--step--1);
  font-weight: 400;
  line-height: 14px;
}
/* Phone only. Above this the row does not wrap: it lets the two labels shrink
   and their text run to a second line, which is a taller result than wrapping
   would give and is what live shows at every width from 768 up. */
@media (max-width: 767px) {
  .tcard__meta { flex-wrap: wrap; }
}

.tcard__quote { margin: 0; }
.tcard__quote p {
  margin: 0 0 14.4px;
  color: var(--muted);
  font-size: var(--step-0);
  font-style: normal;
  line-height: 1.5;
  /* The quotation marks are content, not decoration, so they come from the
     stylesheet rather than being typed into the markup — that keeps the quoted
     text itself copy-pasteable and lets the glyphs follow the locale. */
  quotes: "\201C" "\201D";
}
.tcard__quote p::before { content: open-quote; }
.tcard__quote p::after  { content: close-quote; }

.tcard__by {
  display: flex;
  flex-direction: column;
}
/* Capped at live's 200px from 1025 up, which is narrow enough that a long role
   wraps to two lines. That wrap is what sets the height of every card in the
   row, so the cap is load-bearing rather than cosmetic. Live drops it below
   1025 and lets the attribution take the card's full width, which is why the
   cap has to be gated: held everywhere it wrapped every card at phone and
   tablet widths and put 24px on the band. */
@media (min-width: 1025px) {
  .tcard__by { max-width: 200px; }
}
.tcard__name {
  color: var(--text);
  font-size: var(--step-0);
  font-weight: 600;
  line-height: 24px;
}
.tcard__role {
  color: var(--muted);
  font-size: var(--step--1);
  font-weight: 400;
  line-height: 24px;
}

/* ==========================================================================
   Generic feature cards
   ========================================================================== */

/* Card metrics are measured off live: 12px grid gap, 18px radius, 20px padding,
   a 48px icon, an 18px title and 16px/24px body. The tight 12px gutter is what
   makes the grid read as one panel rather than four floating tiles. */
.cards {
  list-style: none;
  display: grid;
  gap: var(--cards-gap, var(--sp-3));
  grid-template-columns: 1fr;
}
@media (min-width: 40rem) { .cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) { .cards--4 { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 64rem) { .cards--3 { grid-template-columns: repeat(3, 1fr); } }

.card {
  display: flex;
  flex-direction: column;
  gap: var(--card-gap, var(--sp-4));
  /* 20px, not the --sp-5 24px the rest of the panels use. The foot is 20 as
     well: live carries its extra bottom air on the paragraph (a 14.4px widget
     margin, below) rather than on the box, which matters because the margin
     collapses away on a card whose last child is not a paragraph. */
  padding: var(--card-pad, 20px);
  background: var(--card-bg, var(--surface));
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.card > * { margin: 0; }
/* The icon sits 32px off the title on the standard card and only the flex gap
   off it on the roomy one, so the difference rides on a variable rather than on
   the shared gap, which also has to hold the title-to-body distance. */
.card__icon {
  flex: none;
  width: var(--card-icon, 48px);
  height: var(--card-icon, 48px);
  margin-bottom: var(--card-icon-gap, 16px);
}
/* -0.02em: the one place the live site tightens tracking. It only shows at this
   size, where the titles run long enough to wrap.
   The margin is derived rather than set: what live holds constant per card
   variant is the distance from the title to the body, and that has to survive a
   variant changing the flex gap above the title. Subtracting the gap keeps the
   declared number the one that is actually visible. */
.card h3 {
  font-size: var(--card-title-fs, var(--step-1));
  font-weight: var(--card-title-fw, 700);
  /* 1.2, not the inherited 1.8: a card title is set solid like the headings, and
     the body leading made every one of ours 10.8px taller than live's. */
  line-height: var(--card-title-lh, 1.2);
  letter-spacing: -0.02em;
  margin-bottom: calc(var(--card-title-body-gap, 16px) - var(--card-gap, var(--sp-4)));
}
.card p { color: var(--muted); font-size: var(--step-0); line-height: 1.5; }
.card p + p { margin-top: var(--sp-3); }
/* Live's paragraph widget carries a 0.9em bottom margin. Two classes so it
   outranks the global `p:last-child { margin-bottom: 0 }`, which is more
   specific than a single class; on a card the paragraph is nearly always last,
   and this 14.4px is load-bearing in every card row's height. */
.cards .card p { margin-bottom: 14.4px; }

/* One card in the /for-growth/ solution grid was authored on an 8px stack where
   its three siblings run on 16, so it stands 16px shorter than the same content
   would in any of them. Reproduced, since the grid is one panel and the shorter
   card is visible as the shorter left-hand column; live only hides it at the
   two-up tier, where the taller card beside it sets the row.

   Live's own version of this card overlaps its wrapped title into the paragraph
   under it by 8px, which is a rendering fault rather than a design, so the 16 it
   saves is taken here as 8 off each of the two gaps instead. Same height, no
   collision. Logged in www/todo.md as a copy-side fix for both sites. */
.card--tight { --card-gap: 8px; --card-title-body-gap: 8px; }

/* The roomier card live uses for a problem band: more padding, more air above
   the title, a bigger title on a smaller icon, and a tighter title-to-body gap
   than the standard card. It also sits on a warmer near-black than the blue
   --surface, which is what separates the band from the sections either side of
   it. Live gives this variant a wider gutter between cards too, so it reads as
   three statements rather than one panel. */
.cards--roomy {
  --cards-gap: 20px;
  --card-gap: 24px;
  --card-pad: 28px;
  --card-bg: #16171a;
  --card-icon: 32px;
  --card-icon-gap: 0px;
  --card-title-fs: var(--step-2);
  --card-title-fw: 600;
  --card-title-lh: 1.2;
  --card-title-body-gap: 8px;
}
/* The roomy card is only roomy on a desktop. Below the three-up tier live drops
   it to the standard 16px box and the standard 18px title, which is what keeps
   the three of them at 182 rather than 230 apiece; the wider padding also costs
   24px of measure, and at 326 that is the third line the body does not have. */
@media (max-width: 1024px) {
  .cards--roomy {
    --card-pad: 16px;
    --card-title-fs: var(--step-1);
  }
  /* One column all the way to the three-up tier. The auto-fit base drops these
     to two-up at 768 and leaves a widowed third card; live never splits the trio
     and runs them full width instead. */
  .cards--roomy { grid-template-columns: 1fr; }
}

/* The tighter stack live uses where the grid runs two-up across the full
   measure: the icon sits straight on the title with no gap of its own, and the
   body follows 9px under it rather than a full step. At 634px wide the standard
   spacing reads as a half-empty box. */
.cards--tight {
  --card-gap: 0px;
  --card-title-body-gap: 9px;
}
/* Live sets that gap per band and not per site: 9px on /for-marketing/, 8px on
   /for-revops/. Nothing in the layout predicts which, so it is a class. */
.cards--title-snug { --card-title-body-gap: 8px; }

/* Numbered how-it-works cards. The counter comes from CSS so the numbers can
   never disagree with the source order the way hand-typed "01/02/03" did. */
.cards--numbered { counter-reset: hiw; }
.cards--numbered > .card { counter-increment: hiw; }
.cards--numbered > .card::before {
  content: "0" counter(hiw);
  display: block;
  font-family: var(--font-display);
  font-size: var(--step-2);
  color: var(--teal);
  margin-bottom: var(--sp-3);
}

/* ==========================================================================
   Step rail
   --------------------------------------------------------------------------
   The numbered "how it works" row. Live gives it no card chrome at all: an icon
   with the step number beside it, then a title and body sitting directly on the
   page background. Given cards it runs ~100px taller than live and competes
   with the feature grid it follows, which is the whole reason live left the
   chrome off.

   Distinct from .flow, the home page's three-column band: that one carries a
   48px icon, a category label and a rule, and threads its icons together with a
   gradient line. This is a number and a paragraph.
   ========================================================================== */

.steprail {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr;
  /* 12 at every width, not a step that opens up once the rail stacks: live keeps
     the same gutter between the three steps whether they sit side by side or on
     top of each other, so the rail reads as one run rather than three blocks. */
  gap: 12px;
  margin: 0;
  padding: 0;
  counter-reset: steprail;
}
/* 1025px rather than 64rem, matching .flow: this is one of live's own tier
   boundaries. */
@media (min-width: 1025px) {
  .steprail { grid-template-columns: repeat(3, 1fr); }
}

.steprail__step {
  display: flex;
  flex-direction: column;
  gap: 20px;
  counter-increment: steprail;
}
/* The digits come from the counter so they can never disagree with the source
   order. The colour is live's dimmest text step, a stop below --muted-deep: the
   number is a position marker rather than something to read. */
.steprail__n {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--step-0);
  font-weight: 600;
  line-height: 1.2;
  color: #4b5668;
}
.steprail__n::after { content: "0" counter(steprail); }
/* Sized explicitly rather than left to the intrinsic box. Our step glyphs are
   drawn on a 28x32 canvas where live's are on 28x28, and the global height:auto
   wins over the height attribute, so unsized they render 32 tall and push the
   whole rail 4px past live's. The viewBox letterboxes the glyph inside the
   square, so pinning both axes scales it down rather than distorting it. */
.steprail__n img { flex: none; width: 28px; height: 28px; }

.steprail__body { display: flex; flex-direction: column; gap: var(--sp-2); }
.steprail__body > * { margin: 0; }
.steprail__body h3 { font-size: var(--step-1); font-weight: 600; line-height: 1.2; }
/* The 14.4px is live's paragraph-widget bottom margin, which the flex gap does
   not replace; without it the rail sits 14px shorter than live's. */
.steprail__body p {
  color: var(--muted);
  font-size: var(--step--1);
  line-height: 1.5;
  margin-bottom: 14.4px;
}

/* ==========================================================================
   Point blocks
   --------------------------------------------------------------------------
   A claim with its supporting points: an icon and title on one row, then a
   short dotted list under it. Live uses this where the copy is two statements
   rather than a paragraph, and gives it no card chrome for the same reason as
   the step rail. Two columns, and a wider gutter than the card grids, because
   each block is a self-contained argument rather than one tile of a panel.
   ========================================================================== */

.pointblocks {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr;
  /* Two fr rows rather than auto rows, at every width. Two-up that sizes all
     four blocks to the tallest, so they read as one set instead of four boxes.
     Stacked it survives as ~24px of air under the second block, which looks like
     a defect but is what live's own band is composed on: the rows are declared
     once for the desktop grid and never redeclared underneath it. */
  grid-template-rows: repeat(2, 1fr);
  gap: 25px;
  margin: 0;
  padding: 0;
}
/* 768, not 64rem: live goes two-up a whole tier earlier than the card grids do,
   because a point block is a short list rather than a paragraph. */
@media (min-width: 48rem) {
  .pointblocks { grid-template-columns: repeat(2, 1fr); }
}

.pointblock { display: flex; flex-direction: column; gap: var(--sp-2); }
.pointblock__head { display: flex; align-items: center; gap: 20px; }
.pointblock__icon { flex: none; width: 48px; height: 48px; }
.pointblock__head h3 {
  margin: 0;
  font-size: var(--step-2);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.02em;
}
/* Phone: live drops the glyph to 32 and pulls the title in to 8. Both are height
   changes as well as width ones, because the head row is sized by the glyph and
   the title only stays on one line while the 32px box leaves it the measure. */
@media (max-width: 767px) {
  .pointblock__head { gap: 8px; }
  .pointblock__icon { width: 32px; height: 32px; }
  .pointblock__head h3 { font-size: 18px; }
}

.pointblock__points { list-style: none; margin: 0; padding: 0; }
/* Centred rather than top-aligned: live puts the dot on the optical middle of
   the whole item, so a two-line point keeps its marker between the lines. */
.pointblock__points li {
  display: flex;
  align-items: center;
  /* Live splits this as 20px after the marker and 5px before the text. */
  gap: 25px;
  color: var(--muted);
  font-size: var(--step-0);
  line-height: 1.5;
}
.pointblock__points li + li { margin-top: 18px; }
.pointblock__points li::before {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--teal-bright);
}

/* ==========================================================================
   Flow columns
   --------------------------------------------------------------------------
   The "how it works" row. Deliberately has no card chrome on the live site: an
   icon, a flat-teal category label, a title, a rule, and body copy, sitting
   directly on the page background. Giving these cards makes the section compete
   with the feature grid above it.

   Named .flow rather than .step because .step above is the ICP-loop component,
   which is a different thing (alternating image/text rows inside a panel).
   ========================================================================== */

.flow {
  list-style: none;
  display: grid;
  gap: var(--sp-6);
  grid-template-columns: 1fr;
  margin: 0;
  padding: 0;
}
/* 1025px, not the 64rem the card grids use: this is one of live's own tier
   boundaries, and at exactly 1024 the two flow layouts would otherwise both
   apply. */
@media (min-width: 1025px) {
  .flow { position: relative; grid-template-columns: repeat(3, 1fr); gap: 32px; }
  /* The ICP loop is the one four-step instance. Same column geometry, same
     gutter, same threading rule: only the count changes, and at 1280 the four
     296px columns land on the same 52px inset the three-column version does. */
  .flow--4 { grid-template-columns: repeat(4, 1fr); }

  /* The rule that threads the three step icons together. It is decorative, so it
     lives in a pseudo-element rather than the markup, and it only exists once
     the columns are side by side; stacked, it would be a line to nowhere.

     The end stops are --bg, not transparent: the line is meant to emerge from
     and dissolve back into the page rather than stop dead, and interpolating to
     `transparent` in sRGB fades through grey instead, which reads as a smudge.

     52px = the 48px icon plus the 4px live leaves before the rule starts, so the
     line runs from the edge of the first icon to the edge of the last. */
  .flow::before {
    content: "";
    position: absolute;
    top: 25px;
    left: 52px;
    right: 52px;
    height: 3px;
    background-image: linear-gradient(90deg,
      var(--bg) 0%,
      var(--teal-bright) 38.9423%,
      var(--blue-grad) 66.8269%,
      var(--bg) 100%);
    pointer-events: none;
  }
  /* The middle icon sits on top of the rule it interrupts. */
  .flow__icon { position: relative; }
}

.flow__col { display: flex; flex-direction: column; gap: var(--sp-2); }
.flow__col > * { margin: 0; }
/* 24px + the 8px column gap gives the 32px live puts between the icon and the
   top of the eyebrow's box, which is far more air than between any later pair.
   Measured to the box, not the text: the eyebrow centres 14px of text in a 24px
   box, so measuring to the glyph would overshoot by 5px. */
/* Height-pinned, width free. The loop icons are 48px squares but the data
   waterfall's are 78-80 wide at the same height, and an SVG given both axes
   letterboxes inside the box rather than distorting, so pinning the width
   shrinks those three to 48x29 and drops the row off its own rule.
   align-self is required with it: the column is a flex column, so an auto-width
   child stretches to the full 405px measure and then gets squashed to 48 tall. */
.flow__icon { flex: none; align-self: flex-start; width: auto; height: 48px; margin-bottom: 24px; }
/* 1.125 rather than solid: live sets these 24px titles on a 24px line-height but
   renders them inline, so the line box grows to the font's own 27px content
   height. Setting it solid costs 3px per column and shortens the whole section. */
.flow__col h3 { font-size: var(--step-2); font-weight: 700; line-height: 1.125; }
.flow__col hr {
  width: 100%;
  height: 1px;
  border: 0;
  background: var(--border);
  margin-block: var(--sp-4);
}
/* :not(.eyebrow) for the same reason as .section__head p: the category label in
   each column is an .eyebrow <p>, and this rule would otherwise outrank it. */
.flow__col p:not(.eyebrow) { color: var(--muted); font-size: var(--flow-body-fs, var(--step--1)); line-height: 1.5; margin-bottom: 14.4px; }
/* Live sets the loop's body copy a step above the three-column flow's. The
   columns are the same width, so the larger size costs lines; it is a per-band
   decision on live rather than something derivable from the layout, which is why
   it rides on the modifier instead of the base. */
.flow--4 { --flow-body-fs: var(--step-0); }
/* Same decision taken independently on a three-column band: live sets the
   marketing page's flow copy at the body size where the growth page's runs a
   step down. Nothing in the layout predicts which, so it is a class rather than
   a rule. */
.flow--body-lg { --flow-body-fs: var(--step-0); }
/* The data waterfall's three glyphs are wider than they are tall. Only the
   stacked layout needs to know: side by side the icon sits above its text and
   its width costs nothing, but stacked the text is indented past it.

   59, not the icon's own width plus a gap: live sizes these glyphs to 48 wide
   and then lets them overhang the column they sit in, so the text starts about
   11px past the glyph rather than a full gutter past it. Live's own measured
   text starts are 55.8 / 58.7 / 59.9 / 60.9 across the stacked tiers, so one
   value sits inside a pixel of all four.

   The exact figure matters at 768 and nowhere else: the third paragraph sets
   one line at 644.2px, against a 644.1px column on live, so the measure is
   decided in tenths. The 69 this rule used to carry cost that paragraph a
   second line; 60 still cost it one by 0.2px. */
.flow--wide { --flow-indent: 59px; }

/* Below 1025px live does not simply stack the three columns: it turns each one
   on its side. The icon shrinks to 32px and moves to the left of the text, the
   text column narrows to a single 8px-gapped stack beside it, and the rule that
   threaded the icons horizontally becomes a vertical line running down through
   their centres. A stacked-column fallback is what made this section 280px
   taller than live at 1000px.

   The icon is taken out of flow and the column indented past it, rather than
   made a grid cell. Live wraps the text in its own element and we do not, and a
   grid icon spanning `1 / -1` silently collapses to row 1 (there are no explicit
   rows for -1 to point at), which pushes the eyebrow's row to the icon's 32px
   and costs 8px per column. */
@media (max-width: 1024px) {
  .flow { position: relative; }

  .flow__col {
    position: relative;
    gap: 8px;
    /* The icon's rendered width plus live's 16px gap. The mesh glyphs are taller
       than they are wide, so at the 32px height live sets here they measure 27.7
       across, not 32: assuming a square icon costs the text 4.3px of measure,
       which is the line the last /for-growth/ column gained at 390. A flow whose
       icons are wider than they are tall overrides this with its own width. */
    padding-left: var(--flow-indent, 43.7px);
  }
  .flow__icon {
    position: absolute;
    top: 0;
    left: 0;
    width: auto;
    height: 32px;
    margin-bottom: 0;
  }
  /* The wide glyphs are sized on their width here, not their height: live caps
     them at 48 and lets the height fall where the artwork puts it, so the three
     of them line up on their left edge rather than on a common height. */
  .flow--wide .flow__icon { width: 48px; height: auto; }
  /* 18px on an 18px line-height renders inline at 22px, and live's row is 24.
     Setting the line-height to the row height gets both without a fudge. */
  .flow__col h3 { font-size: 18px; line-height: 24px; }
  /* 25px row: 12px either side of the 1px rule, against 16px when the columns
     are side by side and each has more room to breathe. */
  .flow__col hr { margin-block: 12px; }

  /* The vertical counterpart to .flow::before, on the same gradient. It starts
     at the first icon's centre (16px down) and runs past the last row, which is
     live's own overshoot: the line is a fixed-height image there and ends 103px
     below the last column. It fades into --bg well before that, so it reads as
     the line dissolving rather than as a line to nowhere. */
  .flow::before {
    content: "";
    position: absolute;
    top: 16px;
    bottom: -103px;
    left: 16px;
    width: 3px;
    transform: translateX(-50%);
    background-image: linear-gradient(180deg,
      var(--bg) 0%,
      var(--teal-bright) 38.9423%,
      var(--blue-grad) 66.8269%,
      var(--bg) 100%);
    pointer-events: none;
  }
}

/* ==========================================================================
   Callout card (the bordered "the problem" panel)
   ========================================================================== */

.callout {
  max-width: 50rem;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
/* Below 1025px live lets the panel run the full container instead of holding the
   800px measure, and tightens it: 20px of padding rather than 24, and 24px
   between the pill and the body rather than 40. Keeping the wide-screen values
   costs two extra wrapped lines per paragraph and 22px of panel height. */
@media (max-width: 1024px) {
  .callout {
    max-width: none;
    gap: 24px;
    padding: 20px;
  }
}

/* The panel's own header: a centered pill on the raised surface, flanked by a
   glyph on each side. */
.callout__head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  margin: 0;
  padding: var(--sp-3) var(--sp-5);
  background: var(--surface-alt);
  border-radius: var(--radius);
  font-size: var(--step-0);
  font-weight: 600;
  /* 24px, which is a whole line rather than the 19.2 this size would set on its
     own. On one line the min-height below hides the difference; on a phone the
     line wraps and each of the two runs 4.8px short, so the pill lands at 62
     against live's 72. */
  line-height: 24px;
  min-height: 48px;
  letter-spacing: -0.02em;
  color: var(--text);
  text-align: center;
}
.callout__head img { flex: none; width: 15px; height: 15px; }

.callout__body { display: flex; flex-direction: column; gap: 6px; }
.callout__body p {
  /* Same 0.9em trailing margin live's paragraph widgets carry everywhere; here
     it stacks with the 6px gap to give the 20px live puts between the two. */
  margin: 0 0 14.4px;
  color: var(--text);
  font-size: var(--step--1);
  line-height: 1.5;
}

/* ==========================================================================
   Before / after comparison (/for-revops/)
   ========================================================================== */

/* Live builds this as one panel split by a hairline, inset inside a darker band
   of its own, not as two cards with a gutter between them. The distinction is
   the whole point of the component: a single table reads as one before/after
   comparison, where two separated cards read as two unrelated lists, and the
   rows only line up across the divider because they share the panel. */
.compare {
  max-width: 1088px;
  margin-inline: auto;
  padding: 32px 24px;
  background: #0a0e14;
  /* Both before/after embeds carry their own stack, and it puts the reader's OS
     UI face ahead of the site's own body font. Not a decision worth having, but
     it is a measurable one: the OS face is narrower than Inter, so on Inter the
     first row of the marketing table takes a third line at 768 and the columns
     stop shrinking ~4px sooner on a phone. Reproduced rather than normalised so
     the table wraps where live's wraps. The revops embed declares a slightly
     different tail on the same stack; the resolved face is the same. */
  font-family: -apple-system, "system-ui", Inter, "Segoe UI", Helvetica, Arial, sans-serif;
}
/* The two columns stay side by side at every width, including a phone. This is
   live's behaviour and it is deliberate on live's part: the component is a
   before/after table, and a stacked one is two lists that no longer line up row
   for row, which is the only thing the comparison is made of.

   `flex: 1 1 0` with the default `min-width: auto` is what live uses, and the
   min-width is load-bearing: below about 500px the columns stop shrinking at
   their longest word and the panel overflows its own box. Live clips that
   overflow rather than scrolling it, so the right edge of the "with" column is
   cut off on a phone. Reproduced here; logged in www/todo.md as a live bug,
   since switching `hidden` to `auto` would make the same layout readable
   without moving a pixel of it. */
.compare__panel {
  display: flex;
  overflow: hidden;
  background: #111827;
  border: 1px solid #232d3f;
  border-radius: 16px;
}

/* The 4px foot is live's, not a typo: the last row of each column drops its own
   rule, so the column ends 4px under the last line rather than a full step. */
.compare__col { flex: 1 1 0; padding: 24px 32px 4px; }
.compare__col--before { border-right: 1px solid #232d3f; }
.compare__col h3 {
  margin: 0 0 16px;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8b93a1;
}
.compare__col--after h3 { color: var(--teal); }
.compare__col ul { list-style: none; border-top: 1px solid #1f2937; }
.compare__col li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 14px 0;
  border-bottom: 1px solid #1f2937;
  color: #c7cdd6;
  /* 24px, not the page's 1.8 body leading: every row here is one or two lines of
     the same 24px line box on live, and the looser leading adds ~10px per row,
     which compounds across eight rows into a section 38px too tall. */
  font-size: var(--step-0);
  line-height: 1.5;
}
.compare__col--after li { color: #e5e9ef; }
.compare__col li:last-child { border-bottom: 0; }
/* The mark is decorative relative to the wording, so it is generated rather
   than typed — a literal "✕" in the markup gets read aloud by screen readers
   as "multiplication x" before every single line. The 2px nudge centres the
   20px disc against the first 24px line rather than against the whole item. */
.compare__col li::before {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin-top: 2px;
  border-radius: 50%;
  font-size: 12px;
  line-height: 18px;
}
.compare__col--before li::before {
  content: "\2715";
  color: #ef4444;
  background: rgb(239 68 68 / 0.12);
}
.compare__col--after li::before {
  content: "\2713";
  color: var(--teal);
  background: rgb(45 212 200 / 0.12);
}

/* /for-revops/ ships a second embed of the same idea, and it is not a variant of
   the first: a narrower panel with no band behind it, the column heading at full
   size instead of a small label, a hairline gutter instead of a divider border,
   and the "with" column tinted a shade up from the "before". Same markup, other
   skin. */
.compare--fit {
  max-width: none;
  padding: 0;
  background: none;
}
/* This one does stack, where the marketing embed above does not: live's revops
   panel puts the "with" column under the "before" one below 768 rather than
   letting either narrow past its longest word. Two separately authored
   components, so the behaviour is set here rather than shared. */
.compare--fit .compare__panel {
  display: grid;
  grid-template-columns: 1fr;
  max-width: 900px;
  margin-inline: auto;
  /* The 1px gutter is the divider: the panel's own background shows through it,
     which is why the rule reaches the panel's rounded corners where a border on
     the column would stop short of them. */
  gap: 1px;
  background: #232d3f;
  border-radius: 12px;
  overflow: hidden;
}
@media (min-width: 48rem) {
  .compare--fit .compare__panel { grid-template-columns: repeat(2, 1fr); }
}
.compare--fit .compare__col {
  padding: 32px;
  background: #111827;
  border-right: 0;
}
.compare--fit .compare__col--after { background: #161f30; }
.compare--fit .compare__col h3 {
  font-size: 18px;
  line-height: 1.5;
  letter-spacing: 0.04em;
  margin-bottom: 24px;
  padding-bottom: 16px;
  border-bottom: 1px solid #232d3f;
}
/* The heading carries the rule here, so the list does not need its own. */
.compare--fit .compare__col ul { border-top: 0; }
.compare--fit .compare__col li { border-bottom-color: #1a2333; color: #e8ecf3; }
.compare--fit .compare__col li::before { margin-top: 1px; font-weight: 700; }

/* ==========================================================================
   Pricing tiers (/pricing/)
   ========================================================================== */

/* Live sets the plan grid on a band a shade darker than the page, with 60px of
   air above the cards and 80 below, and the footnote inside the band rather than
   under it. The band is what makes four narrow cards read as one price list
   instead of four unrelated boxes. */
.tiers-band {
  padding: 60px 20px 80px;
  background: #060b16;
}
.tiers {
  list-style: none;
  display: grid;
  gap: 20px;
  grid-template-columns: 1fr;
}
@media (min-width: 40rem) { .tiers { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 72rem) { .tiers { grid-template-columns: repeat(4, 1fr); } }

.tier {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 28px 24px;
  background: #0b1220;
  border: 1px solid #1c2536;
  border-radius: 14px;
}
/* A halo and a long shadow, not just a brighter border: live lifts the
   recommended plan off the band rather than recolouring its edge. */
.tier--featured {
  position: relative;
  border-color: #3aa9c9;
  box-shadow: 0 0 0 1px rgb(58 169 201 / 0.25), 0 20px 40px -20px rgb(58 169 201 / 0.35);
}
/* Generated rather than typed: the ribbon is a label on the card, and in the
   markup it would be read out as the first line of the plan's own content. */
.tier--featured::before {
  content: "Most Popular";
  position: absolute;
  top: -13px;
  left: 24px;
  padding: 4px 10px;
  border-radius: 20px;
  background: linear-gradient(90deg, #2dd4bf, #38bdf8);
  color: #04121a;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.tier h3 { margin: 6px 0 10px; font-size: 21px; line-height: 1.2; color: #fff; }
/* The min-height is live's and it is load-bearing: it holds the four feature
   lists on a common baseline even though the blurbs run two lines or three. */
.tier__blurb {
  min-height: 62px;
  margin-bottom: 20px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.5;
}
.tier ul { list-style: none; flex: 1; margin-bottom: 24px; }
.tier li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 13px;
  color: #d4d9e3;
  font-size: 14.5px;
  line-height: 1.4;
}
/* The tick is decorative next to the wording, so it is a background rather than
   an <svg> per item: 24 copies of the same mark in the markup is 24 things for a
   screen reader to skip. */
.tier li::before {
  content: "";
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Ccircle cx='12' cy='12' r='11' stroke='%2338bdf8' stroke-width='1.5'/%3E%3Cpath d='M7.5 12.5l3 3 6-6.5' stroke='%2338bdf8' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* The plan CTA is a full-width block, not the site's 36px pill: it is the last
   row of the card and live sizes it to the card. */
.tier .btn {
  display: block;
  width: 100%;
  padding: 13px 0;
  border-radius: 8px;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.5;
}
/* The featured card's CTA carries a gradient rather than the flat teal the
   site's primary button uses, so it reads as the one recommended plan. The stops
   are the embed's own, a shade deeper than --grad-accent at both ends. */
.tier .btn--primary {
  background: linear-gradient(90deg, #2dd4bf, #38bdf8);
  border-color: transparent;
  color: #04121a;
}
.tier .btn--ghost { background: transparent; border-color: #263149; color: #e8ecf3; }
.tier .btn--ghost:hover { background: #131c2e; }
/* Live runs the footnote inside the band, narrower and quieter than the page's
   body copy, so it reads as a footnote to the table rather than a new paragraph
   in the section. */
.tiers__note {
  max-width: 700px;
  margin: 32px auto 0;
  color: #6b7688;
  font-size: 13.5px;
  line-height: 1.5;
  text-align: center;
}

/* ==========================================================================
   Podcast (podcast.html)
   ========================================================================== */

.platforms {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  justify-content: center;
  margin-top: var(--sp-5);
}
.platform {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font-size: var(--step--1);
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.platform:hover,
.platform:focus-visible { border-color: var(--teal); color: var(--teal); }

/* The live page put a host portrait here; no licensed image of Jeremy exists in
   assets yet, so the panel is text-only until one lands (see www/todo.md). */
.host {
  display: grid;
  gap: var(--sp-4);
  max-width: 48rem;
  margin-inline: auto;
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.host__role { color: var(--teal); font-size: var(--step--1); }
.host p { color: var(--muted); }

.ep-list { list-style: none; display: grid; gap: var(--sp-4); }
.ep {
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.ep--featured { background: var(--surface-alt); border-color: var(--border-bright); }
.ep__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  color: var(--muted-deep);
  font-size: var(--step--1);
}
.ep h3 { font-size: var(--step-1); margin-block: var(--sp-3) var(--sp-3); }
.ep p { color: var(--muted); }
.ep__guest { margin-top: var(--sp-4); font-size: var(--step--1); }
.ep__guest strong { color: var(--text); }
.ep__guest span { display: block; color: var(--muted-deep); }

/* ==========================================================================
   Scroll reveal + motion preferences
   ========================================================================== */

@keyframes fade-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* site.js adds .is-visible when the element scrolls into view. Elements only
   start hidden when JS is present (html.js), so with JS disabled or broken the
   content is never invisible. */
.js .reveal { opacity: 0; transform: translateY(14px); }
.js .reveal.is-visible {
  opacity: 1;
  transform: none;
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}

/* Animated ring (the live site's .avili0h). */
.ring circle { transform-origin: center; animation: ring-spin 2s linear infinite; }
@keyframes ring-spin { to { transform: rotate(360deg); } }

/* Respect the OS-level reduced-motion setting: stop the marquee, the ring, and
   every reveal, and make transitions effectively instant. An infinite marquee is
   exactly the kind of motion this setting exists to suppress. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .marquee__track { animation: none; }
  .js .reveal { opacity: 1; transform: none; }
}

/* ==========================================================================
   Podcast (/podcast/)
   --------------------------------------------------------------------------
   The one light page on the site, and the only place these tokens are used.
   Live composes /podcast/ on white with a mint-to-blue gradient hero and a
   #0096ff accent while the header and footer around it stay dark, so the whole
   file is scoped under .pod rather than folded into the site palette. Nothing
   below leaks: every selector is a descendant of .pod.

   The content column here is the viewport minus 16px gutters (1358 at 1390),
   not the 1280 .container the rest of the site uses. That is live's, and it is
   what puts the episode grid at three 442px cards.
   ========================================================================== */

.pod {
  --pod-ink: #0f1828;
  --pod-head: #1c2938;
  --pod-accent: #0096ff;
  --pod-muted: #697281;
  --pod-line: rgba(0, 150, 255, 0.4);
  --pod-chip: #f3f4f6;
  --pod-chip-blue: #e9f8ff;
  --pod-orange: #ff8000;
  --pod-rule: #e5e7eb;
  --pod-card-shadow: rgba(0, 74, 152, 0.12) 8px 8px 12px 0;

  background: #fff;
  color: var(--pod-ink);
  font-family: var(--font-body);
}

.pod-wrap {
  width: 100%;
  max-width: 1358px;
  margin-inline: auto;
}

.pod h2,
.pod h3 { font-family: var(--font-body); color: var(--pod-head); letter-spacing: normal; }
.pod p { color: var(--pod-ink); }
.pod .pod-accent { color: var(--pod-accent); }

/* Hero -------------------------------------------------------------------- */

.pod-hero {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-height: 920px;
  padding-inline: 16px;
  background-image: linear-gradient(0deg, #e0f6ef 4.57%, #bbecf4 72.69%, #c3e6fa 93.52%);
}

.pod-hero__inner {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: 0 64px;
  /* Live sets this row's height rather than letting the two columns find it:
     the copy column is 521 where its content is 452, and the spare 69px is what
     space-between spreads between the lede, the button and the platform row. */
  min-height: 521px;
}

.pod-hero__media {
  position: relative;
  flex: 0 1 647px;
  align-self: center;
  display: block;
  aspect-ratio: 4 / 3;
  border-radius: 12px;
  overflow: hidden;
}
.pod-hero__media img { width: 100%; height: 100%; object-fit: cover; }
.pod-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.pod-play svg { width: 100px; height: 100px; }

.pod-hero__col {
  flex: 0 1 647px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
}
.pod-hero__lede { display: flex; flex-direction: column; gap: 25px; width: 100%; }
.pod-hero__title { display: flex; flex-direction: column; gap: 16px; }

.pod-hero h1 {
  margin: 0;
  font-size: 72px;
  line-height: 72px;
  font-weight: 600;
  letter-spacing: normal;
  color: #000;
}

/* 24px line box around 18px text, as everywhere else live stacks a label under
   a heading: the row is composed on 24 whatever it contains. */
.pod-host { display: flex; align-items: center; gap: 12px; margin: 0; line-height: 24px; }
/* 18px text on a 24px box. The row is a flex line, so the box has to be stated
   on the items themselves; there is no strut to inherit it from. */
.pod-host__name,
.pod-host__role { font-size: 18px; line-height: 24px; font-weight: 600; }
.pod-host__name { color: var(--pod-accent); }
/* Half-strength accent, as live: the role is a qualifier on the name, not a
   second label competing with it. */
.pod-host__role { color: rgba(0, 150, 255, 0.5); }
.pod-host__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(0, 150, 255, 0.5);
}

/* Scoped through the parent so it outranks the global p:last-child reset: this
   paragraph is the last child of the lede, and live keeps its 14.4px. */
.pod-hero__lede .pod-hero__sub {
  margin: 0 0 14.4px;
  font-size: 24px;
  line-height: 36px;
  font-weight: 500;
}

.pod-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 12px 24px;
  border-radius: 32px;
  background: #f78811;
  color: #fff;
  font-size: 21px;
  line-height: 21px;
  font-weight: 900;
  text-decoration: none;
}
.pod-cta:hover { background: #e07a0c; }

/* The platform pills are brand-coloured on live and each one sizes to its own
   label, so the row is deliberately not a set of equal-height buttons. */
.pod-listen { display: flex; align-items: flex-start; gap: 24px; width: 100%; }
.pod-listen__label {
  flex: none;
  margin: 0;
  font-size: 24px;
  line-height: 26px;
  font-weight: 600;
  color: #000;
  white-space: nowrap;
}
.pod-listen--stack { flex-direction: column; align-items: center; gap: 8px; }

.pod-pills {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px;
  margin: 0;
  padding: 0;
}
.pod-listen--stack .pod-pills { justify-content: center; }
/* Beside the "Listen on" label the row is narrower than the three pills want,
   and live resolves that by letting the pills shrink until one of the labels
   takes a second line, not by dropping a pill to a second row. */
.pod-listen:not(.pod-listen--stack) .pod-pills { flex-wrap: nowrap; }
.pod-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 14px 24px;
  border-radius: 100px;
  color: #fff;
  font-size: 20px;
  line-height: 20px;
  font-weight: 700;
  text-decoration: none;
}
/* 24px line box around the label, as live: it is what puts a wrapped pill at 76
   rather than 68, and it is the floor the icon has to beat to raise the pill. */
.pod-pill span { line-height: 24px; }
/* Live hangs each icon as an inline image, so its box is the icon plus the
   baseline descender. That 1.8px is why the two 24px icons make a 53.8 pill and
   the shorter YouTube mark leaves its pill at 52. */
.pod-pill img { flex: none; box-sizing: content-box; padding-bottom: 1.8px; }
.pod-pill--spotify { background: #00db4d; }
.pod-pill--apple { background: #c049ec; }
.pod-pill--youtube { background: #f00; }
.pod-pill:hover { filter: brightness(0.93); }

/* Bands ------------------------------------------------------------------- */

.pod-band {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 140px 16px;
}
.pod-band--episodes { gap: 80px; padding-block: 120px; }
.pod-band--cta { gap: 80px; padding-block: 80px; }

.pod-band__title {
  margin: 0 0 24px;
  font-size: 36px;
  line-height: 36px;
  font-weight: 700;
}

/* About the show */

.pod-show { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.pod-show__body { display: flex; flex-direction: column; gap: 25px; flex: 0 1 624px; }
.pod-show__body h2,
.pod-host-band > h2 {
  margin: 0;
  font-size: 56px;
  line-height: 56px;
  font-weight: 700;
}
.pod-show__body p { margin: 0 0 14.4px; font-size: 24px; line-height: 36px; font-weight: 500; }
.pod-show__media { flex: 0 1 710px; height: auto; border-radius: 12px; }

/* About your host */

.pod-host-band { display: flex; flex-direction: column; align-items: center; gap: 64px; }
.pod-host-band > h2 { text-align: center; }

.pod-hostcard__frame { position: relative; width: 100%; padding: 8px; }
.pod-hostcard__wash { position: absolute; top: 0; width: 378px; height: auto; z-index: 0; }
.pod-hostcard__wash--l { left: 0; }
.pod-hostcard__wash--r { right: 0; }

.pod-hostcard {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 32px;
  padding: 32px;
  background: #fff;
  border: 1px solid var(--pod-line);
  border-radius: 12px;
  box-shadow: var(--pod-card-shadow);
}
/* No height: the portrait takes the card's, which is set by the copy beside it.
   Live crops it the same way. */
.pod-hostcard__portrait { flex: 0 0 479px; width: 479px; border-radius: 12px; object-fit: cover; }
.pod-hostcard__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 16px;
}
.pod-hostcard__main { display: flex; flex-direction: column; gap: 32px; }
.pod-hostcard__head { display: flex; align-items: center; gap: 24px; }
.pod-hostcard__who { flex: 1 1 auto; display: flex; align-items: center; gap: 16px; }
.pod-hostcard__who h3 {
  margin: 0;
  font-size: 32px;
  line-height: 32px;
  font-weight: 600;
  color: var(--pod-accent);
}
.pod-hostcard__who a { display: flex; }
.pod-hostcard__org {
  display: inline-flex;
  align-items: center;
  padding: 14px 24px;
  border-radius: 100px;
  background: var(--pod-orange);
  color: #fff;
  font-size: 16px;
  /* 24px line box, so the pill stands 52 and sets the header row's height. */
  line-height: 24px;
  font-weight: 600;
}
.pod-hostcard__prose p { margin: 0 0 14.4px; font-size: 20px; line-height: 30px; font-weight: 400; }
.pod-hostcard__prose strong { font-weight: 700; }

.pod-hostcard__facts { display: flex; align-items: flex-start; gap: 32px; margin: 0; }
.pod-hostcard__facts > div { display: flex; flex-direction: column; gap: 2px; }
.pod-hostcard__facts dd {
  margin: 0;
  font-size: 24px;
  line-height: 26px;
  font-weight: 600;
  color: var(--pod-accent);
}
.pod-hostcard__facts dt { font-size: 16px; line-height: 24px; font-weight: 500; color: var(--pod-muted); }

/* Episodes ---------------------------------------------------------------- */

.pod-ep {
  display: flex;
  gap: 32px;
  padding: 16px;
  background: #fff;
  border: 1px solid var(--pod-line);
  border-radius: 24px;
  box-shadow: var(--pod-card-shadow);
  text-decoration: none;
}
/* Two equal halves, not artwork-plus-remainder: at 1358 the card is 646 + 32 +
   646 inside 16 of padding, and the body column is composed against that 646. */
.pod-ep--featured { align-items: stretch; }
.pod-ep--featured .pod-ep__media,
.pod-ep--featured .pod-ep__body { flex: 1 1 0; min-width: 0; }

.pod-ep__media { position: relative; }
.pod-ep__media img { display: block; width: 100%; height: auto; border-radius: 24px; }
/* The same artwork is cropped to two different shapes: 16:9-ish across the
   featured card, square in the grid. */
.pod-ep--featured .pod-ep__media img { aspect-ratio: 646 / 377; object-fit: cover; }
.pod-ep--card .pod-ep__media img { aspect-ratio: 1; object-fit: cover; }

/* The episode number and the runtime ride on the artwork rather than under it,
   which is what keeps the card's body column free for the guest block. */
.pod-chip {
  position: absolute;
  top: 16px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 12px 24px;
  border-radius: 100px;
  background: var(--pod-chip-blue);
  color: #020713;
  font-size: 16px;
  line-height: 16px;
  font-weight: 600;
}
.pod-chip b { font-weight: 600; }
.pod-chip--tl { left: 16px; }
.pod-chip--tr { right: 16px; }

.pod-ep__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 8px;
}
.pod-ep__main { display: flex; flex-direction: column; gap: 8px; }
.pod-ep__body h3 { margin: 0; font-size: 24px; line-height: 24px; font-weight: 600; color: var(--pod-ink); }
.pod-ep__desc { margin: 0; font-size: 18px; line-height: 27px; font-weight: 400; }

.pod-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  padding: 14px 24px;
  border-radius: 100px;
  background: var(--pod-orange);
  color: #fff;
  font-size: 20px;
  line-height: 24px;
  font-weight: 700;
  text-decoration: none;
}
.pod-btn:hover { background: #e57300; }

.pod-guest {
  display: flex;
  gap: 8px;
  padding: 8px;
  background: var(--pod-chip);
  border-radius: 12px;
}
.pod-guest__rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
}
.pod-guest__rail img { width: 26px; height: 26px; border-radius: 100px; object-fit: cover; }
.pod-guest__line { width: 4px; flex: 1 1 auto; border-radius: 100px; background: var(--pod-rule); }
.pod-guest__body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 12px; }
.pod-guest__id { display: flex; flex-direction: column; }
.pod-guest__row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0; }
.pod-guest__name { font-size: 18px; line-height: 24px; font-weight: 500; }
.pod-guest__tag { font-size: 14px; line-height: 24px; font-weight: 400; color: var(--pod-muted); }
.pod-guest__role { margin: 0; font-size: 14px; line-height: 24px; font-weight: 400; }
.pod-guest__quote { margin: 0; font-size: 16px; line-height: 24px; font-weight: 500; color: var(--pod-ink); }

.pod-eps {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin: 0;
  padding: 0;
}
.pod-eps > li { display: flex; }
.pod-ep--card { flex-direction: column; width: 100%; }
.pod-ep--card .pod-ep__media { flex: none; }
.pod-ep--card .pod-ep__body { flex: none; justify-content: flex-start; }
.pod-ep--card .pod-guest__rail { justify-content: flex-start; }

/* Closing CTA ------------------------------------------------------------- */

.pod-subscribe {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 24px;
  max-width: 800px;
  text-align: center;
}
.pod-subscribe h2 {
  margin: 0;
  font-size: 64px;
  line-height: 64px;
  font-weight: 700;
  color: var(--pod-accent);
}
.pod-subscribe > p { margin: 0 0 14.4px; font-size: 20px; line-height: 30px; font-weight: 400; }

/* Responsive -------------------------------------------------------------- */

@media (max-width: 1024px) {
  .pod-hero { min-height: 0; padding-block: 80px; }
  .pod-hero__inner { flex-direction: column-reverse; min-height: 0; gap: 40px; }
  .pod-hero__media,
  .pod-hero__col { flex: 1 1 auto; width: 100%; }
  .pod-hero h1 { font-size: 48px; line-height: 52px; }
  .pod-band { padding-block: 80px; }
  .pod-show { flex-direction: column; align-items: stretch; }
  .pod-show__body h2,
  .pod-host-band > h2 { font-size: 40px; line-height: 44px; }
  .pod-show__body p { font-size: 20px; line-height: 30px; }
  .pod-hostcard { flex-direction: column; }
  .pod-hostcard__portrait { flex: none; width: 100%; max-width: 479px; }
  .pod-ep--featured { flex-direction: column; }
  .pod-eps { grid-template-columns: repeat(2, 1fr); }
  .pod-subscribe h2 { font-size: 40px; line-height: 44px; }
}

@media (max-width: 700px) {
  .pod-hero h1 { font-size: 36px; line-height: 40px; }
  .pod-hero__sub { font-size: 18px; line-height: 28px; }
  .pod-listen { flex-direction: column; align-items: flex-start; gap: 12px; }
  .pod-hostcard { padding: 20px; }
  .pod-hostcard__head { flex-wrap: wrap; gap: 12px; }
  .pod-eps { grid-template-columns: 1fr; }
  .pod-hostcard__wash { display: none; }
}

/* ==========================================================================
   Print
   ========================================================================== */

@media print {
  .site-header, .site-footer, .consent, .modal, .marquee { display: none; }
  body { background: #fff; color: #000; }
}
