/* ═══════════════════════════════════════════════════════════════════════
   CoreWeave — Home page
   Figma: CW / node 2337:168 "Homepage" (1440 × 8966)

   Loaded after styles.css (tokens, base, nav) and form.css (the form kit,
   whose palette — #0541e9, #02030b, #f5f5f5, 60px fields, 2px radii — is the
   same design system this frame is drawn in, so the contact block is the kit
   rather than a copy of it).

   Everything here is prefixed `hp-`. The two class families it borrows are
   `nav*` (styles.css) and `cw-*` (form.css); nothing else is shared, so the
   old landing at home-old.html keeps working untouched.

   Every size is a clamp() whose upper bound is the Figma value at 1440, so
   the page is the frame at 1440 and scales proportionally below it.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ── palette, off the frame ─────────────────────────────────────── */
  --hp-ink:        #02030b;
  --hp-blue:       #0541e9;
  --hp-blue-light: #5998fc;   /* the blue that reads on ink — the hover colour of the dark bento cards (2026-09-22) */
  --hp-grey:       #f2f2f3;   /* 5691:5901 (2026-09-21) */
  --hp-tile-bg:    #e6e6e7;   /* the bento card's fill — a flat grey, not ink at 10% (2026-09-22, George) */
  --hp-white:      #ffffff;
  --hp-ring:       rgba(2, 3, 11, .30);   /* the circled arrows at rest   */
  --hp-ring-hover: var(--hp-ink);        /* the ring, hovered — black */
  --hp-ring-light: rgba(255, 255, 255, .30);
  --hp-blue-10:    rgba(5, 65, 233, .10); /* the "Ready to scale?" band   */
  --hp-teal:       #c7ffff;               /* every hover surface — see below */
  --hp-teal-line:  rgba(2, 3, 11, .10);   /* the outlined button's hover rule */
  /* ── the type scale — STEPS, not a glide (George, 2026-09-18) ──────
     Every size used to be a clamp on vw, so a heading changed with every
     pixel of a window drag. It now holds its size across a band and changes
     once, at the breakpoint. The numbers are the old clamps read off at the
     foot of each band, so nothing jumps to a size the page never had:

       band      ≤720   ≥721   ≥1024   ≥1440
       hero        40     52      74     104
       stat        44     44      60      84
       h2          28     28      32      44
       h3          24     24      24      32
       h4          20     20      20      24
       lede        16     16      16      18

     The phone block at the foot of this file still has the last word on the
     sizes 4972:6840 draws differently. */
  --hp-fs-hero:  40px;   /* 104 / 98 at 1440 */
  --hp-fs-stat:  44px;   /*  84 / 80 */
  --hp-fs-h2:    28px;   /*  44 / 46 */
  --hp-fs-h3:    24px;   /*  32 / 34 */
  --hp-fs-h4:    20px;   /*  24 / 26 */
  --hp-fs-lede:  16px;   /*  18 / 28 */
  --hp-fs-body:  16px;                          /*  16 / 24  */
  --hp-fs-mono:  14px;                          /*  14 / 20  */

  /* ── spacing ────────────────────────────────────────────────────── */
  /* The page gutter. 5376:7127 (2026-09-18) replaces the centred 1440 column with the frame's own
     ladder — 60 at 1024, 82 at 1440, 160 at 1920 and held there — so the content goes on growing
     with the window instead of standing still in the middle of it. Piecewise, one leg per media
     query below; this base leg is the one under 1440. */
  --hp-gutter: clamp(20px, calc(5.8px + 5.288vw), 60px);
  --hp-g10:    clamp( 8px, 0.695vw, 10px);
  /* Every tile's corner, in one place. The set used to disagree with itself —
     the bento tiles were square, the cases and the logo cards were 2, and the
     pattern and resource cards were 4. Under the cursor it goes to 0: the box
     squares up as it answers, alongside the 1px stroke arriving. */
  --hp-tile-radius: 4px;

  --hp-g20:    clamp(12px, 1.389vw, 20px);
  --hp-g30:    clamp(18px, 2.084vw, 30px);
  --hp-g40:    clamp(24px, 2.778vw, 40px);
  --hp-g60:    clamp(28px, 4.167vw, 60px);
  /* The air a section keeps between its content and its own edge — the
     frame's 60 and 120 taken up 30%. A token of its own rather than a bigger
     --hp-g60, because that one also sets gaps between things *inside*
     components, and those were not what needed the room. */
  --hp-edge:    clamp(36px, 5.417vw,  78px);    /*  60 × 1.3 */
  --hp-edge-lg: clamp(73px, 10.834vw, 156px);   /* 120 × 1.3 */
  /* The one number every section on the page keeps between its boundary and
     its content, at both ends: 124 at 1440, scaling down with the rest. The
     section paddings below are written as this minus whatever the first and
     last blocks already carry of their own, so the air measures the same from
     the seam to the eyebrow and from the last card to the next seam. */
  --hp-section-air: 80px;   /* 5655:4064 / 5691:5901 (2026-09-21): 80 on a tablet, 120 at 1440, 160 past 1920; the phone block keeps 60 */
}
/* 5691:5901 (2026-09-21): the gutter is a step, not a glide — 60 up to 1024, then the frames'
   own 100, 120 and 160. Measured off the new breakpoint frames. */
@media (min-width: 1024px) { :root { --hp-gutter: 100px; } }
@media (min-width: 1440px) { :root { --hp-gutter: 100px; } }   /* 2026-09-22 (George): the 1440 step keeps the tablet's 100; it was 120 */
/* 2026-09-22 (client): the page stops growing at the 1920 layout. Past it the column keeps the
   1600 of content it has at 1920 and only the margins widen, so a 2560 monitor shows the same page
   centred rather than a stretched one. Everything that stands on the gutter — sections, the nav,
   the rails, the banner — follows this one number, and the grounds still run edge to edge. */
@media (min-width: 1920px) { :root { --hp-gutter: max(160px, calc((100vw - 1600px) / 2)); } }
/* the vertical rhythm climbs with the gutter */
@media (min-width: 1440px) { :root { --hp-section-air: 120px; } }
@media (min-width: 1920px) { :root { --hp-section-air: 160px; } }

/* the type scale's steps */
@media (min-width: 721px)  { :root { --hp-fs-hero: 52px; } }
@media (min-width: 1024px) { :root { --hp-fs-hero: 74px; --hp-fs-stat: 60px; --hp-fs-h2: 32px; } }
@media (min-width: 1280px) {   /* the step George asked for (2026-09-18): the old glide's own reading at 1280 */
  :root { --hp-fs-hero: 89px; --hp-fs-stat: 72px; --hp-fs-h2: 38px; --hp-fs-h3: 28px; --hp-fs-h4: 22px; --hp-fs-lede: 17px; }
}
@media (min-width: 1440px) {
  :root {
    --hp-fs-hero: 104px;
    --hp-fs-stat: 84px;
    --hp-fs-h2: 44px;
    --hp-fs-h3: 32px;
    --hp-fs-h4: 24px;
    --hp-fs-lede: 18px;
  }
}
/* the header stands on the same gutter as the copy under it — 5375:2228 / 5375:2466 draw the
   floating bar's edge exactly on the column, not on a 24 inset of its own. Latched to the top the
   bar runs edge to edge and only its contents keep to the column, as before. */
@media (min-width: 721px) {
  .nav { padding-inline: var(--hp-gutter); }
  .nav.is-stuck { padding-inline: 0; }
  .nav.is-stuck .nav__bar { padding-inline: var(--hp-gutter); }   /* --nav-pad is 0 when stuck, so this is the same number, written plainly */
  /* 2026-09-22 (George): only the LATCHED bar aligns its contents to the column. Floating, the
     white plate keeps the kit's own inner inset — 24 on the left, 12 on the right — which is the
     air the frame draws inside the card; taking it out there left the logo sitting on the plate's
     very edge. Latched, the plate is the full width of the window and has no edge of its own, so
     its contents must land on the grid like everything else on the page. That is also why the
     bar's own padding below is written off the gutter alone: with the inset gone there is nothing
     left to subtract. (The 2026-09-21 pass applied this to both states, which is the bug.) */
  .nav.is-stuck .nav__left  { padding-left: 0; }
  .nav.is-stuck .nav__right { padding-right: 0; }
  /* 2026-09-22 (George): and the move between the two places is a move, not a swap. Three paddings
     change at the latch — the header's own, the bar's, and the inner inset of each side — so all
     three are given the same curve the bar already used for its inset, and the logo and the links
     travel from one position to the other instead of appearing at it. `border-radius` is repeated
     here because this declaration replaces the bar's own transition rather than adding to it. */
  .nav__bar { transition: border-radius .32s cubic-bezier(.22, 1, .36, 1),
                          padding .32s cubic-bezier(.22, 1, .36, 1); }
  .nav__left, .nav__right { transition: padding .32s cubic-bezier(.22, 1, .36, 1); }
}
:root {
  /* Extra room under every block's content, on top of its own edge padding.
     In overlap mode the section below rides up over exactly this, so it is
     what the overlap eats instead of content. */
  --hp-tail: 0px;
  /* The CTA banner's height. A token rather than a number in one rule,
     because the seam above the banner has to size its half of the
     photograph by it — see .hp-seam--cta. */
  --hp-cta-h: clamp(420px, 60.42vw, 870px);
  /* The empty photograph the banner keeps under its copy for the seam below
     it to rise through. It was a full wedge deep — 210 — which is what every
     other section reserves, and on a picture that read as the banner having
     lost its footing rather than as air. 120 is the number, flat: the wedge
     only reaches its full depth once the banner's foot has left the top of
     the screen, so the tail does not have to clear it. */
  --hp-cta-tail: var(--hp-section-air);
  /* …and what the section actually measures. Every other section reserves
     its tail through `section:has(+ .hp-seam)`; this one has to name it,
     because the seam ABOVE the banner sizes its half of the photograph by
     the section's real height. */
  --hp-cta-box: calc(var(--hp-cta-h) + var(--hp-cta-tail));
  /* How deep a seam's bend goes, which is also the band's height. Written as
     an angle rather than a length: the vertex sits at 68.8% of the width, so
     the short right-hand run is 31.2% of it, and the rise is that run times
     the tangent of the angle wanted. 25° → 0.312 × tan 25° = 14.55vw, which
     is 210 on a 1440 screen. Stated in vw so the angle holds at any width — a
     fixed length would flatten on a wide screen and steepen on a narrow one.
     (35° was 21.85vw, 45° was 31.2vw, if it wants to go back.)

     On :root rather than on .hp-seam because the CTA banner is sized by it
     too: its photograph runs a wedge's worth above the section so that the
     seam over it can carry the same crop. */
  /* 2026-09-21 (George): the gentler wedge the banner's seam already used is the one he
     picked — "мне нравится именно такой переход" — so it is the page's wedge now, not a
     special case. `--hp-section-air` rather than a vw of its own: 80 / 120 / 160 up the
     ladder, which is what the banner has been drawing at each step. The 14.55vw above
     (25°, 210 on a 1440 screen) is what it replaces. */
  --hp-seam-swing: var(--hp-section-air);
  --hp-g80:    clamp(32px, 5.556vw, 80px);
  --hp-g120:   clamp(56px, 8.334vw, 120px);

  --hp-ease: cubic-bezier(.65, 0, .35, 1);
}

/* ── type primitives ─────────────────────────────────────────────────
   Figma measures every one of these text nodes cap-to-baseline, which is
   why a 44px heading is a 29px box in the frame. The same trim here is
   what makes the section heights land on the frame's numbers. */
.hp-h1, .hp-h2, .hp-h3, .hp-h4, .hp-stat,
.hp-lede, .hp-body, .hp-eyebrow__text {
  text-box: trim-both cap alphabetic;
}

.hp-h1 {
  margin: 0;
  font-weight: 500;
  font-size: var(--hp-fs-hero);
  line-height: .942;                    /* 98 / 104 */
  letter-spacing: -.03em;               /* the frame's -3% */
  color: var(--hp-ink);
}

/* ── the hero's own ladder — 5376:7127 (2026-09-18) ──────────────────────
   The frame draws four sizes, and George asked (2026-09-18) for them to be
   STEPS rather than a glide: the type holds its size across a band and
   changes once, at the breakpoint, instead of creeping with every pixel of
   a drag. So no clamps here — four bands, the frame's numbers in each:

     band     ≥1024  ≥1440  ≥1920  ≥2440
     H1          64     84    104    144
     brand       64     64     64     84
     lede        18     24     24     32
     column     442    736    791   1050

   Under 1024 the tablet keeps the 1024 band's type in a column that gives
   way to the window; the phone has its own sizes further down this file. */
.hp-hero, .pdp-hero {
  --hp-hero-h1: 64px;
  --hp-hero-lede: 18px;
  --hp-hero-col: min(100%, 442px);
  --hp-hero-h1-lh: .97;
  --hp-hero-lede-lh: 1.56;
}
/* 5505:9377 (2026-09-18): the 1024 frame draws the headline at 84 over a 64 brand — the two were
   the same size here, which read as two headlines. The column widens with it so the line still
   breaks where the frame breaks it. */
@media (min-width: 1024px) {
  .hp-hero, .pdp-hero {
    --hp-hero-h1: 64px;            /* 5579:12224 — the 1024 frame's own size */
    --hp-hero-lede: 18px;
    --hp-hero-col: min(100%, 527px);
    --hp-hero-h1-lh: .97;
    --hp-hero-lede-lh: 1.56;
  }
}
@media (min-width: 1280px) {
  .hp-hero, .pdp-hero {
    --hp-hero-h1: 84px;            /* the step between the frames' 64 and 104 */
    --hp-hero-lede: 21px;
    --hp-hero-col: min(100%, 589px);
    --hp-hero-h1-lh: .953;
    --hp-hero-lede-lh: 1.48;
  }
}
@media (min-width: 1440px) {
  .hp-hero, .pdp-hero {
    --hp-hero-h1: 104px;           /* 5579:12185 re-cut this from 84 (2026-09-18) */
    --hp-hero-lede: 24px;
    --hp-hero-col: min(100%, 736px);
    --hp-hero-h1-lh: .942;
    --hp-hero-lede-lh: 1.42;
  }
}
/* 5476:8447 (2026-09-18) re-cut the two widest bands: 124/925 at 1920, 144/1050 at 2440 */
@media (min-width: 1920px) {
  .hp-hero, .pdp-hero {
    --hp-hero-h1: 124px;
    --hp-hero-col: min(100%, 925px);
    --hp-hero-h1-lh: .936;
  }
}
/* ── the 2440 step, parked (2026-09-22) ───────────────────────────────
   The client asked for the page to stop at 1920, so this band is gated rather than deleted: set
   `document.documentElement.dataset.wide = '2440'` (or add `data-wide="2440"` to <html>) and the
   2440 layout is back — this hero ladder and the four-up stories below. Remove the gate and the
   `--hp-gutter` cap above to make it the default again. */
@media (min-width: 2440px) {
  :root[data-wide="2440"] .hp-hero, :root[data-wide="2440"] .pdp-hero {
    --hp-hero-h1: 144px;
    --hp-hero-lede: 32px;
    --hp-hero-col: min(100%, 1050px);
    --hp-hero-h1-lh: .931;
    --hp-hero-lede-lh: 1.32;
  }
}
.hp-hero .hp-h1, .pdp-hero .hp-h1 { font-size: var(--hp-hero-h1); line-height: var(--hp-hero-h1-lh); }
.hp-hero .hp-lede { font-size: var(--hp-hero-lede); line-height: var(--hp-hero-lede-lh); }
/* the brand name reads as the identifier over the statement, not as a second headline: half the
   headline's size, on its own line. It is its own element rather than a <b> inside the H1 because
   the char-flip sizes its clips off the first character it finds — a half-size first word cut the
   tails off every letter of the headline behind it. */

.hp-h1 b { font-weight: inherit; color: var(--hp-blue); }

.hp-h2 {
  margin: 0;
  font-weight: 500;
  font-size: var(--hp-fs-h2);
  line-height: 1.046;                   /* 46 / 44 */
  letter-spacing: -.03em;
  color: var(--hp-ink);
}

.hp-h3 {
  margin: 0;
  font-weight: 500;
  font-size: var(--hp-fs-h3);
  line-height: 1.063;                   /* 34 / 32 */
  letter-spacing: -.03em;
  color: var(--hp-ink);
}

.hp-h4 {
  margin: 0;
  font-weight: 500;
  font-size: var(--hp-fs-h4);
  line-height: 1.084;                   /* 26 / 24 */
  letter-spacing: -.03em;
  color: var(--hp-ink);
}

.hp-stat {
  margin: 0;
  font-weight: 500;
  font-size: var(--hp-fs-stat);
  line-height: .953;                    /* 80 / 84 */
  letter-spacing: -.03em;
  color: var(--hp-ink);
}

.hp-lede {
  margin: 0;
  font-weight: 400;
  font-size: var(--hp-fs-lede);
  line-height: 1.556;                   /* 28 / 18 */
  letter-spacing: -.02em;               /* the frame's -2% */
  color: var(--hp-ink);
}

.hp-body {
  margin: 0;
  font-weight: 400;
  font-size: var(--hp-fs-body);
  line-height: 1.5;                     /* 24 / 16 */
  letter-spacing: -.02em;
  color: var(--hp-ink);
}

/* Every letter-spacing above is the frame's own, which it gives as a percent
   of the em rather than in px: -3% on the headings, -2% on the body copy,
   nothing at all on the mono labels. */

/* ── eyebrow: diamond + mono label ─────────────────────────────────── */
.hp-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--hp-g10);
  margin: 0;
}
.hp-eyebrow__mark { flex: none; display: block; color: var(--hp-ink); }
.hp-eyebrow__text {
  font-family: var(--mono);
  font-weight: 400;
  font-size: var(--hp-fs-mono);
  line-height: 1.429;
  text-transform: uppercase;
  color: var(--hp-ink);
}

/* ── the section header block (Figma component 2147223593) ───────────
   eyebrow, heading, optional body, optional buttons — 40 between each,
   60 above the block and 120 below it. Every section but the hero opens
   with one, which is why it is a component in the frame and a block here. */
.hp-head {
  padding: var(--hp-edge) var(--hp-gutter) var(--hp-edge-lg);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--hp-g40);
}
/* The measure for a section's opening: the frame's 984, a shade over two
   thirds of the 1440 page, so the line under a heading is read rather than
   swept. `.hp-lede` is listed beside `.hp-body` on purpose — the constraint
   used to key on one class, so a paragraph given the other silently lost its
   measure and ran the full width of the gutter box. */
/* 2026-09-21 (George): a heading takes the page's full column; only the prose under it keeps the
   frame's 984 measure. */
.hp-head > .hp-body,
.hp-head > .hp-lede { max-width: 984px; }

/* 5604:13122 (2026-09-18): the resources and stories rails open on their cards the way the bento
   does — 60 under the heading, not the section's larger 156. */
.hp-res > .hp-head, .hp-cases > .hp-head { padding-bottom: 60px; }

/* the bento is the one section the frame closes the header block at 60 */
.hp-head--tight { padding-bottom: var(--hp-g60); }

/* Every section but the hero and Forge opens with 60 of its own above the
   header block's 60 — 120 clear above the eyebrow — and closes with 60. The
   Forge section is the frame's own exception: it gives that block 60 above
   and nothing of its own, so its eyebrow sits 60 from the seam, not 120. */
.hp-bento, .hp-logos, .hp-forge, .hp-cloud, .hp-cases, .hp-res { padding-top: var(--hp-edge); }
/* On the home page, under a seam, the header block's own edge is taken out of
   the section's so the eyebrow lands --hp-section-air below the boundary. */
.hp-seam + :is(.hp-bento, .hp-logos, .hp-forge, .hp-cloud, .hp-cases, .hp-res) {
  padding-top: calc(var(--hp-section-air) - var(--hp-edge));
}

/* ── buttons ─────────────────────────────────────────────────────────
   The shape is form.css's `.cw-btn` — same 44px height, 24px sides, 2px
   radius and 16/24 SemiBold label the frame draws. What is added here is
   the hover state: both variants settle to ink — fill, rule and label
   alike — with a lift under them. Everything that changes is a colour or
   a shadow, so the kit's own `transition` on `.cw-btn` (background, color,
   box-shadow) carries it; nothing here needs a duration or an easing of
   its own. 2026-09-14: replaced micro-interaction 2b, the label rolling
   out through the top and back in through the bottom — asked to be gone
   in favour of this plain colour cross-fade. */
.hp-actions { display: flex; flex-wrap: wrap; gap: var(--hp-g20); }

/* ── CTA type — Figma 2758:5290 "UI component set 1" ──────────────────
   The site's CTAs are **Arpona Sans SemiBold**, mixed case, tracked -2%.
   Novel Mono belongs to the form kit's controls and to nothing else.

   This has to be said here because `.hp-btn` inherited the whole of the
   kit's `.cw-btn` — mono, 400, 12px, uppercase — and only added the roll on
   top. The two classes weigh the same, so nothing but sheet order was
   keeping the kit's type off the page's buttons, and nothing was overriding
   it. Written as `.cw-btn.hp-btn` so it wins on specificity rather than on
   the order the two sheets happen to load in.

   `.hp-btn` is the site button and `.cw-btn` alone is the form kit's, which
   is why `form.html` is untouched by this: it carries no `.hp-btn` at all.
   The one place the two meet is the contact form's submit at the foot of
   both pages — it is a CTA in the frame (`2337:168`, SemiBold 16, same as
   every other), so it takes the sans here too.

   Size is the sheet's **CTA : Medium** — 44px tall, 16px label, 24px of
   side padding, 2px radius. The box already matched; only the label did
   not. The sheet's other two sizes (Huge 60/24, Small 32/14) are not used
   on either page, so they are not written until something needs them. */
.cw-btn.hp-btn {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 16px;
  line-height: 1;
  letter-spacing: -.02em;
  text-transform: none;
}

/* On a phone the longest labels ("Learn more about CoreWeave Forge") are wider
   than the column, and the kit's nowrap pushed the page 19px sideways at 320.
   Let the label wrap inside a button that grows, never past its column. */
@media (max-width: 720px) {
  .cw-btn.hp-btn {
    max-width: 100%;
    height: auto; min-height: var(--btn-h);
    padding-block: 12px;
    white-space: normal; text-align: center; line-height: 1.2;
  }
}

.hp-btn {
  /* Lighter than the kit's 0 4px 8px rgba(0,0,0,.25). Overriding the token on
     the element rather than editing form.css keeps the form kit's own page
     exactly as it was — the kit's rules resolve the variable here. Ink rather
     than black, like every other shadow on this page. */
  --btn-shadow: 0 3px 8px rgba(2, 3, 11, .14);

  /* The kit's own --dur/--ease (.22s, a fairly linear in-out) was built for
     a colour swap alone; three properties settling at once read as a snap,
     worst on the way out, where nothing is left rising to hide the stop.
     Slower and eased all the way to a standstill — no bounce, no residual
     velocity at 100% — so leaving the button feels like it settles rather
     than cuts off. `transform` keeps the kit's own timing: the 1px press on
     `:active` is a poke, not a fade, and should stay quick. */
  /* 2026-09-21 (client): no lift and no curve on these — the fill and the label simply change,
     evenly. Shortened from .2s the same day: linear spends its first fiftieth of a second
     barely moving the colour, which reads as the button waiting before it answers the
     pointer. There is no transition-delay anywhere on these — the lag was the duration. */
  --hp-btn-dur:  .12s;
  --hp-btn-ease: linear;
  transition: background var(--hp-btn-dur) var(--hp-btn-ease),
              color      var(--hp-btn-dur) var(--hp-btn-ease),
              box-shadow var(--hp-btn-dur) var(--hp-btn-ease),
              transform  var(--hp-btn-dur) var(--hp-btn-ease);
}

/* ── hover ─────────────────────────────────────────────────────────
   Both variants settle to the same place: ink fill or ink rule, ink label,
   lifted with `--btn-shadow`. The filled button was already most of the way
   there (Figma 2414:849 darkens it to ink); the outlined one used to keep
   its blue throughout and only lift — now its rule and label go ink too, so
   "the button turns black" reads the same on either. */
.cw-btn--secondary.hp-btn { color: var(--hp-blue); }
@media (any-hover: hover) and (min-width: 721px) {
.cw-btn.hp-btn:hover,
.cw-btn.hp-btn:focus-visible,
.cw-btn.hp-btn:active {
  background: var(--hp-ink);
  color: var(--hp-white);
  box-shadow: none;
}
}
@media (any-hover: hover) and (min-width: 721px) {
.cw-btn--secondary.hp-btn:hover,
.cw-btn--secondary.hp-btn:focus-visible,
.cw-btn--secondary.hp-btn:active {
  /* Named, not omitted — the filled button's hover above ties with this one
     on specificity, so dropping a declaration here does not fall back to
     the rest state, it hands the outlined button the ink fill meant for the
     other variant. Background stays transparent: an ink fill under an ink
     label is unreadable, so only the rule and the label go dark. */
  background: transparent;
  color: var(--hp-ink);
  box-shadow: inset 0 0 0 1.5px var(--hp-ink);   /* the rule only — the lift is gone (2026-09-21) */
}
}

/* ── circled arrow ───────────────────────────────────────────────────
   One thing happens on hover: **the ring goes black.** It holds its diameter
   and the glyph holds still; the colour is the whole state.

   It has been three other things. It began as micro-interaction 1b — the ring
   growing 44 → 52 while the glyph flew out to the right and came back in from
   the left. Then the glyph swap went, then the growth came back at a slighter
   4px, and now the size is out too. What is left is the smallest thing that
   still reads as an answer.

   Ring and clip are separate layers from the glyph so neither drags the
   other, and both are centred by translate rather than `inset:0;margin:auto`
   — margin:auto is over-constrained on a stack this small. */
.hp-arrow {
  --ring:       44px;
  /* 2026-09-21 (client): the round controls take the square button's numbers —
     .12s, linear. The .45s eased fade below was the earlier call; asked to
     match the CTAs, so the ring, the glyph and the play disc all answer the
     pointer at the same speed and with no acceleration. `--hp-ease` is
     overridden here rather than at the root so only the arrow's own layers
     (ring, clip, icon) go linear. */
  --dur:        .12s;
  --hp-ease:    linear;
  --line:       var(--hp-ring);
  --line-hover: var(--hp-ring-hover);

  position: relative;
  flex: none;
  width:  var(--ring);
  height: var(--ring);
  display: grid;
  place-items: center;
  color: var(--hp-ink);
  outline: none;
}
.hp-arrow--sm { --ring: 32px; }

.hp-arrow__ring,
.hp-arrow__clip {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width:  var(--ring);
  height: var(--ring);
  border-radius: 50%;
  transition: border-color var(--dur) var(--hp-ease);
}
.hp-arrow__ring { border: 1.5px solid var(--line); }
.hp-arrow__clip { overflow: hidden; display: grid; place-items: center; }

/* Whatever wraps the arrow is what is hovered — usually the whole card, so
   the rule is keyed off `.hp-hit` and off a plain `a` as well as off the
   arrow itself. All three have to be listed: the colour is the entire state,
   so a missing one is a control that does not answer at all. */
@media (any-hover: hover) and (min-width: 721px) {
.hp-arrow:is(:hover, :focus-visible) .hp-arrow__ring,
a:hover > .hp-arrow .hp-arrow__ring,
.hp-hit:hover .hp-arrow__ring { border-color: var(--line-hover); }
}

.hp-arrow__icon { position: relative; display: grid; place-items: center; transition: scale var(--dur) var(--hp-ease); }
.hp-arrow__icon svg { display: block; }

/* 2026-09-15: under the same hover as the ring, the glyph grows 20%. `scale`, not
   `transform`, so it composes with anything else that ever turns the glyph. */
@media (any-hover: hover) and (min-width: 721px) {
.hp-arrow:is(:hover, :focus-visible) .hp-arrow__icon,
a:hover > .hp-arrow .hp-arrow__icon,
.hp-hit:hover .hp-arrow__icon { scale: 1.3; }
}   /* 30% (2026-09-15) */

/* on a photo the ring and the glyph invert */
.hp-arrow--light { --line: var(--hp-ring-light); --line-hover: #fff; color: #fff; }

/* A play control behaves like every other one. On a photo it is the
   frame's filled white disc; on the page ground it is the same outline as
   every other arrow, which is how the hero draws it. */
.hp-arrow--disc {
  --line: var(--hp-ring-light);
  --line-hover: rgba(255, 255, 255, .6);
  color: var(--hp-ink);
}
.hp-arrow--disc .hp-arrow__clip { background: #fff; }

/* ── carousel ────────────────────────────────────────────────────────
   Three of them on this page and one behaviour between them: a track that
   translates by whole pages, a chevron that advances one, dots that jump,
   and pointer drag. Which slides make a page is `data-group`; `data-clamp`
   stops the last page short of an empty gutter where the design's own
   grouping would leave one. */
.hp-carousel { position: relative; }

/* The rail clips horizontally — that is the whole point of it — but
   `overflow` clips both axes, so a card's shadow would be sheared off flush
   at the rail's top and bottom. Same treatment as the logo marquee: padding
   is the room the shadow needs inside the clip, the negative margin gives
   that room back to the layout, and the cards sit exactly where they sat. */
.hp-carousel__viewport {
  --hp-rail-air: 20px;
  overflow: hidden;
  padding: var(--hp-rail-air) 0;
  margin: calc(var(--hp-rail-air) * -1) 0;
}

.hp-carousel__track {
  display: flex;
  align-items: flex-start;
  gap: var(--hp-g20);
  padding-left: var(--hp-gutter);
  will-change: transform;
  transition: transform .7s cubic-bezier(.22, 1, .36, 1);
  cursor: grab;
  touch-action: pan-y;
}
.hp-carousel.is-dragging .hp-carousel__track { transition: none; cursor: grabbing; }
.hp-carousel__track > * { flex: none; }

/* 3036:341 puts the controls in one row under the rail: the dots at the left
   edge, the arrow at the right, the gutter on both sides. The arrow used to
   float over the cards and cross the rail to turn around; now it holds its
   place and only the glyph turns, which is what the frame draws.

   30 above and 60 below, the frame's numbers, and the same gutter the rail's
   own padding uses so the first dot lines up with the first card. */
.hp-carousel__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--hp-g40);
  padding: var(--hp-g30) var(--hp-gutter) var(--hp-g60);
}

/* The arrow takes the circled ring the rest of the page uses, and its hover
   with it: the ring is a pseudo-element so the glyph is untouched, and like
   every other ring it holds its 44 and goes black on hover. Same duration,
   same easing as `.hp-arrow`. No fill under it any more — standing in a row
   rather than over a card, it has no content to cover. */
.hp-carousel__next {
  --ring: 44px;
  /* 2026-09-21 (client): same as `.hp-arrow` — the CTA's .12s linear. */
  --dur:  .12s;
  --hp-ease: linear;

  position: relative;
  flex: none;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  color: var(--hp-ink);
  transition: transform var(--dur) var(--hp-ease);
}
.hp-carousel__next::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width:  var(--ring);
  height: var(--ring);
  border-radius: 50%;
  box-shadow: inset 0 0 0 1.5px var(--hp-ring);
  transition: box-shadow var(--dur) var(--hp-ease);
}
@media (any-hover: hover) and (min-width: 721px) {
.hp-carousel__next:is(:hover, :focus-visible)::before {
  box-shadow: inset 0 0 0 1.5px var(--hp-ring-hover);
}
}
/* the glyph rides above the ring, and is the only thing the press moves */
.hp-carousel__next svg { position: relative; transition: transform var(--dur) var(--hp-ease), scale var(--dur) var(--hp-ease); }
/* the glyph grows on hover; `scale` so it keeps the is-end flip. 2026-09-22 (client): 1.15, not the
   1.3 the small arrows use — this chevron is tall inside its ring, and at 1.3 its tips came within a
   couple of pixels of the stroke, which read as the glyph outgrowing the button. */
@media (any-hover: hover) and (min-width: 721px) {
.hp-carousel__next:is(:hover, :focus-visible) svg { scale: 1.15; }
}
.hp-carousel__next:active { transform: scale(.94); }
.hp-carousel__next[disabled] { opacity: 0; pointer-events: none; }

/* The side it points is the direction it goes. On the last page there is
   nothing further right to reach, so it turns around and goes back a page —
   `is-end` is set by home.js, which owns the index, and it swaps the label
   from Next to Previous at the same moment. */
.hp-carousel.is-end .hp-carousel__next svg { transform: scaleX(-1); }

.hp-carousel__dots {
  display: flex;
  gap: 8px;
}
.hp-carousel__dots button {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--hp-ring);
  transition: background .25s var(--hp-ease);
}
.hp-carousel__dots button[aria-selected="true"] { background: var(--hp-ink); }

/* ═══════════════════════════════════════════ 1 — hero (2337:169) ═════
   1440 × 800. The lead block sits at the top of the padding box and the
   three cards at the bottom of it; the 101px between them in the frame is
   what is left over, so it is space-between rather than a number.

   The frame draws no header, so it starts its 60 of padding at the top of
   the page. The header this page carries is 108 tall and fixed, so the hero
   grows by exactly that and its 60 starts underneath it — the same trade
   the old landing made. Everything below keeps the frame's height. */
/* 2026-09-15: the hero is one window tall and its copy sits in the window's bottom-left corner, the stone's keys in
   its bottom-right, both 60 above the window's foot on every screen. It used to be at least 908 tall with a
   200 tail, so on a 900 window the copy stood 92 above the foot and on a 720 one the cards were cut off by it.
   The hero experiment in home.html's head overrides the height, the foot padding and the gap for the page as it
   ships (no seam under the hero there); these are the values without it. */
.hp-hero {
  --hp-hero-foot: 60px;   /* 120 for a day, 60 at request (2026-09-15) — the page gutter */
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  padding: calc(80px + var(--hp-g60)) var(--hp-gutter) var(--hp-hero-foot);   /* 80 = the drawn nav bar */
  background: var(--hp-grey);
  display: flex;
  flex-direction: column;
  /* 5505:9277 (2026-09-18): the copy is no longer floated in the middle of the field — it starts a
     fixed step under the header and the hero simply holds it, which is what the frame draws and what
     keeps the step the same on every screen. */
  justify-content: flex-start;
  overflow: hidden;
}
/* copy and announcements are one block, glued at the frame's 80 (89 past 2000) — they move together,
   and hero-fit.js slides the pair as a whole when the window is too short to hold it (see that file). */
.hp-hero__stack {
  display: flex;
  flex-direction: column;
  gap: 80px;
  width: var(--hp-hero-col);
  max-width: 100%;
  translate: 0 var(--hp-hero-shift, 0px);
}
@media (min-width: 2000px) { .hp-hero__stack { gap: 89px; } }
/* The diamond's canvas is the grey field, not a layer on it: the renderer
   clears to `--hp-grey` and paints that colour everywhere the stone's
   silhouette isn't, so the section reads as one flat ground with a stone in
   it. It fades in when the texture and the model have both landed; until
   then — and for good if either fails — the section's own background is all
   there is, which is the hero exactly as the frame draws it.

   No pointer events: the buttons and the three cards are underneath, and the
   lean is read off the section instead (hero-diamond.js). */
.hp-hero__stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity .8s cubic-bezier(.22, 1, .36, 1);
}
.hp-hero__stage.is-ready { opacity: 1; }
.hp-hero__stage canvas { display: block; width: 100%; height: 100%; }

.hp-hero > *:not(.hp-hero__stage) { position: relative; z-index: 1; }

.hp-hero__lead {
  max-width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  --hp-lead-gap: var(--hp-g40);
  gap: var(--hp-lead-gap);
}
/* the supporting statement runs the width of the column, as the frame draws it */
.hp-hero__lead .hp-lede { max-width: 100%; }

/* Six columns wide, three of them filled — the frame's row is 1320 across
   with 203 cards, which is a six-up grid with the back half left empty. */
.hp-hero__cards {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--hp-g20);
}

.hp-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--hp-g20);
  color: inherit;
}
.hp-card .hp-h4 { transition: color .25s var(--hp-ease); }
@media (any-hover: hover) and (min-width: 721px) {
.hp-card:hover .hp-h4 { color: var(--hp-blue); }
}

/* ═════════════════════════════════════════ 2 — bento (2337:206) ═════
   A 2660-wide track of four columns read two at a time: [690 + 610] and
   [610 + 690]. Tiles are 310 tall, the tall columns 640. */
.hp-bento { background: var(--hp-white); padding-bottom: var(--hp-edge); }

/* ── the seams between sections (3072:769) ───────────────────────────
   Where two grounds meet the frame draws not an edge but a line with one
   bend in it, and the bend belongs to the scroll: strongest as the seam
   comes up from the foot of the window, flat by the time it leaves the top.

   Measured rather than guessed. The vertex sits at 68.8% of the width — the
   same place in every reference frame — and the bend reaches 120 at the
   frame's 1440.

   The band paints only the LOWER ground, and takes no room in the flow. It
   used to paint both and sit between the sections, which is what a bend that
   could go either way needed — the upper colour above the line had to be
   able to *cover* the lower one. This bend only ever rises, so nothing has
   to be covered: the triangle is added over the section above and the space
   between the sections is the space it always was.

   The chord sits at the band's bottom edge and a negative top margin puts
   that edge exactly on the section boundary. So the boundary sits where it
   always did, in the middle of the two paddings, and the bend is an
   excursion up from it rather than a slab of colour on one side. */
.hp-seam {
  /* --hp-seam-swing is on :root — see the note there. */
  --hp-seam-below: var(--hp-grey);
  position: relative;
  height: var(--hp-seam-swing);
  /* out of the flow: the bend hangs up over the trailing padding of the
     section above, which is empty by construction */
  margin-top: calc(var(--hp-seam-swing) * -1);
  /* and it is paint, not surface. Hanging over the section above puts it on
     top of whatever is up there, and it was swallowing the clicks meant for
     the carousel controls — measured, the band covered the Next button of all
     three carousels over about 400px of scroll each. Nothing here is ever a
     target, so nothing here should be hit-tested. */
  pointer-events: none;
  /* No overflow here, and that is not tidiness: any overflow other than
     visible makes a box a scroll container, and view() resolves against the
     nearest ancestor one — the band would become its own scrollport, the
     ::after would fill it exactly, and the progress would never leave zero. */
}
.hp-seam::after {
  content: "";
  position: absolute;
  /* A pixel past the bottom on purpose. The band's height is a vw value and
     lands on a fractional pixel; the section below it starts on a rounded
     one, and the sliver between the two let the ground *behind* the band show
     through as a hairline — white against the footer's black, which is where
     it was impossible to miss. The overhang is the same colour as the section
     it overlaps, so it costs nothing and can never be seen. */
  inset: 0 0 -1px;
  background: var(--hp-seam-below);
  /* flat, for anything that cannot read the scroll */
  clip-path: polygon(0 100%, 68.8% 100%, 100% 100%);
}

.hp-seam--grey-white { --hp-seam-below: var(--hp-white); }

/* Every block that a seam follows keeps its own edge padding and gains the
   tail. Three rules rather than one because the three bases differ — the
   logo row was always given more room, and the hero is on the page's own
   g60 rather than the section edge. `:has()` carries the specificity: (0,1,1)
   for the general case, (0,2,0) for the two that must beat it. */
/* The break that fades arrives at its full 25° the moment the boundary
   touches the foot of the screen, so the next section's ground is already
   210 deep into this one before the reader has had a chance to look at what
   is down there. It gets 400 more of tail to land on — one token, and every
   block that a seam follows picks it up through the three rules below. The
   break that grows arrives straight and needs none of it.

   Both numbers came down by 150 together, so the 400 between them — which is
   the whole point of this rule — is untouched. */
/* 2026-09-15: in the window's height, not in pixels. The overlap mode holds the
   section back by 80vh while the next one arrives, and a fixed 400 under it
   meant the next section rode over 262px of content in a 860px window but 628px
   in a 1300px one — the break reached into the previous section only on big
   screens. Tied to the same unit as the lag, what it covers is the same share of
   the window at any size: 60vh with the section's air counted in, which is the
   400 it was on a 14" laptop. */
:root[data-bend="fade"] { --hp-tail: max(0px, calc(60vh - var(--hp-section-air))); }

/* Every block a seam follows closes on --hp-section-air plus the tail. The
   last blocks that bring bottom padding of their own give it up just below,
   so the air is measured to content and not to someone else's padding. */
section:has(+ .hp-seam) { padding-bottom: calc(var(--hp-section-air) + var(--hp-tail)); }
:is(.hp-bento, .hp-res):has(+ .hp-seam) > .hp-carousel:last-child .hp-carousel__foot { padding-bottom: 0; }
.hp-forge:has(+ .hp-seam) > .hp-loop:last-child { padding-bottom: 0; }
.hp-seam--white-black { --hp-seam-below: #000; }

/* ── the seam that carries the banner's photograph ───────────────────
   Every other seam paints a flat colour, because the section under it is a
   flat colour. The CTA banner is not: it is a photograph, and a wedge of
   ink over it left a hard horizontal edge at the section boundary — the
   triangle arrived black and the picture began underneath it.

   So this one clips a picture instead. The clip-path and its animation move
   off the ::after and onto the element itself, because the picture is a
   child and a parent's clip-path clips its descendants; the ::after has
   nothing left to paint and steps aside. The element keeps the band's exact
   box, so `view()` resolves over the same window the other seams use and the
   bend stays in step with them.

   The 1px is the same overhang the ::after carried: the band's height is a
   vw value and lands on a fractional pixel, and without it the sliver
   between the band and the section below shows the forge's white as a
   hairline. Given back as a negative margin, so the layout is unmoved. */
.hp-seam--cta {
  height: calc(var(--hp-seam-swing) + 1px);
  margin-bottom: -1px;
  background: var(--hp-ink);   /* what shows until the photograph is decoded */
  clip-path: polygon(0 100%, 68.8% 100%, 100% 100%);
}
/* The banner lays a 60% black wash over its photograph; the wedge has to lay
   the same one over its half of it, or the sliver of picture in the bend
   arrives at full brightness and reads as a different photograph from the one
   it runs into. The ::after is the wash rather than the flat colour it paints
   on every other seam — it is the last box in the element, so it is over the
   <img> without either of them needing a z-index. */
.hp-seam--cta::after {
  inset: 0;
  background: rgba(0, 0, 0, .6);
}
/* The picture's box is the wedge plus the banner, pinned to the wedge's top —
   exactly the box .hp-cta__shot is given below, so the same `cover` crop
   lands in both and the join is invisible. */
.hp-seam--cta > img {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: calc(100% + var(--hp-cta-box));
  object-fit: cover;
}

@supports (animation-timeline: view()) {
  .hp-seam::after {
    animation: hp-seam-grow linear both;   /* the name is swapped per version */
    animation-timeline: view();
    animation-range: contain 0% exit 100%;
  }
  /* Two versions of the break, chosen by html[data-bend] — see bendmode.js.

     grow: the boundary arrives straight and bends further the higher it
           climbs, reaching 25° as it leaves the top.
     fade: it arrives already at 25° and straightens as it climbs, flat by
           the time it reaches the top.

     Both run over the same window as the slowdown does: `contain 0%` is the
     instant the band's bottom edge — the boundary itself — reaches the foot
     of the screen, and `exit 100%` is the instant it reaches the top. The
     band is shorter than the viewport and the section is taller, and the two
     named ranges mean the same thing for both, which is why one window
     serves both animations. `cover` would have started the bend a band's
     height early, with the wedge poking into view before the boundary. */
  :root[data-bend="grow"] .hp-seam::after { animation-name: hp-seam-grow; }
  :root[data-bend="fade"] .hp-seam::after { animation-name: hp-seam-fade; }

  /* the picture-carrying seam bends on the element, not on the ::after */
  .hp-seam--cta {
    animation: hp-seam-grow linear both;
    animation-timeline: view();
    animation-range: contain 0% exit 100%;
  }
  :root[data-bend="grow"] .hp-seam--cta { animation-name: hp-seam-grow; }
  :root[data-bend="fade"] .hp-seam--cta { animation-name: hp-seam-fade; }

  @keyframes hp-seam-grow {
    from { clip-path: polygon(0 100%, 68.8% 100%, 100% 100%); }
    to   { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing)), 100% 100%); }
  }
  @keyframes hp-seam-fade {
    from { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing)), 100% 100%); }
    to   { clip-path: polygon(0 100%, 68.8% 100%, 100% 100%); }
  }
    50%  { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing) * 0.75),   100% 100%); }
    75%  { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing) * 0.4375), 100% 100%); }
    100% { clip-path: polygon(0 100%, 68.8% 100%, 100% 100%); }
  }
    25%    { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing) * 0.5000), 100% 100%); }
    37.5%  { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing) * 0.8536), 100% 100%); }
    50%    { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing)), 100% 100%); }
    62.5%  { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing) * 0.8536), 100% 100%); }
    75%    { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing) * 0.5000), 100% 100%); }
    87.5%  { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing) * 0.1464), 100% 100%); }
    100%   { clip-path: polygon(0 100%, 68.8% 100%, 100% 100%); }
  }
    50%  { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing) * 0.75),   100% 100%); }
    75%  { clip-path: polygon(0 100%, 68.8% calc(100% - var(--hp-seam-swing) * 0.4375), 100% 100%); }
    100% { clip-path: polygon(0 100%, 68.8% 100%, 100% 100%); }
  }
}

/* ── mode 1: the last block slows down and the next one rides over it ─
   Geometry identical to mode 2 — same band, same bend, same spacing. The
   only difference is that the section above falls behind the scroll while
   the one below is arriving, so the two overlap by however far it has
   fallen.

   The window is the one the brief names: from the moment the next section's
   top crosses the foot of the window to the moment it reaches the top. The
   boundary IS this section's bottom edge, so in its own `exit` phase that is
   the last viewport of scroll — `exit calc(100% - 100vh)` to `exit 100%`,
   which comes out the same length whatever the section's height, where a
   plain `cover` would have scaled with it.

   Across that window the speed falls from 100% to 80%. The distance lost is
   the integral of the shortfall, so the travel is quadratic in progress,
   not linear: at a constant 90% it would slide the whole way at one rate and
   never read as slowing. Sampled at four points rather than approximated
   with a bezier, because a curve that happens to look like p² is a guess and
   these numbers are not.

   z-index rather than document order: a transform promotes its element into
   the positioned layer, where it would otherwise paint over the sections
   that follow it. Everything from the first seam down is lifted with it, and
   among equals the document order decides again. */
:root[data-seam="overlap"] .hp-seam,
:root[data-seam="overlap"] .hp-seam + :is(section, footer) { z-index: 1; }
:root[data-seam="overlap"] .hp-seam + :is(section, footer) { position: relative; }

@supports (animation-timeline: view()) {
  :root[data-seam="overlap"] section:has(+ .hp-seam) {
    /* THE KNOB: the speed the section holds while the one below it is coming
       up the screen. Not a speed it falls to — a speed it is at, from the
       first pixel of the next section to the last.

       The window is one viewport of scroll, so a section moving at S covers
       S viewports while the page covers one, and the difference is what the
       next section rides over: (1 - S) × 100vh. At 0.2 that is 80vh — 720 on
       a 900 window. */
    --hp-seam-slow: .6;
    /* …and never further than 360, whatever the window. The lag is in vh, so
       a tall window fell further behind — 1040px on a 2560 × 1300 screen —
       and the bend below climbed over the last rows of the section. 360 is
       inside the ~390 of clear room every section keeps above the bend's
       apex, so the ride-over still reads and no longer covers text.
       (2026-09-15: was .2 uncapped — 80vh.) */
    --hp-seam-lag: min(calc((1 - var(--hp-seam-slow)) * 100vh), 360px);
    animation: hp-seam-lag linear both;
    animation-timeline: view();
    /* Named ranges only, no calc. `contain 0%` is the instant this section's
       bottom edge — which is the next section's top edge — reaches the foot
       of the window: the first pixel of the next section. `exit 100%` is the
       instant that same edge reaches the top. Between them the edge crosses
       exactly one viewport, whatever the section's height.

       This used to be `exit calc(100% - 100vh) exit 100%`, which is the same
       window arithmetically but asks the engine to resolve a percentage of a
       phase against a viewport unit. If that calc does not resolve, both ends
       collapse onto `exit 100%`, the range has zero length, and the animation
       sits on its `fill` value — translateY(0) — for the whole page. Which is
       indistinguishable from no animation at all. */
    animation-range: contain 0% exit 100%;
  }
  /* Two keyframes and linear, which is what a constant speed is. The ramp
     that used to be here made the section ease into its slowness — most of
     the lag arrived in the last quarter, and for the first half the section
     ran almost level with the page. The brief is the opposite: the moment a
     pixel of the next section shows, this one is already at a fifth speed. */
  @keyframes hp-seam-lag {
    from { transform: translateY(0); }
    to   { transform: translateY(var(--hp-seam-lag)); }
  }

  /* …but only under the break that fades. Grows already spends that window
     doing something: it arrives flat and bends to 25° as it climbs, and the
     angle is the event. Holding the section back at the same time gives the
     eye two things changing at once and the bend stops reading as the cause.
     Fades is the opposite — it arrives at full angle and flattens out, so by
     the middle of the window there is little left to watch, and the lag is
     what keeps the transition alive.

     (0,3,1) over the (0,2,1) that sets it, so this is a clean switch-off, not
     a competing value. The knob above stays exactly as it was. */
  :root[data-seam="overlap"][data-bend="grow"] section:has(+ .hp-seam) {
    animation: none;
    transform: none;
  }
}

/* ── mode 2: nothing covers anything ─────────────────────────────────
   The band takes its height in the flow and no section moves, so the bend
   has room of its own and can never reach content.

   It adds nothing to the page: the band takes no flow height at all, and the
   two neighbours keep every pixel of the padding they always had. The
   boundary stays in the middle of that padding, with as much room above it
   as below, and the bend simply reaches up into the empty half. */

/* A bend tied to the scroll is the kind of movement that reads worst when
   movement is unwelcome, so it flattens rather than slows — and the parallax
   goes with it. */
@media (prefers-reduced-motion: reduce) {
  .hp-seam::after, .hp-seam--cta { animation: none; }
  :root[data-seam="overlap"] section:has(+ .hp-seam) { animation: none; transform: none; }
}

.hp-bento__col { display: flex; flex-direction: column; gap: var(--hp-g20); }

.hp-tile {
  position: relative;
  background: var(--hp-tile-bg);
  padding: var(--hp-g40);
  display: flex;
  flex-direction: column;
  color: inherit;
  overflow: hidden;
}
/* ── the hover on the grey tiles (3203:706) ──────────────────────────
   The frame draws a blue rule round the box, turns the headline the same
   blue and closes the padding. Only the colour survived: the rule is gone at
   request and nothing moves. What is left is the headline going blue and
   rolling — and it is now every grey tile in the section, not just the one
   the frame happened to draw.

   The teal went with it. It was this section's hover and nothing else uses
   `.hp-tile`, so it is gone from the page rather than merely overridden.
   The two photo tiles keep what they had: their picture, and its slow scale.
   Their headings are white on a photograph, where this blue would be
   unreadable. */
.hp-tile { transition: box-shadow .25s var(--hp-ease); }

.hp-tile:not(.hp-tile--photo) :is(.hp-h1, .hp-stat) {
  transition: color .25s var(--e-out);
}
@media (any-hover: hover) and (min-width: 721px) {
.hp-tile:not(.hp-tile--photo):is(:hover, :focus-visible) :is(.hp-h1, .hp-stat) {
  color: var(--hp-blue);
}
}

/* 4658:1596 evens the rail out. The two tall boxes went 640 → 690, the pair
   of columns between them is now two 335s each rather than a 310 over a row
   of two 295s, and the second 96% tile — the one that repeated the first
   one's claim a page later — is gone with that row. So every tile in the
   section is one of two sizes now, and every one of them takes the frame's
   40: the 32 that used to tighten the small cluster had nothing left to
   tighten. */
/* the one tile in the set that had no corner of its own */
.hp-tile { border-radius: var(--hp-tile-radius); }

/* The frame's 690 and 610×335 at 1440, scaled with the window below it — 4978:7970 draws the bento at 1024 as the
   same rail about two-thirds the size, not as fixed 560/480 tiles (2026-09-16). At 1440 these are the frame's
   numbers to the pixel; above it the content column holds them there. */
/* Heights have a floor: a half tile's copy needs ~206, which the plain proportion stops giving below ~880, and a
   tall tile is never shorter than the two halves beside it. */
/* 5655:4064 (2026-09-21): the rail is drawn at 318 x 390 on a tablet and 690 x 640 from 1440, so
   the two frames are the ends of one ramp rather than a single vw fraction. */
/* 2026-09-22 (George): the rail has ONE number. Width and height used to ride separate ramps, and
   once the height was capped the tall tile stopped being square — 690 x 540 at 1440, a shape the
   frame never draws. Now a single `--hp-tile-size` sets both, so whatever the rail grows to it keeps
   the proportions it has at 1024: the tall tile square, the two halves stacked inside that same
   square with the 20 between them, and every tile on one width. */
:root { --hp-tile-size: min(690px, calc(100vw * 318 / 1024)); }
/* 2026-09-22 (George): between 1024 and 1440 the rail does not grow at all — it holds the 1024
   frame's own 318, and 1440 is where the next size starts. It used to ramp across that band, so a
   1280 window showed a size no frame draws. */
@media (min-width: 1024px) { :root { --hp-tile-size: 318px; } }
/* 2026-09-22 (George): 438 on the 1440 step, 640 from 1920 — flat on each, no ramp between them.
   Everything inside the tile reads this one number, so the type, the padding and the body gap come
   down with the box and the square is kept. */
@media (min-width: 1440px) { :root { --hp-tile-size: 438px; } }
@media (min-width: 1920px) { :root { --hp-tile-size: 640px; } }
.hp-tile--tall  { width: var(--hp-tile-size); height: var(--hp-tile-size); justify-content: space-between; }
.hp-tile--half  { width: var(--hp-tile-size); height: calc((var(--hp-tile-size) - var(--hp-g20)) / 2); justify-content: space-between; }
/* …and the type rides the TILE, not the window. The frame draws 30 of heading in a 318 card and 94
   in a 690 one, so the size is read off `--hp-tile-size` on that same line: a rail held at 540 gets
   68, not the 94 of a 690 card, and the copy fits the box it is in. Keyed to the window, as it was,
   the heading kept growing after the box had stopped and the last line fell out of the card. */
@media (min-width: 1024px) {
  /* `:not(.hp-tile--photo)` used to be the whole selector, and the 9/10 tile fell out of it the day
     it became a photograph — it then took the page's own `--hp-fs-stat` (84 at 1440) while its
     neighbours were on the tile's 48.8. Named here so every tile heading is one size. */
  .hp-tile:not(.hp-tile--photo) :is(.hp-h1, .hp-stat),
  .hp-tile--photo.hp-tile--stat :is(.hp-h1, .hp-stat) {
    font-size: calc(32px + (var(--hp-tile-size) - 318px) * .139785);   /* the frames: 32 in a 318 card, 84 in a 690 one */
    line-height: .958;
  }
  .hp-tile .hp-lede, .hp-tile .hp-h4 { font-size: calc(14px + (var(--hp-tile-size) - 318px) * .010753); }   /* 14 → 18 */
  .hp-tile { padding: calc(20px + (var(--hp-tile-size) - 318px) * .053763); }
  .hp-tile .hp-tile__body { gap: calc(10px + (var(--hp-tile-size) - 318px) * .080645); }
}
/* The rail holds 540 through the 1280 and 1440 steps, then grows again to 640 at 1920 — that ladder
   is `--hp-tile-size` above, and both the tiles and their type only read it. The flat 94 that stood
   here (the frame's Display 4 for a 690 card) is now the top of that one formula. */
.hp-tile--end   { justify-content: flex-end; gap: var(--hp-g40); text-align: right; }
.hp-tile--end .hp-arrow { align-self: flex-end; }

.hp-tile__body { display: flex; flex-direction: column; gap: var(--hp-g40); }
.hp-tile--tall .hp-tile__body { max-width: 610px; }

/* the two photo tiles: the picture is the tile, the copy sits on it */
.hp-tile--photo { background: #101014; color: #fff; }

/* 5691:5901 (2026-09-21): the two 690 tiles are drawn in ink with their type reversed out; the
   halves keep the light fill. The arrow is redrawn in white on them — and the hover blue would be
   unreadable there — #0541e9 on #02030b is 1.6:1 — so these take the light blue of the page's own
   patterns instead (2026-09-22, after the client reported the hover as missing on the dark cards). */
.hp-tile--tall:not(.hp-tile--photo) { background: var(--hp-ink); color: var(--hp-white); }
.hp-tile--tall:not(.hp-tile--photo) :is(.hp-h1, .hp-stat, .hp-lede) { color: var(--hp-white); }
.hp-tile--tall:not(.hp-tile--photo) .hp-arrow__ring { border-color: rgba(255, 255, 255, .4); }
.hp-tile--tall:not(.hp-tile--photo) .hp-arrow__icon { color: var(--hp-white); }
@media (any-hover: hover) and (min-width: 721px) {
  .hp-tile--tall:not(.hp-tile--photo):is(:hover, :focus-visible) :is(.hp-h1, .hp-stat) { color: var(--hp-blue-light); }
  .hp-tile--tall:not(.hp-tile--photo):is(:hover, :focus-visible) .hp-arrow__ring { border-color: var(--hp-white); }
}
.hp-tile--photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.2s cubic-bezier(.22, 1, .36, 1);
}
.hp-tile--photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .30);
}
.hp-tile--photo > * { position: relative; z-index: 1; }

/* 5655:3356 (2026-09-21): the ClusterMAX card is a light texture in the new frames — ink type, and
   no veil over the picture. */
.hp-tile--light { background: #f6e6dc; color: var(--hp-ink); }
.hp-tile--light::after { display: none; }
.hp-tile--photo.hp-tile--light .hp-h4 { color: var(--hp-ink); }
.hp-tile--light .hp-arrow__ring { border-color: rgba(2, 3, 11, .35); }
.hp-tile--light .hp-arrow__icon { color: var(--hp-ink); }

/* The lattice canvas (tilemesh.js) stands exactly where the <img> stands.
   Keyed off [data-mesh] — the marker the module looks for — so the same two
   rules serve the bento's photo tiles and the product page's picture without
   either being named. */
[data-mesh] > canvas.hp-mesh {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
/* The photograph steps aside only once a context is live and its first frame
   is drawn — a failed WebGL, or a texture that never loads, leaves the plain
   picture untouched. `visibility` rather than `display`, so the <img> keeps
   its box and goes on doing the layout the canvas is measured against. */
[data-mesh].is-mesh img { visibility: hidden; }

/* On a bento tile the canvas has to stay under the tint, the heading and the
   play button. (0,2,1) beats the `> *` rule above, which would otherwise lift
   it onto the text's layer. */
.hp-tile--photo > canvas.hp-mesh { z-index: 0; }
.hp-tile--photo .hp-h4 { color: #fff; }
@media (any-hover: hover) and (min-width: 721px) {
.hp-tile--photo:hover img { transform: scale(1.04); }
}

/* ═════════════════════════════════════════ 3 — logos (2337:251) ═════
   Two rows of 280 × 160 tiles running in opposite directions. The frame
   draws the second row offset by 700 and bleeding off both edges, which is
   a marquee shown at one moment; here it keeps moving. */
.hp-logos { background: var(--hp-grey); padding-bottom: var(--hp-edge-lg); }

/* The row has to clip horizontally — it holds the seven logos twice and
   slides — but `overflow` clips both axes, so a shadow on a card would be
   cut off flush at the row's top and bottom edges. The padding is the room
   the shadow needs *inside* the clip, and the negative margin gives that
   room back to the layout, so the logos sit exactly where they sat.

   The gap between the two rows then has to be restated: the first row's
   -20 bottom collapses with whatever the second declares, and the two
   paddings add 40 between the logos, so the second needs 17 - 40 to land on
   the 17 the frame draws. It is more negative than -20, which is what makes
   the collapse pick it. */
.hp-marquee {
  /* Was 20: enough for the hover shadow, not for the 44 the cards now rise
     through — the bottom of the arrival was being cut by this very box.
     Padding and the negative margin below cancel, so this costs no layout. */
  --hp-logo-air: 52px;
  overflow: hidden;
  padding: var(--hp-logo-air) 0;
  margin: calc(var(--hp-logo-air) * -1) 0;
}
.hp-marquee + .hp-marquee { margin-top: calc(17px - var(--hp-logo-air) * 2); }
/* the third row is the phone's: two rows of two logos read as a pair, three read as a marquee (2026-09-16) */
.hp-marquee--mob { display: none; }
@media (max-width: 720px) { .hp-marquee--mob { display: block; } }

/* The rows run on a clock of their own — home.js advances this transform at a
   constant speed, so the logos move whether or not the page does, and slow to
   a fifth under the cursor. The row holds the seven logos twice, which is
   what lets that transform wrap seamlessly. */
.hp-marquee__row {
  display: flex;
  gap: var(--hp-g20);
  width: max-content;
  will-change: transform;
}

.hp-logo { pointer-events: none; }   /* the cursor passes over the row, not through it (2026-09-18) */
.hp-logo {
  flex: none;
  width: 280px;
  height: 160px;
  border-radius: var(--hp-tile-radius);
  background: var(--hp-white);
  display: grid;
  place-items: center;
  color: inherit;
  text-decoration: none;
  transition: box-shadow .25s var(--hp-ease);
}
.hp-logo img { max-width: 60%; max-height: 66%; width: auto; height: auto; }

/* The logo cards rise 44 and fade in, stepped along the row. They opened on
   the CardMask for a while; the unfold is the platform cards' alone now, at
   the client's request, and this is the plain arrival underneath it.

   Driven from the marquee rather than from each card, and that is not a
   shortcut. Every row holds its seven logos twice so the scroll transform can
   wrap, and the row is far wider than its clipped box — a per-card observer
   would never see the copies parked outside it, and they would sit at
   opacity 0 forever, appearing as gaps as the row wrapped around. One
   observer on the marquee opens all of them, the off-screen ones included.

   So the marquee itself stops moving: it is the trigger now, not a thing that
   arrives. (0,3,0) on the second selector, over the base [data-reveal]. */
.hp-marquee[data-reveal],
.hp-marquee[data-reveal].is-in {
  opacity: 1;
  transform: none;
  transition: none;
}

.hp-marquee[data-reveal] .hp-logo {
  --hp-logo-in: 0ms;
  opacity: 0;
  transform: translate3d(0, 44px, 0);
  /* The delay rides the three arrival properties only. Put on the shorthand
     it would sit on box-shadow as well, and the seventh card would make you
     wait out its place in the queue before it lifted under the cursor. */
  transition: opacity       1.2s var(--e-reveal) var(--hp-logo-in),
              transform     1.2s var(--e-reveal) var(--hp-logo-in),
              box-shadow    var(--t-tap) var(--e-out),
              border-radius var(--t-tap) var(--e-out);
}
.hp-marquee[data-reveal].is-in .hp-logo {
  opacity: 1;
  transform: none;
}

/* Stepped by position in the row, not by index: 7n+1 and 7n+8 are the same
   logo, so a card and its copy open on the same beat and the wrap stays
   invisible. */
.hp-marquee .hp-logo:nth-child(7n+2) { --hp-logo-in:  60ms; }
.hp-marquee .hp-logo:nth-child(7n+3) { --hp-logo-in: 120ms; }
.hp-marquee .hp-logo:nth-child(7n+4) { --hp-logo-in: 180ms; }
.hp-marquee .hp-logo:nth-child(7n+5) { --hp-logo-in: 240ms; }
.hp-marquee .hp-logo:nth-child(7n+6) { --hp-logo-in: 300ms; }
.hp-marquee .hp-logo:nth-child(7n+7) { --hp-logo-in: 360ms; }

@media (prefers-reduced-motion: reduce) {
  .hp-marquee[data-reveal] .hp-logo,
  .hp-marquee[data-reveal].is-in .hp-logo {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ═════════════════════════════════════════ 4 — forge (4658:1598) ═══
   Head, diagram, loop. The frame used to leave 638 empty under the buttons;
   4658:1598 fills that room with the loop diagram and drops the buttons, so
   the section is drawn all the way down now and the min-height that used to
   hold the empty half open is no longer holding anything open. It stays as a
   floor rather than as a size — the section is taller than it either way. */
/* The only section that had no edge padding of its own, at either end: sized
   to the frame's 1080 and never given what the rest of the page got. Its
   heading started flush against the section's top and the loop closed
   straight onto its end. The top is in the shared rule above now; this is
   the other half. */
.hp-forge {
  background: var(--hp-white);
  min-height: 1080px;
  padding-bottom: var(--hp-edge);
}

/* The diagram panel — the frame's grey, gutter to gutter, which is where the
   rest of the page's blocks begin and end. It ran full bleed for a version
   and came back inside the grid: a band that ignores the gutters every other
   block obeys reads as a mistake rather than as emphasis.

   The panel is sized by the picture rather than by a ratio of its own, so it
   stops growing when the picture does instead of going on getting taller the
   wider the window gets.

   The export is a flat one: the frame carries the diagram as a pasted image,
   not as layers, so there is nothing here to rebuild as vector. It is a
   picture of a diagram and it is marked up as one — the alt text says what it
   shows, because at this size the labels inside it are the content. */
/* 2026-09-16: CW / 5040:7973 is the whole frame — the diagram on white with the FPO mark over it, 1280 × 800 —
   so the grey panel and its padding are gone and the picture stands on the section's own white. */
.hp-forge__diagram {
  margin: 0 var(--hp-gutter) var(--hp-g60);
  border-radius: var(--hp-tile-radius);
  padding: 0;
  background: none;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.hp-forge__diagram img {
  /* the frame's own 1280, never upscaled, and never wider than the column */
  width: min(1280px, 100%);
  height: auto;
}
/* the live AI Loop (see ai-loop.html): at most 745 tall (913.2 wide at its 1140:930), and scaled down with the column below that */
.hp-forge__diagram .ai-loop { display: block; width: min(913.2px, 100%); height: auto; max-height: 745px; }
/* 5716:5904 (2026-09-21): the loop is one flat drawing — the frame places it 607 wide on a
   desktop and lets it take the column on a phone. */
.hp-forge__diagram .lg { display: block; width: min(607px, 100%); height: auto; }
/* 2026-09-23: the same drawing as a Rive file. The artboard is 1440 x 740, so
   the canvas is wider and shallower than the PNG it replaces; it takes the
   column up to the frame's 1280 and holds the artboard's ratio. It is hidden
   until ai-loop-rive.js has drawn a frame, and the picture is hidden only
   then — whichever way it goes there is exactly one drawing on screen. */
.hp-forge__rive { display: none; width: min(1280px, 100%); aspect-ratio: 1440 / 740; }
.hp-forge__diagram.is-rive .lg { display: none; }
.hp-forge__diagram.is-rive .hp-forge__rive { display: block; }
/* a phone crops the artboard's empty sides rather than shrink the whole of it
   (ai-loop-rive.js switches to Fit.Cover under the same 720). 1.6:1 is as far
   as the crop goes: past it the loop's own outer curves start to be cut, and
   the box has to stay wider than the artboard's 1.95 for the crop to keep to
   the left and right, where there is nothing to lose. No max-height for that
   same reason — a cap would push the ratio past 1.95 and start cropping the
   drawing top and bottom instead. */
@media (max-width: 720px) {
  .hp-forge__rive { width: 100%; aspect-ratio: 1.6; }
}
.ai-loop .h { font-family: var(--sans); font-weight: 600; font-size: 22px; }
.ai-loop .cap { font-family: var(--mono); font-size: 13.333px; text-anchor: middle; dominant-baseline: text-before-edge; }
/* captions start 16px below their stem */
/* a node: its resting shadow plus the cube that hops 24px off it */
.ai-loop .cube { transition: transform .44s cubic-bezier(.33, 1, .68, 1); }
.ai-loop .shadow { opacity: 0; transition: opacity .44s ease; }
.ai-loop .node.is-up .cube { transform: translateY(-24px); }
.ai-loop .node.is-up .shadow { opacity: 1; }
.ai-loop .node.is-down .cube { transition-timing-function: cubic-bezier(.45, 0, .55, 1); }

@media (max-width: 720px) {
  .hp-forge__diagram { padding: 0; }
  .hp-forge__diagram img { width: 100%; }
}

/* ══════════════════════════════════════ 5 — CTA banner (4658:1599) ══
   New. The page's one full-bleed block: a photograph under a 60% black wash
   with the display line across the top and a line of copy plus the platform
   button on the foot. 1440 × 870 in the frame, 60 all round.

   The height is a min-height, not a height. The display line is three
   authored lines at 124 and the foot is one line at 18; at 1440 that lands
   inside 870 with room to spare, and on a narrow screen the copy wraps and
   the banner grows rather than clipping. */
/* Three layers, and all three stack forward rather than backward: the ink
   ground is the section's own background, so a photograph on a negative
   z-index would paint *behind* it and never be seen. Photo 0, wash 1,
   copy 2. The ground is still declared — it is what shows while the
   photograph is still on the wire, and it is the colour the seam above
   hands over to. */
.hp-cta {
  position: relative;
  isolation: isolate;
  min-height: var(--hp-cta-h);
  display: flex;
  color: #fff;
  background: var(--hp-ink);
  overflow: hidden;
}
/* Followed by a seam, the banner keeps the frame's 870 of *drawn* height and
   grows --hp-cta-tail of plain photograph under it for the bend to rise
   through — the same bargain `section:has(+ .hp-seam)` strikes for every
   other section, written out here because the padding it applies is the
   wrong depth. (0,2,0), so it beats that rule outright.

   The gutter comes off, because the inner puts it back: what has to measure
   120 under the foot of the copy is the two of them together. */
.hp-cta:has(+ .hp-seam) {
  min-height: var(--hp-cta-box);
  padding-bottom: calc(var(--hp-cta-tail) - min(var(--hp-gutter), 160px));
}
/* …and the bend that rises through that tail is cut to fit it. Everywhere
   else on the page the wedge is deeper than the room the section leaves it
   and crosses a little way into the content — on grey over grey that reads
   as nothing, and it is how every other break on the page already behaves.
   Over this one it would take a bite out of the end of the line, in white
   type on a photograph, where there is no missing it. So this wedge is
   exactly as deep as the empty band it rises through: nothing of the banner
   is ever under it. A gentler angle than the rest, and the one place on the
   page that has earned one. */
.hp-cta + .hp-seam { --hp-seam-swing: var(--hp-cta-tail); }

/* ── the banner's own hover ──────────────────────────────────────────
   2026-09-21 (George): everywhere else on the page a button settles to ink under
   the pointer, which on this section is the photograph's own near-black — the
   button would read as switching off rather than answering. Here it inverts
   instead: white plate, ink label. Written at (0,3,0) — class, class, class —
   so it beats the page-wide hover, which ties at (0,2,0) and is declared later
   in the sheet. The timing is untouched: it inherits the CTA's .12s linear. */
@media (any-hover: hover) and (min-width: 721px) {
.hp-cta .cw-btn.hp-btn:is(:hover, :focus-visible, :active) {
  background: var(--hp-white);
  color: var(--hp-ink);
  box-shadow: none;
}
}
/* The picture runs a wedge's worth above the section and is clipped back to
   it, so the same `cover` crop serves both this box and the seam's — which
   is what lets the wedge above carry the photograph on into the banner
   without a seam you can see. The overflow on .hp-cta does the clipping. */
.hp-cta__shot {
  position: absolute;
  left: 0;
  top: calc(var(--hp-seam-swing) * -1);
  width: 100%;
  height: calc(100% + var(--hp-seam-swing));
  object-fit: cover;
  z-index: 0;
}
/* the frame's rgba(0,0,0,.6) over the photograph — a pseudo-element rather
   than a fourth box in the markup, since it is paint and never a target */
.hp-cta::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .6);
  z-index: 1;
  pointer-events: none;
}
.hp-cta__inner {
  position: relative;
  z-index: 2;
  flex: 1 1 auto;
  min-width: 0;
  /* 2026-09-22: the foot's own air is the gutter, but capped at 160. Past 1920 the gutter is what
     centres the 1600 column — 480 at 2560 — and as a bottom padding it pushed the banner 190 taller
     than the 870 the frame draws. Sides still take the full gutter: that is the column. */
  padding: var(--hp-section-air) var(--hp-gutter) min(var(--hp-gutter), 160px);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--hp-g60);
}
.hp-cta__h {
  font-weight: 500;
  /* 124 / 1440 — Display 2, the biggest type on the page after the hero */
  /* 40 is the floor, not 36 (2026-09-16): at 430 the old floor put this display line at 37 — smaller than the
     hero's own headline and than every other heading on the phone, where 4978:7970 sets it large. 44 was tried
     first and took the line onto five, leaving "AI" alone on one; at 40 it breaks as the frame breaks it. */
  font-size: 62px;
  line-height: .935;             /* 116 / 124 */
  letter-spacing: -.03em;        /* -3.72 / 124 */
  /* The frame sets this in an 837 box and authors two breaks inside it, and
     the second authored line is wider than 837 — so it wraps, and the line
     reads on four. Keeping the measure is what keeps that fourth line: given
     the whole 1320 the middle line would fit and the shape would be three
     lines of a quite different rhythm. */
  max-width: min(837px, 58.2vw);
  color: #fff;
  text-box: trim-both cap alphabetic;
}
/* One row, and it stays one row: the line shrinks and wraps inside itself
   rather than pushing the button onto a row of its own. `wrap` would break
   at the first width where the two no longer fit side by side, and at 1440
   the frame has them fitting with nine pixels to spare. */
.hp-cta__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--hp-g40);
}
/* The line and the button share the row and the frame has them just fitting
   it at 1440. `flex: 1 1 auto` with `min-width: 0` is what makes "just" safe:
   below 1440 the line wraps to a second one rather than shouldering the
   button onto a row of its own. */
.hp-cta__foot .hp-lede { color: #fff; margin: 0; flex: 1 1 auto; min-width: 0; }
.hp-cta__foot .hp-btn { flex: none; }
/* 2026-09-22 (George): below 1200 the call goes UNDER the line rather than beside it. At 1024 the
   line wrapped to two and the button was squeezed against the right edge; from 1200 there is room
   for the two to share a row again (the client's own exception to the frame). */
@media (max-width: 1199px) {
  .hp-cta__foot { align-items: flex-start; flex-direction: column; }
}
/* On a phone the frame breaks the display line into four — Everything / to power / demanding / AI workloads.
   The desktop measure (58.2vw) is what broke it into five below ~430: at 375 it is 218 wide and "AI workloads."
   needs 249 at 40px. Any measure from 249 up to just under 381 ("to power demanding" on one line) gives the
   frame's four lines; 300 holds it on every phone down to a 289 column (2026-09-16). */
@media (max-width: 720px) {
  .hp-cta__h { max-width: 300px; }
}

/* ═════════════════════════════════════════ 6 — cloud (4658:1600) ═══ */
.hp-cloud { background: var(--hp-grey); padding-bottom: var(--hp-edge); }

.hp-grid {
  padding: 0 var(--hp-gutter);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--hp-g40);
}

/* The card from the pattern handoff. Its box: 640×320 at the frame's width.
   The height is a flat 320 rather than the 2/1 aspect it used to be — an
   aspect stays 320 only at 1440 and shrinks with the column below it, and
   the panel beside the copy is a canvas, which has no content of its own to
   hold a height open. Radius 4, the
   panel flush to three edges, the copy in 40 of padding.

   The circled arrow is this page's, not the handoff's — its CTA is a
   different component. The teal hover is gone: the handoff's card does not
   colour on hover, and neither does this one now. */
.hp-svc {
  display: flex;
  align-items: stretch;
  gap: var(--hp-g40);
  height: 320px;
  border-radius: var(--hp-tile-radius);
  overflow: hidden;
  background: var(--hp-white);
  color: inherit;
}

/* the pattern's canvas host. Its colour is set by patterns.js from the card's
   own cfg.bg, so the panel and the field drawn on it can never disagree. */
.hp-svc__panel {
  flex: none;
  width: 223px;
  align-self: stretch;
  position: relative;
  overflow: hidden;
  border-radius: var(--hp-tile-radius);
  /* Its own, because it had none: the panel took the hover corner in a single
     frame while the card around it was still easing, so for a quarter second
     the card was round and the panel inside it square. Same duration and curve
     as the card, so the two edges move as one. */
  transition: border-radius var(--t-tap) var(--e-out);
}
.hp-svc__panel canvas { display: block; width: 100%; height: 100%; }

.hp-svc__text {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--hp-g40) var(--hp-g40) var(--hp-g40) 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--hp-g20);
}
.hp-svc__copy { display: flex; flex-direction: column; gap: var(--hp-g30); }
/* 30, up from the handoff's 22. It rolls on hover — see the attributes in
   home.html; the roll takes the character split and `data-roll-only` keeps
   that split from also bringing a per-character reveal the card never asked
   for, since the card already arrives as a whole. */
/* 30, up from the handoff's 22. The copy is not its 15 either: the page sets
   descriptive copy in Paragraph M — 18/28 — everywhere it appears, and a card
   that reads a size smaller than the rest is a card the eye stops at. */
.hp-svc__title { font-size: 30px; line-height: 1.05; letter-spacing: -.025em; }
/* the one heading on the page that never scaled: 30 held all the way down, against 16px copy in a 390 card.
   On the phone the frame sets it with the other card titles (2026-09-16). */
@media (max-width: 720px) { .hp-svc__title { font-size: 24px; } }
.hp-svc__body  { max-width: 40ch; }

/* CardMask — the handoff's own arrival, back again. The card is an angled
   sliver that opens to the full rectangle while it rises 44 and fades in.
   Four points at both ends of the polygon, which is what lets it interpolate
   at all; a different count between the two keyframes and it would jump.

   (0,2,0), so it beats the base [data-reveal] outright rather than tying with
   it. Everything the card transitions is named here on purpose: `transition`
   is a shorthand and replaces the list, so anything left to a lower rule
   would be silently dropped — the trap at 0.10. There is nothing else to
   name now that the hover fill is gone. */
.hp-svc[data-reveal] {
  opacity: 0;
  transform: translate3d(0, 44px, 0);
  clip-path: polygon(0% 6%, 61% 37%, 100% 94%, 30% 67%);
  transition: opacity       1.2s var(--e-reveal),
              transform     1.2s var(--e-reveal),
              clip-path     1.2s var(--e-reveal),
              box-shadow    var(--t-tap) var(--e-out),
              border-radius var(--t-tap) var(--e-out);
}
/* Six angles, one per card, so the row does not open six times the same way.

   Each is a thin rectangle through the card's centre, rotated, and long
   enough that its ends fall outside the box — the edges do the trimming, so
   each one is a true sliver rather than a shape fitted by eye.

   The vertex ORDER is the whole trick. clip-path interpolates point to point,
   and the open state is (0,0) (100,0) (100,100) (0,100) in that order; a
   sliver whose first vertex starts nowhere near the top-left has to drag
   across the box to reach it, and on the way the quad folds through a
   hairline — rendered, 136° and 162° collapsed to a line at 15% and then
   swelled back out. So each ring is rotated to send every vertex to its
   nearest corner. All six widen monotonically now; checked frame by frame at
   0 / .15 / .35 / .6 / 1.

   (0,3,0), over the (0,2,0) that sets the shared shape above — and placed
   ABOVE the open state on purpose. `.hp-svc[data-reveal].is-in` weighs
   (0,3,0) as well, so nothing but source order separates them. Below it,
   each card kept its own sliver after arriving: the clip never changed,
   there was nothing to transition, and the only thing that ever opened the
   card was the inline release 2.2s later — one frame of sliver, one frame
   of whole card. */
.hp-svc[data-reveal]:nth-of-type(1) { clip-path: polygon(-84% -17%, 190% 105%, 184% 117%, -90% -5%); }  /* 24° */
.hp-svc[data-reveal]:nth-of-type(2) { clip-path: polygon(-48% -64%, -37% -73%, 148% 164%, 137% 173%); }  /* 52° */
.hp-svc[data-reveal]:nth-of-type(3) { clip-path: polygon(12% -95%, 26% -98%, 88% 195%, 74% 198%); }  /* 78° */
.hp-svc[data-reveal]:nth-of-type(4) { clip-path: polygon(90% -95%, 103% -90%, 10% 195%, -3% 190%); }  /* 108° */
.hp-svc[data-reveal]:nth-of-type(5) { clip-path: polygon(-63% 149%, 153% -59%, 163% -49%, -53% 159%); }  /* 136° */
.hp-svc[data-reveal]:nth-of-type(6) { clip-path: polygon(-95% 90%, 190% -3%, 195% 10%, -90% 103%); }  /* 162° */

.hp-svc[data-reveal].is-in {
  opacity: 1;
  transform: none;
  clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}

@media (prefers-reduced-motion: reduce) {
  /* :nth-of-type(n) matches every card and carries the (0,3,0) needed to
     out-weigh the six per-card slivers above; without it they would survive
     into reduced motion and the cards would arrive as splinters. */
  .hp-svc[data-reveal]:nth-of-type(n),
  .hp-svc[data-reveal].is-in {
    opacity: 1;
    transform: none;
    clip-path: none;
    transition: none;
  }
}

/* the "Ready to scale?" band */
.hp-band {
  padding: var(--hp-edge) var(--hp-gutter);
  /* It arrives on the page's block preset (data-fade-up: --t-block on
     --e-out) rather than the shared 18px reveal, which is the page's
     smallest and reads as a twitch under something this wide. 32 puts it
     between the 30 a heading rises and the 44 a pattern card does — a full
     bleed panel should not travel less than the line of type above it. */
  --fade-y: 32px;
}
/* ── 3669:2338 — the platform band ────────────────────────────────────
   The variant that closes the cloud section. It is not the "Ready to scale?"
   panel with a different heading: it has no fill at all, and the panel is
   drawn by two hairlines instead — the node carries only a top and a bottom
   border, so what shows between them is whatever the section is standing on.
   Here that is the section's own grey, which is what the frame does too.

   The button is the page's existing primary, unchanged: measured against the
   node it already matches on every count — #0541e9, white label, radius 2,
   44 tall, 24 of side padding, 16px at 600. Nothing new was written for it.

   Two inheritances from the base band are deliberate. The 60 of padding is
   `--hp-g60`, so the heading sits 60 inside the rules exactly as it does in
   the frame, and the rules themselves land on the page gutter — the heading
   is therefore further in than the section's other content, which is what
   the design draws. And `--fade-y: 32px` still governs its arrival. */
.hp-band--platform .hp-band__inner {
  background: none;
  border-top: 1px solid rgba(2, 3, 11, .2);
  border-bottom: 1px solid rgba(2, 3, 11, .2);
  /* the node is items-start: against a 62px line the 44px button sits high,
     level with the cap line rather than centred on it */
  align-items: flex-start;
}

/* 64/62 with -3% of tracking, which is Headline 2 in the file. Written as a
   clamp on 4.445vw — 64 at the frame's 1440 — because every other size on
   this page is fluid and a fixed 64 would be the one heading that does not
   move with the window. */
.hp-band--platform .hp-band__lead {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 32px;
  line-height: .97;
  letter-spacing: -.03em;
  color: var(--hp-blue);
  margin: 0;
  /* The frame measures this band at 166: 60 of padding, 46 of text, 60 again.
     46 is the cap height, not the line box — Figma trims to it. Untrimmed, the
     62px line box put the band at 184 and the heading sat visibly low between
     its two rules. The same idiom the other pages here use (contents.css,
     code-snippets.css); where it is unsupported the band is 18 taller and
     nothing else changes. */
  text-box: trim-both cap alphabetic;
}

.hp-band__inner {
  background: var(--hp-blue-10);
  padding: var(--hp-g60);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--hp-g40);
}
/* 3028:334. The heading is the blue; the line beside it is the ink, not a
   second blue — that is the whole of what changed when the node was redrawn,
   and the ruled variant the two pages used before it is gone with it. */
.hp-band .hp-h3 { color: var(--hp-blue); }
.hp-band .hp-body { color: var(--hp-ink); text-align: right; }

/* A band closing a section takes over the spacing above it. The loop above
   carries g120 of its own, which the band's 60 would be added to rather than
   replace — 180 of white between the last row and the panel. */
.hp-loop:has(+ .hp-band) { padding-bottom: 0; }

/* ═════════════════════════════════════════ 6 — cases (2337:400) ═════ */
.hp-cases { background: var(--hp-white); padding-bottom: var(--hp-edge); }

/* ═════════════════════════ 7 — customer stories (4658:1601) ════════
   Four cards, two across, 20 apart. Each card is two boxes stacked: a grey
   copy block that takes the height its own copy needs, and a photograph that
   takes the rest — so the pictures in a row start at different heights and
   all four cards still end level. That is the frame's own behaviour and it
   is why the photo is the flexible one rather than the copy.

   The play disc sits on the foot of the picture, inside its 40, with the
   photograph absolutely positioned behind it. */
.hp-stories {
  padding: 0 var(--hp-gutter);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--hp-g20);
}
/* 5655:4064 (2026-09-21): on a tablet a story is one wide card — the still on the left, the copy
   beside it — and they stack one to a row. Two abreast return at 1440. */
@media (min-width: 1024px) and (max-width: 1439px) {
  .hp-stories { grid-template-columns: minmax(0, 1fr); gap: var(--hp-g20); }
  .hp-stories .hp-story { flex-direction: row-reverse; align-items: stretch; }   /* the still leads, the copy follows */
  .hp-stories .hp-story__shot { flex: 0 0 47.6%; aspect-ratio: auto; }
  .hp-stories .hp-story__copy { flex: 1 1 auto; justify-content: flex-start; }
}

/* 5691:5901: the 2440+ column runs the four stories in one row, 40 apart — parked behind the same
   `data-wide="2440"` switch as the hero ladder (2026-09-22). */
@media (min-width: 2440px) {
  :root[data-wide="2440"] .hp-stories { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 40px; }
}

/* 2026-09-16: the photograph is a 16:9 video preview at every width and the copy block takes whatever height the
   row leaves — the opposite way round from before, when the picture stretched and the 740 floor set the card. */
.hp-story {
  display: flex;
  flex-direction: column;
  border-radius: var(--hp-tile-radius);
  overflow: hidden;
  background: var(--hp-grey);
  color: inherit;
}

.hp-story__copy {
  flex: 1 1 auto;
  padding: var(--hp-g40);
  display: flex;
  flex-direction: column;
  gap: var(--hp-g40);
}
/* 130 × 32 in the frame, and the seven wordmarks have seven different
   proportions — so the box is a bound and `contain` decides which of the two
   edges the artwork actually touches. */
.hp-story__logo {
  height: 32px;
  width: auto;
  max-width: 130px;
  object-fit: contain;
  object-position: left center;
  align-self: flex-start;
}
.hp-story__head { display: block; }
.hp-story__body { display: block; }

.hp-story__shot {
  position: relative;
  flex: none;
  aspect-ratio: 16 / 9;
  display: flex;
  align-items: flex-end;
  /* 2026-09-21 (George), corrected the same day: 40 from 1440 up, 30 below it. It was
     --hp-g40 — 24 on a phone gliding to 40 — so the play disc stood at a different inset
     on every screen and matched the frame on none of them. The disc is a fixed 44 at every
     width, so the room around it is fixed too: two numbers, not a glide. The bento keeps
     its own pair (20 at 1024, 40 at 1440) — that ramp is the tile's, not the disc's. */
  padding: 30px;
  background: var(--hp-ink);
  overflow: hidden;
}
.hp-story__shot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.2s cubic-bezier(.22, 1, .36, 1);
}
@media (any-hover: hover) and (min-width: 721px) {
.hp-story:hover .hp-story__shot img { transform: scale(1.04); }
}
/* above the photograph, and the disc is white on it rather than ringed */
.hp-story__shot .hp-arrow { position: relative; z-index: 1; }

/* the headline goes blue under the cursor, like every other card's */
.hp-story__head { transition: color .25s var(--e-out); }
@media (any-hover: hover) and (min-width: 721px) {
.hp-story:is(:hover, :focus-visible) .hp-story__head { color: var(--hp-blue); }
}

@media (max-width: 900px) {
  .hp-stories { grid-template-columns: minmax(0, 1fr); }
  .hp-story { min-height: 0; }
}

/* ═════════════════════════════════ 8 — related resources (4658:1602) ═ */
.hp-res { background: var(--hp-grey); padding-bottom: var(--hp-edge); }
/* the frame centres this row in an 80 band rather than the 30/60 the other
   two carousels use */

/* The rail aligns its slides to the top, which is right for the bento's
   tiles of deliberately different heights and wrong here: these four are one
   row of the same card, and a title that runs to a fourth line should not
   leave its neighbours short. Stretch makes the row as tall as its tallest
   member, and the 569 is the floor the frame draws. */
.hp-res .hp-carousel__track { align-items: stretch; }

.hp-post {
  width: 427px;
  min-height: 569px;
  background: var(--hp-white);
  border-radius: var(--hp-tile-radius);
  padding: var(--hp-g40);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--hp-g40);
  color: inherit;
}

/* ── the title goes blue under the cursor ────────────────────────────
   The hero cards and the bento tiles do this too, so a hovered heading
   reads the same wherever it is. The roll carries the colour for free:
   both copies of every glyph are the same element tree, so they colour
   together and the letter that arrives is already blue.

   Declared where nothing else claims `transition` on these three, so there
   is no list to be replaced — the trap at 0.10 is only a trap when two
   rules want the shorthand on one element. */
.hp-loop__word,
.hp-svc__title,
.hp-post .hp-h4 { transition: color .25s var(--e-out); }
@media (any-hover: hover) and (min-width: 721px) {
.hp-loop__row:is(:hover, :focus-visible) .hp-loop__word,
.hp-svc:is(:hover, :focus-visible) .hp-svc__title,
.hp-post:is(:hover, :focus-visible) .hp-h4 { color: var(--hp-blue); }
}

/* ── the corner, flattening under the cursor ─────────────────────────
   4 at rest, 0 while pointed at. The box squares up as it answers, and it
   does it alongside the stroke, on the same --t-tap, so the edge reads as one
   event rather than two things happening to it.

   The pattern cards' canvas panel is named with them: it is flush to three of
   the card's edges and carries the same corner, so a card that squared up
   while the panel inside it stayed round would show the seam.

   `border-radius` has to be added to each component's own `transition` list
   as well — those are shorthands, and a property they do not name is not
   transitioned at all. Done at the four places that own those lists: the two
   presets in motion.css, .hp-svc[data-reveal], and the marquee's logo rule. */
/* 2026-09-22 (George): the bento tile is out of this list — its corners stay as they are drawn.
   On a 690 card the square-up read as the tile changing shape under the pointer rather than as it
   answering; the other components keep it. */
@media (any-hover: hover) and (min-width: 721px) {
.hp-svc:is(:hover, :focus-visible),
.hp-svc:is(:hover, :focus-visible) .hp-svc__panel,
.hp-post:is(:hover, :focus-visible),
.hp-story:is(:hover, :focus-visible),
.hp-logo:is(:hover, :focus-visible),
.pdp-feat:is(:hover, :focus-visible),
.pdp-path:is(:hover, :focus-visible) { border-radius: 0; }
}

/* ── the 1px stroke, outside the box ─────────────────────────────────
   Outside, so the line sits beyond the card's own edge rather than eating a
   pixel of it — the card gains a hairline rather than losing one.

   That change is what let the overlay go. The stroke used to be a ::before
   above the content, and it had to be: an INSET shadow paints inside the
   card's own box, where anything reaching the edge covers it — the pattern
   cards' canvas is flush to three sides, the photo tiles' image is inset:0,
   and on both the line simply vanished. An outset shadow is drawn entirely
   outside that box, so nothing the card contains can reach it, and it can go
   back on the element itself.

   It has to be on the element for a second reason as well: an outset shadow
   on a CHILD is clipped by the parent's `overflow: hidden`, which four of
   these seven set. An element's own shadow is not — overflow clips what is
   inside a box, not the box's own paint.

   Transparent at rest rather than absent, so what changes on hover is the
   colour and not the presence of a shadow: a box-shadow interpolates to
   another box-shadow, but not to `none`, and that would have snapped.

   An eighth of the ink, not the ink itself: at full strength a 1px line round
   a card reads as a border the card always had rather than as a response to
   the cursor. It follows each card's own corner for free — an outset shadow
   takes the element's border-radius, so it squares up with the card. */
.hp-tile,
.hp-svc,
.hp-post,
.hp-story,
.hp-logo,
.pdp-feat,
.pdp-path { box-shadow: 0 0 0 1px rgba(2, 3, 11, 0); }
@media (any-hover: hover) and (min-width: 721px) {
.hp-tile:is(:hover, :focus-visible),
.hp-svc:is(:hover, :focus-visible),
.hp-post:is(:hover, :focus-visible),
.hp-story:is(:hover, :focus-visible),
.hp-logo:is(:hover, :focus-visible),
.pdp-feat:is(:hover, :focus-visible),
.pdp-path:is(:hover, :focus-visible) { box-shadow: 0 0 0 1px rgba(2, 3, 11, .125); }
}

/* the dark path card keeps its own teal edge and never takes this one */
@media (any-hover: hover) and (min-width: 721px) {
.pdp-path--dark,
.pdp-path--dark:is(:hover, :focus-visible) { box-shadow: none; }
}
@media (any-hover: hover) and (min-width: 721px) {
.pdp-path--dark:is(:hover, :focus-visible) { box-shadow: inset 0 0 0 1.5px var(--hp-teal); }
}

/* …except on the dark path card, where a 12.5%-black line is invisible and
   its own teal edge is the hover. */
/* Kept although the stroke no longer needs it: these cards position things
   inside themselves — the pattern panel, the resource card's photo — and a
   card that stopped being a containing block would drop them onto the page. */
.hp-svc, .hp-post, .hp-story, .hp-logo { position: relative; }

@media (prefers-reduced-motion: reduce) {
  .hp-tile::before, .hp-svc::before, .hp-post::before,
  .hp-story::before, .hp-logo::before { transition: none; }
}

/* Two gaps, not one: 40 between the kind and everything under it, 20 between
   the picture, the tag and the title. That is why the lower three are wrapped
   in a box of their own rather than being three more children of the body. */
.hp-post__body { display: flex; flex-direction: column; gap: var(--hp-g40); }
.hp-post__main { display: flex; flex-direction: column; gap: var(--hp-g20); align-items: flex-start; }
/* 192 : 108 in the frame — 16:9, stated as the ratio so the picture keeps it
   as the card narrows instead of holding a height that no longer fits. */
.hp-post__shot { aspect-ratio: 192 / 108; width: 100%; overflow: hidden; }
.hp-post__shot img { width: 100%; height: 100%; object-fit: cover;
                     transition: transform 1.2s cubic-bezier(.22, 1, .36, 1); }
@media (any-hover: hover) and (min-width: 721px) {
.hp-post:hover .hp-post__shot img { transform: scale(1.05); }
}

/* The tag. Outlined rather than filled, in the same mono face and size the
   eyebrows are set in — it is a label of the same order, and a second type
   style here would read as a second kind of thing. Radius 40 makes it a
   stadium at this height, which is the one round corner on the page and the
   frame's own. */
.hp-post__tag {
  display: inline-flex;
  align-items: center;
  padding: 8px 12px;
  border: 1px solid rgba(2, 3, 11, .2);
  border-radius: 40px;
  font-family: var(--mono);
  font-size: var(--hp-fs-mono);
  line-height: 20px;
  letter-spacing: 0;
  color: var(--hp-ink);
  white-space: nowrap;
}
/* Hover is the blue title and nothing else. There was a teal fill here once,
   then a per-character roll on the heading; both are gone at the client's
   request. The roll took the heading's whole character split with it — that
   split existed only to carry it (data-roll-only meant "no arrival flip, just
   the structure the roll rides"), so with the roll gone the split was
   scaffolding around nothing. These titles are plain text now. */

/* ══════════════════════════════════════ 8 — insights (2773:5455) ═════
   Two columns: the copy in the left 750, the form in the right 690.

   **This section used to be `.cw-hero` from the form kit** and only
   overrode the split. The kit has since dropped that block — the controls
   inside it all survive, but `.cw-hero`, its two halves and `.cw-required`
   do not — so the section was inheriting nothing: no `display`, no columns,
   and the copy and the form collapsed into one stack of overlapping text
   at full page width. The layout is written out here now, under the page's
   own names, so it cannot be pulled out from underneath a second time.

   The numbers are the frame's. The section is 1440 × 873, split 750 / 690,
   with 60 top and bottom; the left column carries a 60 gutter either side,
   which is what leaves its copy 630 wide, and the right column pads 60 off
   the page edge to leave the form the same 630. Both stack their contents
   40 apart, which is the frame's gap throughout — and the same 40 the kit's
   surviving `.cw-form` already puts between the field rows.

   The kit's padding was a fixed 60. The page's gutter is a clamp, and the
   two have to be the same number or the section stops lining up below 1440,
   so the gutter token is used on both sides here. */
.hp-insights {
  /* 5561:9957 (2026-09-18): two halves 20 apart — at 1440 the frame's 630 each — with the copy and
     the form on the left and a photograph the same height on the right. The section's own 120 over
     and under is the frame's; the sides take the page's gutter so it lines up with every other
     section rather than with the artboard it was drawn on. */
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
  align-items: start;
  padding: 120px var(--hp-gutter);
  background: var(--hp-white);
}

.hp-insights__left {
  display: flex;
  flex-direction: column;
  gap: 60px;
  min-width: 0;
  padding-right: 40px;   /* the frame holds the copy 40 off the photograph */
}
.hp-insights__copy {
  display: flex;
  flex-direction: column;
  gap: var(--hp-g40);
  min-width: 0;
}
/* the frame sets this eyebrow at half ink, unlike the ones that open a section */
.hp-insights .hp-eyebrow { color: rgba(2, 3, 11, .5); }
.hp-insights .hp-body { max-width: 590px; }

/* the form is the kit's, at the frame's spacing: the two fields 30 apart, then 20 to the
   checkbox and 60 to the button */
.hp-insights__form { display: flex; flex-direction: column; gap: 20px; }
.hp-insights__form .cw-row { display: flex; gap: 30px; align-items: flex-start; }
.hp-insights__form .cw-row > * { flex: 1 1 0; min-width: 0; }
.hp-insights__form > div:last-of-type { margin-top: 40px; }
.hp-insights__agree .cw-choice__text {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 18px;
  text-transform: uppercase;
}

/* the photograph: the frame's 630 × 631, so a square-ish box that holds its shape as the column
   grows, with the frame's 30% black over it and the line set 40 in from its corner */
.hp-insights__shot {
  position: relative;
  margin: 0;
  aspect-ratio: 630 / 631;
  overflow: hidden;
  border-radius: 0;   /* the frame squares this one off */
  isolation: isolate;
}
.hp-insights__shot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hp-insights__shot::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(2, 3, 11, .3);
}
.hp-insights__cap {
  position: relative;
  z-index: 1;
  margin: 0;
  padding: 40px;
  max-width: 242px;
  font-family: var(--sans);
  font-weight: 500;
  font-size: var(--hp-fs-h4);
  line-height: 1.084;
  letter-spacing: -.03em;
  color: #fff;
}
@supports (text-box: trim-both cap alphabetic) {
  .hp-insights__cap { text-box: trim-both cap alphabetic; }
}

/* ═══════════════════════════════════════════════════ the footer ═════
   footer.html's second exploration, "Hero Wordmark" (stage f7, 1440 × 922).
   There it is an absolutely positioned stage; here the same geometry is a
   grid, so it reflows instead of scaling off the side of a phone. */
.hf {
  background: #000;
  color: #fff;
  /* f7 leaves 51 under the wordmark; the sides take the page's gutter rather
     than the exploration's own 40, so the footer lines up with the sections
     above it instead of with the artboard it was drawn on */
  padding: 40px var(--hp-gutter) 51px;
  font-family: var(--sans);
}
.hf__top {
  display: grid;
  grid-template-columns: 387px 1fr 310px;
  gap: var(--hp-g40);
  padding-bottom: 40px;
}
.hf__news { grid-column: 1; display: flex; flex-direction: column; gap: 24px; }
.hf__sub  { grid-column: 3; align-self: end; display: flex; flex-direction: column; gap: 24px; }

.hf__eyebrow {
  display: flex; align-items: center; gap: 10px; margin: 0;
  font-family: var(--mono); font-size: 12px; line-height: 1;
  text-transform: uppercase; color: #fff;
}
.hf__h {
  margin: 0; font-weight: 600;
  font-size: 30px; line-height: 1.2;
}
/* the break is authored; f7 keeps the two lines whole rather than letting
   the 387 column decide where "from CoreWeave" splits */
@media (min-width: 1025px) { .hf__h { white-space: nowrap; } }
.hf__sub p { margin: 0; font-size: 16px; line-height: 1.2; color: rgba(255, 255, 255, .5); }

.hf__field {
  position: relative;
  height: 56px;
  border-radius: 4px;
  overflow: hidden;
  background: rgba(255, 255, 255, .1);
  transition: background .2s var(--hp-ease);
}
.hf__field:focus-within { background: rgba(255, 255, 255, .16); }
.hf__field input {
  width: 100%; height: 100%; padding: 0 56px 0 16px;
  border: 0; background: transparent; outline: none;
  color: #fff; font-family: inherit; font-size: 16px;
}
.hf__field input::placeholder { color: rgba(255, 255, 255, .5); }
.hf__field button {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; border-radius: 4px;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .1);
  transition: background .2s var(--hp-ease);
}
@media (any-hover: hover) and (min-width: 721px) {
.hf__field button:hover { background: rgba(255, 255, 255, .25); }
}

.hf__rule { display: block; width: 100%; height: 1px; background: rgba(255, 255, 255, .4); }

.hf__cols {
  display: grid;
  /* The frame's widths are ceilings, not floors: at 1440 the tracks are
     exactly 330/350/350, and between there and the 1024 stack they give way
     instead of pushing the fourth column off the right edge. The fourth
     holds its 1440 width as a floor, or it is the one that gives way to 0. */
  grid-template-columns: minmax(0, 330px) minmax(0, 350px) minmax(0, 350px) minmax(170px, 1fr);
  gap: var(--hp-g40);
  padding: 40px 0;
}
.hf__col { display: flex; flex-direction: column; gap: 24px; align-items: flex-start; }
.hf__col h3 {
  margin: 0; font-weight: 500; font-size: 20px; line-height: 1.2; letter-spacing: -.4px;
}
.hf__links { display: flex; flex-direction: column; }
.hf__links a {
  color: rgba(255, 255, 255, .5); font-size: 16px; line-height: 1.8;
  white-space: nowrap; transition: color .2s var(--hp-ease);
}
@media (any-hover: hover) and (min-width: 721px) {
.hf__links a:hover { color: #fff; }
}
.hf__addr { margin: 0; font-size: 14px; line-height: 1.4; color: rgba(255, 255, 255, .5); }
.hf__inq  { margin: 0; font-size: 16px; line-height: 1.6; color: rgba(255, 255, 255, .5); }
.hf__inq a { color: #fff; text-decoration: underline; text-underline-offset: 2px; }
@media (any-hover: hover) and (min-width: 721px) {
.hf__inq a:hover { opacity: .7; }
}
.hf__tag  { margin: 0; font-size: 16px; line-height: 1.4; color: #fff; }
.hf__soc  { display: flex; gap: 8px; }
.hf__soc a { display: block; transition: opacity .2s var(--hp-ease); }
@media (any-hover: hover) and (min-width: 721px) {
.hf__soc a:hover { opacity: .65; }
}

.hf__legal-row {
  display: flex; justify-content: space-between; gap: var(--hp-g40);
  padding: 23px 0 0;
  font-size: 14px; line-height: 1.6;
}
.hf__copy { margin: 0; color: rgba(255, 255, 255, .5); }
.hf__legal { margin: 0; display: flex; gap: 24px; }
.hf__legal a { color: rgba(255, 255, 255, .5); transition: color .2s var(--hp-ease); }
@media (any-hover: hover) and (min-width: 721px) {
.hf__legal a:hover { color: #fff; }
}

.hf__word { width: 100%; height: auto; margin-top: 34px; }

/* The wordmark lights up as the reader arrives at it: black while it is
   coming into view, white by the time the page has nothing left to give.

   Driven by the scroll position itself rather than by script — a view()
   timeline is resolved by the compositor, so this costs nothing on the thread
   that Lenis is already using to move the page.

   The hold to 45% is the point of it. On a straight ramp the mark is already
   a third lit the moment its top edge appears, which is exactly the state
   this is meant to avoid; holding it at zero through the first half means it
   is genuinely black until the reader is nearly at the end.

   Guarded, and the initial opacity lives inside the guard: without support
   there is no timeline to run the animation, and an unguarded `opacity: 0`
   would leave the mark invisible for good. */
@supports (animation-timeline: view()) {
  .hf__word {
    opacity: 0;
    animation: hf-word-in linear both;
    animation-timeline: view();
    /* from its top edge entering the foot of the window to its bottom edge
       clearing it — which, being the last thing on the page, is the bottom */
    animation-range: entry 0% entry 100%;
  }

  @keyframes hf-word-in {
    0%   { opacity: 0; }
    45%  { opacity: 0; }
    100% { opacity: 1; }
  }

  @media (prefers-reduced-motion: reduce) {
    .hf__word { opacity: 1; animation: none; }
  }
}

/* ─ the Forge loop (2929:813) ────────────────────────────────────────
   Five turns of the loop as five rows: the turn's name set large on the
   left, what it does in the middle, the circled arrow at the right edge.

   Ruled rather than boxed. A hairline above every row and one under the
   last is what makes the five read as a single table; give each a border
   all the way round and they become five cards, which is the section next
   door. The rule is the same 12% ink the frame draws. */
.hp-loop { padding: 0 var(--hp-gutter) var(--hp-g120); }

.hp-loop__row {
  display: grid;
  /* Two columns now, not three: 4658:1598 takes the circled arrow off the
     rows and runs the copy to the right-hand rule instead, so the word holds
     the left end of the line and the copy holds the right. */
  grid-template-columns: minmax(0, 31%) minmax(0, 1fr);
  align-items: center;
  gap: var(--hp-g40);
  /* 34 above and below puts the row on the frame's 118, and nothing either
     side: the rule is the row's own border-top and runs its full width, so
     zero padding is what puts the word's first letter and the arrow's right
     edge on the ends of that line. There were 24 here as air for the teal
     fill; the fill is gone, and the air with it. */
  padding: 34px 0;
  border-top: 1px solid rgba(2, 3, 11, .12);
  color: inherit;
}

/* The row carries data-reveal and data-stagger-item, and motion.css sets a
   `transition` on that pair to carry the card reveal. `transition` is a
   shorthand: it replaces the whole property list rather than adding to it,
   and that rule is the more specific of the two — so a transition declared on
   .hp-loop__row alone is silently dropped and the property snaps instead of
   moving. Everything the row transitions has to be declared together, in the
   rule that wins. The first two are the card preset from motion.css; if that
   changes, this follows. (`padding` was the third until the row stopped
   opening on hover; it is a constant now and has no business here.)

   Both attributes are named on purpose. An attribute selector weighs the
   same as a class, so .hp-loop__row[data-stagger-item] only ties with
   [data-reveal][data-stagger-item] — and a tie goes to the sheet loaded
   later, which is motion.css. Three simple selectors against two wins it
   outright. */
.hp-loop__row[data-reveal][data-stagger-item] {
  transition: opacity      var(--t-card) var(--e-out),
              transform    var(--t-card) var(--e-out),
              border-color .25s         var(--e-out);
}

/* Hovering a turn rolls its word and does nothing else. Two states were
   here before and are gone at request: the teal fill, and the row closing on
   itself — the word stepping 16 right while the arrow stepped 16 left.

   The roll that rode the scroll-in flip's per-glyph clips is gone too, at the
   same request. The clips stay — they are the arrival animation's own
   structure and it still uses them; only the hover hook came off, which was
   two attributes in home.html and no rule here.

   So the row's whole response to a cursor is the word going blue and the
   divider under it going black. Nothing moves or reflows. */

.hp-loop__row:last-child { border-bottom: 1px solid rgba(2, 3, 11, .12); }

/* ── the rule under the hovered row goes black ────────────────────────
   The line below a row is the *next* row's border-top, which is why this is
   a sibling selector and not something on the hovered row itself. The last
   row has no next sibling, so it darkens its own bottom border instead.

   Colour only, at the 1px the row already had. It was 2 for a while, and
   that needed a pixel taken back out of the padding to stop the content
   shifting; at one pixel there is nothing to compensate, so the padding
   compensation is gone with it. */
@media (any-hover: hover) and (min-width: 721px) {
.hp-loop__row:is(:hover, :focus-visible) + .hp-loop__row {
  border-top-color: var(--hp-ink);
}
}
@media (any-hover: hover) and (min-width: 721px) {
.hp-loop__row:last-child:is(:hover, :focus-visible) {
  border-bottom-color: var(--hp-ink);
}
}

.hp-loop__word {
  font-weight: 500;
  font-size: 40px;
  line-height: 1.05;
  letter-spacing: -.03em;
  color: var(--hp-ink);
  text-box: trim-both cap alphabetic;
}

/* The copy sits on its own baseline rather than the word's — at 72 against
   16 there is no shared one worth having. */
.hp-loop__copy { max-width: 62ch; margin-left: auto; text-align: right; }
/* 4658:1598 took the circled arrow off these rows at desktop widths and 4978:7970 puts it back on a phone, under
   the copy: there the row is three things stacked, and the arrow is what says the row is a link (2026-09-16). */
.hp-loop__row .hp-arrow { display: none; }
@media (max-width: 720px) {
  /* the row is three things stacked — the word, its copy, the arrow — with the rule between rows carrying the rest */
  .hp-loop__row { row-gap: 16px; padding: 24px 0; }
  .hp-loop__row .hp-arrow { display: block; }
}
/* 2026-09-15: the copy comes up out of its own mask as one line (motion.js
   buildLineFlip), on the word's clock rather than the line flip's own — the
   word beside it flips over DURATION 1256ms on --e-in-out, and a line on
   1512ms --e-out lagged it and read as a second, separate movement. */
.hp-loop__copy.lineflip.is-in .lineflip__track {
  transition: transform var(--t-block) var(--e-in-out);
}

/* 1023, not 1024: 4978:7970 keeps the word and its copy side by side at the tablet width and only stacks them
   below it (2026-09-16) */
@media (max-width: 1023px) {
  .hp-loop__row { grid-template-columns: minmax(0, 1fr); row-gap: 12px; }
  /* the word takes its own line and the copy runs under it, left-aligned:
     ragged-left copy under a word that starts at the margin reads as two
     things that have come apart. */
  .hp-loop__copy { text-align: left; margin-left: 0; }
}

/* ═══════════════════════════════════════════════════ responsive ═════
   One 1440 layout that scales, with four places where it has to change
   shape rather than shrink. */

@media (max-width: 1180px) {
  .hp-hero__cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }

}

@media (max-width: 1023px) {   /* 5655:4064 keeps two service cards abreast at 1024 (2026-09-21) */
  .hp-forge { min-height: 0; padding-bottom: var(--hp-g120); }
  .hp-grid { grid-template-columns: minmax(0, 1fr); }
  .hp-svc__panel { width: 200px; }
  .hp-band__inner { flex-direction: column; align-items: flex-start; }
  .hp-band .hp-body { text-align: left; }

  .hf__top { grid-template-columns: minmax(0, 1fr); }
  .hf__sub { grid-column: 1; align-self: start; }
  .hf__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hf__links a { white-space: normal; }
}

/* the contact block keeps the frame's two columns — heading left, fields right — down to the tablet width, and
   stacks only below it (4978:7970, 2026-09-16) */
@media (max-width: 1023px) {
  /* one column: the copy and the form, then the photograph under them */
  .hp-insights { grid-template-columns: minmax(0, 1fr); gap: 60px; }
  .hp-insights__left { padding-right: 0; }
}

@media (max-width: 760px) {
  .hp-hero__cards { grid-template-columns: minmax(0, 1fr); gap: var(--hp-g30); }
  .hp-svc { flex-direction: column; height: auto; }
  .hp-svc__panel { width: 100%; min-height: 200px; }
  .hp-svc__text { padding: var(--hp-g30); }
  /* on a phone the tiles are the column's width and as tall as their copy needs */
  .hp-tile--tall  { width: 82vw; height: auto; min-height: calc(480px + var(--hp-g20)); }   /* = the two 240 halves and the gap between them */
  .hp-tile--half  { width: 82vw; height: auto; min-height: 240px; }
  .hp-post { width: 82vw; }
  .hf__cols { grid-template-columns: minmax(0, 1fr); }
  .hf__legal-row { flex-direction: column; gap: 16px; }
  .hf__legal { flex-wrap: wrap; gap: 12px 24px; }
}

/* ══════════════════════════════════════════════ wide screens ═════
   Past 1440 the content holds a 1440 column in the middle of the window and
   the extra width is white space either side (2026-09-15) — the gutter token
   at the top of this file does that. It replaces a day of the page changing
   shape up there instead (two-across headers, a type step at 1920, extra grid
   columns at 2200 and 2520), all of which is gone: above 1440 the page is the
   1440 frame. What is left here is what still holds in a centred column.

   Everything is behind a min-width of 1441, so 1440 is still the frame to
   the pixel. */

@media (min-width: 1441px) {

  /* The contact block sizes itself off the window rather than off the gutter: its two columns are fractions of
     the section, so the heading's half lost the whole margin and was squeezed to a word a line. The section keeps
     its full-width ground and pads in to the column.

     The rails are NOT held to the column: 4978:7970 runs them from the column's left edge off the right edge of
     the window, exactly as they run at 1440. (They were clipped to the column for a day; the frame says otherwise.) */

  /* The seam's depth is no longer a vw, so there is nothing here to cap: the wedge
     takes `--hp-section-air` (80 / 120 / 160) and already stops growing on its own.
     What stood here held the 1440 depth — 210 — against the old 14.55vw, which on a
     wide screen kept deepening and cut into the loop's last rows. (2026-09-21) */

  /* The rails measure their cards against their own width, not 100vw, which
     counts a Windows scrollbar and would push the last card past the edge. */
  .hp-carousel__viewport { container-type: inline-size; }
  /* Gone rather than hidden: an invisible row of controls still kept its 74
     under the cards, and the section's air measured 274 instead of 200. */
  .hp-carousel.is-static .hp-carousel__foot { display: none; }
  .hp-carousel.is-static .hp-carousel__track { cursor: auto; }

  /* Four resource cards: 427 each until four of them and the gutters fit the
     rail (1828), and from there they share the row. Continuous — at the
     width where they first fit they are still 427. */
  .hp-post {
    width: max(427px, calc((100cqw - 2 * var(--hp-gutter) - 3 * var(--hp-g20)) / 4));
  }
}

/* ═══════════════════════════════════════ phone — CW / 4972:6840 ═════
   The mobile frame (420 wide) is its own grid, not the desktop page shrunk:
   30 at the sides, 60 above and below every section, 30 inside a header, 60
   from a header to what follows it, cards padded 30 and set 20–30 apart. The
   tokens take those numbers here, and the blocks whose sizes the frame pins
   down get them outright. Last in the sheet on purpose, so it wins over the
   fluid rules above (2026-09-16). */
@media (max-width: 720px) {
  :root {
    --hp-gutter: 30px;
    --hp-section-air: 60px;
    --hp-edge: 0px;           /* the header block sits at the section's 60 … */
    --hp-edge-lg: 60px;       /* … and keeps 60 to what follows it */
    --hp-g20: 20px;
    --hp-g30: 30px;
    --hp-g40: 30px;
    --hp-g60: 60px;
    --hp-fs-h2: 32px;         /* Headline 4, 32/34 */
    --hp-fs-h4: 18px;         /* Headline 6, 18/20 */
    --hp-fs-lede: 18px;       /* Paragraph M, 18/28 */
    --hp-cta-h: 630px;
  }
  .hp-h2 { line-height: 1.0625; }
  .hp-h4 { line-height: 1.111; }

  /* hero — Headline 3, 44/46 */
  .hp-hero .hp-h1 { font-size: 44px; line-height: 1.045; }

  /* bento — 360 × 480, and two 360 × 230 in the column beside it, 20 apart */
  .hp-tile--tall { width: calc(100vw - 60px); height: 480px; min-height: 0; }
  .hp-tile--half { width: calc(100vw - 60px); height: 230px; min-height: 0; }
  /* the dots keep 30 under them before the section's own 60 */
  :is(.hp-bento, .hp-res):has(+ .hp-seam) > .hp-carousel:last-child .hp-carousel__foot,
  .hp-carousel__foot { padding-bottom: 30px; }

  /* logos — 175 × 100 cards, 20 apart across and down */
  .hp-logo { width: 175px; height: 100px; }
  .hp-marquee + .hp-marquee { margin-top: calc(20px - var(--hp-logo-air) * 2); }

  /* forge — the diagram runs edge to edge and the first rule sits right under it */
  /* 5716:5939: the drawing keeps the page's gutter on a phone (353 of the frame's 420) */
  .hp-forge__diagram { margin: 0 var(--hp-gutter); }
  .hp-loop__row { row-gap: 30px; padding: 30px 0; }
  .hp-loop__word { font-size: 44px; line-height: 1.045; }
  .hp-loop__copy { font-size: 16px; line-height: 1.5; }

  /* banner — 630 tall, 70 top and bottom, 44/46, 40 between the line and the button */
  .hp-cta__inner { padding: 70px var(--hp-gutter) 40px; }
  .hp-cta__h { font-size: 44px; line-height: 1.045; max-width: none; }
  .hp-cta__foot { gap: 40px; }

  /* cloud — the pattern is 180 tall over the copy */
  .hp-svc__panel { height: 180px; min-height: 180px; }

  /* the arrows inside cards are the 32 size on a phone; the loop's rows keep 44 */
  :is(.hp-svc, .hp-post, .hp-tile) .hp-arrow { --ring: 32px; width: 32px; height: 32px; }

  /* customer stories — 30 between cards (the 16:9 preview is 360 × 203 here, the frame's 356 × 200) */
  .hp-stories { gap: 30px; }

  /* resources — 360 × 480 cards */
  .hp-post { width: calc(100vw - 60px); min-height: 480px; }

  /* the frame's rails have dots and no arrow under them */
  .hp-carousel__next { display: none; }

  /* the desktop's authored line breaks are not the phone's: the frame lets these headings wrap on their own */
  .hp-head > .hp-h2 br, .hp-insights .hp-h2 br, .hp-svc__title br { display: none; }

  /* the banner is 630 in all, the photograph under the seam included */
  :root { --hp-cta-h: calc(630px - var(--hp-cta-tail)); }

  /* the form's fields stand 24 apart, one under the other, each label 6 over its field, and the
     "*Required" note 24 over the first (4972:6849) */
  /* phone (4972:6849 and the rules the other sections settled on): the section's own 60 over and
     under, the two fields stacked 24 apart, and the photograph at 4:3 under the form */
  .hp-insights { padding: var(--hp-section-air) var(--hp-gutter); gap: 40px; }
  .hp-insights__left { gap: 40px; }
  .hp-insights__form { gap: 24px; }
  .hp-insights__form .cw-row { flex-direction: column; gap: 24px; }
  .hp-insights__form .cw-row > * { width: 100%; }
  .hp-insights__form > div:last-of-type { margin-top: 6px; }
  .hp-insights .cw-label { margin-bottom: 12px; }
  .hp-insights__shot { aspect-ratio: 4 / 3; }
  .hp-insights__cap { padding: 30px; max-width: 220px; }
  .hp-hero { padding-top: 110px; }   /* lead 30 under the 80 nav bar */
  .hp-svc { gap: 0; height: 460px; } /* panel → 30 → title; fixed 460 */
}

@media (prefers-reduced-motion: reduce) {
  .hp-carousel__track { transition: none; }
  .hp-arrow__ring, .hp-arrow__clip, .hp-arrow__icon, .hp-carousel__next svg { transition: none; }
  .hp-tile--photo img, .hp-post__shot img { transition: none; }
}

/* ── the sizes that live in their own rules take the same steps (2026-09-18) ──── */
@media (min-width: 1024px) { .hp-cta__h { font-size: 64px; } }    /* the 1024 frame */
@media (min-width: 1440px) { .hp-cta__h { font-size: 124px; } }   /* Display 2, 124 at 1440 */
@media (min-width: 1024px) { .hp-band--platform .hp-band__lead { font-size: 46px; } }
@media (min-width: 1280px) { .hp-band--platform .hp-band__lead { font-size: 55px; } }
@media (min-width: 1440px) { .hp-band--platform .hp-band__lead { font-size: 64px; } }   /* the platform band's lead */
@media (min-width: 1024px) { .hf__h { font-size: 40px; } }
@media (min-width: 1280px) { .hf__h { font-size: 48px; } }
@media (min-width: 1440px) { .hf__h { font-size: 56px; } }   /* the footer's headline */
@media (min-width: 1024px) { .hp-loop__word { font-size: 64px; } }   /* the 1024 frame */
@media (min-width: 1440px) { .hp-loop__word { font-size: 84px; } }   /* …and the 1440 one */


/* ── the round controls' room, 2026-09-21 (George) ────────────────────
   One inset for every disc and ring on the page that stands inside a box:
   40 from 1440 up, 30 below it — and at 1024 that 30 is what separates these
   from the bento, which is on 20 there by the frame's own ramp. The post card
   carried --hp-g40 (28 at 1024, 36 at 1280), the story's photograph 30 flat;
   both read as a different number in every window. */
@media (min-width: 1440px) {
  .hp-story__shot { padding: 40px; }
  .hp-post { padding: 40px; }
}
@media (max-width: 1439px) {
  .hp-post { padding: 30px; }
}



/* 2026-09-22 (George): the Resources card hugs its content. It was 569 of minimum, then a flat 570,
   and both left a field of empty white under the title with the arrow pushed to the foot — the card
   is now exactly as tall as the eyebrow, the picture, the tag and the title, with 40 to the arrow.
   `align-items: flex-start` on the rail is the other half: stretched, the cards would take the
   tallest one's height back. The phone keeps the fixed card — there the rail is one card per page
   and a ragged foot reads as a mistake rather than as a measure. */
@media (min-width: 721px) {
  /* 2026-09-22 (George): the card still hugs — 40 from the title to the arrow, no field of white
     under it — but the row levels up: every card takes the height of the tallest, so a two-line
     title does not leave its neighbours short. `stretch` on the rail is what does that; the card's
     own `flex-start` keeps the content at the top rather than spreading it. */
  .hp-post { min-height: 0; height: auto; gap: 40px; justify-content: flex-start; }
  .hp-res .hp-carousel__track { align-items: stretch; }
}
@media (max-width: 720px) {
  .hp-post { min-height: 570px; height: 570px; }
}


/* ── the photo tiles' caption (5655:3437 and its siblings, 2026-09-22) ──
   The frames draw this on a ladder of its own — 16/20 at 1024, 24/26 at 1440, 32/34 from 1920, and
   24/26 on a phone — not on the tile's own ramp, which is what it had been riding since the tile
   geometry was unified and which made it a different size on every window. */
.hp-tile--photo .hp-h4 { font-size: 24px; line-height: 26px; }
@media (min-width: 721px) and (max-width: 1439px) {
  .hp-tile--photo .hp-h4 { font-size: 16px; line-height: 20px; }
}
@media (min-width: 1920px) {
  .hp-tile--photo .hp-h4 { font-size: 32px; line-height: 34px; }
}


/* ── the logo row's head (2026-09-22, George) ──────────────────────────
   120 from the heading to the first card on every step above the tablet, 40 on the 1024 frame
   itself. It was riding `--hp-edge-lg` (111 at 1024, 156 from 1440), which is the page's own
   section rhythm and far more air than this row wants under its one line of heading. */
@media (min-width: 1024px) { .hp-logos > .hp-head { padding-bottom: 40px; } }
@media (min-width: 1440px) { .hp-logos > .hp-head { padding-bottom: 120px; } }


/* ── the section heads over the card grids (2026-09-22, George) ────────
   Cloud services (head → cards), Customer stories (button → cards) and Resources (heading → cards):
   120 from 1440 up, 60 on the 1024 frame. The logo row above takes the same shape on its own 40. */
@media (min-width: 1024px) {
  .hp-cloud > .hp-head, .hp-cases > .hp-head, .hp-res > .hp-head { padding-bottom: 60px; }
}
@media (min-width: 1440px) {
  .hp-cloud > .hp-head, .hp-cases > .hp-head, .hp-res > .hp-head { padding-bottom: 120px; }
}


/* ── the service card's drawn panel at the 1024 step (2026-09-22, George) ──
   120 of pattern, the rest of the card is the copy's. It was 223 (200 below 1024), which on a
   tablet card took a third of the width and left the title breaking after every second word. */
@media (min-width: 1024px) and (max-width: 1439px) {
  .hp-svc__panel { width: 120px; }
  .hp-svc { height: 256px; }   /* the 1024 frame's card (2026-09-22, George); 320 from 1440 */
}


/* ── the round controls on the 1024 step (2026-09-22, George) ─────────
   The 1024 frame draws them at 32, not 44: the ring, the carousel's Next and the play disc alike.
   From 1440 they are back on the page's 44. */
@media (min-width: 1024px) and (max-width: 1439px) {
  .hp-arrow { --ring: 32px; }
  .hp-carousel__next { --ring: 32px; width: 32px; height: 32px; }
}


/* ── the 1024 frame's type, held to 1440 (2026-09-22, George) ─────────
   Every size below was read off the frames themselves — 5655:4064 for 1024 and 5880:7640 for 1440 —
   and the page is now on those two sets with nothing in between. The 1280 step added on 2026-09-18
   is gone with it: the design draws no 1280 frame, and holding the 1024 sizes to 1440 is the same
   call that was made for the bento. */
@media (min-width: 1024px) and (max-width: 1439px) {
  :root {
    --hp-fs-hero: 56px;
    --hp-fs-stat: 32px;
    --hp-fs-h2:   24px;
    --hp-fs-h3:   18px;   /* service titles, story heads */
    --hp-fs-h4:   16px;   /* the hero's announcements */
    --hp-fs-lede: 16px;
  }
  .hp-hero, .pdp-hero { --hp-hero-h1: 56px; --hp-hero-lede: 16px; }
  .hp-eyebrow__text { font-size: 12px; }                 /* 12 here, 14 from 1440 */
  .hp-post .hp-h4 { font-size: 18px; }                   /* the resource card is 18, not the 16 of an announcement */
  .hp-svc__title { font-size: 18px; }
  .hp-svc__body, .hp-story__body, .hp-insights .hp-body { font-size: 14px; }
}
@media (min-width: 1440px) {
  .hp-svc__title { font-size: 32px; }                    /* was 30 */
  .hp-cta__foot .hp-lede { font-size: 16px; }            /* the banner's line is 16, not the page lede's 18 */
}


/* ── the 9/10 tile: a photograph under a stat (2026-09-22, George) ─────
   It is a photo tile like its neighbours — same diamond lattice on the pointer (`data-mesh`), same
   dark scrim — but it carries a stat and its lede rather than a single line, so the copy block that
   `.hp-tile--photo` leaves alone has to be lifted above the picture and reversed out. */
.hp-tile--photo.hp-tile--stat .hp-tile__body { position: relative; z-index: 1; }
.hp-tile--photo.hp-tile--stat :is(.hp-stat, .hp-lede) { color: var(--hp-white); }
.hp-tile--photo.hp-tile--stat .hp-arrow__ring { border-color: rgba(255, 255, 255, .5); }
.hp-tile--photo.hp-tile--stat .hp-arrow__icon { color: var(--hp-white); }
@media (any-hover: hover) and (min-width: 721px) {
  .hp-tile--photo.hp-tile--stat:is(:hover, :focus-visible) :is(.hp-stat, .hp-lede) { color: var(--hp-white); }
  .hp-tile--photo.hp-tile--stat:is(:hover, :focus-visible) .hp-arrow__ring { border-color: var(--hp-white); }
}


/* ── the 1024 frame, measured off 6192:8715 (2026-09-22, George) ──────
   The sizes the frame draws on this step, which the page was carrying at the 1440 scale. */
.hp-br-sm { display: none; }                      /* the headline's third line, 1024 only */
@media (min-width: 1024px) and (max-width: 1439px) {
  /* hero: 64 over three lines, and the lede on its own 442 column */
  .hp-hero, .pdp-hero { --hp-hero-h1: 64px; }
  .hp-br-sm { display: inline; }
  .hp-hero .hp-lede { max-width: 442px; }

  /* who we power: the frame's card is 191 × 109, the rows 10 apart */
  .hp-logo { width: 191px; height: 109px; }
  .hp-marquee__row { gap: 10px; }
  .hp-marquee + .hp-marquee { margin-top: calc(10px - var(--hp-logo-air) * 2); }

  /* resources: 261 wide (the card keeps hugging, which lands it on the frame's 328) */
  .hp-post { width: 261px; }

  /* newsletter: 467 of copy beside a 317 picture, 40 between them, 80 of air top and bottom */
  .hp-insights {
    grid-template-columns: 467fr 317fr;
    gap: 40px;
    padding: 80px var(--hp-gutter);
    align-items: stretch;
    /* 6202:9852: the 65 of a field is its label (12, trimmed to 9), the 12 under it and a 44 box —
       not a 65-tall control, which is what the first pass read it as. */
    --field-h: 44px;
  }
  .hp-insights__left { gap: 40px; padding-right: 0; }
  .hp-insights__copy { gap: 20px; }
  .hp-insights__form { gap: 20px; }
  .hp-insights__form .cw-row { gap: 20px; }
  .hp-insights__form > div:last-of-type { margin-top: 20px; }
  .hp-insights__shot { aspect-ratio: auto; height: 100%; }
}


/* ── a rail with nothing to page (2026-09-22, George) ─────────────────
   `is-static` is set by home.js whenever the track already fits its viewport, but the rules that
   act on it sat inside the 1441-and-up block, so a rail that fit at 1024 still showed its dots and
   its Next. They belong at every width: three Resources cards fit from 1024 on. */
.hp-carousel.is-static .hp-carousel__foot { display: none; }
.hp-carousel.is-static .hp-carousel__track { cursor: auto; }


/* ── Resources: a row, not a rail, on every desktop width (2026-09-22, George) ──
   Three cards share the column and there is nothing to page, so the viewport stops clipping, the
   track becomes a grid and the foot goes. The phone keeps the carousel — there the cards are
   82vw and one page is one card. */
@media (min-width: 721px) {
  .hp-res .hp-carousel__viewport { overflow: visible; }
  .hp-res .hp-carousel__track {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--hp-g20);
    padding-inline: var(--hp-gutter);
    transform: none !important;
    cursor: auto;
  }
  .hp-res .hp-post { width: auto; }
  .hp-res .hp-carousel__foot { display: none; }
}


/* the banner is 560 tall on the 1024 step — 6192:8715 draws the section at 1024 x 560 (2026-09-22).
   `--hp-cta-h` is the DRAWN height and the tail is added to it, so the number written here is the
   560 minus that tail (80 on this step) and the section measures the frame's own height. */
@media (min-width: 1024px) and (max-width: 1439px) {
  :root { --hp-cta-h: calc(560px - var(--hp-cta-tail)); }
}


/* ── the banner and the newsletter on the big steps (2026-09-22, George) ──
   The banner's section is 870 from 1440 up (the frame's own height, tail counted in), and the
   newsletter is 850 from 1920: 160 of air top and bottom around a 530 row, the two columns 80
   apart, with the picture stretched to the copy rather than held to its own ratio (6212:10929). */
@media (min-width: 1440px) {
  :root { --hp-cta-h: calc(870px - var(--hp-cta-tail)); }
}
@media (min-width: 1920px) {
  .hp-insights {
    min-height: 850px;
    padding: 160px var(--hp-gutter);
    gap: 80px;
    align-items: stretch;
  }
  .hp-insights__shot { aspect-ratio: auto; height: 100%; }
}


/* the stories grid opens up on the bigger steps (2026-09-23, George) */
@media (min-width: 1440px) { .hp-stories { gap: 60px; } }
@media (min-width: 1920px) { .hp-stories { gap: 80px; } }


/* the banner's line is 810 wide from 1440 up (2026-09-23, George) — it used to take whatever the
   row left it, which stretched it to 1100 on a wide screen */
@media (min-width: 1440px) {
  .hp-cta__foot .hp-lede { flex: 0 0 810px; max-width: 810px; }
}
