/* ===========================================================================
   Homepage — page-specific styles.
   Loaded AFTER tokens.css and shell.css, so scoped overrides here win on
   source order. Shared shell (nav, footer, buttons, .frame, .cm-h, .sec-label)
   belongs in shell.css, not here.
   =========================================================================== */
/* design tokens live in tokens.css; the shared shell (reset, nav, buttons, footer) lives in shell.css.
   Rules below are homepage-specific only — do NOT re-declare shell elements here (override with scoped selectors). */
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%;-webkit-text-size-adjust:100%;text-size-adjust:100%}   /* stop iOS from auto-inflating long text blocks (keeps sub + body the same size on phones) */
html{scroll-behavior:smooth}
section[id]{scroll-margin-top:96px}
body{color:var(--white);font-family:var(--sans);background:var(--ink)}
/* fixed studio backdrop behind the hero + sections; a morph layer sits over it for the dark->light scroll */
/* studio backdrop scrolls with the page and fades to dark just above the cards title (height set in JS) */
/* ---- HERO BACKDROP — one switch, easy to revert ------------------------
   Change ONLY the two variables below to swap the backdrop.
     photo (original):  --backdrop-img: url("img/home/hero_bg.jpg");
                        --backdrop-size: cover;
     Figma gradient:    --backdrop-img: linear-gradient(...);
                        --backdrop-size: 100% 180%;
   The SIZE matters. This layer is scroll-panned: the JS at the bottom of the
   page sets background-position from 50% 0% to 50% 100% so it travels from the
   beam at the hero down to the floor behind the cards. `cover` on a GRADIENT
   makes it fill the box exactly, leaving nothing to pan through, so the effect
   dies silently. Give a gradient a height over 100% to keep the travel. */
:root{
  --backdrop-img: url("img/home/hero-bg-gradient.svg");   /* Figma 822:1262 — radial, #24459D -> #19306C -> black */
  --backdrop-size: 100% 100%;
  /* SIZE IS NOT `cover` ON PURPOSE. The gradient is a blue band with black top
     AND bottom. `cover` scales it to fill the width, which makes it taller than
     the viewport and crops the black bottom away — measured mean luminance 50.4
     against Figma's 41.1, i.e. visibly bluer than designed. Stretching to exactly
     the viewport gives 41.6, matching the source. Side effect: the scroll-pan
     below (background-position 0% -> 100%) now has no overflow to travel through,
     so it is inert. That is the trade for matching the design.
     REVERT to the studio photo:
       --backdrop-img: url("img/home/hero_bg.jpg");
       --backdrop-size: cover;              <- the photo NEEDS cover, and its pan works
     The photo is still in img/home/, nothing was deleted. */
}
.backdrop{position:fixed;top:0;left:0;width:100%;height:100vh;z-index:-2;
  background:var(--backdrop-img) center top / var(--backdrop-size) no-repeat;will-change:opacity}   /* locked in place so it stays steady behind the pinned cards; JS fades its opacity out as the panels rise */
.env-bg{display:none}.hero{position:relative;height:100vh;overflow:hidden}
a{color:inherit;text-decoration:none}

#bg{position:absolute;inset:0;z-index:1}
/* darken behind the headline so the graphic sits clearly behind the text */
.top-scrim{position:absolute;top:0;left:0;right:0;height:46%;z-index:6;pointer-events:none;
  background:linear-gradient(180deg, rgba(13,26,59,.95) 0%, rgba(13,26,59,.72) 38%, rgba(13,26,59,0) 100%)}

/* dot info cards — real DOM/CSS text (crisp Geist), one shared style for every label */
/* blocky cursor trail (hero only) */
.hero-trail{position:absolute;inset:0;z-index:8;pointer-events:none;overflow:hidden}
.trail-block{position:absolute;width:8px;height:8px;border-radius:var(--radius-xs);
  background:rgba(58,111,255,.36);
  box-shadow:0 0 6px rgba(58,111,255,.25);
  animation:trailFade 1s ease-out forwards;will-change:opacity,transform}
@keyframes trailFade{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.5)}
  16%{opacity:.7;transform:translate(-50%,-50%) scale(1)}
  100%{opacity:0;transform:translate(-50%,-50%) scale(.8)}
}
@media (prefers-reduced-motion:reduce){ .hero-trail{display:none} }

#dot-cards{position:absolute;inset:0;z-index:7;pointer-events:none}
.dot-card{position:absolute;display:flex;padding:12px 16px;
  background:linear-gradient(155deg, rgba(26,42,84,.82), rgba(10, 19, 48,.82));
  border:1px solid rgba(124,152,248,.28);border-radius:var(--radius-md);
  box-shadow:0 10px 30px rgba(0,5,30,.55), 0 0 18px rgba(58,111,255,.14), inset 0 1px 0 rgba(255,255,255,.07);
  backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);
  opacity:0;will-change:transform,opacity}
.dot-card .r1{display:flex;align-items:center;gap:8px}
.dot-card .r1 svg{width:16px;height:16px;flex:none;display:block}
.dot-card .ttl{font-family:"Geist",sans-serif;font-weight:400;font-size:12px;line-height:1.3;color:var(--panel);white-space:nowrap}
/* radar sweep — pivots on Ava's center and scans the field */
.radar{position:absolute;left:64%;top:50%;width:900px;height:900px;margin-left:-450px;margin-top:-450px;z-index:3;
  transform-origin:50% 50%;pointer-events:none;
  background:url("img/home/radar.webp") center center / contain no-repeat;
  opacity:.8;mix-blend-mode:screen}
/* Ava = the sun, centered below the headline */
.ava-glow{position:absolute;left:64%;top:50%;width:720px;height:720px;transform:translate(-50%,-50%);z-index:2;pointer-events:none;
  background:radial-gradient(circle, rgba(58,111,255,.42) 0%, rgba(58,111,255,.12) 33%, transparent 66%)}
/* Blurred shape behind the sphere · Figma 717:9188. z-index 2 puts it ABOVE the
   particle canvas (#bg, z-index 1) and below the radar (3) and sphere (4). It shares
   z-index 2 with .ava-glow and sits earlier in the DOM, so the glow paints over it.
   Anchored to the same 64%/50% point as the sphere so it tracks it at every width.
   --blob-opacity is the tuning knob; drop it toward .5 if it reads too strong. */
.hero-blob{position:absolute;left:64%;top:50%;width:768px;height:719px;z-index:2;
  transform:translate(-50%,-50%);pointer-events:none;opacity:var(--blob-opacity,1);
  background:url("img/home/hero-blob.svg") center / 100% 100% no-repeat}
#ava-sun{position:absolute;left:64%;top:50%;width:344px;height:344px;transform:translate(-50%,-50%);z-index:4;pointer-events:none}
/* ---- HERO ENTRANCE (2026-08-06) ---------------------------------------------------------
   Plays once on load: the nav drops in and the sphere fades up together, then the headline
   types itself character by character, then the body copy fades in top to bottom.

   Everything here is opacity/transform ONLY. The headline's 3-line wrap is fragile (it broke
   once already when the font weight changed), so the typing reveals pre-laid-out characters
   rather than inserting text — the layout never reflows.

   The "go" state repeats each element's resting opacity (.radar is .8, .hero-blob is a
   variable) because these override a 0. That duplication is deliberate and temporary:
   home-hero-intro.js REMOVES .hero-anim when the sequence finishes, so the resting page is
   governed only by the base rules above and this block stops applying entirely.

   The nav animates .nav-in, not <nav>, because nav's own transform belongs to the
   hide-on-scroll behaviour in home-nav-scroll.js (.nav-hidden). */
body:has(.hero-anim) .nav-in{opacity:0;transform:translateY(-14px)}
body:has(.hero-go) .nav-in{opacity:1;transform:none;
  transition:opacity .6s ease, transform .6s cubic-bezier(.22,.61,.36,1)}

.hero-anim #bg,.hero-anim .radar,.hero-anim .hero-blob,.hero-anim .ava-glow,.hero-anim #ava-sun{opacity:0}
/* The sphere group fades on its OWN trigger (.sphere-go), not with the nav. The blob is a
   1.3KB SVG and the orb a 31.5KB webp, so on a cold load they used to fade together and the
   blurred blue shape appeared with no sphere inside it. home-hero-intro.js only adds
   .sphere-go once the orb image has actually loaded (with a 1.5s cap so a slow image can
   never hold the hero hostage). */
.hero-anim.sphere-go #bg,.hero-anim.sphere-go .radar,.hero-anim.sphere-go .hero-blob,
.hero-anim.sphere-go .ava-glow,.hero-anim.sphere-go #ava-sun{transition:opacity 1.2s ease}
.hero-anim.sphere-go #bg{opacity:1}
.hero-anim.sphere-go .radar{opacity:.8}                    /* matches the base .radar opacity */
.hero-anim.sphere-go .hero-blob{opacity:var(--blob-opacity,1)}
.hero-anim.sphere-go .ava-glow{opacity:1}
.hero-anim.sphere-go #ava-sun{opacity:1}

/* headline: characters are laid out immediately and revealed one at a time, so the wrap is
   decided once, up front. Words are nowrap so a word can never break mid-typing. */
/* The headline is hidden until the script has split it into characters. Without this it
   painted as plain text at first paint (~203ms), then vanished when the split ran and typed
   itself in — a visible flash. The per-character rule below cannot do this job because the
   .c spans do not exist yet at that point.
   Failsafe: if the script never runs, the keyframe reveals the headline after 3s so the copy
   is never permanently invisible. .split cancels it the moment the script takes over. */
.hero-anim h1{opacity:0;animation:heroH1Failsafe 0s 3s forwards}
.hero-anim h1.split{opacity:1;animation:none}
@keyframes heroH1Failsafe{to{opacity:1}}
.hero-anim h1 .w{white-space:nowrap}
/* each character fades AND sharpens rather than snapping on. With a 28ms step, roughly 15
   letters are mid-fade at any moment, so the reveal reads as a soft wave instead of a stutter.
   The blur runs longer than the fade (580 vs 420ms) so a letter arrives soft and then resolves,
   instead of both finishing together and reading as one plain fade.
   filter is safe here where transform is not: transform does not apply to non-replaced inline
   elements, so it would need display:inline-block, which risks changing the 3-line wrap. filter
   applies to inline elements as-is, so the wrap is untouched.
   No will-change: it is set per character, and 38 promoted layers costs more than it saves.
   The resting headline carries NO filter at all — home-hero-intro.js drops .hero-anim when the
   sequence ends, so these rules stop applying and the text goes back to plain subpixel-rendered
   glyphs rather than sitting on a permanent blur(0), which renders text subtly softer. */
.hero-anim h1 .c{opacity:0;filter:blur(10px);
  transition:opacity .42s cubic-bezier(.25,.46,.45,.94), filter .58s cubic-bezier(.25,.46,.45,.94)}
.hero-anim h1 .c.on{opacity:1;filter:blur(0)}

/* Body copy enters fast and LANDS slow. Three things make that read:
     1. cubic-bezier(.16,1,.3,1) is an expo ease-out — ~75% of the travel happens in the first
        fifth of the time, then it glides. The old pair fought each other: opacity used plain
        `ease`, which starts slow, against a mild ease-out on the transform, so it felt rigid.
     2. the transform runs 2.6x as long as the fade, so the text is fully readable early and
        keeps settling for another second, rather than arriving and stopping dead.
     3. 24px of travel, so there is something to decelerate through. */
.hero-anim .body,.hero-anim .emph{opacity:0;transform:translateY(-24px)}
.hero-anim .body.on,.hero-anim .emph.on{opacity:1;transform:none;
  transition:opacity .5s cubic-bezier(.33,1,.68,1), transform 1.3s cubic-bezier(.16,1,.3,1)}

@media (prefers-reduced-motion: reduce){
  .hero-anim #bg,.hero-anim .radar,.hero-anim .hero-blob,.hero-anim .ava-glow,.hero-anim #ava-sun,
  .hero-anim h1,.hero-anim h1 .c,.hero-anim .body,.hero-anim .emph,body:has(.hero-anim) .nav-in{
    opacity:1;transform:none;filter:none;transition:none}
  .hero-anim .radar{opacity:.8}
  .hero-anim .hero-blob{opacity:var(--blob-opacity,1)}
}
/* base nav + nav-scroll behaviour (nav.scrolled / nav.nav-hidden / .menu-fab) live in shell.css (single source) */

/* nav (.nav-in/.brand/.navlink/.nav-burger…) + button system (.btn/variants) now live in shell.css (single source) */

/* Concept C: text in a left column, vertically centered */
.hero-top{position:absolute;left:max(120px, calc((100% - 1728px) / 2));top:31%;bottom:9%;z-index:10;display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-start;text-align:left;gap:0;max-width:480px}h1{font-family:var(--display);font-weight:500;font-size:clamp(40px, 7vw, 72px);line-height:1.05;max-width:480px;margin-top:0}   /* 480 not 460: the headline must break "Beat your / number without / hiring for it." At the real Anaheim 500 weight "number without" is 472px, so 460 forced a 4th line. It used to fit at exactly 460 because the font was silently falling back to weight 400 (fixed 2026-08-05). Safe range is 472-521; 480 keeps 8px of slack and matches .hero-top. */   /* fluid: scales smoothly with the viewport instead of snapping at breakpoints */
/* narrow desktop: pin the sphere assembly so it can never slide left into the headline's lane; reverts to 64% once the viewport is wide enough (>~1190px). Text wrapping is left untouched. */
@media (min-width:1025px){ .radar, .ava-glow, #ava-sun{ left:min(max(64%, 760px), calc((100% - 1728px) / 2 + 1100px)) } }   /* cap the sphere within the centred 1728px frame so it stops drifting right on huge monitors */
h1 em{font-style:normal;color:var(--bridge)}.body{font-family:var(--sans);font-weight:300;font-size:16px;line-height:24px;color:var(--white);max-width:440px;margin-top:24px}
.emph{font-family:var(--sans);font-weight:500;font-size:16px;line-height:28px;color:var(--soft);max-width:460px;margin-top:24px}
/* Clients / trusted-by strip — gentle auto-scrolling logo slider */
.clients{background:transparent;width:100%;display:flex;justify-content:center;padding:20px 0;margin-top:32px;overflow:hidden}   /* side gutter now comes from the shared .frame on .clients-inner */
.clients-inner{display:flex;align-items:center;gap:48px;min-width:0}   /* width + 1408 cap + 24px gutter come from .frame */
.clients-label{flex:none;font-family:var(--sans);font-weight:500;font-size:12px;letter-spacing:2px;text-transform:uppercase;color:var(--white);white-space:nowrap}
.clients-marquee{flex:1;min-width:0;overflow:hidden;
  -webkit-mask-image:linear-gradient(to right, transparent 0, #000 6%, #000 94%, transparent 100%);
  mask-image:linear-gradient(to right, transparent 0, #000 6%, #000 94%, transparent 100%)}
.clients-track{display:flex;align-items:center;gap:80px;width:max-content;animation:clientsSlide 45s linear infinite}
@media (prefers-reduced-motion:reduce){.clients-track{animation:none}}
@keyframes clientsSlide{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.clients-track img{display:block;width:auto;flex:none}
.clients-track .l-bombbomb{height:39px}
.clients-track .l-oec{height:24px}
.clients-track .l-homestory{height:29px}
.clients-track .l-commerce{height:29px}
.clients-track .l-shipstation{height:26px}

/* Stats cards — 4-up grid of upright cards */
.stats{position:sticky;top:0;z-index:1;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:120px 0 96px;
  background:linear-gradient(to bottom, rgba(13,26,59,0) 0%, rgba(13,26,59,0) 72%, var(--ink) 94%)}   /* mostly transparent so the real studio background shows behind the cards; fades to navy at the very bottom to hand off to the panels */
.stats-hold{height:44vh}   /* charts stay pinned for this much scroll before the shift rises over them (lower = panel comes up sooner) */
.stats-inner{position:relative;z-index:1}   /* width + 1408 cap + 24px gutter come from .frame */
.stats-blend{position:absolute;inset:0;background:var(--void);opacity:0;pointer-events:none;z-index:0}   /* fades in as the charts leave, so the area ends on the panel's color */
.stats .soc2{position:relative;z-index:1}.stats-grid{display:flex;gap:24px;justify-content:center;flex-wrap:wrap}   /* the 1208-wide card row centers within the 1360 frame */
/* SOC 2 attestation badge */
.soc2{margin-top:52px;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:12px;padding:8px;border-radius:var(--radius-sm);
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  border:1px solid rgba(255,255,255,.2);
  background:linear-gradient(90deg, rgba(241, 243, 255,.14) 0%, rgba(174,174,175,.14) 31%, rgba(63,63,63,.14) 100%), var(--ink)}
.soc2 img{width:37px;height:37px;display:block;object-fit:contain;flex:none}
.soc2-txt{display:flex;flex-direction:column;gap:4px;color:var(--white)}
.soc2-txt .t{font-family:var(--sans);font-weight:500;font-size:16px;line-height:17px;text-transform:uppercase}
.soc2-txt .s{font-family:var(--sans);font-weight:400;font-size:12px;line-height:14px}
.stat-card{position:relative;width:284px;height:420px;border-radius:var(--radius-md);overflow:hidden;
  border:2px solid rgba(36,69,157,.5);
  background:rgba(13, 26, 59,.5);backdrop-filter:blur(20px) saturate(1.1);-webkit-backdrop-filter:blur(20px) saturate(1.1);   /* glass, per Figma — floats on the studio floor behind it */
  opacity:0;transform:translateY(-40px);
  transition:opacity .7s cubic-bezier(.22,1,.36,1),transform .7s cubic-bezier(.22,1,.36,1)}
.stat-card::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;   /* soft internal glow the Figma glass has */
  background:radial-gradient(78% 58% at 50% 44%, rgba(124, 152, 248,.16) 0%, rgba(124, 152, 248,0) 70%)}
.stat-card.in{opacity:1;transform:none}
.stats-grid .stat-card:nth-child(2){transition-delay:.1s}
.stats-grid .stat-card:nth-child(3){transition-delay:.2s}
.stats-grid .stat-card:nth-child(4){transition-delay:.3s} .stat-chart, .stat-num, .stat-cap{z-index:1}   /* keep chart + text above the card's inner glow */
.stat-card.mid{box-shadow:0 4px 24px rgba(0,0,0,.25)}/* chart graphic, one image per card — exact Figma position + size */
.stat-chart{position:absolute;pointer-events:none}
@media (prefers-reduced-motion:reduce){
  .stat-card{transform:none;transition:opacity .3s ease}}
.stat-chart img{display:block;width:100%;height:auto}
.stat-chart svg{display:block;width:100%;height:auto;overflow:visible}
.c-47 .stat-chart{left:24px;top:64px;width:117px;height:117px}    /* progress ring */
.c-70 .stat-chart{left:32px;top:64px;width:73px;height:117px}     /* two bars */
.c-348 .stat-chart{left:24px;top:64px;width:117px;height:117px}   /* progress ring */
.c-4w .stat-chart{left:28px;top:70px;width:106px;height:92px}     /* 4x3 dot grid, smaller circles */
/* chart fills animate when the card scrolls in (.stat-card.in), in sync with the count-up */
.ring-val{stroke-dasharray:100;stroke-dashoffset:100;transition:stroke-dashoffset 1.1s cubic-bezier(.22,1,.36,1)}
.stat-card.in .ring-val{stroke-dashoffset:calc(100 - var(--p,0))}   /* white arc = the percentage */
.bars rect{transform:scaleY(0);transform-box:fill-box;transform-origin:bottom;transition:transform 1.1s cubic-bezier(.22,1,.36,1)}
.bars rect:nth-of-type(2){transition-delay:.12s}
.stat-card.in .bars rect{transform:scaleY(1)}
.dots circle{opacity:0;transform:scale(0);transform-box:fill-box;transform-origin:center;transition:opacity .45s ease,transform .5s cubic-bezier(.34,1.56,.64,1)}
.stat-card.in .dots circle{opacity:1;transform:scale(1)}
.dots circle:nth-of-type(1){transition-delay:0s}    .dots circle:nth-of-type(2){transition-delay:.04s}
.dots circle:nth-of-type(3){transition-delay:.08s}  .dots circle:nth-of-type(4){transition-delay:.12s}
.dots circle:nth-of-type(5){transition-delay:.16s}  .dots circle:nth-of-type(6){transition-delay:.2s}
.dots circle:nth-of-type(7){transition-delay:.24s}  .dots circle:nth-of-type(8){transition-delay:.28s}
.dots circle:nth-of-type(9){transition-delay:.32s}  .dots circle:nth-of-type(10){transition-delay:.36s}
.dots circle:nth-of-type(11){transition-delay:.4s}  .dots circle:nth-of-type(12){transition-delay:.46s}
@media (prefers-reduced-motion:reduce){
  .ring-val,.bars rect,.dots circle{transition:none}
  .ring-val{stroke-dashoffset:calc(100 - var(--p,0))}
  .bars rect{transform:scaleY(1)} .dots circle{opacity:1;transform:scale(1)}
}
.stat-num{position:absolute;left:20px;top:264px;font-family:var(--display);font-weight:400;font-size:64px;line-height:64px;color:var(--white)}
.stat-cap{position:absolute;left:20px;top:336px;width:224px;font-family:var(--sans);font-weight:300;font-size:16px;line-height:24px;color:var(--soft)}

/* scroll-reveal */
.reveal{opacity:0;transform:translateY(26px);transition:opacity .7s ease, transform .8s cubic-bezier(.22,.61,.36,1)}
.reveal.in{opacity:1;transform:none}
@media (prefers-reduced-motion: reduce){ .reveal{opacity:1;transform:none;transition:none} }

/* "The shift" — light editorial section */
.shift{position:relative;background:transparent;color:var(--surface);padding:120px 0 120px}
.shift-inner{position:relative;z-index:2;max-width:1408px;margin:0 auto;padding:0 24px}
.shift-toplines{margin-bottom:16px}
.shift-toplines .ln{display:block;height:0;border-top:1px solid var(--deep)}
.shift-toplines .ln + .ln{margin-top:12px}.shift-spiral{position:absolute;z-index:0;top:44px;right:0;width:560px;height:460px;max-width:48%;pointer-events:none;opacity:.85;overflow:hidden}
.shift-spiral .spiral-layer{position:absolute;inset:0;width:100%;height:100%;opacity:0;transition:opacity 1.15s ease}@media (prefers-reduced-motion:reduce){.shift-spiral .spiral-layer{transition:none}}
.shift-nav{display:flex;align-items:stretch;flex-wrap:nowrap;margin:-24px -32px 0;border-bottom:1px solid var(--deep);
  position:sticky;top:0;z-index:6;background:var(--ink)}   /* stays reachable while you scroll the section's content */
.shift-nav .item{position:relative;display:flex;align-items:center;gap:12px;background:none;border:none;border-right:1px solid var(--deep);padding:16px 28px;font-family:var(--display);font-weight:500;font-size:20px;letter-spacing:2px;color:rgba(124,152,248,.6);white-space:nowrap;text-decoration:none;cursor:pointer;transition:color .2s ease}
.shift-nav .item:hover{color:var(--surface)}
.shift-nav .item:focus-visible{outline:2px solid var(--canvas);outline-offset:-4px}
.shift-nav .item.on{color:var(--surface)}
.shift-nav .item .lead{flex:none;width:22px;display:inline-flex;align-items:center}
.shift-nav .item .sq{width:9px;height:9px;background:var(--surface);flex:none;display:none}
.shift-nav .item.on .sq{display:block;transform-origin:center;animation:dotBeat 2.6s ease-in-out infinite}
@keyframes dotBeat{0%,100%{transform:scale(1)}50%{transform:scale(1.3)}}
@media (prefers-reduced-motion:reduce){.shift-nav .item.on .sq{animation:none}}
.shift-nav .item.on .num{display:none}
.shift-panels{min-height:720px}
.shift-panel{display:none}
.shift-panel.on{display:flex;flex-direction:column;min-height:720px}
.panel-head{display:none}   /* mobile-only header for the stacked layout */
.shift-h{font-family:var(--display);font-weight:400;font-size:56px;line-height:64px;color:var(--surface);max-width:860px;margin-top:60px}
.shift-h .w{opacity:0;transition:opacity .22s ease}
.shift-h .w.on{opacity:1}@keyframes caretBlink{0%,100%{opacity:1}50%{opacity:0}}
.shift-sub{font-family:var(--sans);font-weight:400;font-size:16px;line-height:26px;color:var(--bridge);margin:0}   /* callouts = final points, right column{font-family:var(--sans);font-weight:400;font-size:16px;line-height:30px;color:var(--surface);padding:8px 0}.shift-body{flex:0 1 640px}   /* narrative: sub-head + paragraphs, left column */
.shift-body p{font-family:var(--sans);font-weight:300;font-size:16px;line-height:24px;color:var(--soft)}
.shift-body p + p{margin-top:32px}
.shift-body strong{font-weight:600}.cat-tiers{flex:0 1 600px}
.cat-detail{font-family:var(--sans);font-weight:300;font-size:16px;line-height:24px;color:var(--soft);margin:0}
/* ---- Category tiers · Figma 275:3227 (restructured 2026-08-05) ---------------------------
   The eyebrow, a "/" and the title now share ONE row with the body beneath, instead of
   eyebrow / title / body stacked in three lines. Tier 3's row is ~498px wide, so the three
   tiers can no longer sit side by side in 177px columns; they stack in the 601 column, which
   is how the Figma has them. Title is Figma's 16/600 in --panel; the eyebrow keeps the shared
   .cat-eyebrow entirely, including its --bridge colour. */
.cat-tier{display:flex;flex-direction:column}
.cat-tierhead{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0;line-height:30px}
.cat-tierhead .cat-eyebrow{margin:0}   /* colour stays the shared --bridge; the Figma said --action but that made these the only --action eyebrows on the site, clashing with the chart axis labels right beside them */
.cat-sep,.cat-title{font-family:var(--sans);font-weight:600;font-size:16px;line-height:30px;color:var(--panel)}
.cat-tiers hr{border:none;border-top:1px solid var(--deep);margin:16px 0}/* "Where the capacity goes to work" tab panel */
.shift-panel.cap-panel.on{display:flex;flex-direction:column;min-height:720px}/* ---- Fortune article · Figma 867:4513 (restructured 2026-08-05) --------------------------
   Was the FORTUNE logo on a row with a rule under it and the copy beneath. Now one bordered
   row: logo + arrow button, a vertical divider, then the copy with "Read more" inline.
   Figma's 30px divider padding snapped to 32 (4px grid) and its 54px radius on a 24px circle
   replaced with --radius-pill; both are visually identical. */
.cap-source{flex:0 1 548px;display:flex;align-items:center;gap:40px;border:1px solid var(--deep);padding:16px 24px;box-sizing:border-box}
.cap-source-brand{display:flex;align-items:center;gap:16px;align-self:stretch;flex:none;padding-right:32px;border-right:1px solid var(--deep)}
.cap-source-logo{width:82px;height:auto;display:block;flex:none}
.cap-source-badge{flex:none;width:24px;height:24px;border-radius:var(--radius-pill);background:var(--action);display:inline-flex;align-items:center;justify-content:center;transition:transform .2s ease}
.cap-source-badge svg{width:16px;height:16px;fill:none;stroke:var(--white);stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
/* The WHOLE block is one click target. The "Read more" link stretches over the container
   with a pseudo-element, so there is exactly ONE link in the accessibility tree instead of a
   nested pair, and a click anywhere in the box opens the article. Trade-off of this pattern:
   the overlay makes the copy non-selectable. */
.cap-source{position:relative}
.cap-source-more::after{content:"";position:absolute;inset:0}
.cap-source:hover{border-color:var(--action)}
.cap-source:hover .cap-source-badge{transform:scale(1.06)}
.cap-source:hover .cap-source-more{color:var(--white)}
.cap-source:has(.cap-source-more:focus-visible){outline:2px solid var(--action);outline-offset:2px}
.cap-source-more:focus-visible{outline:none}
.cap-source-head{font-family:var(--sans);font-weight:400;font-size:16px;line-height:24px;color:var(--soft);margin:0;flex:1 1 0;min-width:0}
.cap-source-more{color:inherit;text-decoration:underline;white-space:nowrap}   /* text link — underlined always, per the link rule */
.cap-source-more:hover{color:var(--white)}.proof-cases{display:flex;align-items:stretch;gap:80px}
.proof-case{flex:1;min-width:0;display:flex;flex-direction:column}
.proof-logo{display:block;height:25px;width:auto;align-self:flex-start;margin-bottom:16px}   /* height-only: each logo keeps its own ratio (the old 126x25 box stretched HomeStory ~8%). align-self is load-bearing: .proof-case is a column flex, so without it the img box stretches full-width and the SVG centres inside it — same lesson as .pf-case-logo on results */.proof-body{font-family:var(--sans);font-weight:300;font-size:16px;line-height:24px;color:var(--soft);margin:0 0 12px}.proof-cases hr{display:none}
/* Lower page — shared blue-streak background across testimonial + see it work,
   faded to transparent at the top so it blends into the dark shift section above */
.lower{position:relative}
/* The base colour matters: section-grad.svg is a rotated, stretched rect that does NOT cover
   its own canvas, so whatever sits behind it shows through. .shift-pin puts var(--void) behind
   it; this had nothing, so the body's --ink showed instead and the two sections met at a
   tonal step. Same base on both sides now (2026-08-06). */
.lower-bg{position:absolute;inset:0;z-index:0;pointer-events:none;
  background:var(--void) url("img/home/section-grad.svg") center / 100% 100% fixed no-repeat}
/* fade removed: the background is now continuous with the shift section, so no blend seam is needed */
.lower-fade{display:none}
.lower > section, .lower > footer{position:relative;z-index:1}   /* footer now lives inside .lower so it floats on the same gradient */

/* Client testimonial — split card on the dark background */
/* ---- Testimonial · Figma 844:3018 --------------------------------------
   Rebuilt on the page grid 2026-08-04. Left column 632, 96 gutter, right 632,
   i.e. the same .col-half split the shift panels use. Figma had the right
   column at x772; the grid puts it at 804, and it is snapped to the grid on
   purpose (agreed with Victoria).
   Type: .sec-label (shell.css) for the eyebrow, .cm-h + a scoped override for
   the name, and ONE new style for the quote — 20/32 Geist Regular in --panel,
   which nothing on the site already provides.
   The two photos and the blurred blob are absolutely placed inside the left
   column; positions snapped to 4, image sizes left at their true pixel ratios. */
.testi{position:relative;background:transparent;padding:60px 0 96px}
.testi .cm-h{color:var(--white);font-weight:400;font-size:40px;line-height:44px;margin:0}
.testi .cm-h b{font-weight:600}
/* Two columns on the page grid: 632 + 96 + 632 = 1360, the same split .col-half uses.
   Flex rather than absolute px so the columns keep their proportions between 1024 and 1408.
   Inside the left column the images are placed as percentages of the column, so the
   photo overlap holds at every width. Ratios come straight off the Figma boxes. */
/* 116 puts the name's cap-height level with the top of the portrait, which is why the
   photo column is nudged 12 lower rather than both starting on the same line. */
/* 48px between the label and the name: the same gap About uses when .sec-label stacks above
   its heading (.ab-believe-in gap at <=1024). Reused, not invented. */
.testi-say > .sec-label{margin:0 0 32px}   /* was 48; tightened 2026-08-11 alongside the wider portrait moving up */
.testi-cols{display:flex;gap:96px;margin-top:0}   /* was 116: the label used to sit above this row and no longer does */
.testi-visual{flex:1 1 0;min-width:0;position:relative;aspect-ratio:632/488;margin-top:12px}
/* Single-photo layout (Tony/Chad 08-11): the product screenshot is gone, the portrait grew
   44% -> 50% and re-centres on the blob, and the blob itself grew so the blue shadow reads
   as the section's backdrop rather than a box edge. Blob centre stays at 41.95% / 52.9%. */
.testi-blob{position:absolute;left:0;top:-19.25%;width:84%;height:126%;pointer-events:none;
  background:url("img/home/testi-blob.svg") center/100% 100% no-repeat}   /* shrunk ~15% (2026-08-13, Victoria: the glow overwhelmed the card) — centre still pinned to the portrait centre (42%, 43.75%) */
.testi-face{position:absolute;left:6%;top:4%;width:72%;height:auto;aspect-ratio:511/436;border-radius:var(--radius-sm);
  object-fit:cover;display:block}   /* wider landscape portrait (2026-08-11, 1022x872, rounded corners baked in); top 4% + the visual's 12px margin lands its top edge ~32px below the column top, level with the text block */
.testi-say{flex:1 1 0;min-width:0;align-self:flex-start;display:flex;flex-direction:column}
/* NEW STYLE (approved 2026-08-04): the quote. 20/32 Geist Light (300) in --soft.
   .cm-sub is 16/24 weight 300 in --deep, so overriding it would mean replacing
   size, weight, line-height and colour — a different style, not an override. */
.testi-quote{margin:36px 0 0;font-family:var(--sans);font-weight:300;font-size:20px;line-height:32px;color:var(--soft)}
.testi-logo{display:flex;align-items:center;gap:14px;margin:80px 0 0}
/* Logo level with the bottom of the portrait. The portrait is 44% of a column at 278:381,
   so its bottom sits at 632:393 of this column (the column starts 12 above the photo) —
   auto margin below drops the logo onto that exact line, at any width.
   Only above 1280px though: narrower than that the name plus quote wrap taller than the photo, so
   there is physically no room to hold the logo up there without it colliding with the text.
   Below the breakpoint the logo just follows the quote with a normal gap. The 80px is
   what the removed decorative slash used to occupy (2026-08-06), kept so the composition
   below 1280 does not tighten up. */
@media (min-width:1280px){
  .testi-say{aspect-ratio:632/393;min-height:min-content}
  .testi-logo{margin-top:auto}
}
.testi-logo img{display:block}

/* ---- vertical stack, framed: each of the 4 blocks sits in its own bordered box with a corner label cell ---- */
.shift{height:auto;padding:0;position:relative;z-index:3;margin-top:0}   /* solid block that scrolls up over the pinned charts */
.shift-pin{position:static;height:auto;overflow:visible;display:block;
  background:linear-gradient(180deg, var(--void) 0%, rgba(6,16,41,0) 100%) center top / 100% 360px no-repeat, var(--void) url("img/home/section-grad.svg") center / 100% 100% fixed}   /* top fade blends the cards' var(--void) into the mesh gradient so there's no hard line at the cards->shift boundary */
.shift-inner{display:none}   /* the shared tab row is gone; each panel carries its own label */
.shift-spiral{display:none}
.shift-panels{position:relative;z-index:1;display:flex;flex-direction:column;gap:64px;width:100%;
  padding:64px max(24px,calc((100vw - 1360px)/2)) 96px;box-sizing:border-box;transform:none}   /* content band = 1360, matches the shared .frame so the panels align with every other section */
.shift-panel,.shift-panel.on{position:relative;display:flex;flex-direction:column;width:100%;min-height:0;opacity:1;transform:none;
  border-radius:var(--radius-md);overflow:hidden;padding:0;box-sizing:border-box;
  background:var(--ink-deep) radial-gradient(150% 140% at 100% 70%,
    #13255A 0%, #13255A 18%, #11214E 26%, var(--ink) 34%, var(--ink-deep) 43%,
    #060D1F 51%, #040A16 59%, #030913 68%, #030812 100%)}
  /* This was a 1500x838 lossy WebP (box-bg.webp, deleted 2026-08-06). It is a smooth dark
     gradient, which is the worst case for a lossy codec: the encoder spends almost no bits on
     dark areas, so it banded and blocked. It was then scaled UP ~1.4x by `cover` on a tall
     phone panel and rendered at 3x DPR, which turned each 8px compression block into ~34
     screen pixels. That is the mottling Victoria saw — worse on mobile, but present on desktop.
     A gradient is math, so CSS draws it exactly at any size and DPR: no banding, no upscale,
     no 20KB download. The stops are not eyeballed — the original was numerically fitted to an
     ellipse (centre 100% 70%, radii 150% x 140%) with a mean error of 4.17/255, i.e. 1.6%.
     Note it now fills each panel instead of being crop-to-cover, so the bottom-right glow is
     visible in full where the raster used to be cropped out of narrow panels.
     Two fitted stops came out within a few units of existing tokens (#0D1B40 was 5 off --ink,
     #0A1530 was 2 off --ink-deep), which says the original artwork was drawn THROUGH the
     palette. They are snapped to the tokens rather than exempted, so the gradient tracks the
     palette if it ever changes. check.mjs flagged both — that check earned its keep here. */
.shift-panel::before{display:none}   /* the two top lines above each frame — removed */
.panel-head{align-self:flex-start;display:inline-flex;align-items:center;gap:12px;background:none;border:none;
  border-right:1px solid var(--deep);padding:0 40px;margin:0;position:relative;top:32px;   /* nudged down without pushing the content below */
  color:var(--surface);cursor:default;text-align:left;line-height:normal}
/* Type, gap and the 9px square all come from .sec-label in shell.css now (2026-08-06) — this
   used to redeclare every one of them, which is why changing the label size only moved one of
   the two. What stays here is genuinely panel-specific: the right divider, the gutter padding,
   the 32px nudge, and --surface instead of the shared --white.
   line-height is deliberately NOT inherited: .sec-label sets 28px, and this element's height is
   what sets the gap down to .panel-row, so adopting it would push every panel's content down
   8px. Natural leading keeps the layout identical.
   display/align-items must stay too: `.panel-head{display:none}` appears earlier in THIS file,
   and index.css loads after shell.css, so it would otherwise win over .sec-label. */
.shift-h{border-top:1px solid var(--deep);margin:0;max-width:none;padding:64px 20% 0 40px}.shift-panel .reveal{opacity:1;transform:none}
/* === Concept C layout: label bar, divider, then graphic-left / text-right === */
.panel-row{display:flex;justify-content:space-between;align-items:flex-start;gap:64px;
  padding:80px 40px 64px}   /* padding-top 80 (was 56) — DESKTOP ONLY in effect, because every
  width below 1025 overrides this padding in its own block. No new breakpoint needed.
  Note .panel-head is nudged down 32px by position:relative/top without taking layout space, so
  the gap you actually SEE under the label is this value minus 32, i.e. 48px. */
.panel-media{flex:1 1 400px;max-width:640px;align-self:center;height:320px;overflow:hidden;position:relative}   /* grows on large screens (centered graphic sits toward the middle of the left column), shrinks on smaller so the text keeps its room */
.panel-media img{display:block;width:100%;height:100%;object-fit:cover}
/* The Category orbital graphic — inline SVG, dots orbit their dashed rings */
/* max-height + a shrinkable svg, the same guard .cap-journey-wrap uses above: .panel-media is
   overflow:hidden with a FIXED height, so without this the curve is cut off wherever it grows
   taller than its box. It was being clipped at 480-819 before today, and the stacked tablet
   layout (2026-08-06) made the box wider, which made the curve taller, and clipped it there too. */
.panel-media .cat-curve{position:absolute;top:50%;left:50%;width:92%;max-width:560px;max-height:100%;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;gap:12px}
@media(min-width:1025px){ .shift-panel[data-panel="category"] .panel-media{height:400px} }   /* taller box (desktop only) so the maturity curve can scale up to match the Shift graphic's weight */
.cat-curve-svg{width:100%;height:auto;display:block;flex:0 1 auto;min-height:0}   /* shrinkable so the labels below it are never the thing that gets cut */
.cat-curve-labels{display:grid;grid-template-columns:180fr 100fr 180fr;width:100%}   /* grid keeps the 3 labels under their nodes reliably (avoids the Safari flex-basis bug) */
.cat-lvl{display:flex;justify-content:center}
.cat-curve-labels .cat-eyebrow{margin:0;font-size:10px}   /* reuse cat-eyebrow style; scope size to the chart's axis labels only */
.cat-line{stroke-dasharray:100;stroke-dashoffset:100;animation:catDraw 4.6s ease-in-out infinite}
@keyframes catDraw{0%{stroke-dashoffset:100;opacity:0}7%{opacity:1}52%{stroke-dashoffset:0;opacity:1}80%{stroke-dashoffset:0;opacity:1}92%{opacity:0}100%{stroke-dashoffset:0;opacity:0}}
.cat-nodes circle{opacity:1;transform-box:fill-box;transform-origin:center;animation:catNodeOn 4.6s ease-in-out infinite}
.cat-nodes circle:nth-child(2){animation-delay:1.1s}
.cat-nodes circle:nth-child(3){animation-delay:2.3s}
@keyframes catNodeOn{0%,100%{transform:scale(1)}6%{transform:scale(1.3)}18%{transform:scale(1)}}
.cat-peak{opacity:0;transform-box:fill-box;transform-origin:center;animation:catPeakGlow 4.6s ease-in-out infinite}
@keyframes catPeakGlow{0%{opacity:0;transform:scale(.55)}46%{opacity:0;transform:scale(.6)}56%{opacity:1;transform:scale(1)}80%{opacity:1;transform:scale(1)}92%{opacity:0;transform:scale(1)}100%{opacity:0;transform:scale(.55)}}
@media (prefers-reduced-motion:reduce){.cat-line{animation:none;opacity:1;stroke-dashoffset:0}.cat-nodes circle{animation:none;opacity:1}.cat-peak{animation:none;opacity:1;transform:none}}
/* "Where the capacity goes" journey scan — Ava's scan line travels up and down every stage of the journey, blooming each opportunity it crosses (JS-driven in capJourney()) */
/* The wrap must FIT the .panel-media box (which is overflow:hidden) — adding the
   "Customer Journey" title on 2026-08-05 made it 415px tall inside a 400px box and the
   title's top was clipped. Two guards:
   1. max-width 440 (was 490) so title + rule + drawing fit the 400px desktop box exactly,
      with the rule and the drawing still the same width — no letterboxing on the main view.
   2. max-height + a shrinkable svg, so if the box is ever shorter (tablet 320, phone 240)
      the drawing scales down instead of being cut off. */
.panel-media .cap-journey-wrap{position:absolute;top:50%;left:50%;width:92%;max-width:440px;max-height:100%;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;gap:12px;container-type:inline-size}
.panel-media .cap-journey{width:100%;height:auto;display:block;flex:0 1 auto;min-height:0}
/* "Customer Journey" axis label + the hard rule under it. Journey maps are drawn with the
   title bar across the TOP, which is what Tony asked for on 07-17; the old solid baseline
   at the bottom of the SVG moved up here. Type comes from .cat-eyebrow — layout only. */
.cap-jtitle{width:100%;padding:0 0 8px;margin:0 0 4px;box-sizing:border-box;position:relative;top:8px}   /* top:8px nudges the label down WITHOUT taking layout space (2026-08-06): .cap-journey-wrap is 347 tall in a 368 box and centres itself, so real padding here would grow the wrap, re-centre it, and drag the chart down with the label. This moves only the label. */   /* border-bottom removed 2026-08-06; the padding below the label is kept so the chart does not ride up into it */
@media(min-width:1025px){ .shift-panel[data-panel="capacity"] .panel-media{height:400px} }
.cap-jlead .jglow{opacity:0}
.cap-jlead .jdot{opacity:.35}
/* phase labels live in HTML (not the SVG) so they stay a fixed, readable size at any chart scale; flex padding of 40/560 mirrors the chart's side margins so labels sit under their columns */
/* hide the axis labels whenever the chart column is too narrow to show all five at a readable size (container query on chart width, not viewport). The "Nurturing → ..." sequence beside the chart still names the journey. */
/* "Proof" circuit — pulses flow outward along the orthogonal traces */
.panel-media .proof-curve{position:absolute;top:50%;left:50%;width:82%;max-width:480px;height:auto;max-height:100%;transform:translate(-50%,-50%)}
/* Back to a plain -50% centre (2026-08-06). It briefly used -58% to lift the drawing, because
   the SVG canvas was 460x320 while the drawing only spanned y78..290 — 78 units of dead margin
   baked into the top, so the ink filled just 66% of the canvas height and centring the ELEMENT
   did not centre what you see. The viewBox is now cropped to the drawing (28 66 416 236), so
   the element IS the drawing and an honest centre works. That crop is also what removed the
   empty space at the top of the box; the width went 76->82% and the cap 440->480 to use it. */   /* max-height: see the .cat-curve note */
@media(max-width:640px){ .panel-media .proof-curve{width:92%} }   /* more room on phones — scale the proof chart up */
@media(min-width:1025px){ .shift-panel[data-panel="proof"] .panel-media{height:400px} }   /* match the other panels' box */
/* stable node sits on the curve where it crosses the target line; a heartbeat pulse keeps it alive without moving */
.proof-light .pn-core,.proof-light .pn-ring,.proof-light .pn-glow{transform-box:fill-box;transform-origin:center}
.proof-light .pn-core{animation:pnBeat 2.2s ease-in-out infinite}
.proof-light .pn-ring{animation:pnRing 2.2s ease-out infinite}
.proof-light .pn-glow{animation:pnGlow 2.2s ease-in-out infinite}
@keyframes pnBeat{0%{transform:scale(1)}10%{transform:scale(1.32)}20%{transform:scale(1)}30%{transform:scale(1.18)}40%,100%{transform:scale(1)}}
@keyframes pnRing{0%{transform:scale(1);opacity:0.6}12%{opacity:0.45}28%{transform:scale(2.3);opacity:0}100%{transform:scale(2.3);opacity:0}}
@keyframes pnGlow{0%,40%,100%{opacity:0.85}10%{opacity:1}}
@media (prefers-reduced-motion:reduce){.proof-light .pn-core,.proof-light .pn-ring,.proof-light .pn-glow{animation:none}.proof-light .pn-ring{opacity:0.55}}
.panel-media .panel-anim{display:block;width:100%;height:100%;transform:scale(.85);transform-origin:center}
/* scale(.85) is now the base, not a small-screen override (2026-08-06). The arc is drawn at 90%
   of the box height by home-panel-particles.js, which filled the container box almost edge to
   edge on desktop once that box became visible. Phones and tablets already carried this exact
   .85 from their own media query, so they are unchanged — this only brings desktop down to the
   same 15% reduction, and the two now share one rule instead of disagreeing. */
.panel-content{flex:0 1 604px;min-width:0;display:flex;flex-direction:column;align-items:flex-start}
.panel-content .shift-h{border-top:none;padding:0;margin:0;max-width:none;font-size:40px;line-height:44px}
.panel-content > .shift-sub{margin-top:24px}
.panel-content .shift-body{flex:none;width:100%;margin-top:24px}
.panel-content .shift-body p+p{margin-top:20px}
.panel-cta{align-self:flex-start;margin-top:32px;text-decoration:none}   /* layout only — the button visual comes from .btn.btn-secondary */
.panel-content .cat-tiers{flex:none;width:100%;margin-top:32px;display:flex;flex-direction:column;gap:24px}
/* the old 3-column desktop rule lived here; removed 2026-08-05, see Figma 275:3227 above */
.cap-body{font-family:var(--sans);font-weight:300;font-size:16px;line-height:24px;color:var(--soft);margin:24px 0 0}
.cap-stages-eyebrow{margin-top:28px;margin-bottom:0}   /* layout only — text style comes from .cat-eyebrow. margin-bottom:0 overrides the shared .cat-eyebrow's 8px for THIS eyebrow only (2026-08-06): the gap to .cap-journey-list was 8 + its own 16 = 24px, and the two lines read as one pair at 16. Scoped, so every other eyebrow on the site keeps its 8px. */
/* journey stages as an inline sequence (not pills — reads as a path, not tappable buttons) */
.cap-journey-list{margin-top:16px;max-width:560px;font-family:var(--sans);font-weight:500;font-size:15px;line-height:1.9;color:#D2DDF7}
.cap-journey-list .cjl-sep{color:var(--bridge);margin:0 4px}
.panel-content .cap-source{flex:none;width:100%;margin-top:48px}.panel-content .proof-cases{flex:none;width:100%;margin-top:40px;gap:72px}
.proof-cases .panel-cta{margin-top:auto}   /* push case CTAs to the bottom so the two cards align */
/* Concept C responsive: stack graphic over text; callouts and cases stack too */
/* Tablet STACKS: graphic on top, copy beneath — same shape as mobile (2026-08-06).
   History, so nobody flips this twice: it originally stacked at 1024, was changed to hold two
   columns down to 820 because stacking "made a 900px tablet look like a phone with a lot of
   dead width", then the two-column version was tuned (33/67 -> a fixed 430px copy column) and
   still read wrong, so it is back to stacking by choice.
   The dead-width complaint is addressed here by NOT reusing the phone sizing: the phone caps
   the graphic at 360px, tablet gets 560px and a taller 340px box, so it fills the space it has
   instead of sitting in a column of its own width.
   REVERT to two columns: restore .panel-row{flex-direction:row}, put back
   .panel-media{flex:1 1 0;min-width:0;align-self:center;height:300px} and
   .panel-content{flex:0 1 430px}, and drop the stacked rules below. */
@media(max-width:1024px){
  /* padding-top is 96, not 72, on purpose. .panel-head is nudged down 32px by position:
     relative/top:32px WITHOUT taking layout space, so it eats 32px of this padding: the gap you
     actually see is padding-top minus 32. At 72 that left 40px and the label crowded the box;
     at 28px on phones it was -4px, i.e. they overlapped. 96 -> 64px of real gap. */
  .panel-row{flex-direction:column;gap:48px;padding:96px 40px 48px}   /* generous gap so the two former columns stay distinct once stacked */
  /* no max-width: stacked, the media box takes the SAME width as the copy below it, so the
     box behind the chart lines up with the text on both edges. The graphics do not grow with
     it — each one is already at its own px cap (.cat-curve 560, .cap-journey-wrap 440,
     .proof-curve 440), so they keep their size and simply centre in a wider box. */
  .panel-media{flex:none;width:100%;max-width:none;align-self:flex-start;height:340px}
  .panel-content{flex:none;width:100%}
  /* smaller viewports: shrink the panel graphics ~15% (they read a touch big) */
  /* .panel-anim scale(.85) moved to the base rule — it applies at every width now. The old
     .cat-curve scale(1.05) is also gone: it compensated for a cramped two-column box, and
     stacked it just pushed the curve back outside its own max-height. */
  .panel-content .shift-h{font-size:32px;line-height:40px}
  .panel-content .proof-cases{display:flex;flex-direction:column;gap:56px}   /* generous gap between the stacked cases (override the old display:block) */
  .proof-cases .panel-cta{margin-top:24px}                      /* stacked: normal spacing (auto collapses to 0 here) */
}

@media(max-width:640px){
  /* Two-line hero headline on phones (2026-08-06). The break we want is
     "Beat your number" / "without hiring for it.", and the second line is the binding one:
     at the base 40px floor it measures 347px against 342px of available width on a 390px
     phone, so it lost "it." to a third line by FIVE pixels. Bigger phones (414, 430) already
     fit and are left alone.
     min() means this can only ever shrink the headline, never grow it: it takes the normal
     fluid size unless that would not fit on two lines. The h1 spans .hero-top, which is inset
     24px each side here, hence 100vw - 48px. 0.112 comes from 40/347 (the ratio at which the
     line exactly fills the width) with ~3% held back for kerning differences between the
     canvas measurement and real layout.
     If the headline copy changes, re-measure: the constant is tied to this exact string. */
  h1{font-size:min(clamp(40px, 7vw, 72px), calc((100vw - 48px) * 0.112))}
  .panel-row{padding:64px 20px 32px;gap:24px}
  /* 24px, not the 48 used from tablet up: the testimonial heading is 28px here against 40 on
     desktop, so the same gap reads far larger. 24 is the site's standard small stack gap
     (.body, .shift-body, .shift-sub all use it). */
  .testi-say > .sec-label{margin-bottom:24px}   /* 64 - 32 (see .panel-head note above) = 32px of real gap; was -4, i.e. overlapping */
  .panel-media{max-width:none;height:240px}   /* shorter graphic box on phones; folded in from the old 820px breakpoint 2026-08-06 */
  .panel-content .shift-h{font-size:28px;line-height:34px}
  .panel-content .cat-tiers{margin-top:24px}
  .shift-panel[data-panel="shift"] .panel-anim{transform:translateY(8px) scale(.85)}   /* mobile only: nudge the shift graphic down a touch */
}

/* "See it work" — lead capture on the dark background */
/* "See it work" FORM styles still live in the shared seeit.css (loaded in <head>) —
   the form now only appears inside the call modal. The closing section below is
   homepage-only, so its layout lives here rather than in seeit.css, which is still
   shared with how-it-works.html and use-cases.html and must not change for them.


/* footer (.footer/.footer-card/.footer-nav/.footer-wordmark/.footer-copy) now lives in shell.css (single source) */

@media(max-width:900px){
  .nav-in{padding:0 24px}
  .navlink,.nav-cta{display:none}
  .nav-burger{display:inline-flex}
}
@media(max-width:1024px){
  /* --- mobile + tablet base: stacked layout (phone sizes here; tablet sizes overridden in the 641–1024 block below) --- */
  /* --- header: logo left, hamburger right; hides on scroll-down, returns on scroll-up (like desktop) --- */
  nav{top:0}
  .menu-fab{display:none}              /* replaced by the header hamburger */
  .nav-in{height:56px;padding:0 16px}
  .nav-burger{display:inline-flex}
  /* --- tap the hamburger: a full-viewport menu takes over, burger morphs to an X --- */
  nav.menu-open{z-index:100}                       /* lift the whole nav above the cookie card etc. */
  .brand,.nav-burger{position:relative;z-index:3}  /* logo + X stay above the overlay */
  .nav-right{position:fixed;inset:0;height:100vh;height:100dvh;z-index:2;
    display:flex;flex-direction:column;align-items:stretch;gap:0;
    background:var(--ink);padding:84px 24px calc(28px + env(safe-area-inset-bottom));overflow-y:auto;
    opacity:0;visibility:hidden;transform:translateY(-6px);
    transition:opacity .28s ease, transform .28s ease, visibility 0s .28s}
  nav.menu-open .nav-right{opacity:1;visibility:visible;transform:none;
    transition:opacity .28s ease, transform .28s ease}
  .nav-right .navlink{display:block;font-family:var(--display);font-weight:600;font-size:20px;color:var(--white);
    padding:20px 0;border-bottom:1px solid rgba(124,152,248,.14)}   /* same size/weight as the CTA buttons */
  .nav-right .navlink:first-of-type{border-top:1px solid rgba(124,152,248,.14)}
  .nav-right .navlink:hover{color:var(--soft)}
  .nav-cta{display:flex;flex-direction:column;gap:12px;margin-top:auto;padding-top:28px}
  .nav-cta .btn{width:100%}

  /* --- hero: particles + orb up top, text stacked bottom-left (Figma 226:1456) --- */
  .hero{height:100vh;height:100svh}   /* svh = stable visible height; unlike dvh it does NOT resize when the toolbar hides on scroll (no jump) */
  #dot-cards,.top-scrim,.hero-trail{display:none}
  #ava-sun{left:50%;top:30%;width:108px;height:108px}
  .radar{width:280px;height:280px;left:50%;top:30%;margin-left:-140px;margin-top:-140px}   /* radar re-enabled, sized ~2.3x the phone orb and centered on it */
  .ava-glow{left:50%;top:30%;width:210px;height:210px}
  .hero-blob{left:50%;top:30%;width:224px;height:224px}   /* tracks .ava-glow x1.067 */
  .hero-top{left:24px;right:24px;top:auto;bottom:24px;transform:none;
    align-items:flex-start;text-align:left;max-width:none}  h1{line-height:1.05;width:100%;max-width:none}   /* size handled by the fluid clamp on the base h1 */
  .body{width:100%;max-width:none;margin-top:24px}
  .emph{display:block;width:100%;max-width:none;margin-top:16px;line-height:24px}
  /* clients strip: stack the label above the scrolling logos (two rows), label centered */
  .clients{padding:24px 0}
  .clients-inner{flex-direction:column;align-items:center;gap:28px}
  .clients-label{text-align:center}
  .clients-marquee{width:100%;flex:none}
  .clients-at{display:none}
  /* stats: cards span the full content width (no side negative space); caption at a true 16px */
  .stats{position:static;min-height:0;padding:64px 0 72px}
  .stats-hold{display:none}
  .stat-card{width:100%;box-sizing:border-box;height:344px;zoom:1}   /* shorter overall */
  .stat-chart{transform:scale(.85);transform-origin:center}          /* charts 15% smaller for mobile */
  .stat-num{top:208px;font-size:56px;line-height:56px}               /* pulled up close to the graphic */
  .stat-cap{top:272px;left:20px;width:calc(100% - 40px);font-size:16px;line-height:22px}   /* sits higher -> more air below to the bottom edge */

  /* --- shift: accordion on mobile (reset the desktop horizontal-scroll layout, tap a header to expand) --- */
  .shift{padding:0;height:auto;margin-top:0}
  /* `background:` is a SHORTHAND, so this drops the section-grad image and leaves flat --void.
     That is deliberate (a `fixed` background is unreliable on iOS), but .lower-bg has to be
     flattened WITH it — it was not, so the gradient reappeared at the .shift/.lower boundary as
     a hard horizontal seam under the last panel. Change these two together. (2026-08-06) */
  .shift-pin,
  .lower-bg{position:static;height:auto;overflow:visible;display:block;background:var(--void);box-shadow:none}
  .lower-bg{position:absolute;inset:0}   /* .lower-bg must stay absolute; only its paint is shared above */
  .shift-inner{display:none}
  .shift-nav{display:none}
  .shift-spiral{display:none}
  .shift-panels{display:flex;flex-direction:column;gap:44px;width:auto;padding:44px 16px 56px;transform:none;min-height:0}
  .shift-panel,.shift-panel.on{display:flex;flex-direction:column;position:relative;width:auto;opacity:1;height:auto;max-height:none;padding:0}
  .shift-panel::before{top:-28px}
  .panel-head{align-self:flex-start;display:inline-flex;align-items:center;gap:8px;padding:0 20px;margin:0;box-sizing:border-box;
    background:none;border:none;border-right:1px solid var(--deep);text-align:left;cursor:default;
    font-family:var(--display);font-weight:500;font-size:16px;letter-spacing:2px;color:var(--surface)}
  .panel-head::before{width:8px;height:8px}   /* the square is .sec-label::before now */
  .shift-h{font-size:28px;line-height:34px;max-width:none;margin:0}
  .shift-panel>.shift-h{padding:28px 20px 0}.shift-body,.cat-tiers,.cap-source,.proof-cases{flex:none;width:auto;align-self:auto}
  .proof-cases{display:block}
  .proof-cases hr{display:block;border:none;width:auto;height:0;margin:24px 0 32px;align-self:auto}   /* invisible spacer: no divider line, keep the gap */
  /* when the two columns merge into one, give the SECOND block a generous gap so the two read as distinct */
  /* Fortune block: the single row does not fit a phone, so it stacks and the vertical
     divider becomes a rule under the logo. Figma only covers desktop. */
  .cap-source{flex-direction:column;align-items:flex-start;gap:16px}
  .cap-source-brand{align-self:stretch;padding:0 0 12px;border-right:none;border-bottom:1px solid var(--deep)}
  /* .shift-sub{margin:0 0 28px} REMOVED 2026-08-06. It double-spaced: everything that follows
     a .shift-sub already carries its own top margin (.shift-body 24px in the shift panel,
     .panel-cta 32px in the category panel), and these are flex children so margins do not
     collapse — the callout ended up 52px and 60px clear instead of the 24/32 desktop uses.
     Spacing after the callout is owned by the NEXT element, at every breakpoint. */
  .cap-source,.proof-cases{margin-top:48px}
  /* closing CTA: one step down through pairs the site already uses */
  /* --- testimonial: photos on top, words below --- */
  .testi{padding:40px 0 0}   /* the gap down to .closer is owned by .closer's padding-top, not split across both */
  .testi .cm-h{font-size:28px;line-height:34px}
  .testi-cols{display:block;margin-top:28px}
  .testi-visual{width:100%;max-width:420px}
  .testi-say{padding-top:48px}   /* generous gap so the two stacked columns stay distinct */
  .testi-cols{margin-top:32px}
  .testi-visual{margin-top:0}
  .testi-quote{font-size:18px;line-height:28px}
  .testi-say{display:block}
  .testi-logo{margin-top:80px}
  /* footer: pull the dotted card closer to the screen edges + shorter on mobile/tablet */
  .footer{padding:0 8px 8px}
  .footer-card{min-height:488px}   /* ~20% shorter than the 609px desktop card */
}

/* === BLUR REVEAL (removable feature) — each framed block (frame lines + label + text, all together) focuses in
   from blurry to sharp as it scrolls into view.
   To turn OFF: remove the "blur-reveal" class from the <body> tag (or delete this block and its matching <script>). === */
.blur-reveal .shift-panel{filter:blur(8px);opacity:.5;transform:scale(.97);transition:filter .6s ease, opacity .6s ease, transform .6s ease;will-change:filter,transform}
.blur-reveal .shift-panel[data-panel="shift"]{filter:blur(16px);opacity:.3;transform:scale(.9);transition-duration:.9s}   /* first block: bigger, more deliberate entrance */
.blur-reveal .shift-panel.blur-in{filter:blur(0);opacity:1;transform:none}
/* Panel CTAs, staged (2026-08-06). They used to arrive inside the panel's own blur, which
   made them the least noticed thing in the panel: by the time they resolved, the eye had
   already settled on the headline and the graphic. Now each one waits for its panel to land
   and then rises in on its own, so the button is the LAST thing that moves in the panel.
   .7s delay is chosen against the panel's own timings — the panel blur runs .6s and
   .panel-media 1s — so the CTA reads as a follow-on, not part of the same movement.
   Safe on every panel that has one: only the first panel uses the slower .9s blur, and it
   has no CTA, so a CTA is never fading in while its panel is still blurred.
   The two proof case CTAs stagger so the pair does not fire as a single bar.

   It POPS rather than rises (2026-08-06). The button appears at 60% of its size and expands
   to full, and the easing overshoots slightly past 1 before settling — that overshoot is what
   makes it read as a burst instead of a grow. The rise was dropped: a translate plus a scale
   plus an overshoot fight each other and the result reads as wobble, not impact.
   The fade is deliberately much shorter than the scale (.4s against .95s) so the button is
   already solid while it is still small. If both ran together you would only ever see it at
   full size and the expansion would be invisible. Both were stretched on 2026-08-06 (from
   .26/.55) because the pop read as too abrupt; the RATIO between them was kept, since that
   is what keeps the expansion visible. Start times were left alone.
   It scales about its own centre and is align-self:flex-start, so nothing else moves.
   No will-change: transform settles to none, and a permanently promoted layer renders the
   button text subtly soft for the rest of the session. */
.blur-reveal .shift-panel .panel-cta{opacity:0;transform:scale(.6);
  transition:opacity .4s ease .7s, transform .95s cubic-bezier(.34,1.56,.64,1) .7s}
.blur-reveal .shift-panel .proof-case:nth-child(2) .panel-cta{transition-delay:.86s}
.blur-reveal .shift-panel.blur-in .panel-cta{opacity:1;transform:none}
/* Testimonial entrance. Same expanding blur the cards use, but staged: the words land
   first and the photos follow. .testi-rise is only the trigger now (the observer tags it)
   — the animation lives on the children, because a filter on the wrapper would blur the
   photos and the text together as one block. */
.blur-reveal .testi-rise .testi-say > .sec-label{filter:blur(8px);opacity:0;transform:translateY(10px);
  transition:filter .5s ease .3s, opacity .5s ease .3s, transform .6s cubic-bezier(.16,1,.3,1) .3s}
.blur-reveal .testi-rise.blur-in .testi-say > .sec-label{filter:blur(0);opacity:1;transform:none}
/* The two photos, each on its own beat (2026-08-06). They used to blur in together as one
   wrapper at scale .93, which read as too soft to notice. Now each one GROWS from 78% of
   its size. Both are absolutely positioned and scale about their own centre, so they
   expand from exactly where they sit and move nothing else on the page.
   The portrait leads because it is the person being quoted; the product shot follows it.
   Easing is the hero's landing curve: quick out of the gate, long slow settle. Durations
   were stretched on 2026-08-06 (transform .95s -> 1.5s) to make the growth unhurried. The
   DELAYS were deliberately left alone: the ask was a slower move, not a later start.
   .testi-visual itself no longer animates — it only holds the blob and the portrait. */
.blur-reveal .testi-rise .testi-blob{opacity:0;transform:scale(.9);
  transition:opacity 1.6s ease .12s, transform 1.6s cubic-bezier(.16,1,.3,1) .12s}
.blur-reveal .testi-rise .testi-face{opacity:0;filter:blur(14px);transform:scale(.78);
  will-change:filter,transform;
  transition:opacity .9s ease .22s, filter 1.05s ease .22s, transform 1.5s cubic-bezier(.16,1,.3,1) .22s}
.blur-reveal .testi-rise.blur-in .testi-blob,
.blur-reveal .testi-rise.blur-in .testi-face{opacity:1;filter:blur(0);transform:none}
/* The spoken half, staged (2026-08-06). .testi-say dropped .testi-txt so its parts land
   in reading order instead of as one block: name (.45s), then the quote line by line
   (from .62s), then the Commerce logo. Same .testi-rise.blur-in trigger,
   so it stays in step with the photos, which start at .3s.
   The quote's per-line delays are MEASURED and set inline by home-testi-quote.js —
   line breaks depend on render width, so they cannot live in this file. */
.blur-reveal .testi-rise .testi-say .cm-h{filter:blur(8px);opacity:0;transform:translateY(14px) scale(.98);
  transition:filter .6s ease .45s, opacity .6s ease .45s, transform .7s cubic-bezier(.22,.61,.36,1) .45s;
  will-change:filter,transform}
.blur-reveal .testi-rise.blur-in .testi-say .cm-h{filter:blur(0);opacity:1;transform:none}
/* Hidden until the words exist, so a split can never flash the quote in then out.
   The 3s failsafe is what shows it if home-testi-quote.js fails to run at all. */
.blur-reveal .testi-rise .testi-quote{opacity:0;animation:testiQuoteFailsafe 0s 3s forwards}
.blur-reveal .testi-rise .testi-quote.split{opacity:1;animation:none}
@keyframes testiQuoteFailsafe{to{opacity:1}}
/* inline-block is required: transforms do not apply to plain inline elements. */
.blur-reveal .testi-rise .testi-quote .tq-w{display:inline-block;opacity:0;transform:translateY(14px);
  transition:opacity .55s ease, transform .8s cubic-bezier(.16,1,.3,1)}
.blur-reveal .testi-rise.blur-in .testi-quote .tq-w{opacity:1;transform:none}
.blur-reveal .testi-rise .testi-logo{opacity:0;transform:translateY(10px);
  transition:opacity .5s ease, transform .65s cubic-bezier(.16,1,.3,1)}
.blur-reveal .testi-rise.blur-in .testi-logo{opacity:1;transform:none}
/* ---- hal-leonard un-crop on the shift panel graphics (2026-08-04) ---------
   All four panels: shift, category, capacity, proof. Scoped to .panel-media,
   the image block, NOT the panel. Two reasons: each panel holds body copy and
   a 10% inset would slice ~120px off each side of it mid-reveal, and the
   reference only ever uses this on image blocks.
   Hooked to .blur-in, NOT .in — inside a .shift-panel the reveal script skips
   inner .reveal elements and CSS forces them visible, so .in never arrives here.
   SCALE is what makes it read as expanding, not the clip. The clip-path only
   LOOKS like growth when the content fills its frame edge to edge — true of the
   testimonial photo, false of these centred graphics, where un-cropping just
   reveals empty background. So these also scale .92 -> 1.
   Each panel's own blur entrance still runs underneath: panel 1 is the slow one
   (16px blur, .9s), the other three are lighter (8px, .6s).
   REVERT: delete this block. */
.blur-reveal .shift-panel .panel-media{
  clip-path:inset(10%);transform:translateY(40px) scale(.92);
  transition:clip-path 1s cubic-bezier(.215,.61,.355,1),
             transform 1s cubic-bezier(.215,.61,.355,1)}
.blur-reveal .shift-panel.blur-in .panel-media{
  clip-path:inset(0);transform:none}
@media (prefers-reduced-motion:reduce){
  .blur-reveal .shift-panel .panel-media{clip-path:none;transform:none;transition:none}}
@media (prefers-reduced-motion:reduce){
  .blur-reveal .shift-panel,
  .blur-reveal .shift-panel .panel-cta{filter:none;opacity:1;transform:none;transition:none}
  .blur-reveal .testi-rise .testi-say > .sec-label,
  .blur-reveal .testi-rise .testi-blob,
  .blur-reveal .testi-rise .testi-face,
  .blur-reveal .testi-rise .testi-say .cm-h,
  .blur-reveal .testi-rise .testi-quote,
  .blur-reveal .testi-rise .testi-quote .tq-w,
  .blur-reveal .testi-rise .testi-logo{filter:none;opacity:1;transform:none;transition:none;animation:none}
}

/* === iPad / tablet (641–1024px): mobile stacking, but tablet-appropriate sizing (bigger than phone) === */
@media (min-width:641px) and (max-width:1024px){
  /* hero: larger orb + headline, more breathing room */
  #ava-sun{width:216px;height:216px;top:34%}
  .ava-glow{width:480px;height:480px;top:34%}
  .hero-blob{width:512px;height:512px;top:34%}
  .radar{width:560px;height:560px;top:34%;margin-left:-280px;margin-top:-280px}   /* bigger radar for the larger tablet orb */
  #dot-cards{display:block}                 /* show Ava's floating bubbles on tablet (hidden only on phone) */
  /* mobile-menu CTAs side by side on tablet instead of full-width stacked */
  .nav-cta{flex-direction:row;gap:16px}
  .nav-cta .btn{flex:1;width:auto}
  .hero-top{left:56px;right:56px;bottom:56px}
  h1{line-height:1.05;max-width:760px}
  .body{max-width:620px;font-size:16px;margin-top:28px}
  .emph{max-width:620px;font-size:16px;margin-top:20px}
  /* clients strip back to one row on tablet */
  .clients{padding:32px 0}
  .clients-inner{flex-direction:row;align-items:center;gap:40px}
  .clients-label{text-align:left}
  .clients-at{display:inline}
  /* stats: keep all 4 desktop cards on one row; the JS fitter scales them (zoom) to fit the width */
  /* re-enable the desktop "shift rises over the pinned charts" reveal on tablet (the 4 charts fit on screen here) */
  .stats{position:sticky;top:0;min-height:100vh;justify-content:center;padding:80px 0}
  .stats-hold{display:block;height:44vh}
  /* ---- TABLET 2x2 STATS (removable, added 2026-08-06) --------------------------------------
     Four cards across meant the fitter scaled them to 0.68 at 900px, rendering the caption at
     11px and the number at 44px. Two per row instead: no scaling, type back at 100%, using the
     compact 344-tall card variant already defined in the max-width:1024 block above.
     TO REVERT to four across: delete this block, restore
       .stat-card{width:284px;max-width:none;height:420px}
     and set FOUR_UP_MIN back to 641 in home-stats-fit.js. Nothing else is involved.
     -------------------------------------------------------------------------------------------- */
  .stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,340px));justify-content:center;gap:24px}
  .stat-card{width:100%;max-width:340px;height:344px}
  .stat-chart{transform:none}
  /* .stat-num / .stat-cap deliberately NOT overridden here: the compact positions from the
     max-width:1024 block (num top 208 / 56px, cap top 272) belong to the 344-tall card. The
     old four-across values were top 264 / 336, sized for the 420-tall card, and would push
     the caption past the bottom edge of this one. Restore them if reverting to four across. */
  /* shift: tablet type scale + spacing (bigger than phone's 28px heads / 20px body) */
  .shift-panels{padding:56px 32px 80px;gap:56px}
  .shift-h{font-size:48px;line-height:56px}
  .shift-panel>.shift-h{padding:44px 40px 0}
  .panel-head{padding:0 32px}   /* narrower gutter only. The old font-size:18px here was a step DOWN from a 20px base; the base is 16 now (2026-08-06), so an override would have made tablet larger than desktop. */
  .testi{padding:56px 0 72px}
  .testi .cm-h{font-size:32px;line-height:38px}
  .testi-cols{display:flex;gap:40px;margin-top:36px}
  .testi-visual{max-width:none}
  .testi-say{padding-top:0;align-self:flex-start}
  .testi-cols{margin-top:64px}
  .testi-quote{font-size:18px;line-height:28px}
}

/* === MOBILE COVER (removable feature) — 2×2 compact charts on phone so the desktop "shift covers the pinned charts" reveal works.
   To turn OFF: remove the "mobile-cover" class from the <body> tag (or delete this block). === */
@media (max-width:640px){
  body.mobile-cover .stats{position:sticky;top:0;min-height:100svh;justify-content:center;padding:40px 0}
  body.mobile-cover .stats-hold{display:block;height:70vh}
  body.mobile-cover .stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
  /* min-height dropped from 280 (2026-08-06). It was 24-44px TALLER than the content, and with
     the caption bottom-aligned that slack all landed between the number and the caption — which
     is what made the number look stranded. Rows now size to their own content, so the only gap
     left is real. Cards are 44px shorter on the bottom row, 24px on the top. */
  body.mobile-cover .stat-card{width:auto;max-width:none;min-width:0;height:auto;min-height:0;box-sizing:border-box;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding:20px 16px 24px;zoom:1}   /* top-anchored: graphic, then number at a fixed offset, then caption flows below — keeps numbers aligned across cards */
  body.mobile-cover .stat-chart{position:static;left:auto;top:auto;width:auto;height:64px;transform:none;margin-bottom:8px}   /* 8, was 16: with the 8px margin-top below this the chart-to-number gap is 16px, down from 36 */
  body.mobile-cover .stat-chart img,body.mobile-cover .stat-chart svg{height:100%;width:auto;display:block}
  body.mobile-cover .c-348 .stat-chart img{transform:none}
  body.mobile-cover .stat-num{position:static;left:auto;top:auto;font-size:40px;line-height:44px;margin:8px 0 8px}   /* 8, was 20. Still a FIXED gap below the fixed-height graphic, so numbers stay aligned across cards no matter how the caption wraps. All values here are multiples of 8. */
  /* 14/20. Every 14px TEXT style on the site moved from 24 to 20 on 2026-08-06 (.cm-list li,
     .cm-list h4, .cm-cap .d, .pf-quote-txt cite) — 24 was too loose at this size. The 14px/26px
     rules were deliberately left alone: .cm-stage-pill, .cm-out-row and .cm-opp-label are all
     single-line nowrap pills where line-height sets the element HEIGHT, not the leading.
     margin-top:auto pushes the caption to the BOTTOM of the card, so the captions across a row
     end on the same line however many lines each one wraps to. The number stays put: it keeps
     its fixed offset under the graphic, which is what keeps 70% and 34.8% aligned. */
  body.mobile-cover .stat-cap{position:static;left:auto;top:auto;width:auto;font-size:14px;line-height:20px;margin-top:auto}
}

/* === phone (≤640) tweaks === */
@media (max-width:640px){
  /* "See it work" FORM mobile overrides live in the shared seeit.css.
     Closing CTA: the shard cluster stops being a side-by-side backdrop and just sits
     behind the text, and the copy drops to a readable size. */
  /* Commerce logo ~24% smaller on phones. 32 and 96 are both multiples of 8 and, crucially, the
     SAME scale factor (0.76) — picking the nearest 8-multiple to a flat 20% would have given
     32 and 104, i.e. 0.74 vs 0.83, which would stretch the wordmark relative to its icon.
     The 12px gap is 14 x 0.76 rounded to the 4 grid. */
  .testi-logo{gap:12px;margin-top:32px}   /* 32, not the 80 used from tablet up: at 80 the logo read as floating rather than as the attribution to the quote above it */
  .testi-logo img:first-child{width:32px;height:32px}
  .testi-logo img:last-child{width:96px;height:auto}
  /* the stage stops tracking the shard ratio and is sized by its own padding, so the
     cluster always finishes inside the section instead of running into the footer */
  .cap-source-logo{width:88px}                                     /* smaller FORTUNE logo */
  .clients-track{animation-duration:24s}                           /* logos scroll faster */
  .soc2-txt .t{font-size:12px}                                     /* smaller SOC 2 badge text */
  .soc2-txt .s{font-size:11px}
  #dot-cards{display:block}                                        /* show Ava's bubbles on phone too (one at a time, parked above the orb via JS) */
  .dot-card .ttl{font-size:11px}                                   /* nudge down so the longest snippet stays on one line even on small phones */
}


/* ===== A container box behind each panel graphic (2026-08-06, KEPT — blessed 2026-08-12) — Figma 879:5991 =====
   The four graphics sit straight on the panel background with nothing holding them, so they
   read as floating. This puts the Figma container behind each one: a vertical gradient from
   --ink at the top down to fully transparent at the bottom, so it reads as a solid base that
   dissolves into the panel instead of a hard-edged card. --ink (#0D1A3B) is one step lighter
   than the panel's own --ink-deep (#0A1330), which is what makes it register at all.

   THE BOX IS A LAYER, NOT THE ELEMENT. It is drawn as a ::before on .panel-media rather than
   as a background on .panel-media itself. That is the whole trick: .panel-media keeps its
   full width, so every graphic keeps the exact size it has today, while the visible box is
   inset to 70% of the column. Narrowing .panel-media instead would have shrunk all four
   graphics, because each one is sized as a percentage of it.
   70% is safe because the graphics' VISIBLE drawing only fills 60-66% of the column
   (measured: category 403px, capacity 366px, proof 375px of a 612px column). Both the box and
   the graphics are centred, so the drawing sits inside the box rather than being clipped.

   Widened ~15% on 2026-08-06 (430 -> 496px floor, 70% -> 80%). 496 is a multiple of 8; at the
   desktop column width of 612 the floor governs, so the box goes 430 -> 496. Above a ~1560px
   viewport the 80% takes over and it keeps growing with the column. The chart is untouched:
   both the box and the graphics are centred on .panel-media, so widening one keeps them
   concentric, and every graphic is already at its own px cap so none of them grow into it.

   The px FLOOR is load-bearing, do not remove it. A plain 70% breaks as the window
   narrows: the box keeps shrinking with the column, but each graphic stops shrinking when it
   hits its own px cap (.cat-curve 560, .cap-journey-wrap 440, .proof-curve 440), so below a
   ~1542px window the capacity chart is already wider than the box and spills out of it.
   The floor must stay above the widest ink (capacity, 382px) plus breathing room, so the box
   can never be narrower than the drawing it contains. 496 clears that comfortably. min(100%) then caps it on phones, where
   the column itself is under 430.

   FIXED HEIGHT ON PURPOSE. The box does not follow its content: all four graphics get an
   identically sized frame, and the copy column is free to run taller, which it does on the
   longer panels. Desktop was not uniform before (panel 1 was 320px, the rest 400px); the
   override below is one class deeper than the per-panel rules it replaces so it wins on
   specificity, not source order. Below 1025px every panel already shares one height.

   Top-aligned: .panel-media defaults to align-self:center, which dropped the box below the
   heading on the two panels whose copy runs taller than 400px. flex-start puts its top edge
   on the same line as the heading.

   REVERT: delete the word "panel-boxed" from <div class="shift-panels"> in index.html.
   Every rule here is scoped to that class and nothing else in the codebase references it. */
.shift-panels.panel-boxed .panel-media{align-self:flex-start;isolation:isolate}
.shift-panels.panel-boxed .panel-media::before{content:"";position:absolute;z-index:0;
  top:0;bottom:0;left:50%;transform:translateX(-50%);pointer-events:none;
  width:min(100%, max(496px, 80%));border-radius:var(--radius-sm);
  background:linear-gradient(180deg, var(--ink) 0%, rgba(13,26,59,0) 65%)}
/* the graphic sits above the box. The shift panel's graphic is a statically positioned
   <canvas>, which would otherwise paint UNDER an absolutely positioned ::before, so it needs
   a position for its z-index to take effect. */
.shift-panels.panel-boxed .panel-media > *{z-index:1}
.shift-panels.panel-boxed .panel-media > .panel-anim{position:relative}
@media(min-width:1025px){
  .shift-panels.panel-boxed .shift-panel .panel-media{height:400px}
}
/* Stacked (<=1024) the box is NOT inset. Side by side it sat at 70% so it did not swamp the
   copy beside it; stacked there is nothing beside it, and it should read as the same block as
   the text under it, so it takes the full width of .panel-media — which is now the copy's
   width exactly. The 430px floor is irrelevant here and would fight this on small phones. */
@media(max-width:1024px){
  .shift-panels.panel-boxed .panel-media::before{width:100%}
}
