/* Inventiff design system — tokens, reset, typography, buttons, utilities. Shared by all pages. */

/* DM Sans, self-hosted — same four static weights the staging2 theme ships
   (Wptiff/fonts/). No Light: the system has no 300, emphasis comes from size. */
@font-face { font-family: "DM Sans"; src: url("../fonts/DMSans-Regular.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "DM Sans"; src: url("../fonts/DMSans-Medium.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "DM Sans"; src: url("../fonts/DMSans-SemiBold.woff2") format("woff2"); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "DM Sans"; src: url("../fonts/DMSans-Bold.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }
:root {
    /* ── Brand (DS §02, §04) ─────────────────────────────────────────────────
       Two brand colours, no more. Yellow is the strongest emphasis in the system
       and stays valuable by being rare: primary CTA, key accents. Never a generic
       hover, never a badge default. Navy is the primary dark structural colour —
       text, icons, functional borders — so no near-black alternative is needed. */
    --amber: #ffbd0e;
    --navy:  #04113a;

    /* ── Light neutral palette (DS §04) ──────────────────────────────────────
       Eight values, and these are the eight. Before the first pass the sheets
       carried 31 distinct light greys, most of them within 2–3 points of each
       other. The I2 document splits the hover grey in two — #F2F4F8 for
       lightweight interactive states, #EFF1F5 for the menu and tab surfaces —
       so the two are declared apart and never stand in for each other. */
    --white:        #ffffff;   /* primary light surface, cards, menus            */
    --off-white:    #f8f9fb;   /* subtle differentiation where white isn't enough */
    --soft-hover:   #f2f4f8;   /* lightweight interactive states                 */
    --menu-hover:   #eff1f5;   /* THE menu surface: mega-menu row, nav item, tab  */
    --light-bg:     #e9edf5;   /* larger light sections and supporting surfaces   */
    --light-border: #d9dee8;   /* the subtle border, divider and input boundary   */
    --mid-neutral:  #aab2c0;   /* low-emphasis UI details — sparingly            */
    --muted-text:   #667085;   /* secondary info, labels, metadata (5.4:1 on white) */

    /* ── Role aliases ────────────────────────────────────────────────────────
       Kept so every existing call site resolves, but each now points at one of
       the palette values above rather than at a private hex. Two text tones only:
       navy for primary text, --muted-text for supporting. */
    --bg:              var(--white);
    /* One header band for every content page. The five Master frames all draw it
       730 tall (1355:169 and its four siblings), and the copy starts at the same
       place on each — so the padding and the minimum live here rather than being
       respelled per family, which is how blog posts ended up at 180/96 and the AI
       and awards pages at 168/96 against everyone else's 210/120. */
    --hero-pt: 156px; --hero-pb: 64px; --hero-min: 730px;
    --bg-soft:         var(--off-white);
    --bg-alt:          var(--light-bg);
    --card:            var(--white);
    --card-border:     var(--light-border);
    --card-border-0:   rgba(217, 222, 232, 0);   /* --light-border at zero alpha, for the card stroke */
    --ink:             var(--navy);
    --text:            var(--navy);        /* was #46516e — body copy is primary text */
    --text-dim:        var(--muted-text);  /* was #5c6788 */
    --muted:           var(--muted-text);  /* was #6c7590 */
    --muted-on-soft:   var(--muted-text);  /* was #666f8a — one muted tone, not two */
    --slate:           var(--muted-text);  /* was #606e9c as text; the dark gradient
                                              keeps its own literal stop below      */
    --amber-text:      var(--navy);        /* amber never sets type on light         */
    --amber-dark:      var(--amber);       /* hover feedback is shadow/lift, not gold */
    --amber-soft:      #fff4d6;            /* the one pale amber tint                */

    /* The one colour on this site that is not ours. Clutch's red, taken from the
       Clutch wordmark and star marks we already ship (assets/clutch-wordmark.svg,
       assets/clutch-stars.svg). It exists so the single outbound action into
       Clutch can wear Clutch's identity instead of our amber Primary — see
       .clutch-cta in home.css. It is never used for anything else, and never for type
       on a light ground: #EF4335 on white is 3.8:1, which is enough for a control's
       edge (§17 asks 3:1 there) and not enough for a label. */
    --clutch-red:      #ef4335;

    /* ── Radius (DS §08) ─────────────────────────────────────────────────────
       Five steps. Small for compact UI, medium for controls and standard cards,
       large for softer editorial compositions, pill where intentionally designed.
       23 distinct corner values were in play before this pass.                  */
    --r-xs:   4px;
    --r-sm:   8px;
    --r-md:  12px;
    --r:     16px;
    --r-lg:  24px;
    --r-pill: 100px;

    /* ── Spacing (DS §07) ────────────────────────────────────────────────────
       The preferred token set. Not mandatory for every relationship — an
       intermediate value is allowed where it is structurally necessary — but no
       arbitrary one-off where a token draws the same thing.                     */
    --s-1:   8px;
    --s-2:  16px;
    --s-3:  24px;
    --s-4:  32px;
    --s-5:  48px;
    --s-6:  64px;
    --s-7:  96px;
    --s-8: 120px;

    /* ── Motion (DS §14) ─────────────────────────────────────────────────────
       A small shared vocabulary: five durations, two curves. Fourteen different
       durations were in play before this pass, which is not a vocabulary — it is
       one number per component. --dur-morph and --ease-morph carry the Primary
       Button's signature verbatim; §02 says keep that interaction as it is. */
    --dur-fast:   .15s;   /* colour and background feedback                */
    --dur:        .25s;   /* the default: transform, shadow, border        */
    --dur-slow:    .4s;   /* panels — mega menu, accordion                 */
    --dur-morph:  .55s;   /* the button's shape change and arrow march     */
    --dur-reveal:  .7s;   /* scroll-in                                     */
    --ease:       cubic-bezier(.22,.61,.36,1);
    --ease-morph: cubic-bezier(.23,1,.32,1);

    /* ── Elevation (DS §08) — used minimally, only for layering/floating ── */
    --shadow-sm: 0 4px 14px rgba(4,17,58,0.06);
    --shadow:    0 8px 26px rgba(4,17,58,0.10);
    --shadow-lg: 0 16px 50px rgba(4,17,58,0.14);

    /* ── Sticky header glass (DS §12) ────────────────────────────────────────
       The exact values the document specifies. The fallback is not a "close
       enough" — it is the stated substitute for browsers without backdrop-filter,
       opaque enough that the header never reads as a smudge. */
    --nav-glass:       rgba(255,255,255,0.78);
    --nav-glass-solid: rgba(255,255,255,0.96);
    --nav-blur: 18px;

    /* Approved 2025 gradients — preserved verbatim, not re-derived (DS §04). */
    --grad-gray: linear-gradient(89.75deg, rgba(255,255,255,0) -32.95%, rgba(255,255,255,0.619792) 7.87%, #ffffff 97.68%), linear-gradient(242.37deg, #ffffff 0.8%, #6c82a6 105.35%);
    --grad-card: linear-gradient(213.25deg, rgba(255,255,255,0) -6.52%, #eef1f5 69.45%);
    --grad-dark: linear-gradient(53.1deg, #04113a 4.05%, #606e9c 115.37%);
    /* single source of truth for the hero grey — must match the backdrop baked into hero-animation.mp4 */
    --hero-bg: #edeff3;
    --hero-bg-t: rgba(237,239,243,0);
    --maxw: 1180px;
    --ff: 'DM Sans', system-ui, sans-serif; }

/* ── Breakpoints ────────────────────────────────────────────────────────────
   Four, and they are the ones the layout actually turns on:

     480   phone
     680   the mobile boundary — where the nav bar hands over to the burger CTA,
           where the sticky action bar switches on (shell.js matches this exact
           value), and where two-column blocks become one
     940   where the wide two-column layouts stack
    1280   full desktop

   They are not CSS custom properties: a media query cannot read one. They are the
   contract — QA against these four widths, and any rule that needs a different
   number carries a comment saying why. Fifteen thresholds used to be in play
   (380, 560, 600, 720, 860, 980 …); each of them was a private decision, and the
   gaps between them are where the layout broke (a decoration that folded at 980
   landing on a grid that folded at 940).

   The three deliberate exceptions, each a component rather than the page:
     480/481 and 980/981   the type scale, ported verbatim from the staging theme
     1023/1024             where the nav collapses — a property of the nav's own width
     1100                  where the process rail and the AI grid stop fitting

*/

/* ── Typographic scale ──────────────────────────────────────────────────────
   Ported verbatim from the staging2 theme (Wptiff/_assets/css/wptiff-fonts.css).
   Four steps, same breakpoints as the theme: ≥1280 / 981–1279 / 481–980 / ≤480.
   --fs-h1-hero is the one addition: the theme's Divi hero module overrides h1 to
   48px on desktop, so this token carries what staging actually renders.        */
:root {
    --fs-h2-big: 72px; --fw-h2-big: 400; --lh-h2-big: 1.0em;
    --fs-h2-bigger: 48px; --fw-h2-bigger: 400; --lh-h2-bigger: 1.125em;
    --fs-h1-hero: 48px; --fw-h1-hero: 400; --lh-h1-hero: 1.2em;
    --fs-h1-big: 54px; --fw-h1-big: 500; --lh-h1-big: 1.2em;
    --fs-h1: 42px; --fw-h1: 400; --lh-h1: 1.2em;
    --fs-h2: 36px; --fw-h2: 400; --lh-h2: 1.2em;
    --fs-h3: 32px; --fw-h3: 500; --lh-h3: 1.2em;
    --fs-h4: 28px; --fw-h4: 500; --lh-h4: 1.2em;
    --fs-h5: 24px; --fw-h5: 500; --lh-h5: 1.2em;
    --fs-h6: 16px; --fw-h6: 500; --lh-h6: 1.4em;
    --fs-p: 18px; --fw-p: 400; --lh-p: 1.7em;
    --fs-p-small: 16px; --fw-p-small: 400; --lh-p-small: 1.7em;
    --fs-p-smallest: 12px; --fw-p-smallest: 400; --lh-p-smallest: 1.7em;
    /* Two paragraph roles the token file doesn't name but staging renders distinctly:
       the hero lead (22px/1.2, dropping to 18 on phones) and the section subtitle
       (20px/1.7, flat). Measured off staging2 at 1440/1100/800/390. */
    --fs-lead: 22px; --lh-lead: 1.2em;
    --fs-descriptor: 15px; --lh-descriptor: 1.4em; /* one-line service/list descriptors */
    /* Button and Navigation are roles in the DS §05 hierarchy but had no token — the
       sizes were spelled in px at each call site, which is how the nav CTA drifted. */
    --fs-btn: 16px; --fs-btn-sm: 14px;
    --fs-nav: var(--fs-p-small);
    --fs-subtitle: 20px; --lh-subtitle: 1.7em;
}
@media (min-width: 981px) and (max-width: 1279px) {
  :root {
    --fs-h2-big: 42px; --fs-h2-bigger: 42px; --fs-h1-hero: 48px; --fs-h1-big: 50px;
    --fs-h1: 36px; --fs-h2: 32px; --fs-h3: 28px; --fs-h4: 24px; --fs-h5: 20px;
    --fs-h6: 14px; --fs-p: 18px;
 }
}
@media (min-width: 481px) and (max-width: 980px) {
  :root {
    /* h1-hero tracks h2-bigger at every step. On desktop the two are both 48 and the
       hero reads as the page's biggest line; the two smaller steps had the hero at 36
       and 32 against a 42 and 38 section heading, so on a phone the largest type on
       the page was a heading halfway down it. */
    --fs-h2-big: 42px; --fs-h2-bigger: 42px; --fs-h1-hero: 42px; --fs-h1-big: 38px;
    --fs-h1: 36px; --fs-h2: 32px; --fs-h3: 28px; --fs-h4: 24px; --fs-h5: 20px;
    --fs-h6: 14px; --fs-p: 18px;
 }
}
@media (max-width: 480px) {
  :root {
    --fs-h2-big: 38px; --fs-h2-bigger: 38px; --fs-h1-hero: 38px; --fs-h1-big: 34px;
    --fs-h1: 32px; --fs-h2: 28px; --fs-h3: 24px; --fs-h4: 20px; --fs-h5: 18px;
    /* h6 holds at 14 instead of dropping to the theme's 12. It is the token behind
       every small piece of *text* on the site — testimonial roles, "Read article",
       figure captions, the case-study fact list, breadcrumbs — and 12px of that on a
       phone is the one place this scale, ported from a desktop-first theme, asks too
       much of the reader. 14 is the same value the step above it already uses, so the
       scale simply stops shrinking rather than gaining a new number. Badges and pills
       keep their 12px through --fs-p-smallest. */
    --fs-h6: 14px; --fs-p: 16px; --fs-lead: 18px;
 }
}
/* box-sizing, in the sheet every page loads. The full reset lives in home.css,
   tech.css, landing.css and start.css, and every family pulls in one of those four —
   so nothing showed that base.css lacked this, until /download arrived loading
   base.css and its own sheet only. It inherited content-box, and its stage's padding
   then added to a height:100% child and pushed the slide out under the bar.
   Only box-sizing moves here: the `margin: 0; padding: 0` half of those resets runs
   AFTER base.css on purpose and zeroes element margins this sheet sets, so hoisting
   it would quietly re-space 35 pages. */
* { box-sizing: border-box; }
/* DS 02: DM Sans only. A form control does not inherit the page font, so the
   icon-only buttons -- the burger, the slider arrows -- were sitting in the UA's
   Arial. They draw no text, so nothing looked wrong; the first label added to one
   would have. Every control that already names the family still wins over this. */
button, input, select, textarea { font-family: inherit; }

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
/* ── headline wrapping on a phone ───────────────────────────────────────────
   The <br> in a hero headline is placed for a 1440px screen. On a 390px one each half
   wraps again on its own, and the default greedy algorithm fills the first line and
   drops whatever is left onto the second — which is how "solutions /" ended up alone
   under a full line. `balance` divides each half evenly instead. The break itself
   stays: it separates the two halves of the sentence, and removing it would put the
   amber slash in the middle of a line, where it stops reading as a separator. */
@media (max-width: 680px) {
  .hero h1, .post-hero h1 { text-wrap: balance; }
}

body { font-family: var(--ff); background: var(--bg); color: var(--text); line-height: 1.7; overflow-x: hidden; -webkit-font-smoothing: antialiased; }
/* element defaults straight from the tokens — mirrors wptiff-fonts.css.
   The theme sets no tracking on headings; the LP's old -0.015em was a deviation. */
h1, h2, h3 { font-family: var(--ff); color: var(--ink); }
h1 { font-size: var(--fs-h1); font-weight: var(--fw-h1); line-height: var(--lh-h1); }
h2 { font-size: var(--fs-h2); font-weight: var(--fw-h2); line-height: var(--lh-h2); }
h3 { font-size: var(--fs-h3); font-weight: var(--fw-h3); line-height: var(--lh-h3); }
p  { font-size: var(--fs-p); font-weight: var(--fw-p); line-height: var(--lh-p); }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; }
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
/* DS 02 admits one typeface, so a filename in prose is set in it too and reads as
   code through its surface, not through a second family. */
code { font-family: var(--ff); background: var(--soft-hover);
  border-radius: var(--r-xs); padding: 1px 6px; }
.slash { color: var(--amber); font-weight: 500; }
/* ── focus ──────────────────────────────────────────────────────────────────
   Every interactive element gets one visible ring. The browser default is drawn in
   its own colour and disappears on an amber pill and on the navy panels, so it is
   restated here: navy on light, amber on dark. :focus-visible, not :focus, so a
   mouse click does not leave a ring behind. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--navy);
  outline-offset: 3px;
  border-radius: var(--r-pill);
}
/* the form fields already draw their own amber border on focus — the ring sits
   outside it rather than replacing it, so the field never looks unfocused */
.form-body input:focus-visible, .form-body select:focus-visible, .form-body textarea:focus-visible {
  outline: 3px solid var(--navy); outline-offset: 2px;
}

/* ── skip link (A11Y-06) ────────────────────────────────────────────────────
   Seven nav items and a mega-menu sit before the content on every page; without
   this, a keyboard user tabs through all of them on every single page. */
.skip-link { position: absolute; left: 16px; top: -100px; z-index: 100;
  background: var(--navy); color: #fff; padding: 16px 24px; border-radius: var(--r-sm);
  font-size: var(--fs-p-small); font-weight: 600; transition: top var(--dur-fast); }
.skip-link:focus { top: 16px; }

/* springy easing borrowed from the staging .inff-button interaction */
/* .inff-button from wptiff-base.css: 16px/600, uppercase, 3px tracking, 16/36 padding */
/* One button family (DS §09). Same sizing, typography, icon size and motion for
   every type — only the fill and the border tell Primary from Secondary. Labels are
   uppercase; tracking is normal (the 3px the staging theme carried is a deviation
   this document overrules). */
.btn { position: relative; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; gap: var(--s-1); font-family: var(--ff); font-weight: 600; font-size: var(--fs-btn); line-height: 1; text-transform: uppercase; letter-spacing: normal; padding: var(--s-2) var(--s-4); border-radius: var(--r-pill); cursor: pointer; border: none; white-space: nowrap;
  transition: border-radius var(--dur-morph) var(--ease-morph), box-shadow var(--dur-morph) var(--ease-morph), padding var(--dur-morph) var(--ease-morph), background var(--dur) ease, color var(--dur) ease, border-color var(--dur) ease, transform var(--dur) ease; }
.btn:hover { border-radius: var(--r-md); }       /* pill → soft rounded-rect morph */
.btn:active { transform: scale(.96); }

/* Primary — the strongest action. Interaction preserved verbatim (DS §02): the
   shape morphs, the arrow marches, the fill stays amber and the type stays navy. */
.btn-primary { background: var(--amber); color: var(--navy); box-shadow: 0 8px 22px rgba(255,189,14,0.30), 0 0 0 0 rgba(255,189,14,0.5); }
.btn-primary:hover { background: var(--amber); box-shadow: 0 12px 26px rgba(255,189,14,0.38), 0 0 0 12px rgba(255,189,14,0); }  /* ring expands + fades */

/* Secondary — supports Primary without competing with it (DS §09). Transparent
   fill, navy border, navy label; the same size, shape family and arrow march as
   Primary. On hover it fills navy and the label and arrow go white — the document
   names those four values, and it is never yellow and never the menu/tab grey. The
   arrow is drawn from currentColor, so it turns with the label on its own.
   15px of padding, not 16: the border is part of the box, so 16 drew a taller
   button than the filled one beside it. */
.btn-book { background: transparent; color: var(--navy); border: 1.5px solid var(--navy); font-weight: 600;
  padding-top: 15px; padding-bottom: 15px; }
.btn-book:hover { background: var(--navy); color: var(--white); border-color: var(--navy); }
.btn-sm { padding: 13px var(--s-3); font-size: var(--fs-btn-sm); font-weight: 600; }   /* 40px tall — 8×5 */
/* an outlined small button carries 3px of border the filled one does not, so it
   gives that back in padding and both draw 40px */
.btn-book.btn-sm { padding-top: 11px; padding-bottom: 11px; }
/* The small button draws 40px tall — comfortable under a mouse. A finger needs 44,
   so touch devices get the height without changing the padding. */
@media (hover: none) and (pointer: coarse) {
  .btn-sm { min-height: 44px; }
  /* the review card's two controls: .ti-more is in the markup, .testi-more is
     appended by home.js. 27px of underlined type either way. */
  .ti-more, .testi-more, .testi-all a { min-height: 44px; }
  /* The nav CTA is deliberately drawn at 40 — shell.css sizes it as "the same height
     as .btn-sm" for the 78px bar. .btn-sm is raised to 44 two lines up, so the button
     that was built to match it follows it here rather than staying 4px short. */
  .nav-cta .btn { min-height: 44px; }
}

/* Disabled is a state, not a fade. Opacity alone dropped the label to ~2:1 against
   its own background; this keeps the shape and hands it a readable pair (DS §09). */
.btn:disabled, .btn[aria-disabled="true"], button:disabled.sl-arrow, .cs-reel-nav button:disabled {
  background: var(--soft-hover); color: var(--muted-text); border-color: var(--light-border);
  box-shadow: none; cursor: not-allowed; transform: none; opacity: 1; }

/* There is one button on this site: the .inff-button pill above, filled amber for a
   primary action and white-on-navy-outline for a secondary one. Two other styles used
   to exist beside it — .btn-ghost (a white pill on a pale grey outline) and .btn-line
   (the 2025 Figma homepage button, 14/500 on a thin outline, drawn 56px tall) — so
   three different buttons could sit within a screen of each other at three different
   heights. Both are retired; their markup now says .btn-book. */

/* CTA arrow that "marches" on hover — the trailing arrow exits right, a new one enters from the left */
:root { --ar: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16.1716 10.9999L10.8076 5.63589L12.2218 4.22168L20 11.9999L12.2218 19.778L10.8076 18.3638L16.1716 12.9999H4V10.9999H16.1716Z'/%3E%3C/svg%3E"); }
/* .ar is the directional-arrow primitive: the mask, the size, the colour. It was
   only ever styled *inside* .btn, so the same <i class="ar"> on a tertiary link —
   "See all articles" — rendered 0x0 and the arrow the product language is built on
   simply was not there. Declared here it is an inline 16px icon that slides on
   hover (§09 Tertiary Actions: restrained directional movement, never a filled
   button); .btn re-declares it below as the absolute one that marches. */
.ar { display: inline-block; width: 16px; height: 16px; background: currentColor; flex-shrink: 0;
  -webkit-mask: var(--ar) center / contain no-repeat; mask: var(--ar) center / contain no-repeat;
  transition: transform var(--dur) var(--ease); }
a:not(.btn):hover > .ar { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) { a:not(.btn):hover > .ar { transform: none; } }

/* resting arrow (right) — absolute, so the label centres on its own and can glide */
.btn .ar { position: absolute; right: 16px; top: 50%; width: 16px; height: 16px; background: currentColor; pointer-events: none;
  -webkit-mask: var(--ar) center / contain no-repeat; mask: var(--ar) center / contain no-repeat;
  transform: translateY(-50%);
  transition: transform var(--dur-morph) var(--ease-morph), opacity var(--dur-slow) var(--ease-morph); }
/* label sits slightly left of centre so [text + right arrow] read centred as a group; on hover the paddings swap so the label glides right to stay centred with the new left arrow */
.btn:has(.ar) { padding-left: var(--s-3); padding-right: var(--s-5); }
.btn:has(.ar):hover { padding-left: var(--s-5); padding-right: var(--s-3); }
.btn-sm:has(.ar) { padding-left: var(--s-2); padding-right: 40px; }
.btn-sm:has(.ar):hover { padding-left: 40px; padding-right: var(--s-2); }
.btn:has(.ar)::before { content: ""; position: absolute; left: 16px; top: 50%;
  width: 16px; height: 16px; background: currentColor; pointer-events: none;
  -webkit-mask: var(--ar) center / contain no-repeat; mask: var(--ar) center / contain no-repeat;
  transform: translate(-14px, -50%); opacity: 0;
  transition: transform var(--dur-morph) var(--ease-morph), opacity var(--dur-slow) var(--ease-morph); }
.btn:hover .ar { transform: translateY(-50%) translateX(26px); opacity: 0; }
.btn:hover:has(.ar)::before { transform: translate(0, -50%); opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .btn, .btn .ar, .btn::before { transition: background var(--dur), color var(--dur); }
  .btn:hover { border-radius: var(--r-pill); }
  /* keep the rest layout on hover — no arrow march or label glide. These must stay
     equal to the resting values above or the button jumps under reduced motion. */
  .btn:has(.ar):hover { padding-left: var(--s-3); padding-right: var(--s-5); }
  .btn-sm:has(.ar):hover { padding-left: var(--s-2); padding-right: 40px; }
  .btn:hover .ar { transform: translateY(-50%); opacity: 1; }
  .btn:hover:has(.ar)::before { opacity: 0; }
}
/* ── Segmented Navigation / Tab Switcher (DS §10) ────────────────────────────
   One component, declared once, for every category selector, content switch and
   navigation tab on the site. It was five: a pill container with a 1px border and
   dimmed labels (landing, tech, /start), an 8px container with a shadow and navy
   labels (home), and a row of separately-bordered pills that filled navy when
   selected (/work). §10 makes this a fixed system component, so the five are gone.

   Its rules are independent from the CTA button system — no uppercase, no arrow
   march, no shape morph, no scale or lift. The state is the background and only
   the background:

     inactive  transparent
     hover     #EFF1F5          (--menu-hover, the same grey the mega menu uses)
     active    #FFBD0E          and Active always wins over Hover

   Container: white, no border or stroke, the approved subtle elevation. Item
   radius nests inside it exactly — 8px container less 4px of padding is 4px.
   Margins are deliberately absent: where the switcher sits is composition, so
   each family sheet sets its own. */
.toggle { display: inline-flex; gap: 5px; padding: 4px; border-radius: var(--r-sm);
  background: var(--white); border: none; box-shadow: var(--shadow-sm); }
.toggle button { font-family: var(--ff); font-weight: 500; font-size: var(--fs-p-small);
  line-height: 19px; padding: 8px 24px; border-radius: var(--r-xs); border: none;
  background: transparent; color: var(--navy); cursor: pointer;
  text-transform: none; letter-spacing: normal;
  transition: background var(--dur-fast) ease; }
.toggle button:hover { background: var(--menu-hover); }
/* Active has priority over hover, so it is restated for the hovered case. */
.toggle button.active, .toggle button.active:hover,
.toggle button.on, .toggle button.on:hover { background: var(--amber); color: var(--navy); }
/* A finger needs 44px; the padding and the look are untouched. */
@media (hover: none) and (pointer: coarse) {
  .toggle button { min-height: 44px; }
}

section { position: relative; }
/* Section rhythm — three levels, and only three (DS §07):
     compact  64px  utility strips, trust logos, small supporting blocks
     standard 96px  most website content
     large   120px  hero, portfolio, major CTA                                */
.sec { padding: var(--s-7) 0; }
.sec.compact { padding: var(--s-6) 0; }
.sec.large { padding: var(--s-8) 0; }
.sec.soft { background: var(--grad-gray); }
.sec-head { max-width: 720px; margin: 0 auto 48px; text-align: center; }
/* 8 under the headline, not 16. The frame sets its section heads 4 apart — both of
   them, projects (1317:332) and reviews (1317:625) — which reads as one block rather
   than two lines with air between. 4 is optical rather than a spacing step, so this
   takes §07's first rung. */
.sec-head h2 { font-weight: 400; line-height: var(--lh-h2); font-size: var(--fs-h2); margin: 16px 0 8px; }
.sec-head p { font-size: var(--fs-subtitle); line-height: var(--lh-subtitle); color: var(--text-dim); }
.reveal { opacity: 0; transform: translateY(28px); transition: opacity var(--dur-reveal) var(--ease), transform var(--dur-reveal) var(--ease); }
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
}
@media (max-width: 680px) {
  .sec { padding: var(--s-6) 0; }
  /* fixed-px body copy never scales down on its own — the clamped headings do */
  .sec-head { margin-bottom: 32px; }
}
/* On a phone a nowrap CTA ("Talk to us about your project") sets a
   min-content wider than the column and drags whole grids off-screen. */
/* `body` prefix so this still wins where a family sheet re-declares .btn later */
@media (max-width: 480px) {
  body .btn { white-space: normal; text-align: center; }
  body .btn:has(.ar) { padding-left: 22px; padding-right: 40px; }
  body .btn-sm:has(.ar) { padding-left: 20px; padding-right: 38px; }
}

/* "Book a call" on a dark ground — the outline button inverted, so it reads as
   the secondary action beside the amber one rather than disappearing into the
   panel. An existing dark section, so §04 leaves its palette as it was. */
.case-hero .btn-book { background: rgba(255,255,255,0.08); color: #fff; border-color: rgba(255,255,255,0.28); box-shadow: none; }
.case-hero .btn-book:hover { background: rgba(255,255,255,0.16); color: #fff; border-color: rgba(255,255,255,0.4); }

/* ── sticky mobile CTA ───────────────────────────────────────────────────── */
/* Service pages run 10–20 screens on a phone, so the primary action is off
   screen for almost the whole visit. This brings it back once the hero has
   scrolled away. Desktop keeps the action in the nav bar and does not need it.
   It lives here rather than in shell.css because /download uses it as well, and
   that page loads no shell (DS §18: one solution, one place). */
.mcta { position: fixed; left: 0; right: 0; bottom: 0; z-index: 45; display: none;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
  background: rgba(255,255,255,0.94); backdrop-filter: blur(10px);
  border-top: 1px solid var(--card-border); box-shadow: 0 -6px 24px rgba(4,17,58,0.10);
  transform: translateY(110%); transition: transform var(--dur) var(--ease); }
.mcta.on { transform: none; }
/* two equal actions; each one keeps the 44px a finger needs */
.mcta-row { display: flex; gap: 8px; }
.mcta .btn { flex: 1 1 0; width: auto; min-height: 44px; min-width: 0; padding-left: 12px; padding-right: 12px; }
.mcta .btn:has(.ar) { padding-left: 14px; padding-right: 30px; }
.mcta .btn:has(.ar):hover { padding-left: 30px; padding-right: 14px; }
.mcta .btn .ar { right: 10px; }
/* WhatsApp is an icon, not a third label: at 390px three worded buttons would each
   be too narrow to read, and the glyph is the one everyone already recognises. */
.mcta-wa { flex: 0 0 auto; display: inline-grid; place-items: center; width: 44px; min-height: 44px;
  border-radius: var(--r-pill); border: 1.5px solid var(--ink); color: var(--ink);
  transition: background var(--dur) ease, color var(--dur) ease; }
.mcta-wa:hover { background: var(--ink); color: #fff; }
/* the only line of copy in the sticky bar — it has to be readable at arm's length */
.mcta .mcta-note { display: block; text-align: center; margin: 7px 0 0;
  font-size: var(--fs-h6); color: var(--muted); }
.mcta .mcta-note b { color: var(--ink); font-weight: 700; }
@media (max-width: 680px) {
  .mcta { display: block; }
}
@media (prefers-reduced-motion: reduce) {
  .mcta { transition: none; }
}

/* FAQ (generated by build.mjs on home + service pages; .qa-* so it never clashes with landing .faq-*)
   Rows between hairlines, full width, heading left — the Tech (1460:9552), Agency
   (1460:9519) and AI (1446:9462) master frames all draw it that way. It had been a
   centred 780px column of white cards, read off the homepage frame's FAQ, which is
   a flattened raster with toggle icons laid over it; three vector frames outrank
   one raster. The accordion is shared by build.mjs across every page that declares
   `faq:` front matter, so this is the component's look everywhere. */
.qa { max-width: none; }
/* Left and full width, with the frame's 72 under it. Scoped to .qa-sec rather than
   reusing .sec-head.flush, which only tech.css declares — the homepage and the
   landing families carry a FAQ too. Size stays the section-title role: the masters
   draw this heading at 36 on Tech and 48 on Agency and AI, and one page should not
   run two sizes of section head, so §05's single role settles it. */
.qa-sec .sec-head { max-width: none; text-align: left; margin-bottom: var(--s-5); }
.qa-sec .sec-head h2 { margin: 0; }
/* No card, no border: the separator sits BETWEEN items (the frame has none above
   the first or below the last), with the 32px gap living on the item itself so the
   rule lands centred in it. */
.qa-item + .qa-item { border-top: 1px solid var(--card-border); }
/* 24 — the h5 role. The question is the largest thing in the row and reads as a
   heading, which is what the 18/600 card version could not do. */
.qa-q { width: 100%; display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; background: none; border: none; cursor: pointer; padding: var(--s-4) var(--s-2); font-family: var(--ff); line-height: var(--lh-h5); font-size: var(--fs-h5); font-weight: 400; color: var(--ink); text-align: left; }
.qa-ic { flex-shrink: 0; margin-top: 4px; color: var(--slate); transition: transform var(--dur) var(--ease); }
.qa-item.open .qa-ic { transform: rotate(180deg); }
.qa-a { max-height: 0; overflow: hidden; transition: max-height var(--dur-slow) var(--ease); }
.qa-a-in { padding: 0 var(--s-2) var(--s-4); }
.qa-a-in p { font-size: var(--fs-p); color: var(--text-dim); max-width: 1100px; line-height: 1.6; }
/* must sit after the .qa-* block above — an earlier media query would lose the cascade */
@media (max-width: 680px) {
  .qa-q { padding: var(--s-3) 0; }
  .qa-a-in { padding: 0 0 var(--s-3); }
}

/* ── Clutch review card ───────────────────────────────────────────────────────
   Shared: the homepage runs it in a slider, Work Master (1432:4568) as a static
   three-up with the same card — stars, client, the pulled quote, the reviewer and
   "Read full review". It lived in home.css, which only the homepage loads. The
   slider machinery (.testi-slider, .ti-arrow, the clamp toggle) stays there. */
.testi-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; align-items: stretch; }
/* one review rather than three: the grid would leave it a third of the row */
.testi-solo { max-width: 720px; margin: 0 auto; }
.testi { position: relative; display: flex; flex-direction: column; padding: 32px; border-radius: var(--r-lg); background: var(--grad-card); border: 1px solid transparent; transition: transform var(--dur); }
/* the frame strokes its cards with a gradient rather than a flat line — #D9DBE1 at the
   top fading to nothing at the bottom, top to bottom (handles 0.5,0 -> 0.5,1). The
   border stays in the box at 1px transparent so nothing shifts; the ring is a masked
   pseudo-element, which is the only way to stroke a rounded box with a gradient. */
.testi::before { content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px; pointer-events: none;
  background: linear-gradient(180deg, var(--card-border) 0%, var(--card-border-0) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude; }

.testi:hover { }
/* Clutch's five marks, in Clutch's red — these are Clutch reviews, and the frame
   (1317:634) draws them at #EF4335. They were amber, which read as our own accent
   rating our own work. Same mark the hero badge and the bento card already use. */
.testi .stars { display: block; margin-bottom: 16px; }
.testi .stars img { display: block; width: 83px; height: 14px; }
.testi h3 { line-height: var(--lh-h3); font-size: var(--fs-h3); font-weight: 400; margin-bottom: 16px; }
.testi .body { font-size: var(--fs-descriptor); line-height: var(--lh-p); color: var(--text-dim); margin-bottom: 24px;
  display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; }
/* quote + author pinned to the card bottom so quotes line up across all cards regardless of title/body length */
.testi .ti-pull { display: flex; gap: 16px; align-items: flex-start; min-height: 52px; margin-top: auto; margin-bottom: 16px; }
.testi .ti-chat { width: 32px; height: 32px; flex-shrink: 0; color: var(--mid-neutral); margin-top: 1px; }
.testi .ti-pull p { font-size: var(--fs-descriptor); color: var(--ink); font-style: italic; line-height: 1.5; }
.testi .who { display: flex; align-items: center; gap: 16px; }
.testi .ti-av { width: 32px; height: 32px; flex-shrink: 0; color: var(--mid-neutral); }
.testi .ti-av svg { width: 100%; height: 100%; display: block; }
.testi .who b { display: block; font-size: var(--fs-descriptor); font-weight: 700; color: var(--ink); line-height: 1.25; }
.testi .who small { display: block; color: var(--muted); font-size: var(--fs-p-smallest); line-height: var(--lh-h6); }
/* "Read full review" on every card and "See all 38 reviews" under the row — the
   frame has both (1317:653, 1317:723). The card link did not exist in the markup at
   all, and the section link was inheriting nothing: .testi-all is declared in
   tech.css, which this page never loads, and its .ar-ext arrow was declared nowhere,
   so it drew as bare left-aligned text with an invisible icon. */
.ti-more, .testi-all a { display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--fs-p-small); font-weight: 400; color: var(--ink);
  text-decoration: underline; text-underline-offset: 4px; }
.ti-more { margin-top: 24px; align-self: flex-start; }
.ti-ar { width: 16px; height: 16px; flex-shrink: 0;
  transition: transform var(--dur-morph) var(--ease); }
.ti-more:hover .ti-ar, .testi-all a:hover .ti-ar { transform: translateX(3px); }
.testi-all { margin-top: 32px; text-align: center; font-size: var(--fs-p-small); }
.testi .clutch:hover { color: var(--amber-text); }

/* ── A/B: one founder, never two ──────────────────────────────────────────────
   The "Hi! I'm …" card is a single conversion prompt with two variants, not a pair
   — two of them on one page compete with each other. Every variant is in the
   markup; the one that shows is chosen per visitor by shell.js and stamped on
   <html>. The server default is stamped in the head partial, so a visitor without
   JavaScript sees one card rather than none. */
[data-ab-variant] { display: none; }
:root[data-ab="sorel"] [data-ab-variant="sorel"],
:root[data-ab="victor"] [data-ab-variant="victor"] { display: block; }

/* Visible to screen readers and to the document outline, not on screen. Used where
   a section is self-evident visually but would otherwise leave a gap in the
   heading structure. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }

/* Proof at the point of decision, not eight screens further down (audit #14).
   Defined here because home/index/websites do not load tech.css. */
/* Navy, not the muted grey. DS 04 qualifies muted text with "where contrast remains
   sufficient" -- on the hero's --hero-bg it measures 4.32:1, under the 4.5 this size
   needs, and this line sits on the most-read screen on the site. */
.hero-proof { margin: 24px 0 0; font-size: var(--fs-descriptor); color: var(--ink); }
.hero-proof a { text-decoration: underline; text-underline-offset: 4px;
  text-decoration-color: var(--card-border); transition: color var(--dur-fast); }
.hero-proof a:hover { color: var(--ink); }
.hero-proof b { color: var(--ink); font-weight: 700; }
/* Sends a visitor who landed on the wrong one of two neighbouring services
   straight to the other. Sits under the proof line, deliberately quieter than it. */
.hero-xref { margin: 8px 0 0; font-size: var(--fs-descriptor); color: var(--muted); }
.hero-xref a { color: var(--ink); text-decoration: underline; text-underline-offset: 4px;
  text-decoration-color: var(--card-border); transition: text-decoration-color var(--dur-fast); }
.hero-xref a:hover { text-decoration-color: var(--amber); }

/* ── the answer block ───────────────────────────────────────────────────────
   One paragraph, 40–60 words, directly after the hero: it answers the question the
   page's title implies, in whole sentences, with the specifics a model can attribute.
   It sits here rather than inside the hero on purpose — dropping it above the CTA
   would push the primary action below the fold on a phone, and extraction only cares
   that it is the first content paragraph after the <h1>. */
.answer-sec { padding: 48px 0 0; }
/* It reads as a continuation of the hero, so it keeps the hero's axis: centred on the
   pages whose hero is centred, which is all of them except the two funnel landings
   (index.css and websites.css set it back to the left, where their heroes are). It had
   an amber rule down its left edge at first — that made a left-aligned block with a
   decorative bar sit between a centred hero and a centred stat row, and it read as a
   stray pull quote rather than the page's opening paragraph. */
.answer { max-width: 760px; margin: 0 auto; text-align: center;
  font-size: var(--fs-subtitle); line-height: var(--lh-subtitle); color: var(--text-dim); }
@media (max-width: 680px) {
  .answer-sec { padding-top: 24px; }
  /* centred is right for the four or five lines this is on a desktop; the same
     paragraph runs to ten on a phone, and ten centred lines are ragged on both edges
     and slow to read. Below the mobile boundary it aligns left like body copy. */
  .answer { font-size: var(--fs-p); text-align: left; }
}
