/* site.css — the alternative design direction, applied to the real homepage.
   Self-contained: v2 pages load this INSTEAD of site.css, so nothing here can reach
   the live pages. Same class names and same markup as tools-home.html — only the
   styling differs, so the two can be compared like for like.

   Light is the bare :root and is what every visitor gets on arrival. Dark is opt-in
   only — [data-theme="dark"], set by the toggle and remembered. The OS preference is
   deliberately NOT honoured: the brief is that the site loads white. */

:root {
  color-scheme: light;
  --gs-ground:#F7F3EE; --gs-surface:#FAFBFC; --gs-surface-2:#F4F6F8; --gs-raised:#FFFFFF;
  --gs-fold:#232842; --gs-fold-ink:#EDEFF5; --gs-fold-ink-2:#9AA2B8; --gs-fold-rule:rgba(255,255,255,.14);
  --gs-ink:#1A1D2B; --gs-ink-2:#4C5163; --gs-ink-3:#767D90;
  --gs-rule:#E4E7EC; --gs-rule-2:#CED3DC;
  --gs-green:#1D3C24; --gs-indigo:#455189;
  --gs-on-accent:#FFFFFF; --gs-glow:.16; --gs-stars:.26;
  /* The tools fold is dark in BOTH themes, so the deep brand pair is unreadable on it
     (#1D3C24 on #232842 is ~1.5:1). These are the same hues lifted for that ground —
     the live page already does this for its eyebrow dot with --paid-bright. */
  --gs-fold-green:oklch(0.72 0.14 155); --gs-fold-indigo:oklch(0.70 0.13 273);
  --gs-fold-on-accent:#0B0D14;
  /* ---- tokens the tool pages need on top of the homepage's ---- */
  --gs-paid:var(--gs-green); --gs-organic:var(--gs-indigo);
  --gs-ink-faint:#656C7C; --gs-track:#EAEAEA; --gs-input-bg:var(--gs-surface-2);
  --gs-pos:oklch(0.52 0.14 152); --gs-neg:oklch(0.55 0.2 25);
  --gs-green-text:#1B5E20; --gs-green-bright:#2E7D32; --gs-red:#B3261E;
  --gs-focus-ring:rgba(26,29,43,.20); --gs-overlay:rgba(255,255,255,.55);
  --gs-shadow:0 1px 2px rgba(26,29,43,.05);
  --gs-hero-chip-rule:oklch(0.78 0.015 265);
  --gs-hero-chip-bg:color-mix(in srgb,var(--gs-ground) 80%,transparent);
  --gs-cta-wash:linear-gradient(100deg,color-mix(in srgb,var(--gs-indigo) 9%,var(--gs-ground)),color-mix(in srgb,var(--gs-green) 9%,var(--gs-ground)));
  --gs-cta-gradient:linear-gradient(100deg,var(--gs-indigo),var(--gs-green));
   --gs-foot-head-ink:var(--gs-ink);
    --gs-foot-icon:var(--gs-indigo);
   /* the tool page's name pill: green on the calculator, indigo on the auditor */
   --gs-tool-chip-roi:var(--gs-green); --gs-tool-chip-audit:var(--gs-indigo);
   --gs-tools-bg:var(--gs-ground);
  --gs-tools-ink:var(--gs-ink);
  --gs-tools-ink-2:var(--gs-ink-2);
  /* the tool cards are white in light mode, sitting on the white section; the
     accents they carry are the deep brand pair, readable on white */
  --gs-tool-card:var(--gs-raised);
  --gs-tool-card-ink:var(--gs-ink);
  --gs-tool-card-ink-2:var(--gs-ink-2);
  --gs-tool-card-rule:var(--gs-rule);
  --gs-tool-acc-g:var(--gs-green);
  --gs-tool-acc-i:var(--gs-indigo);
  --gs-tool-job-bg:var(--gs-cta-gradient);
  --gs-tool-job-ink:var(--gs-on-accent);
  --gs-tool-card-shadow:0 1px 2px rgba(26,29,43,.06),0 12px 32px rgba(26,29,43,.07);
  /* the preview panel inside each card (stats strip / checks) rides on the CTA gradient */
  --gs-widget-bg:var(--gs-cta-gradient);
  --gs-widget-ink:#FFFFFF;
  --gs-widget-ink-2:rgba(255,255,255,.72);
  --gs-widget-rule:rgba(255,255,255,.28);
  --gs-widget-acc-g:var(--gs-fold-green);
  --gs-widget-acc-i:var(--gs-fold-indigo);
  --gs-widget-pipeline:var(--gs-widget-ink);
  --gs-widget-check-dot:var(--gs-widget-ink);
  /* the 30-min session card carries the CTA gradient in light mode */
  --gs-agenda-bg:var(--gs-cta-gradient);
  --gs-agenda-ink:#FFFFFF;
  --gs-agenda-ink-2:rgba(255,255,255,.78);
  --gs-agenda-rule:rgba(255,255,255,.24);
  --gs-agenda-time:rgba(255,255,255,.85);
  --gs-agenda-dot:var(--gs-fold-green);
  --gs-agenda-fill:#FFFFFF;
  --gs-agenda-wash:rgba(255,255,255,.10);
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --gs-ground:#07080D; --gs-surface:#0E1018; --gs-surface-2:#141825; --gs-raised:#1A1D2B;
  --gs-fold:#0E1018; --gs-fold-ink:#EDEFF5; --gs-fold-ink-2:#9AA2B8; --gs-fold-rule:rgba(255,255,255,.09);
  --gs-ink:#EDEFF5; --gs-ink-2:#9AA2B8; --gs-ink-3:#6B7389;
  --gs-rule:rgba(255,255,255,.09); --gs-rule-2:rgba(255,255,255,.17);
  --gs-green:oklch(0.74 0.15 155); --gs-indigo:oklch(0.70 0.13 273);
  --gs-on-accent:#07080D; --gs-glow:.50; --gs-stars:.5;
    /* already lifted in dark mode, so the fold reuses them */
    --gs-fold-green:var(--gs-green); --gs-fold-indigo:var(--gs-indigo); --gs-fold-on-accent:#07080D;
  /* tool tokens, lifted for a near-black ground */
  --gs-paid:var(--gs-green); --gs-organic:var(--gs-indigo);
  --gs-ink-faint:#808AA1; --gs-track:rgba(255,255,255,.13); --gs-input-bg:var(--gs-surface-2);
  --gs-pos:oklch(0.74 0.15 152); --gs-neg:oklch(0.70 0.17 25);
  --gs-green-text:oklch(0.80 0.15 150); --gs-green-bright:oklch(0.76 0.16 150);
  --gs-red:oklch(0.70 0.17 25);
  --gs-focus-ring:rgba(237,239,245,.30); --gs-overlay:rgba(7,8,13,.6);
  --gs-shadow:0 1px 2px rgba(0,0,0,.45);
  --gs-hero-chip-rule:var(--gs-rule);
  --gs-hero-chip-bg:transparent;
  --gs-cta-wash:linear-gradient(100deg,color-mix(in srgb,var(--gs-indigo) 9%,var(--gs-ground)),color-mix(in srgb,var(--gs-green) 9%,var(--gs-ground)));
  --gs-cta-gradient:linear-gradient(100deg,var(--gs-indigo),var(--gs-green));
   --gs-foot-head-ink:#FFFFFF;
    --gs-foot-icon:#FFFFFF;
   /* the tool page's name pill reads plain white on the near-black ground */
   --gs-tool-chip-roi:#FFFFFF; --gs-tool-chip-audit:#FFFFFF;
   --gs-tools-bg:var(--gs-fold);
  --gs-tools-ink:var(--gs-fold-ink);
  --gs-tools-ink-2:var(--gs-fold-ink-2);
  --gs-tool-card:var(--gs-fold);
  --gs-tool-card-ink:var(--gs-fold-ink);
  --gs-tool-card-ink-2:var(--gs-fold-ink-2);
  --gs-tool-card-rule:var(--gs-fold-rule);
  --gs-tool-acc-g:var(--gs-fold-green);
  --gs-tool-acc-i:var(--gs-fold-indigo);
  --gs-tool-job-bg:transparent;
  --gs-tool-job-ink:var(--gs-tool-card-ink);
  --gs-tool-card-shadow:none;
  /* the preview panel keeps the card's own navy in dark mode */
  --gs-widget-bg:transparent;
  --gs-widget-ink:var(--gs-fold-ink);
  --gs-widget-ink-2:var(--gs-fold-ink-2);
  --gs-widget-rule:var(--gs-fold-rule);
  --gs-widget-acc-g:var(--gs-fold-green);
  --gs-widget-acc-i:var(--gs-fold-indigo);
  --gs-widget-pipeline:var(--gs-widget-acc-g);
  --gs-widget-check-dot:var(--gs-cta-gradient);
  /* dark mode keeps the card as the lighter panel it already was */
  --gs-agenda-bg:var(--gs-surface);
  --gs-agenda-ink:var(--gs-ink);
  --gs-agenda-ink-2:var(--gs-ink-2);
  --gs-agenda-rule:var(--gs-rule);
  --gs-agenda-time:var(--gs-indigo);
  --gs-agenda-dot:var(--gs-green);
  --gs-agenda-fill:var(--gs-cta-gradient);
  /* the wash sits on the agenda card, not on the page ground, so it mixes into
     --gs-surface. Mixing into --gs-ground (as --gs-cta-wash does, correctly, in
     its own context) computed the tint against a base 7 steps darker than the
     card and over-lifted the row. */
  --gs-agenda-wash:linear-gradient(100deg,color-mix(in srgb,var(--gs-indigo) 3%,var(--gs-surface)),color-mix(in srgb,var(--gs-green) 3%,var(--gs-surface)));

}
:root {
  --gs-head:"Helvetica Neue",Helvetica,Arial,sans-serif;
  --gs-ui:"Space Grotesk","Helvetica Neue",sans-serif;
  --gs-body:var(--gs-ui),"Helvetica Neue",sans-serif;
  --gs-accent:"Libre Caslon Text",Georgia,serif;
  --gs-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  --gs-max:76rem;
}

*{box-sizing:border-box}
html{-webkit-font-smoothing:antialiased}
body{margin:0;background:var(--gs-ground);color:var(--gs-ink);font-family:var(--gs-body);font-size:15px;line-height:1.6;
  transition:background-color .25s ease,color .25s ease}
/* On touch the colour swap is atomic, and body's transition is why it was not.
   With no view transition to hide it, this 250ms ease was the ONLY thing
   animating: the ground and the text that inherits from it crossfaded while
   every token-driven colour elsewhere - cards, chips, rules, the widgets - had
   already flipped on frame one. The page changed in two stages, which reads as
   the type and the colours not landing together. Dropping it lands the whole
   page in a single frame. The toggle's own pill keeps its slide: that is the
   control acknowledging the tap, and it is a composited transform on one 80px
   element, not a full-viewport repaint. */
@media (pointer:coarse){
  /* Only reached when view transitions are unavailable, where this 250ms ease
     was the single thing animating while every other colour snapped on frame
     one — the ground and type visibly landing apart. With a transition running
     the snapshot covers the swap and this is suppressed anyway. */
  body{transition:none}
}
a{color:inherit;text-decoration:none}
h1,h2,h3,h4,p{margin:0}
ul{margin:0;padding:0;list-style:none}
img{max-width:100%}
[hidden]{display:none!important}
.wrap{width:100%;max-width:var(--gs-max);margin-inline:auto;padding-inline:1.5rem}
.sweep{background-image:linear-gradient(100deg,var(--gs-indigo),var(--gs-green))}

/* ---------- header ---------- */
.site-header{position:sticky;top:0;z-index:30;padding:1rem 1.5rem 0;background:transparent}
.site-header>.wrap{display:flex;align-items:center;gap:1rem;padding:.5rem .55rem .5rem 1.4rem;
  border:1px solid var(--gs-rule);border-radius:999px;
  background:color-mix(in srgb,var(--gs-surface) 80%,transparent);backdrop-filter:blur(14px)}
.logo{display:inline-flex;flex-direction:column;align-items:flex-start;line-height:1}
.wordmark{font-family:var(--gs-accent);font-style:italic;font-weight:400;font-size:25px;letter-spacing:-.02em;padding:0 .06em 0 .02em;line-height:1.15}
.wm-a{color:var(--gs-green)} .wm-b{color:var(--gs-indigo)}
.byline{display:inline-flex;align-items:center;gap:.3em;margin-top:4px;font-family:var(--gs-body);font-size:10px;color:var(--gs-ink-3)}
/* Two knockouts of one artwork, swapped by theme. The white one is generated from the
   colour original by assets/make-white-logo.py: identical alpha, every visible pixel forced
   to white, so it is the same mark and not a redraw. 20:1 on the dark ground. */
.byline-logo{height:12px;width:auto;display:block}
.byline-logo.on-dark{display:none}
.nav{display:none;align-items:center;gap:2rem;margin-left:auto;font-family:var(--gs-ui);font-size:14.5px;font-weight:500}
.nav>li{position:relative}
.nav a,.nav button{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem 0;border:0;background:none;
  color:var(--gs-ink-2);font:inherit;cursor:pointer;transition:color .15s}
.nav a:hover,.nav button:hover,.nav a[aria-current="page"]{color:var(--gs-ink)}
.nav button[aria-expanded="true"] svg{transform:rotate(180deg)}
.nav button svg{transition:transform .15s}
.menu{position:absolute;left:50%;top:calc(100% + 14px);transform:translateX(-50%);min-width:19rem;padding:.5rem;
  border-radius:16px;border:1px solid var(--gs-rule);background:var(--gs-surface);box-shadow:0 18px 50px rgba(0,0,0,.18)}
.menu a{display:flex;flex-direction:column;gap:3px;padding:.7rem .85rem;border-radius:10px;cursor:pointer}
.menu a:hover{background:var(--gs-surface-2)}
.menu a>span{font-family:var(--gs-accent);font-style:italic;font-size:17px;line-height:1.25}
.menu a[data-tool="roi"]>span{color:var(--gs-green)}
.menu a[data-tool="audit"]>span{color:var(--gs-indigo)}
.menu a small{font-family:var(--gs-mono);font-size:11px;letter-spacing:.04em;color:var(--gs-ink-3)}
.menu .soon{font-family:var(--gs-mono)!important;font-size:11px!important;letter-spacing:.12em;text-transform:uppercase;
  color:var(--gs-ink-3)!important;padding:.7rem .85rem!important}
.nav-cta{display:inline-flex;align-items:center;gap:.6rem;padding:9px 9px 9px 19px;border-radius:999px;
  color:var(--gs-on-accent);font-family:var(--gs-ui);font-size:14px;font-weight:600;
  background-image:linear-gradient(100deg,var(--gs-indigo),var(--gs-green));transition:filter .15s}
.nav-cta:hover{filter:brightness(1.12)}
.nav-cta .arr{display:inline-flex;align-items:center;justify-content:center;height:26px;width:26px}
.nav-cta .arr svg{width:16px;height:16px}
/* A segmented control, not an icon button: both options and their words are always
   visible, so there is no guessing whether the label means the current state or the one
   you would switch to. Same sliding-chip control the calculator uses for its basis toggle.
    Sun and moon glyphs identify light and dark. */
.themeseg{position:relative;display:inline-flex;align-items:center;flex:none;
  border:1px solid var(--gs-rule);border-radius:999px;padding:3px;background:transparent}
.themeseg::before{content:"";position:absolute;top:3px;bottom:3px;left:3px;width:calc(50% - 3px);
  border-radius:999px;background:var(--gs-ink);
  transition:transform .32s cubic-bezier(.32,.72,0,1)}
:root[data-theme="dark"] .themeseg::before{transform:translateX(100%)}
.themeseg button{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;gap:.4rem;
  flex:1 0 auto;min-width:4.6rem;border:0;background:none;cursor:pointer;padding:6px 12px;border-radius:999px;
  font-family:var(--gs-ui);font-size:12.5px;font-weight:600;letter-spacing:.01em;color:var(--gs-ink-3);
  transition:color .2s}
.themeseg button:hover{color:var(--gs-ink-2)}
.themeseg button[aria-pressed="true"]{color:var(--gs-ground)}
.themeseg svg{width:13px;height:13px;flex:none}
:root[data-theme="dark"] .byline-logo.on-light{display:none}
:root[data-theme="dark"] .byline-logo.on-dark{display:block}
.burger{display:inline-flex;height:38px;width:38px;align-items:center;justify-content:center;border-radius:999px;
  border:1px solid var(--gs-rule);background:transparent;color:var(--gs-ink);cursor:pointer;flex:none}
.mobile-menu{margin:.6rem auto 0;max-width:var(--gs-max);border:1px solid var(--gs-rule);border-radius:20px;
  background:var(--gs-surface);overflow:hidden}
.mobile-menu ul{display:grid;gap:.2rem;padding:.9rem 1.1rem 1.1rem;font-family:var(--gs-ui);font-size:16px;font-weight:500}
.mobile-menu a{display:block;padding:.55rem .2rem;color:var(--gs-ink)}
.mobile-menu .sub{padding-left:1rem;font-family:var(--gs-accent);font-style:italic;font-size:17px}
.mobile-menu .sub[data-tool="roi"]{color:var(--gs-green)}
.mobile-menu .sub[data-tool="audit"]{color:var(--gs-indigo)}
.mobile-menu .mm-theme{display:flex;align-items:center;justify-content:space-between;gap:1rem;
  margin-top:.6rem;padding:.7rem .2rem 0;border-top:1px solid var(--gs-rule)}
.mobile-menu .mm-theme-label{font-family:var(--gs-mono);font-size:10.5px;letter-spacing:.17em;
  text-transform:uppercase;color:var(--gs-ink-3)}
/* inside the menu there is room, so the words stay even on a phone */
.mobile-menu .themeseg button{min-width:4.6rem;padding:6px 12px}
.mobile-menu .themeseg button span{display:inline}
/* The control lives in the header and only falls back to the burger menu when it
   genuinely will not fit. Measured: logo 159 + control 157 + burger 40 + gaps and padding
   needs a 467px viewport, so 480 is the handover. Exactly one copy shows at any width. */
@media (max-width:479px){ .site-header>.wrap>.themeseg{display:none} }
@media (min-width:480px){ .mobile-menu .mm-theme{display:none} }
.mobile-menu .mm-cta{margin-top:.5rem;padding-top:.8rem;border-top:1px solid var(--gs-rule)}
.mobile-menu .mm-cta .nav-cta{display:inline-flex;color:var(--gs-on-accent);
  padding:9px 9px 9px 19px}   /* .mobile-menu a would otherwise flatten it to .2rem */
@media (min-width:900px){
  .nav{display:flex}
  .burger,.mobile-menu{display:none}
}
@media (max-width:899px){
  .nav-cta{display:none}
  .mobile-menu .nav-cta{display:inline-flex}
  .logo{margin-right:auto}
}

/* ---------- shared bits ---------- */
.eyebrow{font-family:var(--gs-mono);font-size:11px;font-weight:500;letter-spacing:.17em;text-transform:uppercase}
.eyebrow-hero{display:inline-flex;align-items:center;border:1px solid var(--gs-rule);border-radius:999px;
  padding:7px 16px;color:var(--gs-ink-2)}
.eyebrow-hero::before{content:"";width:5px;height:5px;border-radius:50%;background:var(--gs-green);margin-right:.6rem;flex:none}
.eyebrow-chip{color:var(--gs-green)}
.btn{display:inline-flex;align-items:center;gap:.6rem;padding:12px 22px;border-radius:999px;
  font-family:var(--gs-ui);font-size:14.5px;font-weight:600;transition:filter .15s,border-color .15s,color .15s}
.btn svg{transition:transform .18s cubic-bezier(.32,.72,0,1)}
.btn-solid{color:var(--gs-on-accent);background-image:linear-gradient(100deg,var(--gs-indigo),var(--gs-green))}
.btn-solid:hover{filter:brightness(1.12)}
.btn-line{border:1px solid var(--gs-rule-2);color:var(--gs-ink)}
.btn-line:hover{border-color:var(--gs-indigo);color:var(--gs-indigo)}
.btn-white{background:var(--gs-fold-ink);color:var(--gs-fold)}
.btn-white:hover{filter:brightness(.94)}

/* ---------- fold 1 (homepage hero only) ----------
   Scoped with :has(.ctas) because the tool pages reuse .hero for a plain page head —
   unscoped, they inherited the centring and the -4.6rem pull that slid the headline
   under the sticky header. */
.hero:has(.ctas){position:relative;overflow:hidden;text-align:center;padding:3.5rem 0 5rem;margin-top:-4.6rem;padding-top:7rem}
/* The glow is the acquret gradient used as a light source. Its centre has to sit INSIDE
   the hero: the hero clips its overflow, so a circle centred above the top edge shows only
   its faintest outer band and reads as flat colour. */
.hero:has(.ctas)::before{content:"";position:absolute;top:-22rem;right:-14rem;width:56rem;height:56rem;border-radius:50%;
  opacity:var(--gs-glow);pointer-events:none;filter:blur(40px);
  background:radial-gradient(circle at 50% 50%,var(--gs-indigo) 0%,transparent 68%)}
.hero:has(.ctas)::after{content:"";position:absolute;inset:0;pointer-events:none;opacity:var(--gs-stars);
  background-image:
    radial-gradient(1.1px 1.1px at 12% 26%,var(--gs-ink-2) 50%,transparent 50%),
    radial-gradient(1px 1px at 78% 16%,var(--gs-ink-2) 50%,transparent 50%),
    radial-gradient(1.2px 1.2px at 34% 62%,var(--gs-ink-2) 50%,transparent 50%),
    radial-gradient(1px 1px at 63% 78%,var(--gs-ink-2) 50%,transparent 50%),
    radial-gradient(1.1px 1.1px at 88% 50%,var(--gs-ink-2) 50%,transparent 50%),
    radial-gradient(1px 1px at 8% 80%,var(--gs-ink-2) 50%,transparent 50%)}
.hero:has(.ctas) .wrap{position:relative;z-index:1}
.hero-theme{display:flex;justify-content:center;margin-bottom:1.25rem}
.hero-theme .themeseg{background:var(--gs-hero-chip-bg)}
.hero-theme .themeseg button{min-height:40px;min-width:5rem}
.hero:has(.ctas) .eyebrow-hero{position:relative;max-width:100%;border-color:transparent;background:var(--gs-hero-chip-bg);text-wrap:balance}
/* The pill's edge is the CTA gradient drawn as a 1px ring — mask-composite keeps the
   pill's own background untouched, so it reads vivid in light and dark alike. */
.hero:has(.ctas) .eyebrow-hero::after{content:"";position:absolute;inset:-1px;border-radius:999px;padding:1px;
  background:var(--gs-cta-gradient);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;pointer-events:none}
.hero:has(.ctas) .eyebrow-hero .t{min-width:0}

/* The eyebrow shortens before it wraps. Measured at the type below: the full
   sentence needs 496px of pill, so it wraps on anything under a 544px viewport —
   tablets included, not just phones. Dropping "A glimpse of" takes it to 386px,
   which clears 434px; tightening the type and the pill's side padding takes it to
   329px, which clears 377px. Under that it wraps, which is the right answer for a
   320-360px device rather than shrinking the type further.
   The dot stays vertically centred throughout: on one line that puts it on the
   line, which is the whole point of not wrapping. */
@media (max-width:543px){
  .hero:has(.ctas) .eyebrow-hero .wide-only{display:none}
}
@media (max-width:433px){
  .hero:has(.ctas) .eyebrow-hero{font-size:10px;letter-spacing:.12em;padding:7px 12px}
  .hero:has(.ctas) .eyebrow-hero::before{margin-right:.5rem}
}
.hero:has(.ctas) h1{font-family:var(--gs-head);font-weight:700;letter-spacing:-0.03em;line-height:1.06;
  font-size:clamp(34px,5.4vw,64px)}
.hero:has(.ctas) h1 .ln{display:block}
/* The accent word must never be orphaned onto its own line. Non-breaking spaces were
   doing this, but the <em> is display:inline-block, which opens a wrap opportunity a
   nbsp does not suppress in every engine: Safari broke there, Chromium did not. It
   only bites between 414px and ~480px, where “Make every stage of the” (366px) still
   fits but the whole line does not — precisely the big-phone band. An explicit nowrap
   is engine-independent, and the longest glued run is 232px, so it still clears 320px. */
.hero:has(.ctas) h1 .nb{white-space:nowrap}
.hero:has(.ctas) h1 em{font-family:var(--gs-accent);font-style:italic;font-weight:400;letter-spacing:-0.01em}
.hero:has(.ctas) h1 em.g{color:var(--gs-green)} .hero:has(.ctas) h1 em.i{color:var(--gs-indigo)}
.hero:has(.ctas) .lede{margin:1.5rem auto 0;max-width:48ch;font-size:16.5px;color:var(--gs-ink-2);text-wrap:balance}
/* balance, not pretty, below 900px: on a phone this runs to four lines and left
   alone the last one holds a single word at 24% of the measure. `pretty` only
   reworks the final line and could not help here; balance evens all four and
   lifts the last to 80%. The reverse of .consult h2, where a <br> leaves balance
   nothing to even out. Desktop inverts this — see the media query below. */
/* From 900px the lede is measured to the headline's own first line so it starts
   where the headline starts, instead of sitting in a narrow column beneath it.
   891px is that line's ink width once h1 hits its 64px ceiling, and 75.2vw is
   the same width while the clamp is still scaling (13.93px of line per px of
   font). It has to be `pretty`: balance minimises the longest line, so at any
   measure from 600px up it returns the same 560/570 split and throws the
   alignment away. Where `pretty` is unsupported the declaration drops and the
   base balance still gives two lines — inset, but never broken. */
@media (min-width:900px){
  .hero:has(.ctas) .lede{max-width:min(891px,75.2vw);text-wrap:pretty}
}
/* The hero's vertical budget on a phone is spent by the time the proof bullets
   arrive - on a small handset they start a full screen below the fold. This
   second sentence is the most expendable thing in that stack, so it goes below
   640px and buys the bullets back three lines. The leading space lives inside
   the span, so hiding it leaves no stray gap after "programs." */
@media (max-width:640px){ .lede-detail{display:none} }
.ctas{display:flex;justify-content:center;flex-wrap:wrap;gap:.8rem;margin-top:2.2rem}
.ctas .btn{justify-content:center;min-height:48px;isolation:isolate;position:relative;overflow:hidden;
  transition:background-position .55s var(--gs-ease),border-color .2s,color .2s}
.ctas .btn svg{flex:none}
.ctas .btn-solid{background-image:var(--gs-cta-gradient);background-size:100% 100%}
.ctas .btn-solid:hover{filter:brightness(1.12)}
:root:not([data-theme="dark"]) .ctas .btn-line .cta-label{background-image:var(--gs-cta-gradient);
  background-clip:text;-webkit-background-clip:text;color:transparent}
/* dark mode: the line CTA carries the solid CTA's gradient too, so it picks up the green
   the plain indigo edge never reached. The ring is that gradient drawn over the 1px border
   (mask-composite leaves the pill's own background alone), the label is the same gradient
   clipped to the text, and the arrow takes the gradient's green end. */
:root[data-theme="dark"] .ctas .btn-line{border-color:transparent;color:var(--gs-green)}
:root[data-theme="dark"] .ctas .btn-line::after{content:"";position:absolute;inset:0;border-radius:999px;padding:1px;
  background:var(--gs-cta-gradient);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;pointer-events:none}
:root[data-theme="dark"] .ctas .btn-line .cta-label{background-image:var(--gs-cta-gradient);
  background-clip:text;-webkit-background-clip:text;color:transparent}
.ctas .btn-line::before{content:"";position:absolute;inset:0;z-index:-1;
  background:var(--gs-cta-wash);opacity:0;transition:opacity .3s var(--gs-ease)}
.ctas .btn:is(:hover,:focus-visible){border-color:var(--gs-indigo)}
.ctas .btn-solid:focus-visible{filter:brightness(1.12)}
.ctas .btn-line:is(:hover,:focus-visible)::before{opacity:1}
.ctas .btn:focus-visible,.hero-theme button:focus-visible{outline:2px solid var(--gs-indigo);outline-offset:4px}
.proof{display:grid;grid-template-columns:minmax(0,1fr);gap:.8rem 2.2rem;margin:2.6rem auto 0;max-width:70rem;width:fit-content;
  font-family:var(--gs-mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--gs-ink-3)}
.proof span{display:grid;grid-template-columns:5px minmax(0,1fr);align-items:start;gap:.6rem;text-align:left;min-width:0}
.proof i{width:5px;height:5px;border-radius:50%;background:var(--gs-green);margin-top:.55em}
/* Type size and column count step up together, each jump gated on the width that step's
   longest line actually needs (measured, with slack), so a bullet never breaks onto a
   second line and the block never shrinks as the screen grows. One below the other until 840. */
@media (max-width:479px){.proof{font-size:12px;letter-spacing:.05em;gap:.7rem;max-width:22rem}}
@media (max-width:389px){.proof{font-size:10.5px}}
/* Below 360 the longest bullet only fits on one line once the tracking is pulled in. */
@media (max-width:359px){.proof{letter-spacing:.01em}}
@media (min-width:840px){.proof{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:920px){.proof{font-size:13px}}
@media (min-width:960px){.proof{font-size:14px;gap:1rem 3.5rem}}
@media (max-width:479px){.ctas{display:grid;grid-template-columns:minmax(0,1fr);max-width:20rem;margin-inline:auto}}

/* ---------- fold 2 ---------- */
.tools{background:var(--gs-tools-bg);color:var(--gs-tools-ink);border-block:1px solid var(--gs-rule);scroll-margin-top:5rem}
.tools .wrap{padding-block:3.25rem 5rem}
.tools .head{display:flex;flex-direction:column;align-items:center;gap:1rem;text-align:center}
.tools .eyebrow-chip{color:var(--gs-fold-green)}
.section-eyebrow{display:flex;width:fit-content;max-width:100%;margin:0 auto 1rem;position:relative;
  border-color:transparent;background:var(--gs-hero-chip-bg);text-align:center;text-wrap:balance}
/* Same treatment as the hero eyebrow: the CTA gradient drawn as a 1px ring,
   mask-composite keeps the pill's own background untouched. */
.section-eyebrow::after{content:"";position:absolute;inset:-1px;border-radius:999px;padding:1px;
  background:var(--gs-cta-gradient);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;pointer-events:none}
.section-eyebrow .t{min-width:0}
.tools .section-eyebrow{color:var(--gs-tools-ink-2);background:var(--gs-hero-chip-bg)}
.tools .section-eyebrow::before{background:var(--gs-fold-green)}
/* the pill lifts clear of the card below it: the fold's top padding gives back the
   same measure the pill's own bottom margin adds, so the card never moves. */
.consult .wrap{padding-block:3.25rem 5rem}
/* No bottom margin here: .slab is a grid with a 32px row-gap, so a margin on the
   eyebrow is a second spacing on the same join. Together with the h2's own top
   margin that stacked three gaps - 28 + 32 + 14.4 = 74px between a label and the
   headline it labels, which on a phone reads as two unrelated blocks. The grid
   gap owns this join now. */
.consult .slab>.section-eyebrow{grid-column:1/-1;margin:0 auto;color:var(--gs-ink-2)}
.consult .consult-copy>h2:first-child{margin-top:0}
.tools h2{margin-top:.8rem;font-family:var(--gs-head);font-weight:700;letter-spacing:-0.025em;line-height:1.1;text-wrap:balance;
  font-size:clamp(26px,3.4vw,38px)}
/* 42rem, not 34rem: at 34rem this set as three lines of ~400px under a 575px
   headline. 42rem is the narrowest measure balance will resolve to two, giving
   603/600 — which runs 14px wider than "Pick a tool. Get a straight answer." on
   each side. That overhang is deliberate: this headline is short enough that the
   paragraph reads better spanning past it than boxed inside it. Balance stays,
   unlike the hero lede, because two even lines are the point here. */
.tools .head p{max-width:42rem;margin-inline:auto;color:var(--gs-tools-ink-2);font-size:15px;text-wrap:balance}
/* "Feel free to take them for a spin. More on the way." reads as one beat, so it
   is kept on one line — but it needs 356px and a 390px phone only offers 342, so
   the glue is released below the width where it physically fits (356 + 48px of
   gutters). Forcing it any narrower would push the paragraph out of the page. */
.tools .head .together{white-space:nowrap}
@media (max-width:403px){.tools .head .together{white-space:normal}}
.grid{display:grid;gap:1.5rem;margin-top:2.6rem}
.tool{display:flex;flex-direction:column;background:var(--gs-tool-card);border:1px solid var(--gs-tool-card-rule);border-radius:18px;overflow:hidden;box-shadow:var(--gs-tool-card-shadow)}
/* Subgrid so the two previews share one row height and the card bodies start on the
   same line — the same device the live page uses. */
@media (min-width:900px){
  .grid{grid-template-columns:1fr 1fr;grid-template-rows:auto 1fr}
  .tool{display:grid;grid-row:span 2;grid-template-rows:subgrid;row-gap:0}
}
.widget{display:grid;grid-template-rows:minmax(min-content,1fr) 4px auto;min-width:0;container-type:inline-size;
  padding:1.6rem 1.8rem;border-bottom:1px solid var(--gs-widget-rule);background:var(--gs-widget-bg)}
.widget>:first-child{align-self:start;margin-bottom:1.3rem}
.widget>.mixbar{margin-top:0}
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}
.stats .k,.widget-k{font-family:var(--gs-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--gs-widget-ink-2)}
.stats .v{margin-top:.35rem;font-family:var(--gs-ui);font-size:21px;font-weight:600;letter-spacing:-0.01em;color:var(--gs-widget-ink)}
.stats .v.g{color:var(--gs-widget-pipeline)}
.mixbar{display:flex;height:4px;border-radius:99px;overflow:hidden;margin-top:1.3rem;background:var(--gs-widget-rule)}
.mixbar .p{background:var(--gs-widget-acc-g)} .mixbar .o{background:var(--gs-widget-acc-i)}
.mixkey{display:flex;justify-content:space-between;margin-top:.6rem;
  font-family:var(--gs-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--gs-widget-ink-2)}
.mixkey .val{color:var(--gs-widget-acc-i)}
/* Two check-rows keep every pill exactly as wide as its own word: the rows are
   independent, so a short label can never be stretched to match a longer one.
   From 460px the rows dissolve (display:contents) and all four sit on one line. */
.checks{display:grid;gap:.45rem;justify-content:center}
.check-row{display:flex;gap:.45rem;justify-content:center}
.check{display:inline-flex;align-items:center;gap:.45rem;border:1px solid var(--gs-widget-rule);border-radius:999px;
  min-width:0;white-space:nowrap;padding:5px 9px;font-family:var(--gs-ui);font-size:12.5px;font-weight:700;color:var(--gs-widget-ink)}
.check i{width:5px;height:5px;border-radius:50%;flex:none}
.check.ok i,.check.flag i{background:var(--gs-widget-check-dot)}
@container (min-width:460px){.checks{display:flex;flex-wrap:wrap;justify-content:center}.check-row{display:contents}}
/* The panel label shares the .stats .k treatment and sits centred over the pills.
   It keeps to one line at every card width by tracking down before it shrinks. */
.widget-k{text-align:center;margin-bottom:.75rem}
@container (max-width:360px){.widget-k{letter-spacing:.09em}}
@container (max-width:320px){.widget-k{font-size:9px;letter-spacing:.06em}}
@container (max-width:280px){.widget-k{font-size:8.5px;letter-spacing:.03em}}
.tool .body{padding:1rem 1.8rem 1.8rem;display:flex;flex-direction:column;flex:1;container-type:inline-size}
.tags{display:flex;flex-wrap:wrap;gap:.4rem}
.tag{border:1px solid var(--gs-tool-card-rule);border-radius:999px;padding:4px 11px;
  font-family:var(--gs-mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--gs-tool-card-ink-2)}
.tag.job.g{color:var(--gs-tool-acc-g);border-color:color-mix(in srgb,var(--gs-tool-acc-g) 45%,transparent)}
.tag.job.i{color:var(--gs-tool-acc-i);border-color:color-mix(in srgb,var(--gs-tool-acc-i) 45%,transparent)}
.tools .tags{flex-wrap:nowrap;gap:.375rem}
.tools .tag{padding:5px 10px;font-size:13px;letter-spacing:0;white-space:nowrap;line-height:1.6}
:root:not([data-theme="dark"]) .tools .tag.job{background:var(--gs-tool-job-bg);color:var(--gs-tool-job-ink);border-color:transparent}
@container (max-width:360px){.tools .tag{font-size:12px;padding-inline:7px}}
@container (max-width:280px){.tools .tag{font-size:11px;padding-inline:4px}}
@container (max-width:240px){.tools .tag{font-size:10.5px;padding-inline:4px}}
@media (max-width:479px){.tools .tool .body{padding-inline:1.25rem}}
.tool h3{margin-top:1.2rem;font-family:var(--gs-accent);font-style:italic;font-weight:400;font-size:25px;line-height:1.25}
.tool:nth-child(1) h3{color:var(--gs-tool-acc-g)}
.tool:nth-child(2) h3{color:var(--gs-tool-acc-i)}
.tool .meta{margin-top:.55rem;font-family:var(--gs-ui);font-size:15px;font-weight:500;color:var(--gs-tool-card-ink)}
.tool .desc{margin-top:.5rem;color:var(--gs-tool-card-ink-2);font-size:14.5px}
.tool ul{display:grid;gap:.55rem;margin-top:1.2rem;font-size:14.5px}
.tool li{display:flex;gap:.6rem;align-items:flex-start;color:var(--gs-tool-card-ink)}
.tool li::before{content:"";flex:none;width:5px;height:5px;border-radius:50%;margin-top:.62em;background:var(--gs-tool-acc-g)}
.tool:nth-child(2) li::before{background:var(--gs-tool-acc-i)}
.tool .foot{margin-top:auto;padding-top:1.6rem}

/* the tool buttons carry the same gradient as the header "Get In Touch" CTA;
   on the white card the darker gradient reads on its own, no edge needed */
.tools .btn-solid{background-image:var(--gs-cta-gradient);color:var(--gs-on-accent)}
/* ---------- fold 3 ---------- */
.slab{display:grid;gap:2.5rem;border:1px solid var(--gs-rule);border-radius:20px;padding:2.5rem;background:var(--gs-surface)}
@media (min-width:900px){.slab{grid-template-columns:1.1fr 1fr;align-items:start;padding:3rem}}
.consult .eyebrow-chip{color:var(--gs-indigo)}
.consult h2{margin-top:.9rem;font-family:var(--gs-head);font-weight:700;letter-spacing:-0.025em;line-height:1.14;
  font-size:clamp(25px,3.2vw,36px);
  /* `pretty`, not `balance`. This heading carries a <br>, which splits it into
     separate runs, so balance has nothing to even out and measurably changes
     nothing: 335/485/120 either way, leaving “needed.” alone on the last line.
     `pretty` targets the orphan directly and pulls a word down with it. */
  text-wrap:pretty}
.consult h2 em{font-family:var(--gs-accent);font-style:italic;font-weight:400;color:var(--gs-indigo)}
.consult h2 em.g{color:var(--gs-green)}
.consult p{margin-top:1.1rem;color:var(--gs-ink-2);max-width:52ch}
.consult .side ul{display:grid;gap:.8rem}
.consult .side li{display:flex;gap:.7rem;align-items:flex-start;font-size:15px}
.consult .side li::before{content:"";flex:none;width:5px;height:5px;border-radius:50%;margin-top:.62em;background:var(--gs-indigo)}
.consult .side .btn{margin-top:1.8rem}
.consult .btn-white{color:var(--gs-on-accent);background-image:linear-gradient(100deg,var(--gs-indigo),var(--gs-green));background-color:transparent}

/* Selected consultation agenda. Homepage-only, liftable static styles. */
.consult-agenda .slab{border:0;border-radius:0;padding:0;background:transparent;gap:2rem}
.consult-agenda .consult-copy{min-width:0;text-align:center}
.consult-agenda .consult-copy p{margin-inline:auto}
.consult-agenda .consult-copy .btn{margin-top:1.8rem;max-width:100%}
.consult-agenda .session-agenda{min-width:0;border:1px solid var(--gs-agenda-rule);border-radius:16px;background:var(--gs-agenda-bg);padding:1.5rem}
.consult-agenda .session-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:1.4rem}
.consult-agenda .session-kicker{font-family:var(--gs-mono);font-size:10px;text-transform:uppercase;color:var(--gs-agenda-ink-2)}
.consult-agenda .session-head h3{margin-top:.3rem;font-family:var(--gs-head);font-size:18px;line-height:1.3;font-weight:700;color:var(--gs-agenda-ink)}
.consult-agenda .session-track{height:4px;border-radius:99px;background:var(--gs-agenda-rule);overflow:hidden;margin-bottom:1.2rem}
.consult-agenda .session-track span{display:block;width:100%;height:100%;background:var(--gs-agenda-fill);transform-origin:left}
.consult-agenda .session-steps{list-style:none;padding:0;margin:0}
.consult-agenda .session-step{position:relative;isolation:isolate;display:grid;grid-template-columns:4.2rem minmax(0,1fr);gap:1rem;padding:1rem 0;border-top:1px solid var(--gs-agenda-rule)}
.consult-agenda .session-step::before{content:"";position:absolute;inset:0 -.6rem;z-index:-1;border-radius:8px;background:var(--gs-agenda-wash);opacity:0}
.consult-agenda .session-time{font-family:var(--gs-mono);font-size:11px;color:var(--gs-agenda-time);padding-top:.15rem}
/* Dark only: the light panel already carries the gradient as its background, so
   there the timestamps stay white on top of it. Here the panel is flat, so the
   gradient moves into the type itself. Guarded on background-clip support —
   without it, color:transparent would render these invisible rather than plain. */
@supports ((background-clip:text) or (-webkit-background-clip:text)){
  :root[data-theme="dark"] .consult-agenda .session-time{
    background-image:var(--gs-cta-gradient);
    -webkit-background-clip:text;background-clip:text;
    color:transparent}
}
.consult-agenda .session-step h4{font-family:var(--gs-head);font-size:15px;font-weight:700;line-height:1.4;color:var(--gs-agenda-ink)}
/* balance on body copy here, not just headings: these sit in narrow columns, so
   left alone the last line holds one word — 8% of the measure on the consult
   paragraph at 414px, 11% on an agenda step. `pretty` only reached 18% and 21%
   because it reworks the final line alone; balance evens the block and lifts them
   to 75% and 67%. Degrades to normal wrapping where it is unsupported. */
.consult .consult-copy p,.consult-agenda .session-step p{text-wrap:balance}
/* Same treatment for the card copy and the two headings that wrap: measured 17%
   on the ROI card description at 430px and 8% on the footer note at 768px. */
.tool .desc,.tool .meta,.consult-agenda .session-step h4,.site-footer .fine-note{text-wrap:balance}
.consult-agenda .session-step p{margin:.35rem 0 0;font-size:13.5px;line-height:1.6;max-width:none;color:var(--gs-agenda-ink-2)}
@media (min-width:900px){.consult-agenda .slab{grid-template-columns:1.05fr 1fr;align-items:center;column-gap:3rem}}
@media (max-width:479px){.consult-agenda .session-agenda{padding:1.1rem}.consult-agenda .session-step{grid-template-columns:3.7rem minmax(0,1fr);gap:.65rem}}
@media (prefers-reduced-motion:no-preference){
  @keyframes gs-session-step{0%,30%{opacity:1}33.333%,100%{opacity:0}}
  @keyframes gs-session-track{0%,30%{transform:scaleX(.333333)}33.333%,63%{transform:scaleX(.666667)}66.666%,100%{transform:scaleX(1)}}
  .consult-agenda .session-agenda.is-in-view .session-step::before{animation:gs-session-step 7.2s var(--gs-ease) infinite}
  .consult-agenda .session-agenda.is-in-view .session-step:nth-child(2)::before{animation-delay:2.4s}
  .consult-agenda .session-agenda.is-in-view .session-step:nth-child(3)::before{animation-delay:4.8s}
  .consult-agenda .session-agenda.is-in-view .session-track span{animation:gs-session-track 7.2s var(--gs-ease) infinite}
}

/* The "did you mean …?" correction on the lead gates. Lives here because the tool pages'
   own copy was moved out when the chrome was centralised — without it the hint inherits
   the error style and renders as unlinked red text. */
.link-fix{border:0;background:none;padding:0;font:inherit;color:var(--gs-indigo);
  text-decoration:underline;text-underline-offset:2px;cursor:pointer}
.link-fix:hover{color:var(--gs-ink)}

/* ---------- footer ---------- */
.site-footer{border-top:1px solid var(--gs-rule);background:var(--gs-surface)}
.site-footer .wrap{display:grid;gap:1.5rem;padding-block:3rem 2rem}
:root .site-footer>.wrap{max-width:calc(var(--gs-max) + 3rem);padding-inline:calc(1.5rem + 1.4rem + 1px)}
@media (min-width:760px){.site-footer .wrap{grid-template-columns:1fr auto auto auto;gap:1.5rem 3rem;align-items:start}}
.site-footer h4{width:fit-content;margin-bottom:1rem;font-family:var(--gs-head);font-size:15px;font-weight:700;
  letter-spacing:-0.01em;text-transform:none;color:var(--gs-foot-head-ink)}
:root:not([data-theme="dark"]) .site-footer h4{background-image:var(--gs-cta-gradient);
  background-clip:text;-webkit-background-clip:text;color:transparent}
.site-footer ul{display:grid;gap:.62rem;font-family:var(--gs-mono);font-size:11.5px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--gs-ink-2)}
/* Every row is the same height in every column — icon rows included — so the Nth
   line of one section sits level with the Nth line of the section beside it. */
.site-footer li{display:flex;align-items:center;min-height:16px}
.site-footer li a{display:inline-flex;align-items:center;gap:.45rem;line-height:1;color:inherit}
.site-footer li a:hover{color:var(--gs-ink)}
.site-footer .founder svg{flex:none;width:13px;height:13px;color:var(--gs-foot-icon)}
.site-footer .founder:hover svg{color:var(--gs-ink)}
.site-footer .fine{grid-column:1/-1;padding-top:1.25rem;margin-top:0;border-top:1px solid var(--gs-rule);
  font-family:var(--gs-mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--gs-ink-3);order:10}
/* The acceptance notice only works if the link reads as one: inside .fine it
   inherits the surrounding muted colour, so without this it is invisible. */
.site-footer .fine a{text-decoration:underline;text-underline-offset:2px;
  text-decoration-color:color-mix(in srgb,currentColor 45%,transparent)}
.site-footer .fine a:hover{color:var(--gs-ink);text-decoration-color:currentColor}
.site-footer .fine-note{margin-top:.5rem;font-family:var(--gs-mono);font-size:10px;line-height:1.9;
  letter-spacing:.06em;text-transform:uppercase;color:var(--gs-ink-3);max-width:none}

/* ---------- motion ----------------------------------------------------------
   Resting state is always the finished one. Every animation here declares
   `both`, so it is the *trigger* that is optional: with no JS, no
   IntersectionObserver, or reduced motion, the page sits still and complete
   rather than parked at opacity 0 waiting for something.

   `html.gs-motion` is set by the inline head bootstrap, before first paint, so
   the hero can start hidden without the browser ever painting it visible.
   The reduced-motion guard at the very end of this file switches the lot off. */

:root{--gs-ease:cubic-bezier(.32,.72,0,1);
  /* Theme-swap speed. One knob: the outgoing layer's dissolve is derived from
     it below, so changing this alone keeps the two in step. */
  --gs-ap-dur:200ms}

/* Touch runs the dissolve rather than the aperture, and a dissolve needs longer:
   a circle opening across the viewport reads as motion at 200ms, a straight
   crossfade at the same speed reads as a cut — which is what made the swap feel
   like the screen blinking. Declared here, after the block above, because a
   media query adds no specificity: sitting earlier in the file it simply lost
   to the 200ms on source order. */
@media (pointer:coarse){
  :root{--gs-ap-dur:300ms}
}

@keyframes gs-fade{from{opacity:0}to{opacity:1}}
@keyframes gs-rise8{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes gs-glow-in{from{opacity:0;transform:scale(.92)}to{opacity:var(--gs-glow);transform:none}}
@keyframes gs-wipe{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@keyframes gs-dot{0%,100%{opacity:.25;transform:translateY(0)}50%{opacity:1;transform:translateY(-2px)}}
@keyframes gs-ping{0%{box-shadow:0 0 0 0 color-mix(in oklab,var(--gs-green) 45%,transparent)}
  70%,100%{box-shadow:0 0 0 7px color-mix(in oklab,var(--gs-green) 0%,transparent)}}
@keyframes gs-cta-down{
  0%{opacity:1;transform:none}
  45%{opacity:0;transform:translateY(9px)}
  46%{opacity:0;transform:translateY(-9px)}
  100%{opacity:1;transform:none}
}
@keyframes gs-cta-out{
  0%{opacity:1;transform:none}
  45%{opacity:0;transform:translate(7px,-7px)}
  46%{opacity:0;transform:translate(-7px,7px)}
  100%{opacity:1;transform:none}
}
@keyframes gs-cta-right{
  0%{opacity:1;transform:none}
  45%{opacity:0;transform:translateX(9px)}
  46%{opacity:0;transform:translateX(-9px)}
  100%{opacity:1;transform:none}
}

/* The hairline under each italic word is drawn rather than underlined, so it can
   be wiped in from the left and so it never sits on a descender. */
.hero:has(.ctas) h1 em{position:relative;display:inline-block}
.hero:has(.ctas) h1 em::after{content:"";position:absolute;left:.06em;right:.06em;bottom:.04em;height:1px;
  background:currentColor;opacity:.55;transform-origin:left}

/* C — edge ring. The old top accent line is gone (it sat on the same edge);
   a 1px CTA-gradient ring now traces the card's border on hover instead,
   drawn with the same mask-composite device as the eyebrow pill. Opacity
   only, nothing moves, the grid underneath stays intact. */
.tool{position:relative}
.tool::after{content:"";position:absolute;inset:0;border-radius:18px;pointer-events:none;z-index:3;opacity:0;
  padding:1px;background:var(--gs-cta-gradient);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
  transition:opacity .5s var(--gs-ease)}
.tool:focus-within::after{opacity:1}
@media (hover:hover){.tool:hover::after{opacity:1}}

/* D — the accent rule across the top of each fold, drawn as the fold enters.
   The green-to-indigo hairline that opened the tools fold now opens every section,
   so one device marks every boundary on the page. */
.tools,.consult,.site-footer{position:relative}
.tools::before,.consult::before,.site-footer::before{content:"";position:absolute;top:-1px;left:0;right:0;height:2px;z-index:1;
  transform-origin:left;background:linear-gradient(90deg,var(--gs-fold-green),var(--gs-fold-indigo))}

/* P — the pending dots on the email gate, used while the MX lookup is in flight. */
.gs-dots{display:inline-flex;align-items:center;gap:3px;margin-left:.45rem}
.gs-dots i{width:3px;height:3px;border-radius:50%;background:currentColor;opacity:.25}

@media (prefers-reduced-motion:no-preference){

  /* One directional beat per interaction, never a loop or a moving button.
     Hover is mouse-only; keyboard focus gets the same response. */
  .ctas .btn-solid:focus-visible svg{animation:gs-cta-down .55s var(--gs-ease) both}
  .ctas .btn-line:focus-visible svg{animation:gs-cta-out .55s var(--gs-ease) both}
  .nav-cta:focus-visible .arr svg{animation:gs-cta-right .55s var(--gs-ease) both}
  @media (hover:hover){
    .ctas .btn-solid:hover svg{animation:gs-cta-down .55s var(--gs-ease) both}
    .ctas .btn-line:hover svg{animation:gs-cta-out .55s var(--gs-ease) both}
    .nav-cta:hover .arr svg{animation:gs-cta-right .55s var(--gs-ease) both}
    .consult .btn-white:hover svg{animation:gs-cta-right .55s var(--gs-ease) both}
  }

  .consult .btn-white:focus-visible svg{animation:gs-cta-right .55s var(--gs-ease) both}

  /* H — hero on load. The glow blooms behind the headline, the lines step up
     70ms apart, and the two hairlines wipe in after the words they belong to. */
  .gs-motion .hero:has(.ctas)::before{animation:gs-glow-in 1.4s var(--gs-ease) both}
  .gs-motion .hero:has(.ctas) .eyebrow,
  .gs-motion .hero:has(.ctas) h1 .ln,
  .gs-motion .hero:has(.ctas) .lede,
  .gs-motion .hero:has(.ctas) .ctas .btn,
  .gs-motion .hero:has(.ctas) .proof span{animation:gs-rise8 .7s var(--gs-ease) both;
    animation-delay:calc(var(--i,0)*70ms)}
  .hero:has(.ctas) .eyebrow{--i:0}
  .hero:has(.ctas) h1 .ln:nth-child(1){--i:1}
  .hero:has(.ctas) h1 .ln:nth-child(2){--i:2}
  .hero:has(.ctas) .lede{--i:3}
  .hero:has(.ctas) .ctas .btn:nth-child(1){--i:4}
  .hero:has(.ctas) .ctas .btn:nth-child(2){--i:5}
  .hero:has(.ctas) .proof span:nth-child(1){--i:6}
  .hero:has(.ctas) .proof span:nth-child(2){--i:7}
  .hero:has(.ctas) .proof span:nth-child(3){--i:8}
  .hero:has(.ctas) .proof span:nth-child(4){--i:9}
  .gs-motion .hero:has(.ctas) h1 em.g::after{animation:gs-wipe .55s var(--gs-ease) both .55s}
  .gs-motion .hero:has(.ctas) h1 em.i::after{animation:gs-wipe .55s var(--gs-ease) both .67s}

  /* The eyebrow dots breathe — a soft ring expands and fades around them, ambient
     like the pending dots, its final frame matching the no-motion look. */
  .hero:has(.ctas) .eyebrow-hero::before,
  .section-eyebrow::before{animation:gs-ping 2.4s var(--gs-ease) infinite}

  /* The proof dots breathe the same way, but four of them firing together would
     strobe rather than breathe. Spreading the delays across the full cycle means
     roughly one dot is mid-ring at any moment, so the row reads as ambient
     rather than as four things demanding attention. The entrance stagger owns
     the first second, so these wait for it to finish. */
  .proof i{animation:gs-ping 2.4s var(--gs-ease) infinite 1s}
  .proof span:nth-child(2) i{animation-delay:1.6s}
  .proof span:nth-child(3) i{animation-delay:2.2s}
  .proof span:nth-child(4) i{animation-delay:2.8s}

  /* D — the dark fold announces itself once, on first entry: ground fades, the
     accent rule draws, the heading rises, the standfirst settles behind it. */
  .gs-motion .tools.is-in{animation:gs-fade .5s ease both}
  .gs-motion .tools.is-in::before,
  .gs-motion .consult.is-in::before,
  .gs-motion .site-footer.is-in::before{animation:gs-wipe .75s var(--gs-ease) both .18s}
  .gs-motion .tools.is-in h2{animation:gs-rise8 .65s var(--gs-ease) both .28s}
  .gs-motion .tools.is-in .head p{animation:gs-fade .55s ease both .48s}

  /* P — three dots, 120ms apart. */
  .gs-dots i{animation:gs-dot .75s ease-in-out infinite}
  .gs-dots i:nth-child(2){animation-delay:.12s}
  .gs-dots i:nth-child(3){animation-delay:.24s}

  /* M — aperture. The incoming theme opens from the control that was pressed,
     so the change reads as caused rather than global, and nothing on the page
     moves. The origin and the radius are measured from the live button in
     site.js: the control sits in the header on desktop and inside the burger
     menu below 480px, so a fixed origin would start the wipe in the wrong
     corner on a phone. Browsers without View Transitions get the plain
     crossfade below, which is the same change minus the shape. */
  @keyframes gs-fade-out{to{opacity:0}}
  @keyframes gs-aperture{
    from{clip-path:circle(0 at var(--gs-ap-x,100%) var(--gs-ap-y,0))}
    to{clip-path:circle(var(--gs-ap-r,150vmax) at var(--gs-ap-x,100%) var(--gs-ap-y,0))}
  }
  /* Held only while a view transition is running. Every colour transition on the
     page — body's 250ms ground, the .15s ones on the nav and segment controls —
     would otherwise still be mid-flight when the new snapshot is taken, and the
     aperture would reveal a half-changed page. */
  .gs-theming,.gs-theming *,.gs-theming *::before,.gs-theming *::after{transition:none!important}

  /* Timing is NOT the house easing here. `cubic-bezier(.32,.72,0,1)` is built for
     something crossing a measured distance; this radius has to overshoot the
     viewport to reach the far corner, so that curve put 66% of the growth in the
     first 130ms and then spent half a second creeping through the last 5% — off
     screen, past the corners. It read as a slash followed by a stall.

     Linear, deliberately. Any ease-out spends its tail decelerating through a
     last few percent of radius that is already past the corners, so the edge
     crawls to a halt somewhere you cannot see and the swap feels like it snags
     at the end. With no deceleration the edge is still travelling at full speed
     when it leaves the frame, and the dissolve lands on the same frame, so there
     is no dead air to sit through. (A growing circle also sweeps more area per
     millisecond as it widens, so linear radius already reads as accelerating —
     an ease-out here fights that rather than helping it.)

     Speed lives in --gs-ap-dur above. Much below 180ms the circle stops reading
     as an aperture and just looks like a cut; above ~350ms you wait for it.

     The outgoing snapshot also dissolves, slightly behind the circle. Two fully
     opaque images meeting at a 1px clip boundary is what makes the edge look cut
     rather than opened; once the old is partly gone the boundary softens on its
     own. It fades to the real page underneath, which is already the new theme,
     so there is nothing else to show through. */
  /* The UA also runs its own 250ms animation on the GROUP, which morphs the
     snapshot box between old and new. Here both are the viewport, so it changes
     nothing visible — but the transition is not finished until every animation
     is, so it held the overlay open for ~70ms after the aperture had landed:
     a dead frame at the end that no duration change could shift, because it was
     never ours. Pinned to the same clock. */
  ::view-transition-group(root){animation-duration:var(--gs-ap-dur)}
  ::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}

  /* Fallback for anything that runs a view transition without a fine pointer.
     Touch does not reach this - the JS takes the instant path there - but a
     stylus or hybrid device might, and a dissolve is the only cheap thing left:
     clip-path is not composited on WebKit, and a transform wipe is not a wipe
     at all, it slides the content instead of revealing it. */
  ::view-transition-old(root){z-index:1;animation:gs-fade-out var(--gs-ap-dur) linear both}
  ::view-transition-new(root){z-index:0}

  /* The aperture is an enhancement, because animating clip-path is only cheap
     where it is composited. WebKit does not composite it, so on iPhone it
     repainted the whole viewport every frame — the stutter on iOS Safari and
     iOS Chrome both. Progressive rather than exclusionary: the dissolve above
     is the default and this replaces it only where it is known to be cheap. */
  @media (pointer:fine){
    ::view-transition-old(root){z-index:0;
      animation:gs-fade-out calc(var(--gs-ap-dur)*.9) linear both calc(var(--gs-ap-dur)*.1)}
    ::view-transition-new(root){z-index:1;
      animation:gs-aperture var(--gs-ap-dur) linear both}
  }
}

/* R — scroll-driven reveal of the two tool cards. Pure CSS and gated on
   support, so where `view()` timelines are missing (Safari and Firefox today)
   the cards are simply already there. The preview bars stay still: their widths
   are the finished state, never animated. */
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .gs-motion .grid .tool{animation:gs-rise8 linear both;
      animation-timeline:view();animation-range:entry 5% cover 30%}
    .gs-motion .grid .tool:nth-child(2){animation-range:entry 12% cover 36%}
  }
}

@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ---------- header theme toggle (homepage) ---------- */
/* The full control lives above the hero eyebrow. Once it scrolls out of view,
   site.js sets .hd-theme-on and this compact icons-only copy appears inside the
   header pill, so the switch is never more than a glance away. It sits in the
   flex flow between the nav and the CTA (or the burger on small screens), so it
   can never overlap the logo, nav links or any other header item at any width. */
.hd-theme{flex:none;transform:scale(.9);opacity:0;visibility:hidden;
  transition:opacity .2s,transform .2s cubic-bezier(.32,.72,0,1),visibility .2s}
.site-header.hd-theme-on .hd-theme{opacity:1;visibility:visible;transform:scale(1)}
.hd-theme button{min-width:0;padding:5px 10px}
.hd-theme svg{width:15px;height:15px}
.hd-theme button:focus-visible{outline:2px solid var(--gs-indigo);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.hd-theme{transition:none}}
/* The legacy rule below 480px hides any header .themeseg; the compact copy is
   the exception — on phones it is the only always-available theme control. */
@media (max-width:479px){
  .site-header>.wrap>.hd-theme{display:inline-flex}
}
/* Below the desktop nav breakpoint the logo owns the left edge, so the toggle
   pushes itself (and the burger) to the right end of the pill. */
@media (max-width:899px){
  .hd-theme{margin-left:auto}
}

/* ---- Share row -------------------------------------------------------------
   One component, two homes: quiet in the footer, and under a prompt line once a
   tool has given someone a result. No Instagram button: there is no Instagram
   web share URL, and the native share sheet was the only route to it. */
.share{display:flex;align-items:center;gap:.7rem;flex-wrap:wrap}
.share-label{font-family:var(--gs-mono);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--gs-ink-faint)}
.share-btn{display:inline-flex;align-items:center;justify-content:center;
  width:38px;height:38px;border-radius:999px;border:1px solid var(--gs-rule-2);
  background:transparent;color:var(--gs-ink-2);cursor:pointer;
  transition:color .15s,border-color .15s,transform .15s}
.share-btn svg{width:17px;height:17px;fill:currentColor;display:block}
.share-btn:hover,.share-btn:focus-visible{color:var(--gs-indigo);border-color:var(--gs-indigo);
  transform:translateY(-1px)}
.share-btn.is-done{color:var(--gs-green);border-color:var(--gs-green);transform:none}
/* Dark mode gives each icon its own brand colour - on a near-black ground they
   read as the services they are, where in light mode the same colours would
   fight the page. The copy icon is the one that is nobody else's brand, so it
   takes ours: the indigo-to-green gradient, via a def site.js injects once. */
:root[data-theme="dark"] .share-btn[data-share="whatsapp"]{color:#25D366}
:root[data-theme="dark"] .share-btn[data-share="linkedin"]{color:#0A66C2}
:root[data-theme="dark"] .share-btn[data-share="x"]{color:#FFFFFF}
:root[data-theme="dark"] .share-btn[data-share="copy"] svg{fill:url(#gs-share-grad)}
:root[data-theme="dark"] .share-btn:hover,
:root[data-theme="dark"] .share-btn:focus-visible{border-color:currentColor;filter:brightness(1.15)}
/* LinkedIn's blue is the darkest of the four; lift its border so the button
   does not disappear into the ground when it is the one being hovered. */
:root[data-theme="dark"] .share-btn[data-share="linkedin"]:hover,
:root[data-theme="dark"] .share-btn[data-share="linkedin"]:focus-visible{border-color:#378FE9;color:#378FE9}
:root[data-theme="dark"] .share-btn[data-share="copy"]:hover,
:root[data-theme="dark"] .share-btn[data-share="copy"]:focus-visible{border-color:var(--gs-green)}
:root[data-theme="dark"] .share-btn[data-share="copy"].is-done svg{fill:currentColor}
.share-status{font-family:var(--gs-mono);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--gs-green)}
/* The row lifts in when its tool reveals a result; the footer copy never gets the class. */
@media (prefers-reduced-motion:no-preference){
  /* Scoped to the footer's own is-in, which site.js sets: .share never carried
     the class itself, so these rules matched nothing and the icons appeared
     flat. The prompt card that did carry it was removed. */
  .site-footer.is-in .share .share-btn{animation:gs-share-in .4s var(--gs-ease) both}
  .site-footer.is-in .share .share-btn:nth-child(3){animation-delay:.05s}
  .site-footer.is-in .share .share-btn:nth-child(4){animation-delay:.1s}
  .site-footer.is-in .share .share-btn:nth-child(5){animation-delay:.15s}
  @keyframes gs-share-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
}
/* Prompt form, used after a result. */
.share-prompt{display:grid;gap:.9rem;border:1px solid var(--gs-rule);border-radius:16px;
  padding:1.25rem 1.4rem;background:var(--gs-raised);margin:2rem 0 0}
.share-prompt p{margin:0;font-size:14.5px;line-height:1.55;color:var(--gs-ink-2);max-width:46ch}
.share-prompt strong{color:var(--gs-ink);font-weight:700}
.site-footer .share{margin-top:1.1rem}


/* Hiding the lede's second sentence put the last proof bullet within a pixel of
   a Pro Max fold, which is no margin at all once Safari's bars change height.
   These two gaps are the loosest in the phone hero, so they give back ~25px and
   put all four bullets clearly inside it. Phones only; the desktop rhythm is
   untouched. */
@media (max-width:640px){
  .hero:has(.ctas) .ctas{margin-top:1.5rem}
  .hero:has(.ctas) .proof{margin-top:1.7rem}
}

/* On a phone the hero pill costs 68px of the scarcest space on the site, and it
   was not buying exploration: a toggle tells you how to switch, not why. So
   below 640px the header's compact control is the only one, visible from the
   first paint rather than fading in once the hero pill scrolls away - it was
   already in the DOM, just gated. Desktop keeps the pill and the handover.
   Revisit when copy differs by mode: an invitation needs words, not size. */
@media (max-width:640px){
  .hero-theme{display:none}
  .hd-theme{opacity:1;visibility:visible;transform:none}
}
