/* Inter, self-hosted as one variable file (latin subset, weights 100-900).
   Replaces the Google Fonts stylesheet: no third-party CSS on the critical
   path and one font request instead of five. Glyphs outside the latin
   subset (₹, →) fall back to the system font exactly as they did before. */
@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(../fonts/inter-latin-var.woff2) format('woff2')}
/* The agent's greeting only (homepage hero): a display face with a hand in it. */
@font-face{font-family:'Bricolage Grotesque';font-style:normal;font-weight:600;font-display:swap;src:url(../fonts/bricolage-600-latin.woff2) format('woff2')}
:root{
  color-scheme:light;

  /* Logo DNA: navy carries the page, white is the ground, and blue is the
     signal square from the mark. Since 2026-09-19 each service also owns a
     colour (see the service tokens below) and its page wears it; blue stays
     the brand, the buttons and the logo. No gradients, no photography. */

  /* ground */
  --bg:#F8FAFC;
  --bg-2:#FFFFFF;
  --surface:#FFFFFF;
  --surface-sunken:#F1F5F9;
  --placeholder:#DCE3EC;
  --panel:#07162D;
  --panel-2:#0D2240;

  /* lines */
  --line:#E5EAF2;
  --line-strong:#CBD5E1;
  --line-invert:rgba(248,250,252,.14);
  /* A hairline that separates is decorative and may stay faint; a line that
     outlines a control (an input, a chip, a ghost button) is the only thing
     saying where the control is, so it clears 3:1 against its ground. */
  --control-line:#7E8DA1;                         /* 3.4:1 on white */
  --control-line-invert:rgba(248,250,252,.42);    /* 3.7:1 on the navy panel */

  /* ink */
  --ink:#07162D;
  --ink-2:#334155;
  --muted:#4F5D70;               /* 6.7:1 on white; was #64748B, which turned every paragraph pale */
  --muted-2:#475569;             /* labels and captions under 15px */

  /* on inverted panel */
  --on-panel:#F8FAFC;
  --on-panel-muted:#9FB0C8;

  /* accent — the blue square in the mark */
  --accent:#1E7BFF;
  --accent-hover:#1668E6;        /* 5.05:1 with white: the blue that may sit under or behind text */
  --accent-soft:rgba(30,123,255,.08);
  --accent-line:rgba(30,123,255,.32);
  --accent-on-panel:#4C94FF;

  /* service colours (2026-09-19). Blue stays the brand, every button and the
     logo’s square; each service also owns a colour, so colour says where you
     are: See = blue, Automate = saffron, Grow = green. Per service:
       base  marks, rules, dots and fills without text
       ink   the same hue as text on white or on its tint (all ≥4.5:1)
       soft  its section bands and tinted fields
       line  borders drawn in that colour
       tag   a fill that carries text, with on as that text’s colour
     --svc-* is the current service: blue by default, switched by .svc-see,
     .svc-auto or .svc-grow on a page’s <body> or on one card or row. */
  --see:#1E7BFF;  --see-ink:#1668E6;  --see-soft:#EDF4FF;  --see-line:#BCD5FF;  --see-tag:#1668E6;  --see-on:#FFFFFF;
  --auto:#FF9A1F; --auto-ink:#A94F00; --auto-soft:#FFF4E6; --auto-line:#FFCF94; --auto-tag:#FF9A1F; --auto-on:#07162D;
  --grow:#16B364; --grow-ink:#08733C; --grow-soft:#E8F7EE; --grow-line:#9FDEBD; --grow-tag:#16B364; --grow-on:#07162D;
  --svc:var(--see); --svc-ink:var(--see-ink); --svc-soft:var(--see-soft); --svc-line:var(--see-line); --svc-tag:var(--see-tag); --svc-on:var(--see-on);

  /* semantic — a gain reads as the signal, a flag as ink */
  --good:#1668E6;
  --warn:#334155;
  --good-invert:#7DB0FF;
  --warn-invert:#C9D4E3;

  /* focus */
  --focus:#1668E6;
  --focus-invert:#7DB0FF;

  /* type — Inter throughout, headings tight and heavy */
  --font-ui:Inter,'Neue Haas Grotesk','Helvetica Neue',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  --font-display:'Bricolage Grotesque',var(--font-ui);
  --font-body:var(--font-ui);
  --font-mono:var(--font-ui);

  --fs-h1:clamp(36px,5vw,64px);
  --fs-h2:clamp(34px,4.3vw,56px);   /* set at 600, not 700: a lighter weight can carry a larger size without shouting */
  --fs-h3:clamp(22px,2.6vw,28px);
  --fs-h4:clamp(18px,2vw,20px);
  --fs-body:clamp(16.5px,1.15vw,17px);
  --fs-small:15px;
  --fs-micro:13px;
  --fs-label:12px;

  /* space */
  --sp-1:4px;
  --sp-2:8px;
  --sp-3:12px;
  --sp-4:16px;
  --sp-5:24px;
  --sp-6:32px;
  --sp-7:48px;
  --sp-8:64px;
  --sp-9:96px;
  --sp-10:128px;

  /* layout */
  --maxw:1360px;
  --gutter:clamp(24px,4vw,64px);
  --maxw-text:720px;
  --maxw-narrow:520px;

  /* shape — sharp by default; nodes are squares */
  --r-sm:0;
  --r-md:0;
  --r-lg:0;
  --r-pill:0;

  /* elevation — hairlines, not shadows; a lift only on hover */
  --shadow-sm:none;
  --shadow-md:none;
  --lift:0 12px 32px -16px rgba(7,22,45,.25);

  /* motion — engineered, never playful */
  --t-press:120ms;                  /* a press answers the finger, 100–160ms */
  --t-fast:200ms;
  --t-base:280ms;
  --t-reveal:520ms;                 /* reveals: long enough to read as arriving, short enough that a fast scroll never meets an empty band (was 850) */
  --t-media:800ms;                  /* a figure opens a little slower than the copy beside it (was 1100) */
  --t-hover:350ms;                  /* an arrow glides; colour feedback stays on --t-fast */
  --t-data:900ms;
  --ease-out:cubic-bezier(.2,.7,.2,1);
  --ease-cine:cubic-bezier(.165,.84,.44,1);   /* power4.out: fast off the mark, long settle */
  --ease-media:cubic-bezier(.23,1,.32,1);     /* the hero video’s clip-open and settle */
  --ease:cubic-bezier(.4,0,.2,1);
}

*{margin:0;padding:0;box-sizing:border-box}
/* No grey tap flash on phones. Buttons, cards and rows answer a press with
   their own :active state instead; a plain link answers by navigating. */
html{scroll-behavior:smooth;scroll-padding-top:88px;-webkit-tap-highlight-color:transparent}
body{
  background:var(--bg);
  color:var(--ink);
  font-family:var(--font-body);
  font-size:var(--fs-body);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  /* Browser-drawn parts wear the palette too: the text caret, checkboxes and
     other native controls. */
  caret-color:var(--accent);
  accent-color:var(--accent-hover);
}
/* Icons drawn with round caps elsewhere get square terminals, matching the mark. */
svg [stroke-linecap]{stroke-linecap:butt}
svg [stroke-linejoin]{stroke-linejoin:miter}
a{color:inherit;text-decoration:none}
.mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums}
h1,h2,h3,h4,h5,.btn,.eyebrow,.nav-links a,.brand,th,label,button{font-family:var(--font-ui)}
h3,h4{line-height:1.3;letter-spacing:-.01em}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 var(--gutter)}
main{overflow-x:clip}
/* Read by screen readers, not shown. */
.sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip-link{position:absolute;left:-9999px;top:0;z-index:300;background:var(--ink);color:var(--bg);font-family:var(--font-ui);font-size:var(--fs-small);font-weight:600;padding:12px 20px;border-radius:0}
.skip-link:focus{left:0}
/* One rule covers every interactive element — simpler and more reliable than
   enumerating selectors, and nothing can ship without a visible focus ring. */
:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
section{position:relative}

/* ===== Scroll progress ===== */
.scroll-progress{position:fixed;top:0;left:0;right:0;height:2px;z-index:200;background:transparent;pointer-events:none}
/* Scaled, not resized: a width change is a layout pass on every scroll frame.
   No transition either; the scroll is the timeline. */
.scroll-progress i{display:block;height:100%;width:100%;background:var(--accent);transform:scaleX(0);transform-origin:0 50%}

/* ===== Nav ===== */
header.nav{position:sticky;top:0;z-index:100;background:var(--bg);border-bottom:1px solid transparent;transition:border-color var(--t-base) var(--ease),box-shadow var(--t-base) var(--ease)}
header.nav.scrolled{border-bottom-color:var(--line);box-shadow:var(--shadow-sm)}
.nav-inner{display:flex;align-items:center;justify-content:space-between;height:72px}
.brand{display:flex;align-items:center;gap:var(--sp-3);font-family:var(--font-ui);font-weight:700;letter-spacing:-.02em;font-size:18px;color:var(--ink)}
.brand .glyph{height:32px;width:auto;display:block;flex:0 0 auto;color:var(--ink)}
/* The separator between mark and name is the hairline from the lockup. */
.brand .wm{position:relative;padding-left:var(--sp-3);white-space:nowrap}
.brand .wm::before{content:"";position:absolute;left:0;top:3px;bottom:3px;width:1px;background:currentColor;opacity:.4}
.brand .wm span{font-weight:400}
.nav-links{display:flex;align-items:center;gap:var(--sp-6)}
@media(max-width:1279px){.nav-links{gap:var(--sp-5)}}
.nav-links a{color:var(--muted);font-family:var(--font-ui);font-size:var(--fs-small);font-weight:500;transition:color var(--t-fast) var(--ease)}
@media (hover:hover) and (pointer:fine){.nav-links a:hover{color:var(--ink)}}
/* Current section, no click required. The underline is drawn rather than
   toggled so it reads as a position indicator, not a hover state. */
.nav-links a{position:relative;padding-block:var(--sp-2)}
.nav-links a::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1.5px;background:var(--accent);transform:scaleX(0);transform-origin:left;transition:transform var(--t-base) var(--ease-out)}
.nav-links a.is-current{color:var(--ink)}
.nav-links a.is-current::after{transform:scaleX(1)}
.nav-cta{display:flex;align-items:center;gap:var(--sp-3)}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);font-family:var(--font-ui);font-weight:600;font-size:var(--fs-small);border-radius:0;padding:13px 22px;min-height:44px;cursor:pointer;border:1px solid transparent;white-space:nowrap;text-decoration:none;transition:background var(--t-fast) var(--ease),color var(--t-fast) var(--ease),border-color var(--t-fast) var(--ease),transform var(--t-press) var(--ease-out)}
/* A press you can see: 1px of travel on a 44px button read as nothing. */
.btn:active{transform:scale(.97)}
.btn-primary{background:var(--ink);color:var(--bg)}
@media (hover:hover) and (pointer:fine){.btn-primary:hover{background:var(--accent-hover);border-color:var(--accent-hover);color:#fff}}
.btn-ghost{background:transparent;border-color:var(--ink);color:var(--ink)}
@media (hover:hover) and (pointer:fine){.btn-ghost:hover{background:var(--ink);border-color:var(--ink);color:var(--on-panel)}}
.btn .arrow{transition:transform var(--t-hover) var(--ease-cine)}
@media (hover:hover) and (pointer:fine){.btn:hover .arrow{transform:translateX(5px)}}
/* Only buttons carry the arrow. On a text link
   the underline already says "this goes somewhere"; an arrow on every link
   was template chrome (premium pass). */
a:not(.btn) .arrow{display:none}
/* "Ask us": the chat, in the header on every page. The one filled button in the
   bar, so it is where the eye lands; the form CTA beside it is outlined. Same
   look as the corner launcher (navy, the blue signal square, the label), so the
   two read as one thing. Hidden until chat.js is ready, because without the
   script it would open nothing. */
/* The header button is a size down from the page buttons: 44px, 14px text. */
.nav-cta .btn{min-height:44px;padding:0 16px;font-size:14px;line-height:1.2}
.nav-ask{gap:9px;padding-inline:14px 16px;line-height:1.2}
.nav-ask[hidden]{display:none}
/* With JS on, the agent button is drawn from the first paint, so moving between
   pages never makes it pop in (chat.js starts after load). If chat.js can't
   start it marks html.no-chat and the button goes. */
.js .nav-ask[hidden]{display:inline-flex}
.no-chat .nav-ask{display:none!important}
/* The AI mark: a four-point star with straight edges, the blue accent. */
.nav-ask-ico{flex:none;width:16px;height:16px;fill:var(--accent)}
@media (hover:hover) and (pointer:fine){.nav-ask:hover .nav-ask-ico{fill:#fff}}
.nav-ask{position:relative}
.nav-ask-dot{position:absolute;top:-5px;right:-5px;width:12px;height:12px;background:var(--accent);border:2px solid var(--bg)}
.nav-ask-dot[hidden]{display:none}
.mm-cta .nav-ask{padding-inline:22px}
.nav-toggle{display:none;background:none;border:1px solid var(--control-line);border-radius:0;width:44px;height:44px;color:var(--ink);cursor:pointer;transition:transform var(--t-press) var(--ease-out)}
.nav-toggle:active{transform:scale(.95)}
/* ===== Mobile menu ===== */
.nav-toggle .nt-close{display:none}
.nav-toggle[aria-expanded="true"] .nt-open{display:none}
.nav-toggle[aria-expanded="true"] .nt-close{display:block}
.mobile-menu{position:fixed;top:72px;left:0;right:0;bottom:0;z-index:99;background:var(--bg);border-top:1px solid var(--line);padding:var(--sp-5);display:flex;flex-direction:column;gap:var(--sp-5);overflow-y:auto;overscroll-behavior:contain;
  transition:opacity 200ms var(--ease-out),transform 240ms var(--ease-out)}
/* Opening drops the menu 8px out from under the header, so it reads as coming
   from the bar the button sits in. Closing is instant: the visitor has
   already chosen where to go. */
@starting-style{.mobile-menu{opacity:0;transform:translateY(-8px)}}
/* An author display:flex beats the UA [hidden]{display:none}, so the menu
   stayed visible on load with the attribute correctly set. */
.mobile-menu[hidden]{display:none}
.mm-links{display:flex;flex-direction:column}
.mm-links a{font-family:var(--font-ui);font-size:clamp(24px,6.4vw,30px);font-weight:600;letter-spacing:-.02em;color:var(--ink);padding:var(--sp-4) 0;border-bottom:1px solid var(--line);min-height:44px;display:flex;align-items:center}
.mm-links a[aria-current="page"]{color:var(--accent)}
.mm-cta{display:flex;flex-direction:column;gap:var(--sp-3);margin-top:auto;padding-top:var(--sp-5)}
.mm-cta .btn{width:100%}
@media(min-width:1120px){.mobile-menu{display:none!important}}

/* ===== Section scaffolding ===== */
/* Premium pass (2026-09-19): a label above every heading, tracked out in caps
   and numbered like chapters, was the loudest template tell on the site. Section
   headings now speak for themselves; the label survives only where it says
   where you are (a page's own head, the contact card, the sent state), and it
   is set in sentence case beside the signal square. */
.eyebrow{display:inline-flex;align-items:center;gap:10px;font-family:var(--font-ui);font-size:var(--fs-small);font-weight:600;letter-spacing:-.005em;color:var(--ink-2);padding:0;border:0;background:none}
.eyebrow .dot{width:6px;height:6px;border-radius:0;background:var(--accent);flex:0 0 auto}
.section-head{max-width:var(--maxw-text)}
.section-head > h2.title:first-child{margin-top:0}
.section-head.center{margin:0 auto;text-align:center}
.section-head.center .eyebrow{justify-content:center}
/* A centred section head can carry a button row too (websites.html leads with
   one), and a flex row ignores the inherited text-align. */
.section-head.center .hero-cta{justify-content:center}
h2.title{font-size:var(--fs-h2);font-weight:600;letter-spacing:-.032em;line-height:1.05;margin:var(--sp-4) 0 var(--sp-3);text-wrap:balance}
.section-head p{color:var(--muted);font-size:var(--fs-body);text-wrap:pretty}
.section-head p strong{color:var(--ink);font-weight:600}
/* padding-block, not the four-side shorthand: .pad and .wrap share a
   specificity, so `padding: X 0` zeroed .wrap’s 24px gutter and every
   section touched the viewport edge below 1180px. */
/* Sections meet across two of these. With the bands now carrying colour the
   boundary no longer needs 208px of air to read, so the step came down:
   176px between sections on desktop, 96px on a phone (was 208 and 128). */
.pad{padding-block:clamp(48px,6vw,88px)}
/* The first section on an inner page opens on its own h1; the nav already sits above it. */
main > section:first-child.pad{padding-top:clamp(48px,6vw,88px)}
/* The closing band is one sentence and two buttons: half the air of a content section. */
section#next.pad{padding-block:clamp(48px,5vw,72px)}

/* ===== Section rhythm =====
   Sections alternate white / --bg-2 down the whole page. Without this the
   page read as three long zones rather than nine sections: five consecutive
   white sections, then one merged band, then two more white.
   The band is full-bleed via margin-inline:calc(50% - 50vw), which does not
   overflow the way left:50%/translateX(-50%) does. The hairline top rule is
   what makes the boundary land even where two grounds are close in value. */
.band{position:relative;isolation:isolate}
.band::before{
  content:"";position:absolute;inset:0;z-index:-1;
  margin-inline:calc(50% - 50vw);width:100vw;
  background:var(--bg-2);
  border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);
}

/* ===== Hero =====
   One message, one figure, one action. The headline and a single sentence on
   the left; on the right the four things we build, drawn as one system on the
   drafting sheet; the audience line underneath. Nothing else. */
.hero{padding-block:clamp(40px,5vw,72px) clamp(40px,5vw,64px);position:relative}
.hero-grid{display:grid;grid-template-columns:minmax(0,1.08fr) minmax(0,1fr);grid-template-rows:auto auto;column-gap:clamp(32px,5vw,80px);row-gap:clamp(32px,4vw,56px);align-items:center}
.hero-copy{grid-column:1;grid-row:1;position:relative;z-index:2}
.hero-trust{grid-column:1;grid-row:2;align-self:start}
/* Sized so the longest authored line ("Your business shouldn’t") fits its column:
   the copy column is ~48% of the viewport in the two-column layout, and full width when stacked.
   Set at 600 since the premium pass (800 read as shouting); the size is the
   one measured for 800, which the slightly narrower 600 fits with room. */
.hero h1{font-size:clamp(34px,3.75vw,54px);line-height:1.02;letter-spacing:-.036em;font-weight:600;margin:0}
/* The full stop is the signal square. */
.hero h1 .accent{color:var(--accent)}
.hero h1 .ln{display:block;overflow:hidden}
.hero h1 .ln > span{display:block}
.hero p.sub{margin:var(--sp-5) 0 0;font-size:clamp(17px,1.3vw,19px);line-height:1.5;color:var(--muted);max-width:38ch;text-wrap:pretty}
.hero-cta{display:flex;align-items:center;gap:var(--sp-5);margin-top:var(--sp-6);flex-wrap:wrap}
.hero .hero-cta .btn-primary{padding:16px 26px}
.link-cta{display:inline-flex;align-items:center;gap:var(--sp-2);font-family:var(--font-ui);font-size:var(--fs-small);font-weight:600;color:var(--ink);text-decoration:underline;text-underline-offset:5px;text-decoration-thickness:1px;min-height:44px}
@media (hover:hover) and (pointer:fine){.link-cta:hover{color:var(--ink);text-decoration-color:var(--accent)}}

/* Audience line: caption over a row of sectors. */
.hero-trust .ht-cap{margin:0;font-family:var(--font-ui);font-size:var(--fs-micro);color:var(--muted-2);max-width:52ch}
/* Under a centred page head (websites.html) the sector row centres with it. */
.hero-trust.center{margin:var(--sp-7) auto 0;text-align:center}
.hero-trust.center .ht-cap{margin-inline:auto}
.hero-trust.center .hero-logos{justify-content:center}
.hero-logos{display:flex;align-items:center;flex-wrap:wrap;gap:var(--sp-3) clamp(14px,1.5vw,20px);margin-top:var(--sp-3)}
.hero-logos .hl{display:inline-flex;align-items:center;gap:7px;font-family:var(--font-ui);font-size:var(--fs-micro);font-weight:600;letter-spacing:-.01em;color:var(--ink-2)}
.hero-logos .hl svg{width:15px;height:15px;color:var(--muted-2);flex:0 0 auto}

/* ===== Hero: the agent =====
   The site chatbot is the whole first screen, on navy: the orb (canvas, drawn
   by hero.js) keeps one size whatever happens, and the conversation lives
   under it. Idle it is one greeting, one input and one line of suggestions;
   once someone asks, the greeting gives way to the thread, which scrolls in
   the space left. One colour, the brand blue; nothing moves but the orb.
   Spec: docs/superpowers/specs/2026-10-01-hero-agent-console-design.md */
.ah{--ah-line:rgba(178,196,230,.13);--ah-line-2:rgba(178,196,230,.26);--ah-dim:#8496B2;--ah-raise:#0C1D3A;--ah-well:#081630;
  --orb:clamp(220px,calc(100svh - 472px),600px);
  position:relative;display:flex;flex-direction:column;align-items:center;height:calc(100svh - 72px);min-height:560px;
  padding:clamp(8px,2.5vh,28px) var(--gutter) clamp(20px,4vh,40px);color:var(--on-panel);
  background:radial-gradient(70vw 62vh at 50% 34%,rgba(30,123,255,.16),transparent 70%),linear-gradient(var(--panel),#061024)}
.ah[hidden]{display:none}
.ah-orb{position:relative;flex:0 0 var(--orb);height:var(--orb);width:100%;max-width:960px}
.ah-orb canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
.hc{flex:1 1 0;min-height:0;width:min(720px,100%);display:flex;flex-direction:column}
.hc:not(.is-talking){justify-content:center;padding-bottom:clamp(8px,3vh,40px)}

.ah-hello{margin:0 0 clamp(16px,2.6vh,26px);min-height:1.05em;text-align:center;font-family:var(--font-display);font-weight:600;
  font-size:clamp(34px,min(4.2vw,6.4vh),58px);line-height:1.05;letter-spacing:-.025em;color:var(--on-panel)}
.ah-caret{display:inline-block;width:.06em;height:.82em;margin-left:.06em;vertical-align:-.04em;background:var(--accent);animation:ahBlink 1s steps(2) infinite}
@keyframes ahBlink{50%{opacity:0}}
.hc.is-talking .ah-hello{display:none}

/* the thread */
/* one left edge, one right edge, one rhythm: 12px inside a turn, 28px between turns */
.hc-answer{display:none;flex:1 1 0;min-height:0;overflow-y:auto;overscroll-behavior:contain;flex-direction:column;gap:12px;padding:4px 0 20px;
  scrollbar-width:thin;scrollbar-color:var(--ah-line-2) transparent;
  -webkit-mask-image:linear-gradient(transparent,#000 16px);mask-image:linear-gradient(transparent,#000 16px)}
.hc.is-talking .hc-answer{display:flex}
.hc-answer > :first-child{margin-top:auto}
.hc-msg + .hc-q{margin-top:16px}
.hc-q{align-self:flex-end;max-width:80%;margin:0;padding:10px 14px;border-radius:12px;background:rgba(255,255,255,.07);font-size:15px;line-height:1.5;
  animation:ahRise 240ms var(--ease-out) both}
.hc-msg{animation:ahRise 320ms var(--ease-out) both}
.hc-body{display:flex;flex-direction:column;align-items:flex-start;gap:12px}
.hc-text{font-size:16.5px;line-height:1.6;color:var(--on-panel);text-wrap:pretty}
.hc-text p{margin:0}
.hc-text p+p,.hc-text p+ul,.hc-text ul+p{margin-top:10px}
.hc-text ul{margin:0;padding-left:18px}
.hc-text strong{font-weight:600}
.hc-text a{color:var(--on-panel);text-decoration:underline;text-decoration-color:var(--ah-line-2);text-underline-offset:4px}
.hc-typing{display:inline-flex;gap:5px;padding-top:9px}
.hc-typing i{width:6px;height:6px;background:var(--ah-dim);animation:ahDot 1s infinite}
.hc-typing i:nth-child(2){animation-delay:.15s}.hc-typing i:nth-child(3){animation-delay:.3s}
@keyframes ahDot{50%{opacity:.25}}
@keyframes ahRise{from{opacity:0;transform:translateY(6px)}}
.ah ::selection{background:rgba(30,123,255,.45);color:#fff}

/* the agent's page cards (chat.js cardNode), turned over for navy, in the one blue */
.hc .dc-card{width:100%;margin:0;padding:14px 16px;gap:3px;border-radius:12px;background:var(--ah-raise);border:1px solid var(--ah-line);color:var(--on-panel);
  --svc:var(--accent);--svc-ink:var(--on-panel);transition:border-color var(--t-fast) var(--ease);animation:ahRise 320ms var(--ease-out) both}
.hc .dc-card-k{font-size:15px;font-weight:600}
.hc .dc-card-r{font-size:14px;color:var(--on-panel-muted)}
.hc .dc-card-go{margin-top:4px;font-size:13.5px;color:#8DB6FF}
@media (hover:hover) and (pointer:fine){.hc .dc-card:hover{border-color:var(--ah-line-2)}}

/* the next step, after every answer */
.hc-next{display:flex;flex-wrap:wrap;align-items:center;gap:10px 24px;animation:ahRise 320ms var(--ease-out) 80ms both}
.hc-go{display:inline-flex;align-items:center;gap:8px;height:42px;padding:0 16px;border:0;border-radius:10px;cursor:pointer;background:var(--on-panel);color:var(--panel);font:600 14.5px/1 var(--font-ui);text-decoration:none;
  transition:transform var(--t-press) var(--ease-out),background-color var(--t-fast) var(--ease)}
.hc-go:active{transform:scale(.97)}
.hc-wa{color:var(--on-panel);font:500 14.5px/1.3 var(--font-ui);text-decoration:underline;text-decoration-color:var(--ah-line-2);text-underline-offset:5px}
@media (hover:hover) and (pointer:fine){
  .hc-go:hover{background:#fff}
  .hc-wa:hover{text-decoration-color:var(--on-panel)}
}

/* the one control */
.hc-ask{flex:none;display:flex;align-items:center;gap:6px;padding:6px 6px 6px 20px;border-radius:16px;background:var(--ah-well);border:1px solid var(--ah-line-2);
  box-shadow:0 24px 60px -30px rgba(0,0,0,.8);transition:border-color var(--t-fast) var(--ease),box-shadow 300ms var(--ease-out)}
.hc-ask:focus-within{border-color:rgba(141,182,255,.6);box-shadow:0 0 0 5px rgba(30,123,255,.12),0 24px 60px -30px rgba(0,0,0,.8)}
.hc-input{flex:1;min-width:0;height:50px;padding:0;border:0;border-radius:0;background:none;color:var(--on-panel);font:400 17px var(--font-ui);caret-color:#8DB6FF;outline:none}
.hc-input::placeholder{color:var(--ah-dim);opacity:1}
.hc-input:focus-visible{outline:none}
.hc-kbd{flex:none;display:inline-flex;align-items:center;gap:6px;padding:0 4px;font-size:12.5px;color:var(--ah-dim);white-space:nowrap;transition:opacity var(--t-fast) var(--ease)}
.hc-kbd span{display:inline-grid;place-items:center;height:22px;padding:0 6px;border-radius:6px;border:1px solid var(--ah-line-2);font-size:11.5px;color:var(--on-panel-muted)}
.hc-ask.filled .hc-kbd,.hc.is-talking .hc-kbd{opacity:0;pointer-events:none}
.hc-voice,.hc-send{flex:none;display:grid;place-items:center;width:50px;height:50px;border:0;border-radius:10px;cursor:pointer;
  transition:transform var(--t-press) var(--ease-out),background-color var(--t-fast) var(--ease),color var(--t-fast) var(--ease)}
.hc-voice:active,.hc-send:active{transform:scale(.94)}
.hc-voice{background:transparent;color:var(--ah-dim)}
.hc-voice[hidden]{display:none}
.hc-voice .on,.hc-voice[aria-pressed=true] .off{display:none}
.hc-voice[aria-pressed=true] .on{display:block}
.hc-voice[aria-pressed=true]{color:#8DB6FF;background:rgba(30,123,255,.14)}
.hc-send{background:var(--accent-hover);color:#fff}
.hc-send:disabled{opacity:.45;cursor:default}
@media (hover:hover) and (pointer:fine){
  .hc-voice:hover{color:var(--on-panel)}
  .hc-send:hover:not(:disabled){background:var(--accent)}
}

/* suggestions: one quiet line, wider than the box so it never wraps on desktop */
.hc-chips{align-self:center;width:max-content;max-width:calc(100vw - 2 * var(--gutter));display:flex;flex-wrap:wrap;justify-content:center;align-items:baseline;gap:6px 26px;margin-top:16px;font-size:14px}
.hc-chips[hidden]{display:none}
.hc-try{padding:4px 0;color:var(--ah-dim)}
.hc-chip{padding:4px 0;border:0;background:none;color:var(--on-panel-muted);font:500 14px var(--font-ui);cursor:pointer;
  text-decoration:underline;text-decoration-color:transparent;text-underline-offset:5px;transition:color var(--t-fast) var(--ease),text-decoration-color var(--t-fast) var(--ease)}
.hc-chip:disabled{opacity:.5;cursor:default}
@media (hover:hover) and (pointer:fine){.hc-chip:hover:not(:disabled){color:var(--on-panel);text-decoration-color:var(--ah-line-2)}}
.ah :is(.hc-chip,.hc-voice,.hc-send,.hc-go,.hc-wa,.dc-card):focus-visible{outline:2px solid var(--focus-invert);outline-offset:3px}
.hc-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}
/* one load sequence: the orb assembles (canvas), the greeting writes itself, then the control arrives */
.hc-ask{animation:ahRise 500ms var(--ease-out) 900ms both}
.hc-chips{animation:ahRise 500ms var(--ease-out) 1050ms both}
@media(max-width:600px){
  .ah{--orb:clamp(200px,calc(100svh - 540px),300px)}
  .hc-kbd{display:none}
  .hc-ask{padding-left:16px}
  .ah-hello{font-size:clamp(28px,8vw,36px)}
  .hc-chips{gap:2px 18px}
  .hc-try{width:100%;text-align:center}
}
/* The hero is the agent: the corner launcher waits until the page has moved on. */
.on-dark .dc-launch[aria-expanded=false]{opacity:0;visibility:hidden;pointer-events:none}
.on-dark .dc-nudge{display:none}

/* The header goes navy while it sits over the hero (hero.js sets .on-dark). */
.on-dark header.nav,.on-dark header.nav.scrolled{--bg:var(--panel);--ink:var(--on-panel);--muted:var(--on-panel-muted);--line:rgba(248,250,252,.12);--control-line:rgba(248,250,252,.3);
  background:var(--panel);color:var(--on-panel);border-bottom-color:transparent;box-shadow:none}
.on-dark header.nav .nav-ask{background:transparent;color:var(--on-panel);border:1px solid rgba(248,250,252,.3)}


/* ===== Hero entrance =====
   One choreographed sequence on load, driven by CSS animation rather than the
   scroll observer: the hero is above the fold. Every pre-state lives in a
   `from` keyframe with fill-mode:both, so reduced motion (animation:none) and
   any other failure leave each element at its natural state.
   Timeline: headline 80/160/240 · sub 460 · CTA 600 · rows 900-1140 · trust 820.
   The headline used to wait 380ms and take 760ms: it is the largest text on
   the page, and a masked line does not count as painted until it is in view,
   so LCP was landing on the paragraph instead. */
@keyframes heroLine{from{transform:translateY(112%)}to{transform:translateY(0)}}
@keyframes heroRise{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
@keyframes heroFrame{from{opacity:0}to{opacity:1}}
.hero h1 .ln > span{animation:heroLine 560ms var(--ease-out) both}
.hero h1 .ln:nth-child(1) > span{animation-delay:80ms}
.hero h1 .ln:nth-child(2) > span{animation-delay:160ms}
.hero h1 .ln:nth-child(3) > span{animation-delay:240ms}
.hero p.sub{animation:heroRise 620ms var(--ease-out) 460ms both}
.hero .hero-cta{animation:heroRise 620ms var(--ease-out) 600ms both}
.hero-trust{animation:heroRise 620ms var(--ease-out) 820ms both}
/* Inner pages open on a centred head rather than the two-column hero, but the
   arrival should feel the same, so it borrows the hero’s keyframes and cadence
   rather than inventing a second one. Same fill-mode:both contract: reduced
   motion and any failure land on the finished state.
   Timeline: eyebrow 100 · headline 60/140/220 · sub 420 · CTA 560 · trust 800 —
   compressed for the same LCP reason as the homepage hero. */
.page-hero .eyebrow{animation:heroRise 520ms var(--ease-out) 100ms both}
.page-hero h1 .ln{display:block;overflow:hidden}
.page-hero h1 .ln > span{display:block;animation:heroLine 560ms var(--ease-out) both}
.page-hero h1 .ln:nth-child(1) > span{animation-delay:60ms}
.page-hero h1 .ln:nth-child(2) > span{animation-delay:140ms}
.page-hero .section-head > p{animation:heroRise 620ms var(--ease-out) 420ms both}
.page-hero .hero-cta{animation:heroRise 620ms var(--ease-out) 560ms both}
.page-hero .hero-trust{animation:heroRise 620ms var(--ease-out) 800ms both}
.page-hero .hero-video{animation:heroFrame 900ms var(--ease) 600ms both}
.page-hero h1 .ln:nth-child(3) > span{animation-delay:220ms}

/* ===== Service-page hero with a video beside it =====
   The homepage’s split: copy left, the 3:2 loop right and above the fold,
   so the video is part of the hero rather than the section after it.
   Choreography follows the motion system’s rule — media first, then the
   headline lines, the supporting copy, the CTA last: the frame opens with a
   clip reveal (power4-out) while the footage inside settles from 1.08, and the
   type follows it in. Global reduced motion already sets animation:none, so
   that path lands on the finished layout. */
@keyframes mediaClip{from{clip-path:inset(0 0 100% 0)}to{clip-path:inset(0 0 0 0)}}
@keyframes mediaSettle{from{transform:scale(1.08)}to{transform:none}}
.page-hero.has-video{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.6fr);grid-template-rows:auto auto;column-gap:clamp(32px,5vw,80px);row-gap:clamp(24px,3vw,40px);align-items:center}
.page-hero.has-video .section-head{grid-column:1;grid-row:1;max-width:none;margin:0;text-align:left}
/* The three masked lines are authored breaks; at phone width the largest line
   ("Your website should") needs ≤36px to stay whole at 390, and once the
   split layout starts at 1001px the column is only ~47% of the viewport, so
   the desktop slope is set to fit that column. The video is the dominant
   element (1.6fr against the copy’s 1fr), so the column is ~38% of the
   viewport: 34px at 1001, 44px cap (the wrap stops growing at 1360, so the
   column is ~440px from 1488 up and 49 wraps). Measured at 360–1920. */
.page-hero.has-video .section-head .page-title{margin-inline:0;font-size:clamp(30px,9.2vw,56px)}
@media(min-width:1001px){.page-hero.has-video .section-head .page-title{font-size:clamp(34px,3.75vw - 3px,44px)}}
.page-hero.has-video .section-head > p{max-width:50ch}
.page-hero.has-video .hero-trust{grid-column:1;grid-row:2;align-self:start;margin:0}
/* Five sectors never fit one row of the narrow column, and a 4+1 wrap reads
   as an orphan. Three columns give a deliberate 3+2 at every width. */
.page-hero.has-video .hero-logos{display:grid;grid-template-columns:repeat(3,max-content);justify-content:start;gap:var(--sp-2) var(--sp-5)}
.page-hero.has-video .hero-video{grid-column:2;grid-row:1 / span 2;align-self:center;margin:0;max-width:none;overflow:hidden;
  animation:mediaClip 1.1s var(--ease-media) 80ms both}
.page-hero.has-video .hero-video video{aspect-ratio:3 / 2;animation:mediaSettle 1.5s var(--ease-media) 80ms both}
.page-hero.has-video .eyebrow{animation-delay:120ms}
.page-hero.has-video h1 .ln:nth-child(1) > span{animation-delay:80ms}
.page-hero.has-video h1 .ln:nth-child(2) > span{animation-delay:160ms}
.page-hero.has-video h1 .ln:nth-child(3) > span{animation-delay:240ms}
.page-hero.has-video .section-head > p{animation-delay:460ms}
.page-hero.has-video .hero-cta{animation-delay:600ms}
.page-hero.has-video .hero-trust{animation-delay:1250ms}
/* Stacked below the homepage’s breakpoint: copy, then the video at a size that
   still leaves the next section in view, then the sector row. */
@media(max-width:1000px){
  .page-hero.has-video{grid-template-columns:1fr;grid-template-rows:auto auto auto;row-gap:var(--sp-6)}
  .page-hero.has-video .section-head{grid-row:1}
  .page-hero.has-video .hero-video{grid-column:1;grid-row:2;width:100%;max-width:720px}
  .page-hero.has-video .hero-trust{grid-row:3}
}

/* ===== Hero video (service pages) =====
   A silent 30s loop rendered from motion/<page>.html. Framed like the other
   figures so its own pale ground doesn’t dissolve into the page. The pause
   button is always visible — it is the WCAG 2.2.2 control for motion that
   runs longer than five seconds, and a hover-only control does not exist on
   a phone. */
.hero-video{position:relative;max-width:1120px;margin:var(--sp-7) auto 0;border:1px solid var(--line-strong);background:var(--bg)}
.hero-video video{display:block;width:100%;height:auto;aspect-ratio:16 / 9}
.hv-toggle{
  position:absolute;right:var(--sp-3);bottom:var(--sp-3);
  width:36px;height:36px;display:grid;place-items:center;
  border:1px solid var(--ink);background:var(--surface);color:var(--ink);cursor:pointer;
  opacity:.72;transition:opacity var(--t-fast) var(--ease),background var(--t-fast) var(--ease),color var(--t-fast) var(--ease),transform var(--t-press) var(--ease-out);
}
.hv-toggle:focus-visible{opacity:1;background:var(--ink);color:var(--on-panel)}
@media (hover:hover) and (pointer:fine){.hv-toggle:hover{opacity:1;background:var(--ink);color:var(--on-panel)}}
.hv-toggle:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
.hv-toggle:active{transform:scale(.95)}
.hv-toggle svg{width:14px;height:14px}
.hv-toggle .hv-play{display:none}
.hero-video.is-paused .hv-toggle .hv-play{display:block}
.hero-video.is-paused .hv-toggle .hv-pause{display:none}
@media(max-width:640px){
  .hero-video{margin-top:var(--sp-6)}
  .hv-toggle{right:var(--sp-2);bottom:var(--sp-2);width:32px;height:32px}
}
/* The mark signs the page before the headline speaks: the blue square switches on,
   the D is drawn outward from its curve (a stroke mask sweeping both ways from the
   rightmost point), both stems rise from the baseline, the curl unfolds from the stem.
   Pre-states live only in the from keyframes, so reduced motion lands on the finished mark.
   Timeline: dot 0 · ring 100-620 · stems 500-880 · curl 760-1020 */
@keyframes markOn{from{opacity:0}}
@keyframes markDraw{from{stroke-dashoffset:272}}
@keyframes markRise{from{transform:scaleY(0)}}
@keyframes markCurl{from{transform:scaleX(0)}}
.mark .mk-draw{fill:none;stroke:#fff;stroke-width:50;stroke-linecap:butt;stroke-dasharray:272;stroke-dashoffset:0}
.mark .mk-stem,.mark .mk-curl{transform-box:fill-box}
.mark .mk-dot{animation:markOn 140ms linear both}
.mark .mk-draw{animation:markDraw 520ms var(--ease-out) 100ms both}
.mark .mk-stem{transform-origin:50% 100%;animation:markRise 380ms var(--ease-out) 500ms both}
.mark .mk-curl{transform-origin:0 50%;animation:markCurl 260ms var(--ease-out) 760ms both}
/* Once per visit (premium pass): an inline script in each page's <head> adds
   .mark-seen after the first page, so moving between pages shows the finished
   mark instead of replaying the signature every time. */
.mark-seen .mark :is(.mk-dot,.mk-draw,.mk-stem,.mk-curl){animation:none}

@media(max-width:1000px){
  .hero{padding-block:clamp(28px,4vw,48px) clamp(40px,5vw,56px)}
  .hero-grid{grid-template-columns:1fr;grid-template-rows:none;row-gap:var(--sp-6)}
  .hero-copy{grid-column:1;grid-row:auto}
  .hero-trust{grid-column:1;grid-row:auto}
  .hero h1{font-size:min(calc((100vw - 2 * var(--gutter)) / 11.3),60px)}
  .hero p.sub{max-width:none}
}
@media(max-height:560px) and (orientation:landscape){
  .hero{padding-block:var(--sp-4) var(--sp-5)}
  .hero-grid{row-gap:var(--sp-5)}
}

/* ===== Growth & Intelligence showcases (Websites/Marketing/Dashboards/Automation) ===== */
.web-show{position:relative;isolation:isolate}
.web-show .section-head{margin-bottom:var(--sp-7)}
.web-block{margin-bottom:clamp(64px,9vw,110px)}
.web-block:last-child{margin-bottom:0}
.web-grid{display:grid;grid-template-columns:1fr 1.1fr;gap:56px;align-items:center}
/* Centring only reads well while the two columns are close in height. Where the
   copy runs much longer than its figure, the figure floats in the middle of a
   tall column; .top pins it to the headline instead. */
.web-grid.top{align-items:start}
.web-grid.rev{grid-template-columns:1.1fr 1fr}
.web-grid.rev .web-copy{order:2}
.web-grid.rev .web-visual{order:1}
.web-copy h2.title,.web-copy .title.sm{margin:var(--sp-4) 0 var(--sp-3)}
.title.sm{font-family:var(--font-ui);font-size:var(--fs-h3);font-weight:600;letter-spacing:-.012em;line-height:1.25;text-wrap:balance}
.web-copy p{color:var(--muted);font-size:var(--fs-body);max-width:var(--maxw-narrow);text-wrap:pretty}
.web-copy p + p{margin-top:var(--sp-3)}
.web-copy p strong{color:var(--ink);font-weight:600}
.web-copy p.uses{font-size:var(--fs-small);border-left:2px solid var(--line);padding-left:var(--sp-3);margin-top:var(--sp-3)}
.web-stats{display:flex;flex-direction:column;gap:var(--sp-4);margin-top:var(--sp-6);padding-top:var(--sp-5);border-top:1px solid var(--line)}
.web-stats .ws{display:flex;align-items:baseline;gap:var(--sp-3)}
.web-stats .ws b{font-family:var(--font-ui);font-size:var(--fs-h3);font-weight:700;letter-spacing:-.012em;color:var(--ink);min-width:78px;flex:0 0 auto;font-variant-numeric:tabular-nums}
.web-stats .ws span{font-family:var(--font-ui);font-size:var(--fs-small);color:var(--muted)}

.web-visual{position:relative;display:flex;flex-direction:column;gap:0}
/* Media reveal: the figure opens top-down behind a clip while the mock inside
   settles from 1.06 — the same move the hero video makes on load, so a page
   reads as one motion language. The inner staggers (.dk, bars, sources) still
   run off .in as before; this is the frame they run inside. */
/* The clip sits on the mock, not on .web-visual: the IntersectionObserver
   that adds .in reads a clipped element as zero area and never fires. */
.web-visual > *{clip-path:inset(0 0 100% 0);transform:scale(1.06);transform-origin:50% 0;transition:clip-path var(--t-media) var(--ease-cine),transform 1.25s var(--ease-cine)}
.web-visual.in > *{clip-path:inset(0 0 0 0);transform:none}

/* Browser mockup */
/* Real browser chrome is light. On the dark version this was dark-on-dark and
   read as an illustration rather than a window. */
.bm-bar{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--line);background:var(--surface-sunken)}
.bm-dot{width:9px;height:9px;border-radius:0;display:block;background:var(--line-strong)}
.bm-url{display:flex;align-items:center;gap:var(--sp-1);margin:0 auto;padding:5px 14px;border-radius:0;background:var(--bg);border:1px solid var(--line);color:var(--muted-2);font-family:var(--font-mono);font-size:var(--fs-label)}
.bm-url svg{color:var(--muted-2)}
.bm-live{font-family:var(--font-mono);font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;color:var(--good);opacity:0;transition:opacity var(--t-base) var(--ease)}
.web-visual.in .bm-live{opacity:1;transition-delay:700ms}

/* ===== Dashboards: KPI + chart mock ===== */
/* Inverted panel. A black dashboard on a white page reads as a real product
   screenshot; on the dark version a dark mockup was just more dark page. */
.dash-mock{position:relative;z-index:1;border:1px solid var(--panel);border-radius:0;overflow:hidden;background:var(--panel);color:var(--on-panel);box-shadow:var(--shadow-md)}
.dash-mock .bm-bar{background:var(--panel-2);border-bottom-color:var(--line-invert)}
.dash-mock .bm-dot{background:rgba(248,250,252,.22)}
.dash-mock .bm-url{background:var(--panel);border-color:var(--line-invert);color:var(--on-panel-muted)}
.dash-mock .bm-live{color:var(--good-invert)}
.dash-body{padding:var(--sp-5);display:flex;flex-direction:column;gap:var(--sp-5)}
.dash-kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-3)}
.dk{display:flex;flex-direction:column;gap:var(--sp-2);padding:var(--sp-4);border:1px solid var(--line-invert);border-radius:0;background:var(--panel-2);opacity:0;transform:translateY(10px);transition:opacity var(--t-reveal) var(--ease-out),transform var(--t-reveal) var(--ease-out)}
.web-visual.in .dk{opacity:1;transform:none}
.web-visual.in .dk:nth-child(1){transition-delay:60ms}
.web-visual.in .dk:nth-child(2){transition-delay:120ms}
.web-visual.in .dk:nth-child(3){transition-delay:180ms}
.dk-label{font-family:var(--font-mono);font-size:var(--fs-label);color:var(--on-panel-muted);letter-spacing:.12em;text-transform:uppercase}
.dk b{font-family:var(--font-ui);font-size:var(--fs-h3);font-weight:700;letter-spacing:-.012em;color:var(--on-panel);font-variant-numeric:tabular-nums}
.dk-delta{font-family:var(--font-ui);font-size:var(--fs-micro);font-weight:600}
.dk-delta.good{color:var(--on-panel-muted)}
.dk-delta.warn{color:var(--warn-invert)}

.dash-chart{display:flex;flex-direction:column;gap:var(--sp-2);padding-top:var(--sp-2);border-top:1px solid var(--line-invert)}
.dc-bars{display:flex;align-items:flex-end;gap:var(--sp-2);height:90px}
/* Each bar is laid out at its final height and grown from the baseline with a
   scale, so the chart never re-runs layout while it draws. */
.dc-bars i{display:block;flex:1 1 auto;border-radius:0;height:var(--h);background:rgba(159,176,200,.4);transform:scaleY(0);transform-origin:50% 100%;transition:transform var(--t-data) var(--ease-out)}
.web-visual.in .dc-bars i{transform:none}
.web-visual.in .dc-bars i:nth-child(1){transition-delay:240ms}
.web-visual.in .dc-bars i:nth-child(2){transition-delay:290ms}
.web-visual.in .dc-bars i:nth-child(3){transition-delay:340ms}
.web-visual.in .dc-bars i:nth-child(4){transition-delay:390ms}
.web-visual.in .dc-bars i:nth-child(5){transition-delay:440ms}
.web-visual.in .dc-bars i:nth-child(6){transition-delay:490ms}
.web-visual.in .dc-bars i:nth-child(7){transition-delay:540ms}
/* Only the strongest day carries the signal colour. */
.dc-bars i:last-child{background:var(--accent)}
.dc-labels{display:flex;gap:var(--sp-2)}
.dc-labels span{flex:1 1 auto;text-align:center;font-family:var(--font-mono);font-size:var(--fs-label);color:var(--on-panel-muted)}

/* ===== Automation: pipeline flow mock ===== */
/* Inverted panel, matching the dashboard it sits beside. */
.flow-mock{position:relative;z-index:1;border:1px solid var(--panel);border-radius:0;padding:var(--sp-6) var(--sp-5);background:var(--panel);color:var(--on-panel);box-shadow:var(--shadow-md);display:flex;flex-direction:column;gap:var(--sp-5)}
.flow-row{display:flex;align-items:center;gap:var(--sp-3)}
.flow-node{flex:1 1 0;display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);text-align:center;padding:var(--sp-4) var(--sp-3);border:1px solid var(--line-invert);border-radius:0;background:var(--panel-2);font-family:var(--font-ui);font-size:var(--fs-micro);color:var(--on-panel-muted);opacity:0;transform:translateY(10px);transition:opacity var(--t-reveal) var(--ease-out),transform var(--t-reveal) var(--ease-out)}
.web-visual.in .flow-node{opacity:1;transform:none}
.web-visual.in .flow-node:nth-child(1){transition-delay:60ms}
.web-visual.in .flow-node:nth-child(3){transition-delay:120ms}
.web-visual.in .flow-node:nth-child(5){transition-delay:180ms}
.web-visual.in .flow-node:nth-child(7){transition-delay:240ms}
.web-visual.in .flow-node:nth-child(9){transition-delay:300ms}
.flow-node .fl-icon{width:38px;height:38px;border-radius:0;display:grid;place-items:center;background:transparent;border:1px solid var(--line-invert);color:var(--on-panel)}
.flow-node.fl-done{border-color:var(--on-panel-muted);background:var(--panel);color:var(--on-panel)}
.flow-node.fl-done .fl-icon{background:var(--on-panel);border-color:var(--on-panel);color:var(--panel)}
.fl-arrow{flex:0 0 auto;color:var(--on-panel-muted);opacity:0;transition:opacity var(--t-reveal) var(--ease-out)}
.web-visual.in .fl-arrow{opacity:1;transition-delay:100ms}
.web-visual.in .fl-arrow:nth-of-type(2){transition-delay:160ms}
.web-visual.in .fl-arrow:nth-of-type(3){transition-delay:220ms}
.web-visual.in .fl-arrow:nth-of-type(4){transition-delay:280ms}
/* Five nodes across one panel: the labels are single words, so the padding
   is what has to give, not the type size. */
.flow-row{gap:var(--sp-2)}
.flow-node{padding:var(--sp-4) var(--sp-2)}
.flow-foot{display:flex;align-items:center;gap:var(--sp-2);padding-top:var(--sp-4);border-top:1px solid var(--line-invert);font-family:var(--font-ui);font-size:var(--fs-micro);color:var(--on-panel-muted)}
.flow-pulse{width:8px;height:8px;border-radius:0;background:var(--accent);position:relative;flex:0 0 auto}

/* ===== Problem ===== */
.prob-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-top:var(--sp-7)}
.prob{border:1px solid var(--line);border-radius:0;padding:var(--sp-5);background:var(--surface);position:relative;overflow:hidden;display:flex;flex-direction:column;height:100%}
/* Side by side, a heading that wraps to three lines pushed its own body copy a
   line below its neighbours’, so the row of cards read as misaligned. Subgrid
   hands every card the same two rows — heading, body — so the bodies start on
   one line whatever the headings do. (The icon tile and the 01–04 numbers left
   in the premium pass: the four are not a sequence, and the tile was kit.) The flex fallback above still
   applies wherever subgrid is unavailable. */
@supports (grid-template-rows: subgrid){
  .prob-grid{grid-template-rows:auto 1fr}
  /* row-gap:0 — a subgrid inherits the parent’s 18px gap between its own rows,
     which sat on top of the heading margin as dead space. */
  .prob{display:grid;grid-row:span 2;grid-template-rows:subgrid;row-gap:0}
}
.prob h3{font-family:var(--font-ui);font-size:var(--fs-h4);font-weight:600;letter-spacing:-.005em;margin-bottom:var(--sp-2)}
.prob p{font-family:var(--font-ui);font-size:var(--fs-small);color:var(--muted)}

/* ===== Process ===== */
.proc-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin-top:var(--sp-7);position:relative}
.proc-line{position:absolute;top:28px;left:8%;right:8%;height:1px;background:var(--line-strong);z-index:0}
/* The rust fill is the progress through the engagement, driven by --pp on
   .proc-grid. The grey rule underneath is the whole path, always visible, so
   the reader can see how much is still ahead. */
.proc-line i{position:absolute;inset:0;background:var(--accent);transform:scaleX(var(--pp,0));transform-origin:left center;transition:transform 180ms linear}
.proc{position:relative;z-index:1;padding:0 var(--sp-4);text-align:center}
.proc .num{width:56px;height:56px;margin:0 auto var(--sp-4);border-radius:0;display:grid;place-items:center;font-family:var(--font-mono);font-weight:500;font-size:var(--fs-h4);background:var(--bg);border:1px solid var(--line-strong);color:var(--muted);position:relative;transition:background var(--t-base) var(--ease),border-color var(--t-base) var(--ease),color var(--t-base) var(--ease)}
/* Passed, here, not yet — three states, so the row reads as a path being
   walked rather than four things lighting up and staying lit. */
.proc.done .num{background:var(--ink);border-color:var(--ink);color:var(--on-panel)}
.proc.active .num{background:var(--accent-hover);border-color:var(--accent-hover);color:#fff}
.proc h3{font-family:var(--font-ui);font-size:var(--fs-h4);font-weight:600;letter-spacing:-.005em;margin-bottom:var(--sp-2)}
.proc p{font-family:var(--font-ui);font-size:var(--fs-small);color:var(--muted);max-width:230px;margin:0 auto}
.proc .ptag{display:inline-block;margin-top:var(--sp-3);font-family:var(--font-mono);font-size:14px;font-weight:500;color:var(--muted-2);letter-spacing:0}

/* ===== Results ===== */
.res-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:var(--sp-7)}
.res{border:1px solid var(--line);border-radius:0;padding:var(--sp-5);background:var(--surface)}
.res .sector{font-family:var(--font-mono);font-size:14px;color:var(--muted-2);letter-spacing:0}
.res .big{font-family:var(--font-ui);font-size:48px;font-weight:600;letter-spacing:-.03em;line-height:1;margin:var(--sp-3) 0 var(--sp-1);color:var(--ink);font-variant-numeric:tabular-nums}
.res .metric{font-family:var(--font-ui);font-size:var(--fs-small);font-weight:600;margin-bottom:var(--sp-3)}
.res p{font-family:var(--font-ui);font-size:var(--fs-small);color:var(--muted)}
.res-note{margin-top:var(--sp-6);text-align:center;font-family:var(--font-ui);font-size:var(--fs-small);color:var(--muted-2);max-width:var(--maxw-text);margin-inline:auto}
/* Centred under a grid of cases; left where it trails a left-aligned CTA row. */
.res-note.left{text-align:left;margin-inline:0;margin-top:var(--sp-4)}
.res-note a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--accent);text-underline-offset:3px;display:inline-block;padding-block:3px}


/* ===== Why ===== */
/* ===== Founder ===== */
/* ===== FAQ ===== */
.faq-list{max-width:var(--maxw-text);margin:var(--sp-7) auto 0;border-top:1px solid var(--line)}
.faq-item{border-bottom:1px solid var(--line)}
.faq-item summary{list-style:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);font-family:var(--font-ui);font-size:var(--fs-h4);font-weight:600;color:var(--ink);padding:var(--sp-5) 0;min-height:44px}
.faq-item summary::-webkit-details-marker{display:none}
.faq-item summary::after{content:"";flex:0 0 auto;width:11px;height:11px;border-right:2px solid var(--muted);border-bottom:2px solid var(--muted);transform:rotate(45deg);transition:transform var(--t-base) var(--ease);margin-right:4px}
.faq-item[open] summary::after{transform:rotate(-135deg)}
.faq-item summary:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
.faq-body{padding:0 0 var(--sp-5);animation:faqOpen var(--t-base) var(--ease-out)}
@keyframes faqOpen{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){.faq-body{animation:none}}
/* The answer’s box opens to its height instead of snapping, so the questions
   below slide down rather than jump. Kept short: it is a layout animation.
   Browsers without interpolate-size (Safari, Firefox) keep the snap. */
@supports (interpolate-size:allow-keywords){
  .faq-item{interpolate-size:allow-keywords}
  .faq-item::details-content{height:0;overflow:clip;transition:height var(--t-fast) var(--ease-out),content-visibility var(--t-fast) allow-discrete}
  .faq-item[open]::details-content{height:auto}
}
@media(prefers-reduced-motion:reduce){.faq-item::details-content{transition:none}}
.faq-item summary{transition:color var(--t-fast) var(--ease)}
@media (hover:hover) and (pointer:fine){.faq-item summary:hover{color:var(--ink)}}
@media (hover:hover) and (pointer:fine){.faq-item summary:hover::after{border-color:var(--accent)}}
.faq-body p{font-size:var(--fs-body);color:var(--muted);margin:0}
/* ===== Mid-page CTA ===== */
.mid-cta{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--sp-4) var(--sp-6);padding:var(--sp-6);margin:var(--sp-8) 0;border:1px solid var(--line);border-radius:0;background:var(--surface)}
.mid-cta p{font-family:var(--font-ui);font-size:var(--fs-h4);font-weight:600;color:var(--ink);margin:0}
/* The same shell carrying a statement and its onward links rather than one
   line and a button: left-aligned, and the heading sized as a strip heading,
   not a section head. Rules follow .mid-cta so they win on source order. */
.more-strip{justify-content:space-between;text-align:left;margin:0}
.more-strip h2{font-family:var(--font-ui);font-size:var(--fs-h4);font-weight:600;letter-spacing:-.005em;line-height:1.25;margin:0;color:var(--ink)}
.more-strip p{font-size:var(--fs-small);font-weight:400;color:var(--muted);margin:6px 0 0;max-width:56ch}
.more-links{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2) var(--sp-5)}
.more-links .link-cta{min-height:auto}
/* A closing strip does not need a full section’s air above and below it. */
.pad.tight{padding-block:clamp(44px,4.5vw,64px)}

/* ===== CTA ===== */
.cta-band{position:relative;border-radius:0;padding:0;text-align:center;background:transparent;color:var(--on-panel)}
.cta-band h2{font-family:var(--font-ui);font-size:var(--fs-h2);font-weight:600;letter-spacing:-.022em;line-height:1.10;color:var(--on-panel);text-wrap:balance}
.cta-band p{color:var(--on-panel-muted);font-size:var(--fs-body);margin:var(--sp-4) auto 0;max-width:var(--maxw-narrow);text-wrap:pretty}
.cta-band .hero-cta{justify-content:center;margin-top:var(--sp-6)}
.cta-band .btn-primary{background:var(--on-panel);color:var(--panel)}
@media (hover:hover) and (pointer:fine){.cta-band .btn-primary:hover{background:var(--accent-hover);color:#fff}}
.cta-band .btn-ghost{background:transparent;border-color:var(--control-line-invert);color:var(--on-panel)}
@media (hover:hover) and (pointer:fine){.cta-band .btn-ghost:hover{background:rgba(248,250,252,.08);border-color:var(--on-panel)}}
.cta-band :focus-visible{outline-color:var(--focus-invert)}
.cta-band .eyebrow{color:var(--on-panel-muted)}
.cta-band .contact-form .cf-note{font-size:var(--fs-micro);margin:0;max-width:none}
.cta-sub{margin-top:var(--sp-4);font-family:var(--font-mono);font-size:var(--fs-micro);color:var(--on-panel-muted)}
/* ===== Contact form (inside the inverted panel) ===== */
.contact-form{max-width:560px;margin:var(--sp-7) auto 0;text-align:left;display:flex;flex-direction:column;gap:var(--sp-4)}
.cf-row{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-4)}
.cf-field{display:flex;flex-direction:column;gap:var(--sp-2)}
.cf-field > span{font-family:var(--font-ui);font-size:14px;font-weight:500;letter-spacing:0;color:var(--on-panel-muted)}
.cf-field > span em{font-style:normal}
.cf-field input,.cf-field textarea,.cf-field select{font-family:var(--font-ui);font-size:var(--fs-small);color:var(--on-panel);background:var(--panel-2);border:1px solid var(--control-line-invert);border-radius:0;padding:12px 14px;min-height:44px;width:100%;transition:border-color var(--t-fast) var(--ease)}
.cf-field textarea{resize:vertical;min-height:88px}
/* iOS zooms into any field under 16px and doesn’t zoom back out. Touch only,
   so the desktop form keeps the UI size. */
@media (pointer:coarse){.cf-field input,.cf-field textarea,.cf-field select{font-size:16px}}
.cf-field input::placeholder,.cf-field textarea::placeholder{color:var(--on-panel-muted);opacity:.7}
@media (hover:hover) and (pointer:fine){.cf-field input:hover,.cf-field textarea:hover,.cf-field select:hover{border-color:rgba(248,250,252,.72)}}
.cf-field input:focus-visible,.cf-field textarea:focus-visible,.cf-field select:focus-visible{outline:2px solid var(--focus-invert);outline-offset:2px;border-color:transparent}
/* The native arrow is drawn in the OS accent on a dark panel and reads as a
   defect, so the control is stripped and given the page’s own chevron. */
.cf-field select{
  appearance:none;-webkit-appearance:none;cursor:pointer;
  padding-right:42px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f8fafc' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 14px center;background-size:15px 15px;
}
/* The dropdown itself is painted by the OS, so its own colours have to be set
   or a dark-panel select opens as white text on white. */
.cf-field select option{background:var(--panel-2);color:var(--on-panel)}
.cf-submit{align-self:flex-start;margin-top:var(--sp-2)}
.cf-note{font-family:var(--font-ui);font-size:var(--fs-micro);color:var(--on-panel-muted);margin:0}
/* Honeypot: off-screen, not display:none, so naive bots still fill it. */
.contact-form[hidden]{display:none}
.cf-honey{position:absolute;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none}
.cf-submit:disabled{opacity:.6;cursor:progress}
/* Post-submit state: same width as the form, same panel voice. */
/* The one moment a visitor sees once, so it gets a little more time: it rises
   into place as the page brings it clear of the sticky header (site.js). */
.cf-success{max-width:560px;margin:var(--sp-7) auto 0;padding:var(--sp-6);border:1px solid var(--line-invert);background:var(--panel-2);text-align:left;
  transition:opacity 400ms var(--ease-cine),transform 500ms var(--ease-cine)}
@starting-style{.cf-success{opacity:0;transform:translateY(8px)}}
.cf-success .eyebrow{color:var(--on-panel-muted)}
.cf-success h3{font-family:var(--font-ui);font-size:var(--fs-h4);font-weight:600;letter-spacing:-.012em;color:var(--on-panel);margin:var(--sp-3) 0 0;text-wrap:balance}
.cf-success p{margin:var(--sp-3) 0 0;max-width:none}
.cf-success a{color:var(--on-panel);text-decoration:underline;text-decoration-color:var(--accent-on-panel);text-underline-offset:3px}
.cf-success:focus{outline:none}
.cta-alt{margin-top:var(--sp-7);padding-top:var(--sp-5);border-top:1px solid var(--line-invert)}
.cta-alt-label{display:block;font-family:var(--font-ui);font-size:14px;font-weight:500;letter-spacing:0;color:var(--on-panel-muted);margin-bottom:var(--sp-3)}
.cta-alt-links{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--sp-3) var(--sp-6)}
.cta-alt-links a{font-family:var(--font-ui);font-size:var(--fs-small);color:var(--on-panel);text-decoration-color:var(--accent-on-panel);text-decoration:underline;text-underline-offset:3px;padding:var(--sp-2) 0}

/* ===== Footer ===== */
/* Navy close, as in the DNA: the page ends on the colour it is built from. */
footer{border-top:0;margin-top:0;padding:var(--sp-9) 0 var(--sp-7);background:var(--panel);color:var(--on-panel)}
footer .brand{color:var(--on-panel)}
footer :focus-visible{outline-color:var(--focus-invert)}
.foot-grid{display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;gap:var(--sp-7)}
.foot-brand p{font-family:var(--font-ui);color:var(--on-panel-muted);font-size:var(--fs-small);margin-top:var(--sp-4);max-width:300px}
.foot-col .foot-h{font-family:var(--font-ui);font-size:14px;font-weight:500;letter-spacing:0;color:var(--on-panel-muted);margin:0 0 var(--sp-4)}
.foot-col a{display:block;color:var(--on-panel);font-family:var(--font-ui);font-size:var(--fs-small);padding:var(--sp-2) 0;transition:color var(--t-fast) var(--ease)}
.foot-col span{display:block;color:var(--on-panel-muted);font-family:var(--font-ui);font-size:var(--fs-small);padding:var(--sp-2) 0}
@media (hover:hover) and (pointer:fine){.foot-col a:hover{color:var(--accent-on-panel)}}
/* Where the navy contact section is the last thing in <main> (index), the
   footer follows navy on navy: a hairline marks the seam and the footer’s top
   padding drops to match its bottom, so the two do not read as one empty band. */
main:has(> section:last-child .cta-band) + footer{border-top:1px solid var(--line-invert);padding-top:var(--sp-7)}
.foot-bottom{display:flex;justify-content:space-between;align-items:center;margin-top:var(--sp-7);padding-top:var(--sp-5);border-top:1px solid var(--line-invert);font-family:var(--font-mono);font-size:var(--fs-micro);color:var(--on-panel-muted);flex-wrap:wrap;gap:var(--sp-3)}

/* ===== Reveal animation =====
   All four variants collapse to one behaviour. The directional and scale
   variants existed to manufacture scroll rhythm that the section grounds
   now supply. Class names are kept so no markup changes. */
/* Heading blocks never fade in as a whole: a block fading in on every section
   reads as a template. What is inside them arrives in reading order instead
   (below), on the hero’s own motion. */
.section-head.reveal,.section-head.reveal-scale,.page-head .reveal,.climb-head > div.reveal,.webx-head .reveal,.climb-pull.reveal{opacity:1!important;transform:none!important;transition:none!important}
/* The owner found the site dead with every heading already in place: the eye
   lands on the headline, and nothing there moved. So the label fades, the
   headline wipes up out of its own box on heroLine’s 760ms curve (the move the
   hero and the closing band already make), and the intro follows. The clip
   ends .25em outside the box so descenders at line-height 1.05 are not cut.
   The service-page heroes run their own keyframes and are left out. */
:is(.section-head.reveal,.section-head.reveal-scale,.climb-head > div.reveal):not(.page-hero *) > .eyebrow{opacity:0;transition:opacity 480ms var(--ease-out)}
:is(.section-head.reveal,.section-head.reveal-scale,.climb-head > div.reveal):not(.page-hero *) > .title{
  clip-path:inset(-.25em -.25em calc(100% + .25em) -.25em);transform:translateY(.3em);
  transition:clip-path 560ms var(--ease-out),transform 560ms var(--ease-out)}
:is(.section-head.reveal,.section-head.reveal-scale):not(.page-hero *) > :is(p,.hero-cta){opacity:0;transform:translateY(16px);
  transition:opacity var(--t-reveal) var(--ease-cine) 120ms,transform var(--t-reveal) var(--ease-cine) 120ms}
:is(.section-head.reveal,.section-head.reveal-scale,.climb-head > div.reveal).in:not(.page-hero *) > :is(.eyebrow,p,.hero-cta){opacity:1;transform:none}
:is(.section-head.reveal,.section-head.reveal-scale,.climb-head > div.reveal).in:not(.page-hero *) > .title{clip-path:inset(-.25em);transform:none}
/* How-we-work’s intro sits beside its heading block rather than in it. */
.climb-head > p.reveal{transition-delay:120ms,120ms,0s,0s,0s}
/* One transition list for every reveal, hover properties included: a card’s
   own hover rule used to replace this shorthand, so .prob and .res popped in
   with no opacity transition at all. Hover lifts use `translate`, reveals use
   `transform`, and the stagger delays below only touch the first two. */
.reveal,.reveal-scale,.reveal-left,.reveal-right{opacity:0;transform:translateY(12px);transition:opacity var(--t-reveal) var(--ease-cine),transform var(--t-reveal) var(--ease-cine),translate var(--t-fast) var(--ease),border-color var(--t-fast) var(--ease),box-shadow var(--t-fast) var(--ease)}
.reveal.in,.reveal-scale.in,.reveal-left.in,.reveal-right.in{opacity:1;transform:none}
.reveal[data-d="1"]{transition-delay:60ms,60ms,0s,0s,0s}
.reveal[data-d="2"]{transition-delay:120ms,120ms,0s,0s,0s}
.reveal[data-d="3"]{transition-delay:180ms,180ms,0s,0s,0s}
.reveal[data-d="4"]{transition-delay:240ms,240ms,0s,0s,0s}
.reveal[data-d="5"]{transition-delay:300ms,300ms,0s,0s,0s}

/* ===== Capability lists inside the showcase copy columns ===== */
.points{
  list-style:none;margin:var(--sp-5) 0 0;padding:0;
  display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-2) var(--sp-4);
  max-width:var(--maxw-narrow);
}
.points li{
  position:relative;padding-left:var(--sp-4);
  font-family:var(--font-ui);font-size:var(--fs-small);color:var(--ink-2);line-height:1.45;
}
.points li::before{content:"";position:absolute;left:0;top:.55em;width:5px;height:5px;background:var(--ink)}

/* One idea worth saying louder than the paragraph around it. */
.web-copy p.pull{
  font-family:var(--font-ui);font-size:var(--fs-h4);font-weight:500;line-height:1.35;letter-spacing:-.01em;color:var(--ink);
  border-left:1px solid var(--accent);padding-left:var(--sp-5);margin:var(--sp-5) 0;
}

/* ===== Website use cases ===== */

/* ===== AI assistant mock ===== */
.ai-mock{
  border:1px solid var(--line-invert);border-radius:0;background:var(--panel);
  overflow:hidden;box-shadow:var(--shadow-lg,var(--shadow-md));
}
.ai-body{padding:var(--sp-5);display:flex;flex-direction:column;gap:var(--sp-4)}
.ai-who{display:block;font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--on-panel-muted);margin-bottom:var(--sp-2)}
.ai-q,.ai-a{border:1px solid var(--line-invert);border-radius:0;padding:var(--sp-4);background:var(--panel-2)}
.ai-q p,.ai-a p{font-family:var(--font-ui);font-size:var(--fs-micro);line-height:1.55;color:var(--on-panel);margin:0}
.ai-a{border-color:var(--accent)}
.ai-a p b{color:var(--on-panel);font-weight:700}
.ai-sources{display:flex;flex-wrap:wrap;gap:var(--sp-2);opacity:0;transition:opacity var(--t-reveal) var(--ease-out) 220ms}
.web-visual.in .ai-sources{opacity:1}
.ai-src{
  display:inline-flex;align-items:center;gap:6px;
  font-family:var(--font-mono);font-size:10px;letter-spacing:.06em;color:var(--on-panel-muted);
  border:1px solid var(--line-invert);border-radius:0;padding:4px 10px;
}
.ai-foot{display:flex;align-items:center;gap:var(--sp-2);padding-top:var(--sp-4);border-top:1px solid var(--line-invert);font-family:var(--font-ui);font-size:var(--fs-micro);color:var(--on-panel-muted)}
.ai-q,.ai-a{opacity:0;transform:translateY(10px);transition:opacity var(--t-reveal) var(--ease-out),transform var(--t-reveal) var(--ease-out)}
.web-visual.in .ai-q{opacity:1;transform:none;transition-delay:80ms}
.web-visual.in .ai-a{opacity:1;transform:none;transition-delay:360ms}

/* ===== Work cards: problem / solution / outcome ===== */
.res{display:flex;flex-direction:column;gap:var(--sp-3);position:relative}
.res-tag{
  align-self:flex-start;font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-2);
  border:1px solid var(--line);border-radius:0;padding:2px 8px;
}
.res-lines{margin:0;display:flex;flex-direction:column;gap:var(--sp-3)}
.res-lines dt{
  font-family:var(--font-mono);font-size:14px;letter-spacing:-.005em;
  color:var(--ink);font-weight:600;margin-bottom:var(--sp-1);
}
.res-lines dd{margin:0;font-family:var(--font-ui);font-size:var(--fs-small);line-height:1.55;color:var(--muted)}

/* Funnel now shows what is measured rather than numbers we cannot stand behind. */

/* ===== Contact: primary action plus the WhatsApp route ===== */
.cf-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-4);margin-top:var(--sp-2)}
.cf-submit{align-self:auto;margin-top:0}
.cf-alt{
  font-family:var(--font-ui);font-size:var(--fs-small);font-weight:600;color:var(--on-panel);
  text-decoration:underline;text-underline-offset:5px;text-decoration-thickness:1px;
  display:inline-flex;align-items:center;gap:var(--sp-2);
}
@media (hover:hover) and (pointer:fine){.cf-alt:hover{color:var(--on-panel);text-decoration-color:var(--accent-on-panel)}}

/* ===== Responsive ===== */
@media(max-width:1000px){
  .points{max-width:100%}
}
@media(max-width:760px){
  .points{grid-template-columns:1fr}
  .cf-actions{flex-direction:column;align-items:stretch;gap:var(--sp-3)}
  .cf-submit{width:100%;justify-content:center}
  .cf-alt{justify-content:center}
  .ai-body{padding:var(--sp-4);gap:var(--sp-3)}
}

/* ===== 02 · The climb =====
   The ladder drawn as a staircase on a drafting sheet. Rung 01 is solid: the
   client’s finished business, the foundation everything stands on. Rungs 02-06
   are outlines, work still to be built. The blue square from the mark stands on
   the rung being read, and reaches another rung by stepping through every rung
   in between: you cannot skip one.

   Motion: one sequence when the sheet enters view (floor, then each riser and
   tread left to right, rung 01 filling from the floor, the handoff line
   dropping, the signal arriving and stepping up to where we start). After that
   motion only answers a choice. Pre-states hang off .climb:not(.in); the
   reduced-motion and no-JS blocks release them. */
/* Head and foot share the instrument’s tracks, so the right-hand copy starts on the panel’s text edge. */
.climb-head{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);align-items:end;margin-bottom:clamp(32px,4vw,56px)}
.climb-head > div{padding-right:var(--sp-7)}
.climb-head > p{padding-left:clamp(24px,2.6vw,40px)}
.climb-head h2.title{margin-bottom:0}
.climb-head p{margin:0;max-width:46ch;color:var(--muted);text-wrap:pretty}

.climb{--stair-h:clamp(280px,24vw,360px);display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);border:1px solid var(--line-strong);background:var(--surface)}
/* A drafting sheet: 24px construction grid at 3.5%. */
.climb-sheet{position:relative;min-width:0;padding:clamp(24px,2.6vw,40px);border-right:1px solid var(--line);
  background-image:linear-gradient(rgba(7,22,45,.035) 1px,transparent 1px),linear-gradient(90deg,rgba(7,22,45,.035) 1px,transparent 1px);
  background-size:24px 24px;background-position:-1px -1px}

/* Dimension marks over the stair: what is theirs, what is ours. */
.climb-dims{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));height:24px;margin-bottom:16px}
.dim{position:relative;display:flex;align-items:center;justify-content:center;min-width:0}
.dim::before{content:"";position:absolute;left:8px;right:8px;top:50%;height:9px;margin-top:-4.5px;
  border-left:1px solid var(--ink-2);border-right:1px solid var(--ink-2);
  background:linear-gradient(var(--ink-2),var(--ink-2)) center/100% 1px no-repeat;
  transition:transform 520ms var(--ease-out) 900ms}
.dim span{position:relative;padding:0 8px;background:var(--surface);font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);white-space:nowrap;transition:opacity 400ms var(--ease) 1100ms}
.dim span b{font-weight:inherit}
.dim-yours{grid-column:1}
.dim-ours{grid-column:2 / 7}

/* The stair. Each rung is a tab filling its column under its own tread, so the
   whole face of a step is the hit area. */
.stair{position:relative;display:grid;grid-template-columns:repeat(6,minmax(0,1fr));align-items:end;height:var(--stair-h)}
.stair::before{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--ink);transform-origin:left;transition:transform 420ms var(--ease-out)}
.stair::after{content:"";position:absolute;right:0;top:0;bottom:0;width:1px;background:var(--ink);transform-origin:top;transition:transform 320ms var(--ease-out) 1020ms}
.stair-handoff{position:absolute;left:calc(100% / 6);top:-40px;bottom:0;width:0;border-left:1px dashed rgba(7,22,45,.5);transform-origin:top;transition:transform 520ms var(--ease-out) 980ms;pointer-events:none;z-index:1}

.step{position:relative;height:calc(var(--k) / 6 * 100%);min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:4px;
  padding:12px 6px 0 12px;border:0;border-left:1px dashed rgba(7,22,45,.14);background:transparent;
  font:inherit;text-align:left;color:var(--ink);cursor:pointer;transition:background-color var(--t-fast) var(--ease)}
/* Riser: from the tread below up to this one. */
.step::before{content:"";position:absolute;left:-1px;top:0;width:1px;height:calc(100% / var(--k));background:var(--ink);transform-origin:bottom;
  transition:transform 260ms var(--ease-out) calc(var(--k) * 140ms)}
/* Tread. */
.step::after{content:"";position:absolute;left:-1px;right:0;top:0;height:1px;background:var(--ink);transform-origin:left;
  transition:transform 260ms var(--ease-out) calc(var(--k) * 140ms + 160ms),height var(--t-fast) var(--ease),background-color 320ms var(--ease)}
.step.is-handoff{border-left-color:transparent}
.step-num{font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;line-height:1;color:var(--ink-2);font-variant-numeric:tabular-nums;transition:opacity 360ms var(--ease) calc(var(--k) * 140ms + 260ms)}
.step-name{font-size:clamp(14px,1.1vw,16px);font-weight:600;letter-spacing:-.01em;line-height:1.2;color:var(--ink);transition:opacity 360ms var(--ease) calc(var(--k) * 140ms + 260ms)}
@media (hover:hover) and (pointer:fine){.step:hover{background-color:rgba(7,22,45,.035)}}
.step[aria-selected="true"]::after{height:2px}
.step.is-lit::after{background-color:var(--accent)}
.step:focus-visible{outline:2px solid var(--focus);outline-offset:-2px}

/* Rung 01 is built: solid, filling from the floor as the stair draws. */
.step.is-yours{background:linear-gradient(var(--ink),var(--ink)) bottom/100% 100% no-repeat;transition:background-size 520ms var(--ease-out) 300ms}
@media (hover:hover) and (pointer:fine){.step.is-yours:hover{background-color:transparent}}
.step.is-yours .step-num{color:var(--on-panel-muted)}
.step.is-yours .step-name{color:var(--on-panel)}

/* The signal: the blue square from the mark, standing on the rung being read. */
.signal{position:absolute;left:0;top:0;z-index:2;width:12px;height:12px;background:var(--accent);opacity:0;pointer-events:none;transition:opacity 240ms var(--ease)}
.signal.on{opacity:1}
.signal::after{content:"";position:absolute;inset:-5px;border:1px solid var(--accent);opacity:0}
.signal.landed::after{animation:signalLand 720ms var(--ease-out)}
@keyframes signalLand{from{opacity:.9;transform:scale(1)}to{opacity:0;transform:scale(2.3)}}

.climb-note{margin:24px 0 0;max-width:56ch;font-size:var(--fs-small);line-height:1.55;color:var(--muted);text-wrap:pretty}
.climb-note b{color:var(--ink);font-weight:600}

/* The drawing before the sheet has been seen. */
.climb:not(.in) .stair::before,.climb:not(.in) .step::after,.climb:not(.in) .dim::before{transform:scaleX(0)}
.climb:not(.in) .step::before,.climb:not(.in) .stair::after,.climb:not(.in) .stair-handoff{transform:scaleY(0)}
.climb:not(.in) .step-num,.climb:not(.in) .step-name,.climb:not(.in) .dim span{opacity:0}
.climb:not(.in) .step.is-yours{background-size:100% 0%}

/* Every rung’s detail stacked in one grid cell: the panel is as tall as the
   longest rung, so nothing below it moves when the rung changes. */
.climb-panels{display:grid;min-width:0;padding:clamp(24px,2.6vw,40px);transition:opacity 500ms var(--ease) 400ms}
.climb:not(.in) .climb-panels{opacity:0}
.rung-panel{grid-area:1 / 1;min-width:0;opacity:0;visibility:hidden;transform:translateY(calc(var(--dir,1) * 12px));
  transition:opacity 220ms var(--ease),transform 320ms var(--ease-out),visibility 0s linear 320ms}
.rung-panel.is-leaving{transform:translateY(calc(var(--dir,1) * -12px))}
.rung-panel.is-active{opacity:1;visibility:visible;transform:none;transition:opacity 320ms var(--ease) 90ms,transform 460ms var(--ease-out) 90ms,visibility 0s}
.rung-panel:focus-visible{outline:2px solid var(--focus);outline-offset:6px}
.rp-top{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4)}
.rp-num{font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;color:var(--ink-2);font-variant-numeric:tabular-nums}
.rp-own{font-size:10px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;padding:4px 9px;white-space:nowrap}
.rp-own.yours{color:var(--ink-2);border:1px solid var(--line-strong)}
.rp-own.ours{color:var(--on-panel);background:var(--ink);border:1px solid var(--ink)}
.rp-name{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em;line-height:1.15;margin:var(--sp-3) 0 var(--sp-2)}
.rp-line{margin:0;font-size:clamp(17px,1.4vw,20px);font-weight:500;line-height:1.35;color:var(--ink);text-wrap:balance}
.rp-body{margin:var(--sp-5) 0 0;padding-top:var(--sp-5);border-top:1px solid var(--line);font-size:16px;line-height:1.55;color:var(--muted);text-wrap:pretty}
.rp-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-5);margin-top:var(--sp-5)}
.rp-grid p{margin:0;font-size:var(--fs-small);line-height:1.5;color:var(--ink-2);text-wrap:pretty}
.rp-tools{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2);margin-top:var(--sp-5);padding-top:var(--sp-4);border-top:1px solid var(--line)}
.rp-tools .rb-lab{margin:0 var(--sp-2) 0 0}
.rb-lab{display:block;font-size:14px;font-weight:600;letter-spacing:-.005em;color:var(--ink-2);margin-bottom:var(--sp-2)}
.tool{font-family:var(--font-ui);font-size:var(--fs-micro);color:var(--ink-2);border:1px solid var(--line-strong);background:var(--surface);padding:5px 10px;white-space:nowrap}
.tool.none{color:var(--muted-2);border-style:dashed;background:none;font-style:italic;white-space:normal}

/* Under the instrument: the rule it demonstrates, and the way in. */
.climb-foot{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);align-items:end;margin-top:clamp(40px,5vw,64px)}
.climb-foot > .climb-act{padding-left:clamp(24px,2.6vw,40px)}
.climb-pull{margin:0;max-width:34ch;padding-left:var(--sp-5);border-left:2px solid var(--ink);font-size:clamp(20px,1.9vw,26px);font-weight:600;letter-spacing:-.015em;line-height:1.3;color:var(--ink);text-wrap:pretty}
.climb-act{display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-5)}
.ladder-runs{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--sp-2);font-size:var(--fs-micro);color:var(--muted-2)}
.ladder-runs li{display:inline-flex;align-items:center;gap:var(--sp-2)}
.ladder-runs i{display:block;width:5px;height:5px;background:var(--ink)}

@media(max-width:1279px){
  .climb{grid-template-columns:1fr;--stair-h:clamp(220px,30vw,320px)}
  .climb-sheet{border-right:0;border-bottom:1px solid var(--line)}
}
@media(max-width:900px){
  .climb-head,.climb-foot{grid-template-columns:1fr;gap:var(--sp-5)}
  .climb-head > div{padding-right:0}
  .climb-head > p,.climb-foot > .climb-act{padding-left:0}
}
@media(max-width:640px){
  .climb{--stair-h:170px}
  .climb-sheet{padding:var(--sp-5) var(--sp-4)}
  .dim span{padding:0 4px;letter-spacing:.06em}
  .dim span b{display:none}
  .step{padding:10px 4px 0 8px}
  /* Numbers only on a phone; the panel underneath names the rung. */
  .step-name{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
  .signal{width:10px;height:10px}
  .rp-grid{grid-template-columns:1fr}
}

/* ===== 06 · The working page =====
   A website drawn as a system, not a brochure. The wireframe builds itself
   block by block, and each block carries the number of the job it does:
   explain, build trust, answer the obvious questions, capture the enquiry.
   The legend ticks each job off as its block arrives. Then the form’s button
   presses, a wire draws out of the page, and the blue signal runs along it to
   a phone: the enquiry lands the moment it is sent.

   One sequence on first view, driven by classes .p1-.p7 on .webx. After
   that, pointing at a job (or its block) lights that block, and pointing at
   job 4 sends the enquiry again. Reduced motion and no-JS get the final frame. */
.webx-head{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);align-items:end;margin-bottom:clamp(32px,4vw,56px)}
.webx-head > div{padding-right:var(--sp-7)}
.webx-head > p{margin:0;max-width:46ch;padding-left:clamp(24px,2.6vw,40px);color:var(--muted);text-wrap:pretty}

.webx{--pad:clamp(24px,2.6vw,40px);display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);border:1px solid var(--line-strong);background:var(--surface)}
.webx-copy{min-width:0;padding:var(--pad);border-right:1px solid var(--line);display:flex;flex-direction:column}
.webx-copy h3{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em;line-height:1.15;margin:0;max-width:18ch;text-wrap:balance}
.webx-copy > p{margin:var(--sp-4) 0 0;font-size:16px;line-height:1.55;color:var(--muted);max-width:48ch;text-wrap:pretty}

/* The legend: the four jobs, keyed to the numbered blocks in the drawing. */
.jobs{list-style:none;margin:var(--sp-6) 0 0;padding:0;border-top:1px solid var(--line)}
.job{display:grid;grid-template-columns:22px minmax(0,1fr);gap:var(--sp-4);padding:var(--sp-4) 0;border-bottom:1px solid var(--line);cursor:default;transition:background-color var(--t-fast) var(--ease)}
/* The card’s copy arrives with its drawing: title, paragraph, then the four
   jobs 60ms apart, all before their keys start filling at p1. Pre-states only
   once JS has armed the figure, like the rest of the sequence. */
.webx.js-armed .webx-copy > :is(h3,p),.webx.js-armed .job{opacity:0;transform:translateY(16px);
  transition:opacity var(--t-reveal) var(--ease-cine),transform var(--t-reveal) var(--ease-cine),background-color var(--t-fast) var(--ease)}
.webx.js-armed.p0 .webx-copy > :is(h3,p),.webx.js-armed.p0 .job{opacity:1;transform:none}
.webx.js-armed.p0 .webx-copy > p{transition-delay:80ms,80ms,0s}
.webx.js-armed.p0 .job:nth-child(1){transition-delay:160ms,160ms,0s}
.webx.js-armed.p0 .job:nth-child(2){transition-delay:220ms,220ms,0s}
.webx.js-armed.p0 .job:nth-child(3){transition-delay:280ms,280ms,0s}
.webx.js-armed.p0 .job:nth-child(4){transition-delay:340ms,340ms,0s}
.job b{display:block;font-size:var(--fs-small);font-weight:600;color:var(--ink);letter-spacing:-.005em}
.job span:not(.key){display:block;margin-top:2px;font-size:var(--fs-small);line-height:1.45;color:var(--muted)}
.key{display:grid;place-items:center;width:22px;height:22px;margin-top:-1px;border:1px solid var(--ink);background:var(--surface);color:var(--ink);font-size:11px;font-weight:600;font-variant-numeric:tabular-nums;line-height:1;transition:background-color 260ms var(--ease),color 260ms var(--ease)}

/* The drafting sheet. */
.webx-sheet{--gap:clamp(28px,3.4vw,48px);position:relative;min-width:0;padding:var(--pad);display:grid;grid-template-columns:minmax(0,1fr) clamp(120px,13vw,164px);gap:var(--sp-3) var(--gap);align-items:end;align-content:center;
  background-image:linear-gradient(rgba(7,22,45,.035) 1px,transparent 1px),linear-gradient(90deg,rgba(7,22,45,.035) 1px,transparent 1px);
  background-size:24px 24px;background-position:-1px -1px}
.webx-sheet .fig{grid-column:1 / -1;align-self:start;margin:0 0 var(--sp-2);font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2)}

/* Wireframe browser. */
.wf{position:relative;border:1px solid var(--ink);background:var(--surface)}
.wf-bar{display:flex;align-items:center;gap:6px;height:30px;padding:0 10px;border-bottom:1px solid var(--line-strong)}
.wf-bar i{width:6px;height:6px;background:var(--line-strong)}
.wf-url{margin-left:10px;padding:2px 8px;border:1px solid var(--line);font-size:10px;letter-spacing:.02em;color:var(--muted-2)}
.wf-page{--pp:16px;padding:14px var(--pp) 16px;display:grid;grid-template-columns:minmax(0,1fr);gap:10px}
.wf-nav{display:flex;align-items:center;gap:10px;min-width:0;padding-bottom:10px;border-bottom:1px solid var(--line)}
.wf-nav .logo{width:30px;height:9px;background:var(--ink)}
.wf-nav .lk{flex:0 1 22px;min-width:8px;height:4px;background:var(--placeholder)}
.wf-nav .lk:nth-of-type(2){margin-left:auto}
.wf-nav .cta{width:40px;height:13px;border:1px solid var(--ink)}
.bar{display:block;height:6px;background:var(--placeholder);transform-origin:left}
.bar.ink{background:var(--ink)}

/* A block of the page and its number, sitting on the frame’s left edge. */
.wf-part{position:relative;padding:10px 0 10px 18px;transition:opacity 380ms var(--ease),transform 480ms var(--ease-out),filter 260ms var(--ease)}
.wf-part .key{position:absolute;left:-28px;top:50%;margin-top:-11px}
.wf-part .outline{position:absolute;inset:0 -6px 0 6px;border:1px solid transparent;pointer-events:none;transition:border-color var(--t-fast) var(--ease)}
.wf-hero{display:grid;gap:7px}
.wf-hero .bar.ink{height:11px}
.wf-hero .btn-bar{width:64px;height:15px;margin-top:5px;background:var(--ink);transform-origin:left}
.wf-proof{display:grid;gap:8px}
.wf-proof .logos{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
.wf-proof .logos i{height:16px;border:1px solid var(--line-strong);transform-origin:bottom}
.wf-faq{display:grid;gap:0}
.wf-faq .row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 0;border-bottom:1px solid var(--line)}
.wf-faq .row:last-child{border-bottom:0}
.wf-faq .plus{position:relative;width:8px;height:8px;flex:0 0 auto}
.wf-faq .plus::before,.wf-faq .plus::after{content:"";position:absolute;background:var(--ink-2)}
.wf-faq .plus::before{left:0;right:0;top:3.5px;height:1px}
.wf-faq .plus::after{top:0;bottom:0;left:3.5px;width:1px}
.wf-form{display:grid;grid-template-columns:1fr 1fr auto;gap:8px;align-items:center}
.wf-form .in{height:22px;border:1px solid var(--line-strong)}
.wf-form .send{position:relative;width:62px;height:22px;background:var(--ink);transition:transform 160ms var(--ease)}

/* The wire out of the page and the phone it ends at. */
.wire{position:absolute;left:100%;top:50%;height:1px;width:calc(var(--pp) + 1px + var(--gap));background:var(--ink);transform-origin:left}
.wf-form-row{position:relative}
.signal-run{position:absolute;left:0;top:-4px;width:9px;height:9px;background:var(--accent);opacity:0}
.phone{position:relative;align-self:end;border:1px solid var(--ink);background:var(--surface);aspect-ratio:9 / 17;padding:18px 9px 10px;display:flex;flex-direction:column;gap:8px}
.phone .note{margin-top:auto}
.phone::before{content:"";position:absolute;top:7px;left:50%;width:22px;height:3px;margin-left:-11px;background:var(--line-strong)}
.phone .ph-bar{height:5px;background:var(--placeholder)}
.phone .ph-bar.short{width:60%}
.note{position:relative;display:grid;grid-template-columns:8px 1fr;gap:6px;align-items:start;padding:8px;border:1px solid var(--ink);background:var(--surface);transform-origin:top}
.note i{width:8px;height:8px;margin-top:3px;background:var(--accent)}
.note b{display:block;font-size:11px;font-weight:600;line-height:1.25;color:var(--ink)}
.note small{display:block;font-size:10px;line-height:1.3;color:var(--muted-2)}
.phone-cap{position:absolute;left:0;right:0;bottom:-24px;font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);text-align:center;white-space:nowrap}
.webx-sheet{padding-bottom:calc(var(--pad) + 24px)}

/* ---- The sequence. Pre-states apply only once JS has marked the figure (.webx.js-armed). ---- */
/* The reveal clip leaves room either side for the keys on the left edge and the wire leaving on the right. */
.webx.js-armed .wf{clip-path:inset(-2px -90px 100% -44px);transition:clip-path 700ms var(--ease-out)}
.webx.js-armed.p0 .wf{clip-path:inset(-2px -90px -2px -44px)}
.webx.js-armed .wf-part{opacity:0;transform:translateY(6px)}
.webx.js-armed .wf-part .bar,.webx.js-armed .wf-part .btn-bar{transform:scaleX(0);transition:transform 520ms var(--ease-out) var(--d,0ms)}
.webx.js-armed .wf-proof .logos i{transform:scaleY(0);transition:transform 360ms var(--ease-out) var(--d,0ms)}
.webx.js-armed .wire{transform:scaleX(0);transition:transform 420ms var(--ease-out)}
.webx.js-armed .phone{opacity:0;transform:translateY(10px);transition:opacity 420ms var(--ease),transform 520ms var(--ease-out)}
.webx.js-armed .note{opacity:0;transform:translateY(-6px) scaleY(.9);transition:opacity 280ms var(--ease),transform 420ms var(--ease-out)}
.webx.js-armed .job .key{background:var(--surface);color:var(--ink)}

.webx.js-armed.p1 [data-part="1"],.webx.js-armed.p2 [data-part="2"],.webx.js-armed.p3 [data-part="3"],.webx.js-armed.p4 [data-part="4"]{opacity:1;transform:none}
.webx.js-armed.p1 [data-part="1"] .bar,.webx.js-armed.p1 [data-part="1"] .btn-bar,
.webx.js-armed.p2 [data-part="2"] .bar,.webx.js-armed.p3 [data-part="3"] .bar,.webx.js-armed.p4 [data-part="4"] .bar{transform:none}
.webx.js-armed.p2 .wf-proof .logos i{transform:none}
.webx.js-armed.p0 .phone{opacity:1;transform:none;transition-delay:300ms}
/* Keys fill as each job is done. */
.webx.js-armed.p1 [data-job="1"] .key,.webx.js-armed.p2 [data-job="2"] .key,.webx.js-armed.p3 [data-job="3"] .key,.webx.js-armed.p4 [data-job="4"] .key,
.webx:not(.js-armed) .key{background:var(--ink);color:var(--on-panel)}
/* The enquiry: button press, wire, signal, notification. */
.webx.p5 .wf-form .send{transform:scale(.94)}
.webx.wired .wire{transform:none}
.webx.p6 .signal-run{opacity:1;transform:translateX(var(--run,40px));transition:transform 620ms cubic-bezier(.5,0,.2,1),opacity 120ms var(--ease)}
.webx.p7 .signal-run{opacity:0;transition:opacity 160ms var(--ease)}
.webx.p7 .note{opacity:1;transform:none}

/* Pointing at a job lights its block; the others step back. */
.webx[data-focus] .wf-part{filter:opacity(.3)}
.webx[data-focus="1"] .wf-part[data-part="1"],.webx[data-focus="2"] .wf-part[data-part="2"],
.webx[data-focus="3"] .wf-part[data-part="3"],.webx[data-focus="4"] .wf-part[data-part="4"]{filter:none}
.webx[data-focus="1"] [data-part="1"] .outline,.webx[data-focus="2"] [data-part="2"] .outline,
.webx[data-focus="3"] [data-part="3"] .outline,.webx[data-focus="4"] [data-part="4"] .outline{border-color:var(--ink)}
.webx[data-focus="1"] [data-job="1"],.webx[data-focus="2"] [data-job="2"],
.webx[data-focus="3"] [data-job="3"],.webx[data-focus="4"] [data-job="4"]{background-color:var(--bg)}

/* Where most projects start, and the way in. */
.webx-start{display:grid;grid-template-columns:repeat(3,minmax(0,1fr)) auto;gap:0;align-items:end;margin-top:clamp(40px,5vw,64px);border-top:1px solid var(--line-strong)}
.webx-start .lab{grid-column:1 / -1;margin:var(--sp-5) 0 var(--sp-2);font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2)}
.ws{align-self:stretch;padding:var(--sp-3) var(--sp-6) 0 0}
.ws + .ws{padding-left:var(--sp-6);border-left:1px solid var(--line)}
.ws h4{margin:0;font-size:var(--fs-small);font-weight:600;color:var(--ink)}
.ws p{margin:4px 0 0;font-size:var(--fs-small);line-height:1.45;color:var(--muted)}
/* The CTA in this row is a .link-cta, not a .btn — the old selector matched
   nothing, so the link sat hard against the third column with no gutter. */
.webx-start .btn,.webx-start .link-cta{margin-left:var(--sp-6)}

@media(max-width:1199px){
  .webx{grid-template-columns:1fr}
  .webx-copy{border-right:0;border-bottom:1px solid var(--line)}
  /* Scoped: only the .webx figure goes full width here and has the room for
     two columns. The same list inside a .web-copy column does not. */
  .webx .jobs{display:grid;grid-template-columns:1fr 1fr;column-gap:var(--sp-6)}
  .webx-start{grid-template-columns:repeat(3,minmax(0,1fr))}
  .webx-start .btn,.webx-start .link-cta{grid-column:1 / -1;justify-self:start;margin:var(--sp-6) 0 0}
}
@media(max-width:900px){
  .webx-head{grid-template-columns:1fr;gap:var(--sp-5)}
  .webx-head > div{padding-right:0}
  .webx-head > p{padding-left:0}
  .webx-start{grid-template-columns:1fr}
  .ws,.ws + .ws{padding:var(--sp-3) 0;border-left:0;border-bottom:1px solid var(--line)}
  .webx-start .btn,.webx-start .link-cta{margin:var(--sp-5) 0 0;justify-self:start}
}
@media(max-width:640px){
  .webx .jobs{grid-template-columns:1fr}
  .webx-sheet{--gap:22px;grid-template-columns:minmax(0,1fr) 92px;padding-left:calc(var(--pad) + 16px)}
  .wf-page{--pp:12px;padding:12px var(--pp) 14px}
  .wf-part{padding-left:12px}
  .wf-part .key{left:-24px;width:20px;height:20px;margin-top:-10px;font-size:10px}
  .wf-form{grid-template-columns:1fr auto}
  .wf-form .in:nth-child(2){display:none}
  .phone{padding:16px 6px 10px}
  .note{grid-template-columns:1fr;padding:6px}
  .note i,.note small{display:none}
  .phone-cap{letter-spacing:.06em}
}

/* ===== 03 · Signal: the one scroll interaction that argues something =====
   Everywhere else on this page motion reveals content that was already
   written. Here the motion IS the content: the same eighteen data points are
   shown scattered, then tabulated, then plotted, because that is literally
   the service being sold and a paragraph describing it convinces nobody.

   Each dot carries three position sets as custom properties — --sx/--sy
   scattered, --gx/--gy tabulated, --cx/--cy plotted — authored into the
   markup so the geometry is static, indexable and needs no JS to compute.
   JS only writes data-stage="1..5" on the section; every visual consequence
   below hangs off that one attribute, which means the whole interaction has
   exactly one moving part and degrades to a static final frame by setting
   that attribute once.

   Restraint: this is the only sticky section on the page, and the only place
   scroll position drives anything other than a fade. */
.scrolly{position:relative}
.sy-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.06fr);gap:clamp(32px,5vw,72px);margin-top:var(--sp-8);align-items:start}

.sy-step{min-height:30vh;display:flex;flex-direction:column;justify-content:center;padding-right:var(--sp-5);opacity:.3;transition:opacity var(--t-base) var(--ease)}
.sy-step.is-on{opacity:1}
.sy-num{font-family:var(--font-mono);font-size:var(--fs-label);font-weight:600;letter-spacing:.14em;color:var(--ink-2)}
.sy-step h3{font-family:var(--font-ui);font-size:var(--fs-h3);font-weight:600;letter-spacing:-.014em;line-height:1.2;margin:var(--sp-3) 0;text-wrap:balance}
.sy-step p{font-size:var(--fs-body);color:var(--muted);max-width:42ch;text-wrap:pretty}

/* Sticky offset clears the 72px header plus a little air. */
.sy-stage{position:sticky;top:clamp(92px,13vh,140px)}
.sy-panel{position:relative;border:1px solid var(--panel);border-radius:0;background:var(--panel);color:var(--on-panel);box-shadow:var(--shadow-md);padding:var(--sp-5)}
.sy-head{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding-bottom:var(--sp-4);border-bottom:1px solid var(--line-invert)}
.sy-head b{font-family:var(--font-ui);font-size:var(--fs-micro);font-weight:600;color:var(--on-panel)}
/* A readout naming the stage the reader is looking at. It is the caption the
   visual would otherwise need, and it changes without a layout shift. */
.sy-state{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--on-panel-muted);white-space:nowrap}
.sy-state::after{content:"Unstructured"}
.scrolly[data-stage="2"] .sy-state::after{content:"Collected"}
.scrolly[data-stage="3"] .sy-state::after{content:"Plotted"}
.scrolly[data-stage="4"] .sy-state::after{content:"Pattern"}
.scrolly[data-stage="5"] .sy-state::after{content:"Acted on"}
.scrolly[data-stage="5"] .sy-state{color:var(--good-invert)}
.sy-svg{display:block;width:100%;height:auto;margin-top:var(--sp-4)}

/* --- The scrub ---------------------------------------------------------
   Five phase values, 0→1, written by JS from scroll position and consumed
   here as plain numbers in calc(). Nothing below transitions on time: the
   reader’s scroll IS the timeline, so a transition would only add lag
   between the wheel and the picture.

     --t1  scattered  → tabulated
     --t2  tabulated  → plotted   (key points only)
     --t3  the series line drawing, and the axis
     --t4  the area fill and the peak marker
     --t5  the two actions that follow

   Defaults are 1, not 0: with no JS the panel renders its finished frame,
   which is the readable one. JS drops them to the scrubbed value on its
   first frame, before the section is anywhere near the viewport. */
.scrolly{--t1:1;--t2:1;--t3:1;--t4:1;--t5:1}

.sy-dot{
  fill:var(--on-panel-muted);
  transform-box:view-box;transform-origin:0 0;
  /* Position is lerped, so only the colour step needs easing over time. */
  transition:fill 320ms var(--ease);
  --lx:calc(var(--sx) + (var(--gx) - var(--sx)) * var(--t1));
  --ly:calc(var(--sy) + (var(--gy) - var(--sy)) * var(--t1));
  transform:translate(var(--lx),var(--ly));
  opacity:calc(.45 + .45 * var(--t1) - .76 * var(--t2));
}
/* The seven carrying the series continue from wherever --t1 left them to the
   chart, so the two moves compose instead of fighting. The other eleven stay
   put and drop back to a field — nothing is thrown away, which is the whole
   point of the section. */
.sy-dot.key{
  transform:translate(
    calc(var(--lx) + (var(--cx) - var(--lx)) * var(--t2)),
    calc(var(--ly) + (var(--cy) - var(--ly)) * var(--t2))
  );
  opacity:calc(.45 + .55 * var(--t1));
}
.scrolly[data-stage="1"] .sy-dot,.scrolly[data-stage="2"] .sy-dot{fill:var(--on-panel)}
/* The plotted series is the one blue thing; the points that carry it stay white. */
.sy-dot.key{fill:var(--on-panel)}
.scrolly[data-stage="1"] .sy-dot.key,.scrolly[data-stage="2"] .sy-dot.key{fill:var(--on-panel)}

.sy-rule{stroke:var(--line-invert);stroke-width:1;opacity:calc(var(--t1) - var(--t2))}
.sy-line{fill:none;stroke:var(--accent);stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1;stroke-dashoffset:calc(1 - var(--t3))}
.sy-area{fill:rgba(30,123,255,.16);opacity:var(--t4)}
.sy-axis{opacity:var(--t3)}
.sy-axis text{font-family:var(--font-mono);font-size:10px;fill:var(--on-panel-muted)}
.sy-peak{fill:none;stroke:var(--accent);stroke-width:1.5;transform-box:view-box;transform-origin:350px 62px;opacity:calc(.75 * var(--t4));transform:scale(calc(.4 + .6 * var(--t4)))}

/* Reserved in layout at every stage so the panel never changes height. */
.sy-out{margin-top:var(--sp-4);padding-top:var(--sp-4);border-top:1px solid var(--line-invert);display:grid;gap:var(--sp-3);opacity:var(--t5);transform:translateY(calc(8px - 8px * var(--t5)))}
.sy-row{display:flex;align-items:flex-start;gap:var(--sp-3);font-family:var(--font-ui);font-size:var(--fs-micro);line-height:1.5;color:var(--on-panel-muted)}
.sy-row svg{width:15px;height:15px;flex:0 0 auto;margin-top:2px}
.sy-row b{color:var(--on-panel);font-weight:600}
.sy-row.warn svg{color:var(--warn-invert)}
.sy-row.good svg{color:var(--good-invert)}

@media(max-width:900px){
  /* No sticky pin on a phone: the visual leads, the steps read as a list
     under it, and the stage is fixed at its final frame. */
  .sy-grid{grid-template-columns:1fr;gap:var(--sp-5);margin-top:var(--sp-7)}
  .sy-stage{position:static;order:-1}
  .sy-step{min-height:0;opacity:1;padding:var(--sp-5) 0 0;border-top:1px solid var(--line)}
  .sy-step:first-child{border-top:0;padding-top:var(--sp-4)}
  .sy-step p{max-width:none}
}
@media(prefers-reduced-motion:reduce){
  .sy-step{opacity:1!important}
}

/* ===== Motion: lists arrive in reading order =====
   The heading lands first, then its supporting rows, so the eye is led down
   the column instead of the whole block appearing at once. */
.points li,.res-lines > div{
  opacity:0;transform:translateY(16px);
  transition:opacity var(--t-reveal) var(--ease-cine),transform var(--t-reveal) var(--ease-cine);
}
.in .points li,.in .res-lines > div{opacity:1;transform:none}
.in .points li:nth-child(1),.in .res-lines > div:nth-child(1){transition-delay:180ms}
.in .points li:nth-child(2),.in .res-lines > div:nth-child(2){transition-delay:240ms}
.in .points li:nth-child(3),.in .res-lines > div:nth-child(3){transition-delay:300ms}
.in .points li:nth-child(4){transition-delay:360ms}
.in .points li:nth-child(5){transition-delay:420ms}
.in .points li:nth-child(6){transition-delay:480ms}

/* The process rule draws itself rather than being there from the start. */
.proc-line.reveal{opacity:1;transform:scaleX(0);transform-origin:left center;transition:transform var(--t-data) var(--ease-out) 120ms}
.proc-line.reveal.in{transform:scaleX(1)}

/* A step lighting up as the order passes through it — the dark-panel twin of
   .pipe-step.lit in the hero. */
.flow-node{transition:opacity var(--t-reveal) var(--ease-out),transform var(--t-reveal) var(--ease-out),border-color var(--t-base) var(--ease),background var(--t-base) var(--ease),color var(--t-base) var(--ease)}
.flow-node.lit{border-color:var(--accent);background:var(--panel-2);color:var(--on-panel)}
.flow-node .fl-icon{transition:color var(--t-base) var(--ease),transform var(--t-base) var(--ease-out)}
.flow-node.lit .fl-icon{background:var(--accent);border-color:var(--accent);color:#fff;transform:scale(1.12)}
.flow-node.fl-done.lit{border-color:var(--accent)}
.flow-node.fl-done.lit .fl-icon{background:var(--accent);border-color:var(--accent);color:#fff}
.fl-arrow{transition:opacity var(--t-reveal) var(--ease-out),color var(--t-base) var(--ease),transform var(--t-base) var(--ease-out)}
.fl-arrow.lit{color:var(--accent-on-panel);transform:translateX(2px)}

/* ===== Responsive ===== */
@media(max-width:1000px){
  .web-grid{grid-template-columns:1fr;gap:48px}
  .web-grid.rev{grid-template-columns:1fr}
  .web-grid.rev .web-copy,.web-grid.rev .web-visual{order:initial}
  .web-copy p{max-width:100%}
  .prob-grid{grid-template-columns:repeat(2,1fr)}
  .proc-grid{grid-template-columns:repeat(2,1fr);gap:40px 0}
  .proc-line{display:none}
  .res-grid{grid-template-columns:1fr}
  .foot-grid{grid-template-columns:1fr 1fr}
}
/* The header row (measured 2026-09-19): brand, six links and one button,
   "Talk to our AI agent". The form CTA that used to sit here is in the hero,
   mid-page and the footer of every page and in the phone menu; two buttons
   beside six links read as a crowd, and the agent hands people to the form and
   WhatsApp anyway. Under 1120 the links hand over to the menu button; the agent
   button stays in the bar beside it, compact. The three service pages are top-level links on
   purpose — no Solutions dropdown. site.js closes the phone menu on resize at
   the same 1120 threshold. */
@media(max-width:1119px){
  .nav-links,.nav-cta .btn:not(.nav-ask){display:none}
  .nav-toggle{display:grid;place-items:center}
  .nav-inner{gap:var(--sp-3)}
  .nav-cta .nav-ask{min-height:44px;padding:0 14px 0 12px;font-size:14px}
}
/* Phones under 500px: brand, the blue square and the menu button; the label
   stays for screen readers. */
@media(max-width:499px){
  .nav-cta .nav-ask{width:44px;padding:0;justify-content:center}
  .nav-cta .nav-ask-ico{width:20px;height:20px}
  .nav-cta .nav-ask-label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
}
@media(max-width:760px){
  .mid-cta{flex-direction:column;gap:var(--sp-4);text-align:center}
  .mid-cta .btn{width:100%}
  .more-strip{align-items:flex-start;text-align:left;gap:var(--sp-5)}
  .cf-row{grid-template-columns:1fr}
  .contact-form{margin-top:var(--sp-6)}
  .cf-submit{align-self:stretch}
  .prob-grid{grid-template-columns:1fr}
  .proc-grid{grid-template-columns:1fr}
  .foot-grid{grid-template-columns:1fr}
  .foot-col a{padding:var(--sp-3) 0}
  .dash-kpis{grid-template-columns:1fr 1fr}
  .dash-kpis .dk:nth-child(3){grid-column:span 2}
  .flow-row{flex-direction:column;gap:10px}
  .flow-row .fl-arrow{transform:rotate(90deg)}
  .flow-node{flex-direction:row;text-align:left;width:100%}
}
/* ===== Plain-text pages (privacy, 404) =====
   One column of running text at the reading measure, set like the FAQ answers. */
.prose{max-width:var(--maxw-text);margin:var(--sp-7) auto 0}
.prose h2{font-family:var(--font-ui);font-size:var(--fs-h3);font-weight:600;letter-spacing:-.012em;line-height:1.25;margin:var(--sp-6) 0 var(--sp-3)}
.prose h2:first-child{margin-top:0}
.prose p,.prose li{font-size:var(--fs-body);color:var(--ink-2);line-height:1.6;margin:0 0 var(--sp-3)}
.prose ul{margin:0 0 var(--sp-3);padding-left:1.2em}
.prose a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--accent);text-underline-offset:3px}

/* ===== Interaction feedback =====
   Every card on this page was inert. Cards that look clickable and answer
   nothing are worse than plain blocks, so the response is small and the same
   everywhere: a 2px lift, the border firming up, and the one coloured detail
   inside coming forward. Nothing scales, nothing shadows. */
/* Hover lift on `translate`, not `transform`: the reveal owns transform. The
   transition itself lives in the .reveal rule above (every .prob/.res is one). */
/* Premium pass: these cards link nowhere, so they no longer lift like buttons.
   The border firming up is the whole answer. */
@media (hover:hover) and (pointer:fine){.prob:hover,.res:hover{border-color:var(--line-strong)}}

/* The use-case rows are a list, so the feedback is a list’s: the rule at the
   left thickens into the accent and the text steps across to meet it. */
/* The step-across is a transform on the text, not padding on the row: padding
   would relayout the list on every hover, and the rule has to stay put at
   left:0 while the copy moves away from it. */


/* ===== Nav: compaction without layout shift =====
   Shrinking the header’s height would move every pixel of the page under it,
   so the row stays 72px and the compaction is carried by the mark and the
   ground: the bar goes translucent and blurs whatever it is passing over,
   which on this page is the hero photograph. */
.nav-inner{transition:none}
header.nav{background:var(--bg)}
header.nav.scrolled{background:rgba(248,250,252,.88)}
@supports (backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px)){
  header.nav.scrolled{-webkit-backdrop-filter:blur(12px) saturate(1.4);backdrop-filter:blur(12px) saturate(1.4)}
}

/* ===== Final CTA =====
   The last panel on the page gets one moment and no more: a rust rule drawing
   across the top of the band, and the question arriving on a masked line.
   Everything else here stays exactly as still as it was. */
section:has(> .cta-band)::after{content:"";position:absolute;top:0;left:0;height:2px;margin-inline:calc(50% - 50vw);width:100vw;background:var(--accent);transform:scaleX(0);transform-origin:left center;transition:transform 900ms var(--ease-out) 120ms}
section:has(> .cta-band.in)::after{transform:scaleX(1)}
/* The padding keeps descenders on the last line clear of the mask; the
   negative margin gives the space back. */
.cta-band h2 .ln{display:block;overflow:hidden;padding-bottom:.1em;margin-bottom:-.1em}
.cta-band h2 .ln > span{display:block;transform:translateY(108%);transition:transform 560ms var(--ease-out) 120ms}
.cta-band.in h2 .ln > span{transform:none}

@media(prefers-reduced-motion:reduce){
  section:has(> .cta-band)::after{transform:scaleX(1)!important}
  .cta-band h2 .ln > span{transform:none!important}
}

/* Positional motion goes; colour and opacity feedback stays. A blanket
   transition:none also kills hover and focus feedback, which users with
   vestibular sensitivity still need. */
@media(prefers-reduced-motion:reduce){
  *{animation:none!important}
  *{transition-duration:var(--t-fast)!important}
  .reveal,.reveal-scale,.reveal-left,.reveal-right{opacity:1!important;transform:none!important}
  .points li,.res-lines > div{opacity:1!important;transform:none!important}
  .web-visual > *{clip-path:none!important;transform:none!important}
  .climb *,.climb *::before,.climb *::after{transition-delay:0s!important}
  .webx *,.webx *::before,.webx *::after{transition-delay:0s!important}
  .bp .draw{stroke-dashoffset:0!important}
  .proc-line.reveal{transform:none!important}
  /* Pseudo-elements and the SVG points are not covered by the class resets
     above, and both hold their pre-state in a transform. Without these the
     climb rule stays at zero width and the plotted series never arrives. */
  .proc-line i{transform:scaleX(var(--pp,1))!important}
  .sy-dot{transition:none!important}
  .sy-out{opacity:1!important;transform:none!important}
  html{scroll-behavior:auto}
  /* Hover and focus keep their colour and border, not their movement. */
  .prob:hover,.res:hover,.vcard:hover,.vcard:focus-visible{translate:none!important}
  .arrow{transform:none!important}
  /* Entrances fade in place. */
  .mobile-menu,.cf-success{transform:none!important}
  .dc-bars i{transform:none!important}
  :is(.section-head,.climb-head) :is(.eyebrow,.title,p,.hero-cta){clip-path:none!important;transform:none!important}
  .webx-copy > *,.job,.vcard img{transform:none!important}
}

/* ===== Homepage: About =====
   The statement beside three facts. Same rhythm as a case study (label above
   body), so it reads as the same family as the rest of the site. */
.about-grid{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);gap:clamp(40px,6vw,96px);align-items:start}
.about-grid .section-head{max-width:none}
.about-facts{margin:0;display:flex;flex-direction:column;border-top:1px solid var(--line-strong)}
.about-facts > div{padding:var(--sp-5) 0;border-bottom:1px solid var(--line)}
.about-facts dt{font-family:var(--font-mono);font-size:var(--fs-small);font-weight:600;letter-spacing:-.005em;color:var(--ink-2);margin-bottom:var(--sp-2)}
.about-facts dd{margin:0;font-family:var(--font-ui);font-size:var(--fs-small);line-height:1.55;color:var(--muted);text-wrap:pretty}
@media(max-width:900px){
  .about-grid{grid-template-columns:1fr;gap:var(--sp-6)}
}

/* ===== Homepage: Value (the router) =====
   Three cards, one per service page, the whole card a link. The image is that
   page’s hero-video poster, so the card shows the story the page tells; the
   list underneath is the symptoms that page is for. Lift on hover like every
   other card, using translate so the reveal keeps transform. */
.value-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px;margin-top:var(--sp-7)}
.vcard{display:flex;flex-direction:column;border:1px solid var(--line);background:var(--surface);color:inherit;text-decoration:none;overflow:hidden}   /* transitions come from .reveal */
.vcard:focus-visible{translate:0 -2px;border-color:var(--ink);box-shadow:var(--lift)}
@media (hover:hover) and (pointer:fine){.vcard:hover{translate:0 -2px;border-color:var(--ink);box-shadow:var(--lift)}}
.vcard:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
/* Press, for touch as much as mouse: the hover border, snapped on so it shows
   before the page changes. No movement on a card this size. */
.vcard:active{border-color:var(--ink);transition-duration:0s}
/* The still sits in its own clip, so it can lean in on hover without spilling
   over the copy. The rule lives on the frame: a scaled img would scale it too. */
.vc-media{display:block;overflow:hidden;border-bottom:1px solid var(--line)}
.vcard img{display:block;width:100%;height:auto;aspect-ratio:3 / 2;object-fit:cover;background:var(--bg);transition:transform 600ms var(--ease-cine)}
@media (hover:hover) and (pointer:fine){.vcard:hover img{transform:scale(1.025)}}
.vc-body{display:flex;flex-direction:column;flex:1;padding:var(--sp-5)}
.vc-key{font-family:var(--font-mono);font-size:14px;font-weight:600;letter-spacing:0;font-variant-numeric:tabular-nums;color:var(--accent-hover);margin-bottom:var(--sp-3)}
.vcard h3{font-family:var(--font-ui);font-size:var(--fs-h3);font-weight:600;letter-spacing:-.015em;line-height:1.15;margin:0 0 var(--sp-2);color:var(--ink)}
.vc-line{font-family:var(--font-ui);font-size:var(--fs-small);line-height:1.5;color:var(--muted);margin:0 0 var(--sp-4);text-wrap:pretty}
.vc-signs{list-style:none;margin:0 0 var(--sp-5);padding:var(--sp-4) 0 0;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:var(--sp-2)}
.vc-signs li{position:relative;padding-left:var(--sp-4);font-family:var(--font-ui);font-size:var(--fs-small);line-height:1.45;color:var(--ink-2)}
.vc-signs li::before{content:"";position:absolute;left:0;top:.55em;width:5px;height:5px;background:var(--ink)}
.vc-go{margin-top:auto;font-family:var(--font-ui);font-size:var(--fs-small);font-weight:600;color:var(--ink);text-decoration:underline;text-decoration-color:var(--accent);text-underline-offset:5px;text-decoration-thickness:1px}
@media(max-width:1000px){
  .value-grid{grid-template-columns:1fr;gap:var(--sp-5);max-width:560px}
}

/* ===== Solutions: the example under each block ===== */
/* A primary way on and a secondary one, on a single line: two stacked links
   with the usual block spacing read as two orphans rather than one choice. */
.cta-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-3) var(--sp-6);margin-top:var(--sp-6)}
.cta-row .link-cta{margin:0}
.example-link{margin:var(--sp-4) 0 0;font-family:var(--font-ui);font-size:var(--fs-small)}
.cta-row .example-link{margin:0}
/* Two links in one line need air around the divider, and the divider itself
   must not pick up the underline the links carry. */
.example-link .ex-sep{display:inline-block;margin:0 var(--sp-2);color:var(--ink-2);text-decoration:none}
.example-link a{display:inline-block;padding-block:3px;color:var(--ink);font-weight:600;text-decoration:underline;text-decoration-color:var(--accent);text-underline-offset:5px;text-decoration-thickness:1px}

/* ===== Work: each example with its drawing ===== */
.res-list{margin-top:var(--sp-7);border-top:1px solid var(--line-strong)}
.res-list .res{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(32px,5vw,72px);align-items:center;padding:clamp(40px,5vw,64px) 0;border:0;border-bottom:1px solid var(--line-strong);background:transparent;transition:none}
@media (hover:hover) and (pointer:fine){.res-list .res:hover{translate:none;box-shadow:none;border-color:var(--line-strong)}}
.res-list .res:nth-child(even) .res-copy{order:2}
.res-copy{display:flex;flex-direction:column;gap:var(--sp-4);align-items:flex-start}
.res-meta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-3)}
.res-title{font-family:var(--font-ui);font-size:clamp(26px,2.6vw,36px);font-weight:600;letter-spacing:-.025em;line-height:1.1;margin:0;max-width:16ch;text-wrap:balance}
/* 16ch suits the short titles on the work page; a longer one broke into four
   ragged lines, so .wide gives that case the measure it needs. */
.res.wide .res-title{max-width:22ch}
.res-list .res-lines{gap:var(--sp-3);max-width:52ch}
.res-link{margin:var(--sp-2) 0 0;font-family:var(--font-ui);font-size:var(--fs-small)}
.res-link a{color:var(--ink);font-weight:600;text-decoration:underline;text-decoration-color:var(--accent);text-underline-offset:5px;text-decoration-thickness:1px}
.res-fig{position:relative;border:1px solid var(--line-strong);background:var(--surface)}
.res-fig .bp{display:block;width:100%;height:auto}
.res-fig .fig{position:absolute;left:var(--sp-4);bottom:var(--sp-3);font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2)}
/* Blueprint drawing: a 32-unit construction grid, measured rectangles, one blue marker. Lines draw on reveal. */
.bp .bp-grid{fill:none;stroke:var(--ink);stroke-opacity:.09;stroke-width:1}
.bp .g-line{fill:none;stroke:var(--ink);stroke-opacity:.5;stroke-width:1}
.bp .g-lbl{font-family:var(--font-ui);font-size:7px;font-weight:600;letter-spacing:1px;fill:var(--ink-2);text-transform:uppercase}
.bp .g-sig{fill:var(--accent)}
.bp .draw{stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset 900ms var(--ease-out) calc(var(--i,0) * 120ms + 200ms)}
.in .bp .draw{stroke-dashoffset:0}
@media(max-width:1000px){
  .res-list .res{grid-template-columns:1fr;gap:var(--sp-5)}
  .res-list .res:nth-child(even) .res-copy{order:0}
  .res-fig{max-width:560px}
}

/* ===== FAQ: four groups ===== */
.faq-groups{max-width:var(--maxw-text);margin:var(--sp-7) auto 0;display:grid;gap:clamp(40px,5vw,64px)}
.faq-group-title{display:flex;align-items:baseline;gap:var(--sp-3);margin:0 0 var(--sp-2);font-family:var(--font-ui);font-size:var(--fs-h4);font-weight:700;letter-spacing:-.01em}
.faq-group-title .ch{font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;color:var(--muted-2);font-variant-numeric:tabular-nums}
.faq-groups .faq-list{margin-top:0}

/* ===== How we work: the rung’s link to its solution ===== */
.tool-link{color:var(--ink);font-weight:600;border-color:var(--ink);text-decoration:none}

/* ===== Inner pages =====
   Each inner page opens on its own h1. The page head on Solutions indexes
   the three sections below it. */
.page-title{font-size:clamp(40px,5.2vw,68px);font-weight:600;letter-spacing:-.034em;line-height:1.02;margin:var(--sp-5) 0 var(--sp-4);text-wrap:balance}
.section-head .page-title{margin-inline:auto}
.climb-head .page-title{margin-bottom:0}
.section-head:has(> .page-title){max-width:960px}
.page-head{padding-block:clamp(64px,7vw,104px) clamp(16px,2vw,32px)}
.page-head h2.title{max-width:16ch;margin-bottom:var(--sp-4)}
.page-head + section.pad{padding-top:clamp(40px,5vw,72px)}
.page-head .page-title{max-width:16ch}
.page-head > p{max-width:60ch;margin:0;font-size:clamp(17px,1.4vw,20px);line-height:1.5;color:var(--muted);text-wrap:pretty}
.page-index{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin-top:clamp(32px,4vw,56px);border-top:1px solid var(--line-strong)}
.page-index a{display:flex;align-items:baseline;gap:var(--sp-3);padding:var(--sp-4) var(--sp-5) var(--sp-4) 0;font-weight:600;color:var(--ink);transition:color var(--t-fast) var(--ease)}
.page-index a + a{padding-left:var(--sp-5);border-left:1px solid var(--line)}
.page-index a span{font-size:var(--fs-label);font-weight:600;letter-spacing:.12em;color:var(--muted-2);font-variant-numeric:tabular-nums}
@media (hover:hover) and (pointer:fine){.page-index a:hover{color:var(--accent-hover)}}
@media(max-width:760px){
  .page-index{grid-template-columns:1fr}
  .page-index a + a{padding-left:0;border-left:0;border-top:1px solid var(--line)}
}

/* ===== Colour pass (2026-09-19) =====
   The owner found the site monochrome and boring: section bands a shade
   apart (#F8FAFC against #FFFFFF), blue only in 5px dots, every paragraph a
   pale grey. Three moves on the existing structure, no new layout:
   1. Each service owns a colour (tokens in :root) and its page wears it on
      its bands, markers, icons, step numbers and hero tag.
   2. One navy section per page (.band.dark) gives the scroll a heavy beat.
   3. Text darkened (--muted) and section intros set in ink-2. */

/* The current service. Set on <body> for a service page, or on one card,
   row or link that points at a service. */
.svc-see{--svc:var(--see);--svc-ink:var(--see-ink);--svc-soft:var(--see-soft);--svc-line:var(--see-line);--svc-tag:var(--see-tag);--svc-on:var(--see-on)}
.svc-auto{--svc:var(--auto);--svc-ink:var(--auto-ink);--svc-soft:var(--auto-soft);--svc-line:var(--auto-line);--svc-tag:var(--auto-tag);--svc-on:var(--auto-on)}
.svc-grow{--svc:var(--grow);--svc-ink:var(--grow-ink);--svc-soft:var(--grow-soft);--svc-line:var(--grow-line);--svc-tag:var(--grow-tag);--svc-on:var(--grow-on)}

/* Bands are white against the ground. They wore the service tint until the
   premium pass: full-bleed pastel washes read as a template, so the colour
   now lives in the precise places listed below (markers, steps, rules, the
   page label, the scroll bar) and the navy band gives each page its weight.
   The contact section is navy edge to edge; the accent rule sits on its top
   edge (the ::after in the Final CTA block). Any section holding a .cta-band
   gets the navy, not only #cta: the CTA on how-we-work, faq and privacy sits
   under id="next", and keying the navy to the id left light-on-light text. */
.band::before{background:var(--bg-2)}
.band:has(.cta-band)::before{background:var(--panel);border-color:var(--panel)}

/* The navy section: the same components, with the ink and line tokens
   turned over. Light type on navy gets a little more leading. */
.band.dark{
  --bg:#07162D;--bg-2:#07162D;--surface:#0D2240;--surface-sunken:#0A1C36;--placeholder:rgba(248,250,252,.18);
  --ink:#F8FAFC;--ink-2:#D5DEEA;--muted:#A9B8CC;--muted-2:#A9B8CC;
  --line:rgba(248,250,252,.12);--line-strong:rgba(248,250,252,.24);--control-line:var(--control-line-invert);
  --focus:var(--focus-invert);
  color:var(--ink);
}
.band.dark{background:var(--panel)}   /* the full-bleed navy is ::before; this is the same navy under the text itself */
.band.dark::before{background:var(--panel);border-color:var(--panel)}
.band.dark p,.band.dark dd{line-height:1.6}

/* Wayfinding: the page’s own colour on the things that say where you are. */
.scroll-progress i{background:var(--svc)}
.nav-links a.is-current::after{background:var(--svc)}
.mm-links a[aria-current="page"]{color:var(--svc-ink)}
.eyebrow .dot{background:var(--svc)}
section:has(> .cta-band)::after{background:var(--svc)}
::selection{background:var(--svc-tag);color:var(--svc-on)}
/* A service page’s hero label says where you are in its colour: the text in
   the readable ink of that hue, the square in the hue itself. (It was a filled
   tag until the premium pass.) */
:is(.svc-see,.svc-auto,.svc-grow) .page-hero .eyebrow{color:var(--svc-ink)}

/* Links carry the colour of where they go (a .svc-* class on the link). */
.link-cta,.vc-go,.res-link a,.res-note a{text-decoration-color:var(--svc)}
.web-copy p.pull{border-left-color:var(--svc)}

/* Pain-point cards: plain blocks, heading and body. */
.prob h3{font-weight:600;letter-spacing:-.012em}

/* Lists and steps mark in the service colour. */
.points li::before,.vc-signs li::before{background:var(--svc)}
.proc-line i{background:var(--svc)}
.proc h3{font-weight:600}
.proc.done .num{background:var(--svc-tag);border-color:var(--svc-tag);color:var(--svc-on)}
.proc.active .num{background:var(--svc-tag);border-color:var(--svc-tag);color:var(--svc-on);outline:1px solid var(--svc);outline-offset:5px}

/* Proof: the three labels in the service colour, and room between the
   groups so each label reads as the start of its own answer. */
.res-lines dt{color:var(--svc-ink);margin-bottom:6px}
.res-list .res-lines{gap:var(--sp-5)}
.res-meta .sector{color:var(--svc-ink);font-weight:600}

/* Homepage: the cards each wear their service. The card’s still sits on
   its tint and multiplies into it, so a pale wireframe reads as that
   service’s colour instead of grey. */
@media (hover:hover) and (pointer:fine){
  .vcard:hover{border-color:var(--svc)}
}
.vcard:focus-visible,.vcard:active{border-color:var(--svc)}
.vc-media{background:var(--svc-soft)}
.vc-media img{mix-blend-mode:multiply}
.vc-key{color:var(--svc-ink)}

/* Type: section intros in ink-2 rather than the paragraph grey, and no
   interface label below 11px. */
.section-head > p{color:var(--ink-2)}
.res-tag,.rp-own,.ai-who,.ai-src{font-size:11px}

/* ===== Spacing pass (2026-09-19) =====
   Measured empty bands, not eyeballed ones (tools/checks/space.js): margins stacking on
   section padding were leaving 300–420px of nothing in three places. */
/* The mid-page CTA closing a section: the section’s padding is the gap. */
.mid-cta:last-child{margin-bottom:0}
/* Without the scrub (reduced motion) the steps are just a list, so they lose
   the half-screen height that only exists to drive the chart. */
@media(prefers-reduced-motion:reduce){.sy-step{min-height:0;padding-block:var(--sp-5)}}

/* The contact card on a wide screen. Centred, a 560px form sat in a 1,150px
   navy card with bare navy either side and ran ~1,100px tall. Side by side:
   the ask on the left (the ways to reach us pinned to its foot), the form on
   the right. The last row is 1fr so the form’s height lands there instead
   of prising the left column apart. Only cards that carry a form. */
@media(min-width:1100px){
  .cta-band:has(.contact-form){display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.12fr);grid-template-rows:auto auto auto 1fr;column-gap:clamp(56px,6vw,104px);text-align:left;align-items:start}
  .cta-band:has(.contact-form) > :is(.eyebrow,h2,p){grid-column:1}
  .cta-band:has(.contact-form) > .eyebrow{justify-self:start}
  .cta-band:has(.contact-form) > h2{margin-top:var(--sp-4)}
  .cta-band:has(.contact-form) > p{margin-inline:0;max-width:46ch}
  .cta-band:has(.contact-form) > :is(.contact-form,.cf-success){grid-column:2;grid-row:1 / -1;margin:0;max-width:none;width:100%}
  .cta-band:has(.contact-form) > .cta-alt{grid-column:1;grid-row:4;align-self:end;margin-top:var(--sp-7)}
  .cta-band:has(.contact-form) .cta-alt-links{justify-content:flex-start}
}
/* About: the facts column opened with a rule 15px above the statement’s
   label. Without it, BUILT FOR sits level with WHAT DAYAM INSIGHTS IS. */
.about-facts{border-top:0}
.about-facts > div:first-child{padding-top:0}
/* Block-level, so the label has no line box above it and starts on the
   grid line like the dt beside it. */
.about-grid .section-head > .eyebrow{display:flex;width:fit-content}

/* ===== Automation: #work-that (2026-09-19) =====
   The four kinds of work sat in a list beside a 230px diagram, so the
   diagram’s column ran ~360px of empty grey under it, and its top sat 19px
   above the heading beside it. Now the intro and the diagram are a pair,
   centred on each other, and the four kinds take the full width beneath as
   the section’s subject. */
#work-that .web-grid{align-items:center}
#work-that .web-block{margin-bottom:0}
#work-that .web-copy .title.sm{margin-top:0}
.jobs-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin:clamp(40px,4.5vw,64px) 0 0;border-top:1px solid var(--ink)}
.jobs-row .job{grid-template-columns:1fr;align-content:start;gap:var(--sp-4);padding:var(--sp-5) var(--sp-5) 0 0;border-bottom:0}
.jobs-row .job + .job{padding-left:var(--sp-5);border-left:1px solid var(--line)}
.jobs-row .key{width:28px;height:28px;margin-top:0;font-size:12px;background:var(--svc-tag);border-color:var(--svc-tag);color:var(--svc-on)}
.jobs-row .job b{font-size:var(--fs-h4);font-weight:600;letter-spacing:-.012em;line-height:1.25}
.jobs-row .job span:not(.key){margin-top:var(--sp-2);line-height:1.5}
/* Shared rows (key, title, description), so a one-line title does not start
   its description a line above its neighbours’. The inner div steps aside
   so the title and description can sit on the rows themselves. */
@supports (grid-template-rows: subgrid){
  .jobs-row{grid-template-rows:auto auto 1fr}
  .jobs-row .job{grid-row:span 3;grid-template-rows:subgrid;row-gap:0}
  .jobs-row .job > div{display:contents}
  .jobs-row .job b{margin-top:var(--sp-4)}
}
@media(max-width:1000px){
  .jobs-row{grid-template-columns:repeat(2,minmax(0,1fr))}
  .jobs-row .job:nth-child(3){padding-left:0;border-left:0}
  .jobs-row .job:nth-child(n+3){margin-top:var(--sp-5);border-top:1px solid var(--line)}
}
@media(max-width:600px){
  .jobs-row{grid-template-columns:1fr}
  .jobs-row{grid-template-rows:none}
  .jobs-row .job,.jobs-row .job + .job{grid-row:auto;grid-template-rows:none;padding:var(--sp-5) 0;border-left:0;margin-top:0;grid-template-columns:28px minmax(0,1fr);column-gap:var(--sp-4)}
  .jobs-row .job > div{display:block}
  .jobs-row .job b{margin-top:0}
  .jobs-row .job + .job{border-top:1px solid var(--line)}
}
/* The start strip’s button sits on the middle of its three columns rather
   than on the foot of whichever one wraps longest. */
.webx-start .btn{align-self:center}

/* ===== Automation: #ai (2026-09-19) =====
   The assistant mock started 23px above the heading beside it (the heading’s
   own top margin) and stopped ~85px short of the copy column’s foot. Now the
   two columns share both edges: the heading’s margin goes, and the mock
   stretches to the column with its footer line pinned to the bottom. */
#ai .web-grid{align-items:stretch}
#ai .web-copy .title.sm{margin-top:0}
#ai .web-visual,#ai .ai-mock,#ai .ai-body{display:flex;flex-direction:column}
#ai .ai-mock,#ai .ai-body{flex:1 1 auto}
#ai .ai-foot{margin-top:auto}
/* Footer lines inside the mocks: the square hangs in the margin and the text
   runs as one line of prose, so on a phone it wraps under itself instead of
   splitting into two narrow columns or tucking under the square. */
.ai-foot,.flow-foot{display:block;position:relative;padding-left:16px;line-height:1.5}
.ai-foot .flow-pulse,.flow-foot .flow-pulse{position:absolute;left:0;top:calc(var(--sp-4) + .75em - 4px)}
.example-link a{text-decoration-color:var(--svc)}

/* ===== Service pages: section alignment (2026-09-19) =====
   Measured with tools/checks/align.js: in every two-column block the copy column ran
   past its figure (160-310px in #included, 208px in dashboards' #questions),
   and the proof figure floated centred beside copy 60-90px taller. */

/* #included on all three: the eight points leave the copy column for a row
   beneath, so the copy and the handover screen are a pair. The screen then
   stretches to the copy, with its footer line pinned, as the AI mock does. */
#included .web-grid{align-items:stretch}
#included .web-copy .eyebrow{margin-top:0}
#included .web-visual,#included .ai-mock,#included .ai-body{display:flex;flex-direction:column}
#included .ai-mock,#included .ai-body{flex:1 1 auto}
#included .ai-foot{margin-top:auto}
.points-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--sp-3) var(--sp-5);max-width:none;margin:clamp(40px,4.5vw,64px) 0 0;padding-top:var(--sp-5);border-top:1px solid var(--ink)}
.points-row li{font-size:var(--fs-small);line-height:1.45}
@media(max-width:1000px){.points-row{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:600px){.points-row{grid-template-columns:1fr}}

/* Dashboards' #questions: the same shape as automation’s #work-that, so the
   same fix — intro and screen centred on each other, the four questions in a
   full-width row (.jobs-row) beneath. */
#questions .web-grid{align-items:center}
#questions .web-block{margin-bottom:0}
#questions .web-copy .title.sm{margin-top:0}

/* Proof: the figure was centred beside its copy, so on a tablet it floated
   60-90px off both edges. It sits on the copy’s top edge instead, and the
   two share a baseline when their heights are close. */
.res-list .res{align-items:start}
.res-list .res-fig{align-self:stretch;display:flex;flex-direction:column}
.res-list .res-fig .bp{flex:1 1 auto;height:auto;object-fit:contain}

/* ===== Phone compaction (2026-09-19, visitors: "too much text, too long a scroll") =====
   The copy was halved first; what was left of the length was layout. Measured
   at 390px: the footer ran 1,290px (four stacked columns), the four process
   steps 960px, the four problem cards 740px, the three hub cards 1,880px.
   Nothing is hidden; each block just stops spending a screen on chrome. */
@media(max-width:760px){
  /* Footer: brand across the top, Solutions beside Company, the sectors as one line. */
  .foot-grid{grid-template-columns:1fr 1fr;gap:var(--sp-6) var(--sp-5)}
  .foot-brand{grid-column:1 / -1}
  .foot-col:nth-child(3){grid-column:1 / -1;order:1}
  .foot-col:nth-child(3) span{display:inline;padding:0}
  .foot-col:nth-child(3) span + span::before{content:" \00B7  "}
  /* Process: each step a row — number left, words right — not a centred column. */
  .proc-grid{gap:var(--sp-5) 0}
  .proc{display:grid;grid-template-columns:40px minmax(0,1fr);column-gap:var(--sp-4);align-items:start;text-align:left;padding:0}
  .proc .num{width:40px;height:40px;margin:0;font-size:15px;grid-row:1 / span 3}
  .proc h3{margin-bottom:var(--sp-1)}
  .proc p{max-width:none;margin:0}
  .proc .ptag{margin-top:var(--sp-2)}
  /* Problem cards: a hairline list. One line of copy each does not need a box. */
  .prob-grid{gap:0;border-top:1px solid var(--line-strong)}
  .prob{border:0;border-bottom:1px solid var(--line);padding:var(--sp-4) 0;background:transparent}
}
@media(max-width:600px){
  /* Hub cards: a wider crop of the still, so three cards are not three screens. */
  .vcard img{aspect-ratio:2 / 1}
  /* What comes with it: eight short points read fine two-up at 390px. */
  .points-row{grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--sp-3) var(--sp-4)}
}
