/* ============================================================
   INFINITY FABRICATION - home page + chrome
   Prefix: ifab-
   Weights capped at 500 (Part 19). "Bold" comes from scale,
   tracking and contrast, never from heavier type.
   ============================================================ */

/* ---------- fonts (self-hosted; no third-party request) ---------- */
@font-face{font-family:"IBM Plex Mono";src:url("../fonts/IBMPlexMono-400.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap;}
@font-face{font-family:"IBM Plex Mono";src:url("../fonts/IBMPlexMono-500.woff2") format("woff2");
  font-weight:500;font-style:normal;font-display:swap;}

/* Page reset. The block theme does not clear the user-agent body margin, which
   held every full-bleed section 8px off both edges. The background is set here
   too so the page never flashes white before the sections paint. */
html{background:#0A0A0A;}
body{margin:0;padding:0;background:#0A0A0A;}

/* ---------- shared shell ---------- */
.ifab{
  --ifab-ink:#0A0A0A; --ifab-ink-2:#121212; --ifab-ink-3:#1B1B1B;
  --ifab-paper:#F3F4F5; --ifab-paper-2:#E6E8EA;
  --ifab-fg:#F2F4F6; --ifab-muted:#AEB6BD; --ifab-dim:#79828B;
  --ifab-fg-inv:#0C0E11; --ifab-muted-inv:#4A525A;
  --ifab-accent:#7CE600; --ifab-accent-hi:#99FF00; --ifab-accent-ink:#367000;
  --ifab-line:rgba(255,255,255,.09); --ifab-line-2:rgba(255,255,255,.17);
  --ifab-line-inv:rgba(12,14,17,.13);
  --ifab-ff:'IFAB Satoshi',ui-sans-serif,system-ui,-apple-system,'Segoe UI',Arial,sans-serif;
  /* Titles: Open Sans, self-hosted under a prefixed family name. Open Sans is
     widely installed, so a bare name would let a failed download fall through
     to a local cut with different metrics - see inc/fonts.php RULE 1. */
  --ifab-ff-head:'IFAB Open Sans',ui-sans-serif,system-ui,'Segoe UI',Arial,sans-serif;
  --ifab-mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  --ifab-ease:cubic-bezier(.22,.61,.36,1); --ifab-t:.38s;
  --ifab-r:10px; --ifab-r-sm:6px;
  --ifab-pad:80px;                       /* Part 7.3 - 80 / 60 / 50 */
  --ifab-gut:min(5vw,32px);
  --ifab-wide:1310px;
}
@media (max-width:1024px){ .ifab{--ifab-pad:60px;} }
@media (max-width:600px) { .ifab{--ifab-pad:50px;} }

.ifab{font-family:var(--ifab-ff);background:var(--ifab-ink);color:var(--ifab-fg);
  -webkit-font-smoothing:antialiased;}
.ifab *,.ifab *::before,.ifab *::after{box-sizing:border-box;}

.ifab-sec{display:flex;justify-content:center;flex-direction:column;align-items:center;
  padding-left:var(--ifab-gut);padding-right:var(--ifab-gut);
  padding-top:var(--ifab-pad);padding-bottom:var(--ifab-pad);
  margin:0;position:relative;}
.ifab-wrap{max-width:100%;width:var(--ifab-wide);}

/* ---------- type scale - every size clamped (Part 7.2) ---------- */
.ifab-h1{margin:0 0 22px;font-size:clamp(38px,6.4vw,74px);line-height:1.04;
  font-family:var(--ifab-ff-head);font-weight:500;letter-spacing:-.026em;color:var(--ifab-fg);}
.ifab-h2{margin:0 0 18px;font-size:clamp(28px,3.6vw,46px);line-height:1.1;
  font-family:var(--ifab-ff-head);font-weight:500;letter-spacing:-.02em;color:var(--ifab-fg);}
.ifab-h3{margin:0 0 10px;font-size:clamp(17px,1.5vw,21px);line-height:1.3;
  font-family:var(--ifab-ff-head);font-weight:500;letter-spacing:-.008em;color:var(--ifab-fg);}
.ifab-lede{margin:0 0 30px;font-size:clamp(15px,1.28vw,18px);line-height:1.64;
  font-weight:400;color:var(--ifab-muted);max-width:62ch;}
.ifab-body{margin:0;font-size:clamp(15px,1.1vw,16.5px);line-height:1.68;
  font-weight:400;color:var(--ifab-muted);}
.ifab-eyebrow{margin:0 0 20px;font-family:var(--ifab-mono);
  font-size:clamp(12px,.85vw,12.5px);line-height:1.5;font-weight:500;
  letter-spacing:.17em;text-transform:uppercase;color:var(--ifab-accent);}
.ifab-num{font-family:var(--ifab-mono);font-size:clamp(12px,.85vw,12.5px);
  line-height:1.4;font-weight:500;letter-spacing:.14em;color:var(--ifab-accent);}

/* light sections invert the ink */
.ifab-light{background:var(--ifab-paper);}
.ifab-light .ifab-h2,.ifab-light .ifab-h3{color:var(--ifab-fg-inv);}
.ifab-light .ifab-ind-t{color:#F6F7F8;}
.ifab-light .ifab-lede,.ifab-light .ifab-body{color:var(--ifab-muted-inv);}
.ifab-light .ifab-eyebrow,.ifab-light .ifab-num{color:var(--ifab-accent-ink);}

/* ---------- section heading block ---------- */
.ifab-head-center{text-align:center;}
.ifab-head-center .ifab-lede{margin-left:auto;margin-right:auto;}
.ifab-head{max-width:70ch;margin:0 0 46px;}
/* Doubled class (0,2,0) on purpose. .ifab-head sets `margin:0 0 46px` and is
   declared after .ifab-head-center, so at equal specificity the shorthand won
   and killed the auto margins: the 70ch block stayed hard left while its text
   centred inside it, which reads as a mistake rather than a centred heading. */
.ifab-head.ifab-head-center{margin-left:auto;margin-right:auto;}
.ifab-head .ifab-lede{margin-bottom:0;}
/* A heading block with nothing after it must not reserve the 46px that
   separates it from the content below - with no content below, that margin is
   just dead space, and it made the About intro read as bottom-heavy: measured
   51.6px of ink above the eyebrow against 97.6px below the last line, at every
   width. Audited across the site: #about is the only place a head is the last
   child, so nothing else moves. (0,2,0) to beat the .ifab-head shorthand. */
.ifab-head:last-child{margin-bottom:0;}

/* ---------- buttons ---------- */
.ifab-btns{display:flex;flex-wrap:wrap;gap:12px;align-items:center;}
.ifab-btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;
  padding:15px 26px;border-radius:var(--ifab-r-sm);border:1px solid transparent;
  font-family:var(--ifab-ff);font-size:clamp(14px,1vw,15px);line-height:1.2;
  font-weight:500;letter-spacing:.012em;text-decoration:none;cursor:pointer;
  transition:background-color var(--ifab-t) var(--ifab-ease),
             border-color var(--ifab-t) var(--ifab-ease),
             color var(--ifab-t) var(--ifab-ease),
             transform var(--ifab-t) var(--ifab-ease);}
.ifab-btn-p{background:var(--ifab-accent);color:#14100C;}
.ifab-btn-p:hover,.ifab-btn-p:focus-visible{background:var(--ifab-accent-hi);transform:translateY(-1px);}
.ifab-btn-g{background:transparent;color:var(--ifab-fg);border-color:var(--ifab-line-2);}
.ifab-btn-g:hover,.ifab-btn-g:focus-visible{border-color:var(--ifab-accent);color:var(--ifab-accent-hi);transform:translateY(-1px);}
.ifab-light .ifab-btn-g{color:var(--ifab-fg-inv);border-color:var(--ifab-line-inv);}
.ifab-light .ifab-btn-g:hover,.ifab-light .ifab-btn-g:focus-visible{border-color:var(--ifab-accent-ink);color:var(--ifab-accent-ink);}
.ifab-btn .ifab-ico{width:16px;height:16px;flex:0 0 16px;display:block;}

/* focus is always visible (Part 15.2) */
.ifab a:focus-visible,.ifab summary:focus-visible,.ifab button:focus-visible{
  outline:2px solid var(--ifab-accent-hi);outline-offset:3px;border-radius:3px;}

/* ============================================================
   HEADER
   ============================================================ */
/* The gutter goes on the OUTER element and the 1240 container sits inside it,
   exactly as the sections and the footer are built. Putting the gutter inside
   the 1240 container instead inset the header by one gutter, so the logo did
   not line up with the hero or the footer. One container rule, everywhere. */
.ifab-hdr{position:sticky;top:0;z-index:60;background:rgba(10,10,10,.86);
  backdrop-filter:saturate(140%) blur(14px);-webkit-backdrop-filter:saturate(140%) blur(14px);
  border-bottom:1px solid var(--ifab-line);
  padding-left:var(--ifab-gut);padding-right:var(--ifab-gut);}
.ifab-hdr-in{width:var(--ifab-wide);max-width:100%;margin:0 auto;padding:0;
  display:flex;align-items:center;justify-content:space-between;
  gap:20px;min-height:86px;}
@media (max-width:600px){.ifab-hdr-in{min-height:74px;}}

.ifab-brand{display:inline-flex;align-items:center;text-decoration:none;color:var(--ifab-fg);flex:0 0 auto;}
/* The supplied logo, wordmark and all, so it is sized by HEIGHT and left to
   find its own width. It carries the company name itself, which is why the
   separate text beside it is gone - the two together printed the name twice. */
.ifab-ftr .ifab-brand-logo{height:72px;}
@media (max-width:600px){.ifab-ftr .ifab-brand-logo{height:60px;}}
/* 60px inside an 86px bar. The logo is 1.91:1 with two lines of type in it,
   so it needs real height for the "FABRICATION" line to read at all: the 26px
   the bare mark used put that line around three pixels tall. */
.ifab-brand-logo{height:60px;width:auto;display:block;
  transition:opacity var(--ifab-t) var(--ifab-ease);}
.ifab-brand:hover .ifab-brand-logo,.ifab-brand:focus-visible .ifab-brand-logo{opacity:.82;}
/* The bar drops to 66px on a phone, so the logo comes down with it. */
@media (max-width:600px){.ifab-brand-logo{height:50px;}}

/* nowrap is deliberate (a two-row nav looked broken), so any gap increase has
   to be checked against the 1180px breakpoint rather than assumed - see the
   measurement note there. */
.ifab-nav{display:flex;flex-wrap:nowrap;align-items:center;gap:12px;margin:0;padding:0;list-style:none;}
.ifab-nav li{margin:0;padding:0;list-style:none;}
/* Uppercase, matching the mobile panel items so the two menus read as one
   component. Uppercase sets wider and needs tracking to stay legible, so the
   size comes down to pay for the letter-spacing. */
.ifab-nav a{display:block;padding:9px 11px;border-radius:var(--ifab-r-sm);
  font-size:clamp(12px,.8vw,12.5px);line-height:1.5;font-weight:500;letter-spacing:.075em;
  text-transform:uppercase;
  color:var(--ifab-muted);text-decoration:none;
  transition:color var(--ifab-t) var(--ifab-ease),background-color var(--ifab-t) var(--ifab-ease);}
.ifab-nav a:hover,.ifab-nav a:focus-visible{color:var(--ifab-fg);background:rgba(255,255,255,.05);}
.ifab-nav a[aria-current="page"]{color:var(--ifab-accent);}
.ifab-hdr-cta{display:flex;align-items:center;gap:10px;flex:0 0 auto;}
/* Child combinator, not descendant: the open panel carries its own CTA and a
   descendant selector would hide that too. */
@media (max-width:1180px){.ifab-nav,.ifab-hdr-cta > .ifab-btn{display:none;}}

/* ---------- mobile menu - the kit component, tokens restyled only ---------- */
.ifab-mob{display:none;position:static;}
@media (max-width:1180px){.ifab-mob{display:block;}}
.ifab-mob-btn{list-style:none;position:relative;display:block;width:44px;height:44px;
  padding:0;border:1px solid var(--ifab-line-2);border-radius:var(--ifab-r-sm);
  background:transparent;color:var(--ifab-fg);cursor:pointer;}
.ifab-mob-btn::-webkit-details-marker{display:none;}
.ifab-mob-btn::marker{content:"";}
@media (max-width:600px){.ifab-mob-btn{width:38px;height:38px;}}
.ifab-mob-ico{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  transition:opacity .3s var(--ifab-ease),transform .3s var(--ifab-ease);}
.ifab-mob-ico svg{width:20px;height:20px;display:block;}
.ifab-mob-ico-close{opacity:0;transform:rotate(-90deg) scale(.7);}
.ifab-mob[open] .ifab-mob-ico-menu{opacity:0;transform:rotate(90deg) scale(.7);}
.ifab-mob[open] .ifab-mob-ico-close{opacity:1;transform:none;}

/* Panel hangs off the header's real height - never a hard-coded offset. */
/* left/right 0 is already the full header width: an absolutely positioned child
   is placed against the padding BOX, which spans the viewport even though the
   header now has a gutter. Negative offsets here overflowed the page. */
.ifab-mob-panel{position:absolute;top:100%;left:0;right:0;
  background:var(--ifab-ink);border-bottom:1px solid var(--ifab-line);
  box-shadow:0 26px 44px rgba(0,0,0,.5);
  padding:6px var(--ifab-gut) 34px;max-height:calc(100dvh - 100%);overflow-y:auto;
  overscroll-behavior:contain;z-index:5;}
/* No fill-mode: if the animation never runs the panel rests visible, rather
   than being held at its hidden first frame. */
.ifab-mob[open] .ifab-mob-panel{animation:ifab-panel-in .42s var(--ifab-ease);}
@keyframes ifab-panel-in{from{opacity:0;transform:translateY(-8px);}to{opacity:1;transform:none;}}
.ifab-mob-list{list-style:none;margin:0 0 22px;padding:0;}
.ifab-mob-list>li{list-style:none;margin:0;padding:0;}
.ifab-mob-list>li+li{border-top:1px solid var(--ifab-line);}
.ifab-mob-a,.ifab-mob-acc-sum{display:flex;align-items:center;justify-content:space-between;
  gap:12px;padding:15px 2px;font-family:var(--ifab-ff);
  font-size:clamp(13px,.3vw + 12px,14px);line-height:1.5;font-weight:500;letter-spacing:.075em;
  text-transform:uppercase;color:var(--ifab-fg);text-decoration:none;
  transition:color var(--ifab-t) var(--ifab-ease);}
.ifab-mob-acc-sum{list-style:none;cursor:pointer;}
.ifab-mob-acc-sum::-webkit-details-marker{display:none;}
.ifab-mob-acc-sum::marker{content:"";}
.ifab-mob-a:hover,.ifab-mob-a:focus-visible,
.ifab-mob-acc-sum:hover,.ifab-mob-acc-sum:focus-visible{color:var(--ifab-accent);}
.ifab-acc-ico{display:flex;width:18px;height:18px;flex:0 0 18px;
  transition:transform var(--ifab-t) var(--ifab-ease),color var(--ifab-t) var(--ifab-ease);}
.ifab-acc-ico svg{width:18px;height:18px;display:block;}
.ifab-mob-acc[open] .ifab-acc-ico{transform:rotate(180deg);color:var(--ifab-accent);}
.ifab-mob-sub{list-style:none;margin:0;padding:0 0 12px;overflow:hidden;}
.ifab-mob-sub li{list-style:none;margin:0;padding:0;border:0;}
.ifab-mob-sub-a{display:block;padding:10px 2px 10px 16px;font-family:var(--ifab-ff);
  font-size:clamp(14px,.3vw + 13px,15px);line-height:1.5;font-weight:400;letter-spacing:0;
  text-transform:none;color:var(--ifab-muted);text-decoration:none;
  transition:color var(--ifab-t) var(--ifab-ease);}
.ifab-mob-sub-a:hover,.ifab-mob-sub-a:focus-visible{color:var(--ifab-accent);}
.ifab-mob-foot{display:flex;flex-direction:column;gap:12px;}
.ifab-mob-foot .ifab-btn{width:100%;padding:16px 22px;}
.ifab-mob-meta{margin:18px 0 0;font-family:var(--ifab-mono);
  font-size:clamp(12px,.3vw + 11px,13px);line-height:1.7;color:var(--ifab-dim);}
.ifab-menu-js .ifab-mob[open] .ifab-mob-panel{animation:none;}

/* ============================================================
   HERO
   Footage behind the whole section, copy over it. The poster paints
   first and the video is preload="none", so the media never competes
   with the largest paint. A scrim carries the text contrast.
   ============================================================ */
.ifab-hero{position:relative;overflow:hidden;isolation:isolate;
  min-height:clamp(560px,78vh,760px);justify-content:center;}

.ifab-hero-bg{position:absolute;inset:0;z-index:0;background:var(--ifab-ink);}
/* The client's own footage of the notcher cutting. No transform on this rule:
   the scale(1.068) it used to carry was hiding 22px of black baked into every
   frame of the old stock clip, and this one was cropped on encode instead. */
/* The still and the clip occupy the same box, the clip on top. Until it has
   frames the clip is transparent, so what shows through is the still rather
   than the flat background colour it used to fall back to. */
.ifab-hero-still{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  display:block;z-index:0;}
.ifab-hero-video{position:absolute;inset:0;z-index:1;
  width:100%;height:100%;object-fit:cover;display:block;
  filter:grayscale(.35) contrast(1.05) brightness(.72);}

/* Reduced motion: hold the poster rather than play the clip (Part 9). The
   poster is set on the element itself, so hiding the video is enough - an
   earlier version of this rule reached for a --ifab-hero-poster variable that
   was never defined anywhere, and showed a flat colour instead. */
@media (prefers-reduced-motion:reduce){
  .ifab-hero-video{display:none;}
}

/* Two scrims: a flat one for overall legibility, and a left-weighted one so
   the copy column keeps contrast without flattening the whole picture. */
.ifab-hero::before{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(90deg,
    rgba(10,10,10,.94) 0%,
    rgba(10,10,10,.86) 34%,
    rgba(10,10,10,.58) 62%,
    rgba(10,10,10,.42) 100%);}
.ifab-hero::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(to top,
    rgba(10,10,10,.75) 0%,
    rgba(10,10,10,.22) 26%,
    rgba(10,10,10,0) 55%,
    rgba(10,10,10,.55) 100%);}
/* Mobile. The two scrims multiply, and the old pair put .80 over .55 at the
   very top - about 91% black, which merged the hero into the header and read
   as the header overflowing. The top is opened up and the weight moved down to
   where the copy actually sits. */
@media (max-width:760px){
  .ifab-hero::before{background:linear-gradient(180deg,
    rgba(10,10,10,.30) 0%,rgba(10,10,10,.72) 46%,rgba(10,10,10,.90) 100%);}
  .ifab-hero::after{background:linear-gradient(to top,
    rgba(10,10,10,.70) 0%,rgba(10,10,10,.18) 30%,rgba(10,10,10,0) 62%);}
}

.ifab-hero .ifab-wrap{position:relative;z-index:2;}
.ifab-hero-copy{max-width:min(760px,92%);}
.ifab-hero .ifab-lede{margin-bottom:34px;max-width:56ch;}

/* the accepted-input strip: a capability signal, not form furniture */
.ifab-intake{margin-top:clamp(40px,5vw,64px);border-top:1px solid var(--ifab-line-2);
  padding:20px 0 0;display:flex;flex-wrap:wrap;align-items:center;gap:12px 26px;}
.ifab-intake-l{font-family:var(--ifab-mono);font-size:clamp(12px,.8vw,12px);line-height:1.5;
  font-weight:500;letter-spacing:.17em;text-transform:uppercase;color:var(--ifab-muted);}
.ifab-intake-items{display:flex;flex-wrap:wrap;gap:8px 10px;margin:0;padding:0;list-style:none;}
.ifab-intake-items li{list-style:none;margin:0;padding:6px 12px;
  border:1px solid var(--ifab-line-2);border-radius:100px;
  font-family:var(--ifab-mono);font-size:clamp(12px,.8vw,12px);line-height:1.4;
  font-weight:400;letter-spacing:.09em;text-transform:uppercase;color:var(--ifab-fg);
  background:rgba(10,10,10,.45);backdrop-filter:blur(4px);}
/* ============================================================
   PIPELINE - the signature section
   ============================================================ */
.ifab-pipe-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:0;
  border-top:1px solid var(--ifab-line);}
/* 5 stages: three-up on tablet reads as 3+2, which is tidier than 2+2+1 */
@media (max-width:1000px){.ifab-pipe-grid{grid-template-columns:repeat(3,1fr);}}
@media (max-width:660px){.ifab-pipe-grid{grid-template-columns:repeat(2,1fr);}}
@media (max-width:460px){.ifab-pipe-grid{grid-template-columns:1fr;}}
.ifab-stage{position:relative;padding:26px 22px 30px 22px;border-right:1px solid var(--ifab-line);}
.ifab-stage:nth-child(5n+1){padding-left:0;}

@media (max-width:1000px){
  .ifab-stage{padding-right:22px;padding-bottom:26px;padding-left:22px;}
  .ifab-stage:nth-child(3n+1){padding-left:0;}
}
@media (max-width:660px) and (min-width:461px){
  .ifab-stage:nth-child(3n){border-right:1px solid var(--ifab-line);}
  .ifab-stage{padding-left:22px;}
  .ifab-stage:nth-child(2n+1){padding-left:0;}
}
@media (max-width:460px){.ifab-stage{border-right:0;padding-right:0;padding-left:0;}}
/* the accent run that ties the stages together */
.ifab-stage::before{content:"";position:absolute;top:-1px;left:0;width:100%;height:1px;
  background:linear-gradient(90deg,var(--ifab-accent),rgba(124,230,0,.15));}
.ifab-stage:last-child::before{background:var(--ifab-accent);}
.ifab-stage-n{display:block;margin-bottom:14px;}
.ifab-stage h3{margin:0 0 8px;font-family:var(--ifab-ff-head);font-size:clamp(15px,1.25vw,17px);line-height:1.3;
  font-weight:500;letter-spacing:.05em;text-transform:uppercase;color:var(--ifab-fg);}
.ifab-stage p{margin:0;font-size:clamp(13.5px,1vw,14.5px);line-height:1.6;
  font-weight:400;color:var(--ifab-muted);}

/* ============================================================
   CAPABILITIES
   The first pass was even and thin: nothing anchored a row and a
   two-column list of three items always left an orphan. This gives each
   cluster a large numeral to anchor it, a title with real scale, and a
   single-column list so the rhythm is regular.
   ============================================================ */

/* This section runs on a blue accent instead of the brand green, so it separates
   from the process band directly above it. Pitched a little lighter than the
   so the change reads as a deliberate second accent rather than a dimmer one.
   Only the three tokens are redefined - every rule inside still asks for
   var(--ifab-accent) and inherits the new value, so nothing else changes and
   the section cannot drift out of step. The id is the nav anchor and is
   already in the markup, so this needs no content change. */
#capabilities{
  /* Process and capabilities were both plain --ifab-ink, so they ran together
     as one unbroken field. One step up the ink ramp separates them. The ramp
     is neutral, not blue-shifted: a tinted dark reads as a colour decision
     rather than as a surface, which is exactly how the blue pass looked. */
  background:var(--ifab-ink-2);
}

.ifab-caps{border-top:1px solid var(--ifab-line-2);}
.ifab-cap{position:relative;display:grid;
  grid-template-columns:minmax(210px,26%) minmax(0,1fr) minmax(0,360px);
  gap:clamp(24px,2.6vw,44px);align-items:center;
  padding:clamp(34px,4vw,54px) 0;
  border-bottom:1px solid var(--ifab-line-2);}
/* Below 1080 three columns squeeze the list into two-word lines. The text
   stacks into one column and the drawing takes the other, rather than dropping
   under the text - stacked, it left a column of empty space beside it. */
@media (max-width:1080px){
  .ifab-cap{grid-template-columns:minmax(0,1fr) minmax(0,320px);}
  .ifab-cap-head,.ifab-cap-list{grid-column:1;}
  .ifab-svc{grid-column:2;grid-row:1 / span 2;align-self:center;}
}
/* Only once the drawing would be narrower than about 240px does stacking beat
   keeping it alongside. */
@media (max-width:620px){
  .ifab-cap{grid-template-columns:1fr;gap:22px;}
  .ifab-cap-head,.ifab-cap-list,.ifab-svc{grid-column:1;}
  .ifab-svc{grid-row:auto;}
}

/* Copper tick on the row edge: a small anchor that repeats down the section.
   Centred with the rest of the row - pinned to the top it read as a leftover
   once the three columns started centring against each other. */
.ifab-cap::before{content:"";position:absolute;left:0;top:50%;
  transform:translateY(-50%);
  width:2px;height:clamp(40px,3.6vw,52px);background:var(--ifab-accent);}
.ifab-cap-head{padding-left:clamp(18px,2vw,28px);}


.ifab-cap-n{display:block;font-family:var(--ifab-mono);
  font-size:clamp(30px,3.4vw,46px);line-height:1;font-weight:400;
  letter-spacing:-.02em;color:var(--ifab-accent);opacity:.9;}
.ifab-cap-t{margin:14px 0 0;font-family:var(--ifab-ff-head);
  font-size:clamp(18px,1.9vw,26px);line-height:1.2;font-weight:500;
  letter-spacing:-.016em;color:var(--ifab-fg);}
/* Two lines under the title saying what the cluster actually does. Capped by
   measure, not by line count: a hard height would clip the moment the copy or
   the font changed, and at this width the measure lands it on two lines. */
.ifab-cap-d{margin:10px 0 0;max-width:34ch;
  font-size:clamp(13.5px,1vw,14.5px);line-height:1.6;
  font-weight:400;color:var(--ifab-muted);}

/* single column: three items never leave a ragged orphan */
.ifab-cap-list{list-style:none;margin:0;padding:0;display:flex;
  flex-direction:column;gap:clamp(13px,1.2vw,17px);}
.ifab-cap-list li{list-style:none;margin:0;padding:0;
  display:flex;align-items:flex-start;gap:13px;
  font-size:clamp(15.5px,1.25vw,18px);line-height:1.5;font-weight:400;
  color:var(--ifab-fg);}
/* Tick drawn in CSS so each item stays one editable text block, and sat on
   the text cap-height rather than the box top (Part 8). */
.ifab-cap-list li::before{content:"";width:16px;height:16px;flex:0 0 16px;margin-top:.28em;
  background-color:var(--ifab-accent);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5l5 5 11-11'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5l5 5 11-11'/%3E%3C/svg%3E") center/contain no-repeat;}
/* The drawing at the end of each row shows the operation the list describes -
   a scan resolving to CAD, a dimensioned bend, an exploded assembly - in the
   same linework as the hero motif. Free stock has no fabrication footage, and
   a near-miss photograph would say less than a drawing of the actual work.
   Sat in a light frame so it reads as a figure rather than a stray graphic. */
/* Capped at its natural 440: below 1080 the drawing moves into the full-width
   text column, and without this it blows up to twice the size it has on
   desktop - a 440px line drawing upscaled to 600 just looks soft. */
/* ink-3, not ink-2: the section itself moved up to ink-2, and a frame the same
   colour as its ground is not a frame. */
.ifab-svc{padding:clamp(8px,.7vw,11px);max-width:462px;
  border:1px solid var(--ifab-line);background:var(--ifab-ink-3);
  border-radius:var(--ifab-r);}
/* width:100% + height:auto keeps the 420x300 box the markup declares, so the
   row never reflows once the file lands. */
/* The frame is padded, so this is not being clipped by it - the radius is
   its own, and it stops the page having one square-cornered image among
   rounded ones. */
.ifab-svc-img{display:block;width:100%;height:auto;border-radius:var(--ifab-r-sm);}

/* ============================================================
   INDUSTRIES
   Image cards, label bottom-LEFT with a Phosphor icon. Placeholder
   photography from mixed sources, so one desaturation plus one scrim pulls
   them into a single system. Client shots inherit the same treatment.
   ============================================================ */
.ifab-ind-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(12px,1.4vw,18px);}
@media (max-width:900px){.ifab-ind-grid{grid-template-columns:repeat(2,1fr);}}
@media (max-width:520px){.ifab-ind-grid{grid-template-columns:1fr;}}

/* 6/5 rather than 4/3: about 30px taller at the current column width. */
.ifab-ind{position:relative;overflow:hidden;border-radius:var(--ifab-r);
  aspect-ratio:6/5;display:flex;align-items:flex-end;
  background:var(--ifab-ink-2);isolation:isolate;}
@media (max-width:520px){.ifab-ind{aspect-ratio:16/10;}}

.ifab-ind-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  z-index:0;filter:grayscale(.45) contrast(1.04) brightness(.8);}

.ifab-ind::after{content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(to top,
    rgba(10,10,10,.94) 0%,rgba(10,10,10,.74) 28%,
    rgba(10,10,10,.34) 58%,rgba(10,10,10,.16) 100%);}

.ifab-ind-label{position:relative;z-index:2;width:100%;
  display:flex;flex-direction:column;align-items:flex-start;gap:10px;
  padding:clamp(20px,2.2vw,28px);text-align:left;}
.ifab-ind-ico-wrap{display:block;line-height:0;}
.ifab-ind-ico{width:clamp(26px,2.3vw,32px);height:clamp(26px,2.3vw,32px);
  display:block;color:var(--ifab-accent);}
.ifab-ind-t{margin:0;font-family:var(--ifab-ff-head);
  font-size:clamp(16px,1.35vw,19px);line-height:1.25;font-weight:500;
  letter-spacing:-.008em;color:#F6F7F8;}
/* ============================================================
   WORK - slots awaiting the client's photography
   ============================================================ */
.ifab-work-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(14px,1.6vw,20px);}
@media (max-width:900px){.ifab-work-grid{grid-template-columns:repeat(2,1fr);}}
@media (max-width:560px){.ifab-work-grid{grid-template-columns:1fr;}}
.ifab-slot{position:relative;aspect-ratio:4/3;border:1px dashed var(--ifab-line-2);
  border-radius:var(--ifab-r);background:
    repeating-linear-gradient(135deg,rgba(255,255,255,.022) 0 10px,transparent 10px 20px),
    var(--ifab-ink-2);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;
  padding:20px;text-align:center;overflow:hidden;max-width:100%;}
/* slot icon in CSS, so a placeholder card carries no markup of its own */
.ifab-slot::before{content:"";width:24px;height:24px;display:block;
  background-color:var(--ifab-dim);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='14' rx='2'/%3E%3Ccircle cx='8.5' cy='10' r='1.6'/%3E%3Cpath d='M21 16l-5-5-9 8'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='14' rx='2'/%3E%3Ccircle cx='8.5' cy='10' r='1.6'/%3E%3Cpath d='M21 16l-5-5-9 8'/%3E%3C/svg%3E") center/contain no-repeat;}
.ifab-slot-l{font-family:var(--ifab-mono);font-size:clamp(12px,.2vw + 9.4px,12.5px);line-height:1.5;font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ifab-accent);}
.ifab-slot-d{margin:0;font-size:clamp(12px,.25vw + 11.2px,13px);line-height:1.5;font-weight:400;color:var(--ifab-dim);max-width:26ch;}

/* ============================================================
   APPROACH / HOW WE WORK
   Four value cards on the light band. Each is a white card with an accent
   bar on its top edge, opening with its own line glyph. The card edges do
   the separating - there is no connecting rail.
   ============================================================ */
/* Tighter than it was. The 48px gap was carrying the separation on its own
   when these were bare columns; the card edges do that now, and at that width
   four cards stop reading as one set. */
.ifab-appr-grid{position:relative;display:grid;grid-template-columns:repeat(4,1fr);
  gap:clamp(14px,1.5vw,20px);}
@media (max-width:900px){.ifab-appr-grid{grid-template-columns:repeat(2,1fr);gap:18px;}}
@media (max-width:520px){.ifab-appr-grid{grid-template-columns:1fr;gap:14px;}}

/* Each step is a card now. White against the section's --ifab-paper ground:
   on a light grey field a white card is the honest lift, where a darker tint
   would read as a disabled panel. The card edge does the separating, so the
   closing rule that used to do it is gone.
   The accent survives as a short bar on the top edge. overflow:hidden lets the
   corner radius clip its left end, which tapers the start instead of leaving a
   square block sitting proud of a rounded corner. */
.ifab-appr{position:relative;overflow:hidden;
  padding:clamp(24px,2.2vw,32px);
  background:#FFFFFF;
  border:1px solid var(--ifab-line-inv);
  border-radius:var(--ifab-r);}
.ifab-appr::before{content:"";position:absolute;top:0;left:0;
  width:56px;height:3px;background:var(--ifab-accent-ink);}

/* The card is white inside a dark section, so everything in it has to take the
   inverse ink deliberately. Inheriting the section's colour left this copy at
   2.05:1 on its own background. */
.ifab-appr .ifab-body,.ifab-appr p{color:var(--ifab-muted-inv);}

/* One line glyph per stage, drawn in the same language as the rest of the page.
   Carried on the title's own ::before as a mask, so it inherits the section's
   ink and adds nothing to the markup. */
.ifab-appr h3::before{content:"";display:block;width:28px;height:28px;
  margin-bottom:14px;background-color:var(--ifab-accent-ink);
  -webkit-mask:var(--ifab-g) center/contain no-repeat;
  mask:var(--ifab-g) center/contain no-repeat;}
/* 01 precision - a crosshair sighted on a centre */
.ifab-appr:nth-child(1) h3{--ifab-g:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='7.2'/%3E%3Cpath d='M12 1.4v3.4M12 19.2v3.4M1.4 12h3.4M19.2 12h3.4'/%3E%3Ccircle cx='12' cy='12' r='1.7' fill='%23000' stroke='none'/%3E%3C/svg%3E");}
/* 02 efficiency - one continuous path, no hand-off */
.ifab-appr:nth-child(2) h3{--ifab-g:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.4 19h9.2a5.4 5.4 0 0 0 5.4-5.4V6.2'/%3E%3Cpath d='M13.6 9.4L17 6l3.4 3.4'/%3E%3C/svg%3E");}
/* 03 communication - the line runs both ways */
.ifab-appr:nth-child(3) h3{--ifab-g:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.6 8.4h18M16.6 4.4l4 4l-4 4'/%3E%3Cpath d='M21.4 15.6h-18M7.4 11.6l-4 4l4 4'/%3E%3C/svg%3E");}
/* 04 production-ready - the same part, repeatably */
.ifab-appr:nth-child(4) h3{--ifab-g:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'%3E%3Crect x='2.6' y='2.6' width='8' height='8' rx='1'/%3E%3Crect x='13.4' y='2.6' width='8' height='8' rx='1'/%3E%3Crect x='2.6' y='13.4' width='8' height='8' rx='1'/%3E%3Crect x='13.4' y='13.4' width='8' height='8' rx='1'/%3E%3C/svg%3E");}
.ifab-appr h3{margin:0 0 9px;font-family:var(--ifab-ff-head);
  font-size:clamp(17px,1.5vw,21px);line-height:1.25;font-weight:500;
  letter-spacing:-.01em;color:var(--ifab-fg-inv);}

@media (max-width:520px){
  /* The closing rule now carries the whole job of separating stacked steps,
     so it stays here too - there is no longer a vertical rail to cut it. */
  .ifab-appr h3::before{width:24px;height:24px;margin-bottom:12px;}
}
/* ============================================================
   QUOTE BAND
   ============================================================ */
.ifab-quote{position:relative;isolation:isolate;overflow:hidden;
  background:var(--ifab-ink-2);border-top:1px solid var(--ifab-line);}
.ifab-quote::before{content:"";position:absolute;inset:0;z-index:0;
  background:center/cover no-repeat;
  filter:grayscale(.5) brightness(.5) contrast(1.05);}
.ifab-quote.ifab-bg-in::before{
  background-image:url("../img/quote-bg.jpg");
  background-image:image-set(url("../img/quote-bg.webp") type("image/webp"),url("../img/quote-bg.jpg") type("image/jpeg"));}
/* left-weighted scrim: the copy keeps contrast, the picture still reads */
.ifab-quote::after{content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(90deg,
    rgba(10,10,10,.95) 0%,rgba(10,10,10,.88) 38%,
    rgba(10,10,10,.66) 68%,rgba(10,10,10,.5) 100%);}
@media (max-width:880px){
  .ifab-quote::after{background:linear-gradient(180deg,
    rgba(10,10,10,.88) 0%,rgba(10,10,10,.93) 100%);}
}
.ifab-quote > .ifab-wrap{position:relative;z-index:2;}
.ifab-quote-in{display:grid;grid-template-columns:1.15fr .85fr;gap:clamp(26px,4vw,60px);
  align-items:center;}
@media (max-width:880px){.ifab-quote-in{grid-template-columns:1fr;}}
.ifab-quote .ifab-lede{margin-bottom:0;}
.ifab-quote-side{border-left:1px solid var(--ifab-line-2);padding-left:clamp(22px,3vw,40px);}
@media (max-width:880px){.ifab-quote-side{border-left:0;border-top:1px solid var(--ifab-line-2);
  padding-left:0;padding-top:26px;}}
.ifab-quote-side .ifab-btn{width:100%;margin-bottom:12px;}
/* ============================================================
   FOOTER
   ============================================================ */
/* Background image under a dark overlay, built the same way as the quote
   band: image on ::before, overlay on ::after, content lifted above both.
   isolation:isolate keeps the two pseudo elements in their own stacking
   context so nothing above the footer can slide between them.
   The overlay eases lighter through the middle and closes up at both ends, so
   the picture reads as texture where the columns are widest while the join to
   the contact section and the credit bar stay solid. */
.ifab-ftr{position:relative;isolation:isolate;
  background:var(--ifab-ink);border-top:1px solid var(--ifab-line);
  padding:var(--ifab-pad) var(--ifab-gut) 0;
  /* One token steps up over the photograph. Measured against the brightest
     patch of the composited background, --ifab-dim sits at 4.18:1 - under AA -
     so the smallest text borrows the muted grey instead. Copper was also under
     before the overlay was darkened; at the current strength it measures 4.97:1
     on its own, so it keeps the site value and the footer headings match every
     other section. fg and muted were always clear. */
  --ifab-dim:var(--ifab-muted);}
.ifab-ftr::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:center/cover no-repeat;
  filter:grayscale(.45) contrast(1.04);}
.ifab-ftr.ifab-bg-in::before{
  background-image:url("../img/footer-bg.jpg");
  background-image:image-set(url("../img/footer-bg.webp") type("image/webp"),url("../img/footer-bg.jpg") type("image/jpeg"));}
.ifab-ftr::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,
    rgba(10,10,10,.96) 0%,
    rgba(10,10,10,.90) 38%,
    rgba(10,10,10,.93) 72%,
    rgba(10,10,10,.97) 100%);}
/* Everything real sits above both layers. */
.ifab-ftr-in{position:relative;z-index:2;}
.ifab-ftr-in{width:var(--ifab-wide);max-width:100%;margin:0 auto;}
.ifab-ftr-top{display:grid;grid-template-columns:1.4fr 1fr 1fr 1.15fr;
  gap:clamp(26px,3vw,48px);padding-bottom:44px;}
/* Two up on tablet. The single clamped gap was doing both axes, and at this
   width it computed to its 26px floor - fine between columns, far too tight
   between two stacked rows of link lists. Row and column gaps are separate. */
@media (max-width:900px){.ifab-ftr-top{grid-template-columns:1fr 1fr;gap:52px 34px;}}
@media (max-width:520px){.ifab-ftr-top{grid-template-columns:1fr;gap:42px;}}
.ifab-ftr-bio{margin:16px 0 0;font-size:clamp(13.5px,.25vw + 12.7px,14.5px);line-height:1.65;color:var(--ifab-muted);max-width:34ch;}
.ifab-ftr .ifab-ftr-h{display:block;margin:0 0 22px;font-family:var(--ifab-ff-head);font-size:clamp(14px,.3vw + 13.2px,15.5px);line-height:1.5;
  font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--ifab-accent);}
.ifab-ftr ul{list-style:none;margin:0;padding:0;}
.ifab-ftr li{list-style:none;margin:0;padding:0;}
.ifab-ftr li+li{margin-top:9px;}
/* Footer links are a list of targets rather than links inside running text, so
   they carry their own height to clear the 24px pointer-target minimum. */
.ifab-ftr li a{display:inline-flex;align-items:center;min-height:24px;padding:2px 0;}
.ifab-ftr a{font-size:clamp(13.5px,.25vw + 12.7px,14.5px);line-height:1.55;font-weight:400;color:var(--ifab-muted);
  text-decoration:none;transition:color var(--ifab-t) var(--ifab-ease);}
.ifab-ftr a:hover,.ifab-ftr a:focus-visible{color:var(--ifab-accent);}
/* Phosphor caret-right on every footer nav link, drawn in CSS so each link
   stays one editable text block. Bronze, and it steps toward the text on
   hover so the whole row reads as one target. */
.ifab-ftr li a{position:relative;padding-left:17px;}
.ifab-ftr li a::before{content:"";position:absolute;left:0;top:50%;
  width:11px;height:11px;margin-top:-5.5px;
  background-color:var(--ifab-accent);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z'/%3E%3C/svg%3E") center/contain no-repeat;
  transition:transform var(--ifab-t) var(--ifab-ease),background-color var(--ifab-t) var(--ifab-ease);}
.ifab-ftr li a:hover::before,.ifab-ftr li a:focus-visible::before{
  transform:translateX(2px);background-color:var(--ifab-accent-hi);}

/* ---------- contact list ---------- */
.ifab-contact{font-style:normal;display:flex;flex-direction:column;gap:16px;
  font-size:clamp(13.5px,.25vw + 12.7px,14.5px);line-height:1.6;color:var(--ifab-muted);}
.ifab-contact-row{display:flex;align-items:flex-start;gap:11px;}
.ifab-ci-wrap{display:block;flex:0 0 18px;line-height:0;}
/* The icon sits on the text's cap height, not the top of its line box, so it
   reads as aligned rather than merely adjacent (Part 8). */
.ifab-ci{width:18px;height:18px;display:block;margin-top:.14em;color:var(--ifab-accent);
  transition:color var(--ifab-t) var(--ifab-ease);}
.ifab-contact-row:hover .ifab-ci{color:var(--ifab-accent-hi);}
.ifab-contact-row a{color:var(--ifab-muted);text-decoration:none;
  transition:color var(--ifab-t) var(--ifab-ease);}
.ifab-contact-row a:hover,.ifab-contact-row a:focus-visible{color:var(--ifab-accent);}
/* an email must never break across two lines (Part 10) */
.ifab-nowrap{white-space:nowrap;}
.ifab-ftr-bar{border-top:1px solid var(--ifab-line);padding:20px 0 24px;
  display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center;justify-content:space-between;}
.ifab-ftr-bar p{margin:0;font-size:clamp(12px,.25vw + 11.2px,13px);line-height:1.6;color:var(--ifab-dim);}
.ifab-ftr-bar a{font-size:clamp(12px,.25vw + 11.2px,13px);}
/* A link that is the whole paragraph is a standalone target, so it gets the
   24px minimum. The credit link sits inside a sentence and keeps text flow. */
.ifab-ftr-bar p > a:only-child{display:inline-flex;align-items:center;min-height:24px;}
.ifab-credit{color:var(--ifab-muted);}
.ifab-credit:hover,.ifab-credit:focus-visible{color:var(--ifab-accent);}

/* Inline links inside body copy. Without this an anchor in a paragraph falls
   back to the browser default blue, which is exactly what happened to the
   quote-band email once its placeholder class came off. */
.ifab-body a,.ifab-lede a{color:var(--ifab-accent);text-decoration:none;
  border-bottom:1px solid rgba(124,230,0,.35);
  transition:color var(--ifab-t) var(--ifab-ease),border-color var(--ifab-t) var(--ifab-ease);}
.ifab-body a:hover,.ifab-body a:focus-visible,
.ifab-lede a:hover,.ifab-lede a:focus-visible{color:var(--ifab-accent-hi);border-bottom-color:var(--ifab-accent-hi);}
.ifab-light .ifab-body a,.ifab-light .ifab-lede a{color:var(--ifab-accent-ink);
  border-bottom-color:rgba(138,78,39,.4);}

/* ---------- social row ---------- */
.ifab-social{display:flex;align-items:center;gap:10px;margin-top:22px;}
.ifab-soc{display:inline-flex;align-items:center;justify-content:center;
  width:38px;height:38px;flex:0 0 38px;border:1px solid var(--ifab-line);
  border-radius:var(--ifab-r-sm);color:var(--ifab-muted);
  transition:color var(--ifab-t) var(--ifab-ease),
             border-color var(--ifab-t) var(--ifab-ease),
             transform var(--ifab-t) var(--ifab-ease);}
.ifab-soc svg{width:18px;height:18px;display:block;}
.ifab-ftr a.ifab-soc{color:var(--ifab-accent);}
.ifab-ftr a.ifab-soc:hover,.ifab-ftr a.ifab-soc:focus-visible{color:var(--ifab-accent-hi);
  border-color:var(--ifab-accent);transform:translateY(-1px);}

/* placeholder marker - must read as a placeholder, never as a fact (Part 5) */
.ifab-tbd{color:var(--ifab-accent);border-bottom:1px dashed currentColor;
  font-family:var(--ifab-mono);font-size:.92em;letter-spacing:.04em;}

/* ============================================================
   ENTRANCE MOTION - CSS only, so the editor never sees a hidden block
   ============================================================ */
@media (prefers-reduced-motion:no-preference){
  @supports (animation-timeline:view()){
    .ifab-rise{animation:ifab-rise linear both;animation-timeline:view();
      animation-range:entry 8% cover 26%;}
  }
}
/* Transform only, deliberately. This animation is driven by scroll position,
   so an element below the fold is parked on its first keyframe for as long as
   nobody scrolls to it. A fade from zero leaves that text at a measured 1.07:1
   against the background, and hiding it instead would take it out of the
   accessibility tree. Moving it is safe; fading it is not. */
@keyframes ifab-rise{from{transform:translateY(18px);}to{transform:none;}}

/* The same stylesheet loads in the block editor, where the scroll timeline
   behaves differently. A block that starts at opacity 0 there cannot be seen
   or edited, so the reveal is switched off inside the editor only. */
.editor-styles-wrapper .ifab-rise{animation:none !important;transform:none;}

/* ============================================================
   FAQs
   <details>/<summary>, so it opens and closes with no JavaScript,
   stays keyboard operable, and is readable with the CSS missing.
   ============================================================ */
.ifab-faqs{border-top:1px solid var(--ifab-line);}
.ifab-faq{border-bottom:1px solid var(--ifab-line);}
.ifab-faq-q{position:relative;display:block;cursor:pointer;
  list-style:none;padding:clamp(20px,1.8vw,26px) 56px clamp(20px,1.8vw,26px) 0;
  font-family:var(--ifab-ff-head);
  font-size:clamp(16px,1.35vw,19px);line-height:1.35;font-weight:500;
  letter-spacing:-.008em;color:var(--ifab-fg);
  transition:color var(--ifab-t) var(--ifab-ease);}
/* Safari draws its own triangle on summary and ignores list-style there. */
.ifab-faq-q::-webkit-details-marker{display:none;}
.ifab-faq-q:hover{color:var(--ifab-accent-hi);}

/* Plus that becomes a minus. Two bars, and only the vertical one moves, so the
   transition reads as the sign changing rather than the icon spinning. */
.ifab-faq-q::before,.ifab-faq-q::after{content:"";position:absolute;right:8px;
  top:50%;width:15px;height:2px;background:var(--ifab-accent);
  transform:translateY(-50%);
  transition:transform var(--ifab-t) var(--ifab-ease),
             opacity var(--ifab-t) var(--ifab-ease);}
.ifab-faq-q::after{transform:translateY(-50%) rotate(90deg);}
.ifab-faq[open] .ifab-faq-q::after{transform:translateY(-50%) rotate(0deg);opacity:0;}

.ifab-faq-a{margin:0 0 clamp(22px,2vw,28px);max-width:78ch;padding-right:56px;
  font-size:clamp(15px,1.1vw,16.5px);line-height:1.68;
  font-weight:400;color:var(--ifab-muted);}
@media (max-width:600px){
  .ifab-faq-q{padding-right:40px;}
  .ifab-faq-a{padding-right:0;}
}
@media (prefers-reduced-motion:reduce){
  .ifab-faq-q,.ifab-faq-q::before,.ifab-faq-q::after{transition:none;}
}

/* ============================================================
   CONTACT
   ============================================================ */
.ifab-cx{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);
  gap:clamp(36px,5vw,80px);align-items:start;}
@media (max-width:900px){.ifab-cx{grid-template-columns:1fr;gap:40px;}}

.ifab-cx-side .ifab-lede{margin-bottom:34px;}
.ifab-cx-list{display:flex;flex-direction:column;gap:18px;
  border-top:1px solid var(--ifab-line);padding-top:26px;}
.ifab-cx-item{display:flex;flex-direction:column;gap:4px;}
.ifab-cx-l{font-family:var(--ifab-mono);font-size:clamp(12px,.8vw,12.5px);
  line-height:1.4;font-weight:500;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ifab-dim);}
.ifab-cx-v{font-size:clamp(15.5px,1.25vw,18px);line-height:1.4;font-weight:400;
  color:var(--ifab-fg);text-decoration:none;
  transition:color var(--ifab-t) var(--ifab-ease);}
a.ifab-cx-v:hover,a.ifab-cx-v:focus-visible{color:var(--ifab-accent-hi);}

/* ---------- Gravity Forms, restyled (Part 12) ----------
   Scoped to .ifab-form so none of it leaks into the Gravity Forms admin.
   The plugin ships its own stylesheet, so element selectors are used inside the
   scope rather than trusting a single class to win. */
.ifab-form{background:var(--ifab-ink-2);border:1px solid var(--ifab-line);
  border-radius:var(--ifab-r);padding:clamp(22px,2.4vw,36px);}
.ifab-form .gform_wrapper{margin:0;}
.ifab-form .gform_wrapper form{margin:0;}
.ifab-form .gform_fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(16px,1.6vw,22px);margin:0;padding:0;list-style:none;}
.ifab-form .gfield{grid-column:span 2;min-width:0;margin:0;padding:0;list-style:none;}
/* Name, email and phone sit two-up; everything else takes the full width. */
.ifab-form .gfield--type-text,
.ifab-form .gfield--type-email,
.ifab-form .gfield--type-phone{grid-column:span 1;}
@media (max-width:620px){
  .ifab-form .gform_fields{grid-template-columns:1fr;}
  .ifab-form .gfield,
  .ifab-form .gfield--type-text,
  .ifab-form .gfield--type-email,
  .ifab-form .gfield--type-phone{grid-column:span 1;}
}

.ifab-form .gfield_label{display:block;margin:0 0 8px;
  font-family:var(--ifab-ff);font-size:clamp(13px,1vw,14px);line-height:1.4;
  font-weight:500;letter-spacing:.01em;color:var(--ifab-fg);}
.ifab-form .gfield_required{color:var(--ifab-accent);margin-left:3px;}
.ifab-form .gfield_description{margin:7px 0 0;padding:0;
  font-size:clamp(12px,.9vw,13px);line-height:1.5;color:var(--ifab-dim);}

.ifab-form input[type="text"],
.ifab-form input[type="email"],
.ifab-form input[type="tel"],
.ifab-form input[type="url"],
.ifab-form input[type="number"],
.ifab-form textarea,
.ifab-form select{
  width:100%;box-sizing:border-box;
  padding:13px 15px;
  background:var(--ifab-ink);
  border:1px solid var(--ifab-line-2);border-radius:var(--ifab-r-sm);
  font-family:var(--ifab-ff);font-size:clamp(14.5px,1.05vw,16px);line-height:1.5;
  font-weight:400;color:var(--ifab-fg);
  transition:border-color var(--ifab-t) var(--ifab-ease),
             background-color var(--ifab-t) var(--ifab-ease);}
.ifab-form textarea{min-height:132px;resize:vertical;}
.ifab-form input::placeholder,.ifab-form textarea::placeholder{color:var(--ifab-dim);}
.ifab-form input:focus,.ifab-form textarea:focus,.ifab-form select:focus{
  outline:none;border-color:var(--ifab-accent);background:var(--ifab-ink-3);}
.ifab-form input:focus-visible,.ifab-form textarea:focus-visible,
.ifab-form select:focus-visible{outline:2px solid var(--ifab-accent-hi);outline-offset:2px;}

/* Checkboxes. The default list is cramped and inherits the theme list bullets,
   so both are reset here. */
.ifab-form .gfield_checkbox,.ifab-form .gfield_radio{display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 18px;
  margin:0;padding:0;list-style:none;}
@media (max-width:620px){
  .ifab-form .gfield_checkbox,.ifab-form .gfield_radio{grid-template-columns:1fr;}
}
.ifab-form .gfield_checkbox li,.ifab-form .gfield_radio li{margin:0;padding:0;list-style:none;}
.ifab-form .gchoice{display:flex;align-items:flex-start;gap:10px;}
.ifab-form .gchoice input[type="checkbox"],
.ifab-form .gchoice input[type="radio"]{flex:0 0 17px;width:17px;height:17px;
  margin:2px 0 0;accent-color:var(--ifab-accent);cursor:pointer;}
.ifab-form .gchoice label{margin:0;font-size:clamp(13.5px,1vw,14.5px);line-height:1.45;
  font-weight:400;color:var(--ifab-muted);cursor:pointer;}

/* File upload. The default control is a bare browser button, so the input gets
   the field treatment and the button inside it is themed separately. */
.ifab-form input[type="file"]{width:100%;box-sizing:border-box;padding:12px 14px;
  background:var(--ifab-ink);border:1px dashed var(--ifab-line-2);
  border-radius:var(--ifab-r-sm);
  font-family:var(--ifab-ff);font-size:clamp(13px,.95vw,14px);color:var(--ifab-muted);
  cursor:pointer;}
.ifab-form input[type="file"]::file-selector-button{
  margin-right:12px;padding:8px 14px;border:1px solid var(--ifab-line-2);
  border-radius:var(--ifab-r-sm);background:var(--ifab-ink-3);
  font-family:var(--ifab-ff);font-size:clamp(12.5px,.95vw,13.5px);font-weight:500;color:var(--ifab-fg);
  cursor:pointer;transition:border-color var(--ifab-t) var(--ifab-ease);}
.ifab-form input[type="file"]::file-selector-button:hover{border-color:var(--ifab-accent);}
.ifab-form .gform_fileupload_rules,.ifab-form .ginput_preview{
  font-size:clamp(12px,.9vw,13px);color:var(--ifab-dim);}

/* Submit uses the site button, not the plugin default. */
.ifab-form .gform_footer{margin:clamp(22px,2vw,28px) 0 0;padding:0;}
.ifab-form .gform_footer input[type="submit"],
.ifab-form .gform_button{
  display:inline-flex;align-items:center;justify-content:center;
  width:auto;padding:15px 30px;
  border:1px solid transparent;border-radius:var(--ifab-r-sm);
  background:var(--ifab-accent);color:#14100C;
  font-family:var(--ifab-ff);font-size:clamp(14px,1vw,15px);line-height:1.2;
  font-weight:500;letter-spacing:.012em;cursor:pointer;
  transition:background-color var(--ifab-t) var(--ifab-ease),
             transform var(--ifab-t) var(--ifab-ease);}
.ifab-form .gform_footer input[type="submit"]:hover,
.ifab-form .gform_button:hover{background:var(--ifab-accent-hi);transform:translateY(-1px);}

/* Validation. The plugin default is a red block that ignores the palette. */
.ifab-form .gform_validation_errors{margin:0 0 22px;padding:14px 16px;
  background:rgba(124,230,0,.10);
  border:1px solid var(--ifab-accent);border-radius:var(--ifab-r-sm);
  color:var(--ifab-fg);font-size:clamp(13.5px,1vw,14.5px);line-height:1.5;}
.ifab-form .gform_validation_errors h2{margin:0;font-size:clamp(14px,1.05vw,15.5px);font-weight:500;
  color:var(--ifab-fg);}
.ifab-form .gfield_error input,.ifab-form .gfield_error textarea,
.ifab-form .gfield_error select{border-color:#D9705A;}
.ifab-form .gfield_validation_message,.ifab-form .validation_message{
  margin:8px 0 0;padding:0;background:none;border:0;
  font-size:clamp(12.5px,.95vw,13.5px);line-height:1.5;color:#E0917E;}
.ifab-form .gform_required_legend{display:none;}

/* ============================================================
   CANVAS (thank-you page: no header, no footer)
   ============================================================ */
.ifab-canvas{display:flex;align-items:center;justify-content:center;
  min-height:100vh;min-height:100svh;
  padding:var(--ifab-pad) var(--ifab-gut);
  background:var(--ifab-ink);text-align:center;}
/* Wide enough for the heading to hold one line at its clamped maximum. 60ch
   is a measure for body copy, and it broke a 32-character heading in two. */
.ifab-canvas-in{width:100%;max-width:860px;}
.ifab-canvas-mark{display:block;height:34px;width:auto;margin:0 auto 30px;}
.ifab-canvas .ifab-lede{margin-left:auto;margin-right:auto;margin-bottom:34px;}
.ifab-canvas-btns{justify-content:center;}

/* ---------- Gravity Forms: Orbital overrides ----------
   Measured, not guessed. The rendered wrapper carries
   `gform-theme--orbital gform-theme--framework`, the submit is a <button
   class="gform_button button"> rather than an input, and Orbital sets
   display:flex on the checkbox list. So the first pass lost three fights:
   a blue submit, a white file dropzone and a single-column checkbox list.

   Two things fix it together. Orbital is driven by custom properties, so those
   are redefined here and everything that reads them follows. Where a rule wins
   on specificity instead, the theme class is chained to outrank it - Orbital's
   own selectors are long, and a single scoped class does not beat them. */
.ifab-form .gform-theme--framework,
.ifab-form .gform-theme--orbital{
  --gf-ctrl-bg-color:var(--ifab-ink);
  --gf-ctrl-border-color:var(--ifab-line-2);
  --gf-ctrl-border-color-hover:var(--ifab-accent);
  --gf-ctrl-border-color-focus:var(--ifab-accent);
  --gf-ctrl-color:var(--ifab-fg);
  --gf-ctrl-radius:var(--ifab-r-sm);
  --gf-ctrl-label-color-primary:var(--ifab-fg);
  --gf-ctrl-label-color-secondary:var(--ifab-muted);
  --gf-ctrl-desc-color:var(--ifab-dim);
  --gf-ctrl-placeholder-color:var(--ifab-dim);
  --gf-color-primary:var(--ifab-accent);
  --gf-color-primary-rgb:124,230,0;
  --gf-color-primary-contrast:#14100C;
  --gf-ctrl-btn-bg-color-primary:var(--ifab-accent);
  --gf-ctrl-btn-bg-color-hover-primary:var(--ifab-accent-hi);
  --gf-ctrl-btn-bg-color-focus-primary:var(--ifab-accent-hi);
  --gf-ctrl-btn-border-color-primary:var(--ifab-accent);
  --gf-ctrl-btn-border-color-hover-primary:var(--ifab-accent-hi);
  --gf-ctrl-btn-color-primary:#14100C;
  --gf-ctrl-btn-color-hover-primary:#14100C;
  --gf-ctrl-btn-radius:var(--ifab-r-sm);
  --gf-ctrl-file-bg-color:var(--ifab-ink);
  --gf-ctrl-file-border-color:var(--ifab-line-2);
}

/* Submit. It is a <button>, so the earlier input[type=submit] rule never
   applied to it at all. */
.ifab-form .gform-theme--framework .gform_button.button,
.ifab-form .gform_wrapper .gform_footer .gform_button{
  display:inline-flex;align-items:center;justify-content:center;
  width:auto;padding:15px 30px;
  border:1px solid transparent;border-radius:var(--ifab-r-sm);
  background:var(--ifab-accent);color:#14100C;
  font-family:var(--ifab-ff);font-size:clamp(14px,1vw,15px);line-height:1.2;
  font-weight:500;letter-spacing:.012em;cursor:pointer;
  transition:background-color var(--ifab-t) var(--ifab-ease),
             transform var(--ifab-t) var(--ifab-ease);}
.ifab-form .gform-theme--framework .gform_button.button:hover,
.ifab-form .gform_wrapper .gform_footer .gform_button:hover{
  background:var(--ifab-accent-hi);color:#14100C;transform:translateY(-1px);}

/* Checkbox list. Orbital sets display:flex, which quietly ignored the
   grid-template-columns already declared and left one column. */
.ifab-form .gform-theme--framework .gfield_checkbox,
.ifab-form .gform-theme--framework .gfield_radio{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 18px;}
@media (max-width:620px){
  .ifab-form .gform-theme--framework .gfield_checkbox,
  .ifab-form .gform-theme--framework .gfield_radio{grid-template-columns:1fr;}
}

/* File upload: the real control is a drop area with its own select button, not
   the bare input[type=file] the first pass styled. */
.ifab-form .gform-theme--framework .gform_drop_area,
.ifab-form .gform_wrapper .gform_drop_area{
  background:var(--ifab-ink);
  border:1px dashed var(--ifab-line-2);border-radius:var(--ifab-r-sm);
  padding:26px 20px;color:var(--ifab-muted);}
.ifab-form .gform_drop_instructions{color:var(--ifab-dim);
  font-size:clamp(13px,.95vw,14px);}
.ifab-form .gform-theme--framework .gform_button_select_files,
.ifab-form .gform_wrapper .gform_button_select_files{
  padding:9px 18px;border:1px solid var(--ifab-line-2);
  border-radius:var(--ifab-r-sm);background:var(--ifab-ink-3);
  font-family:var(--ifab-ff);font-size:clamp(13px,1vw,14px);font-weight:500;
  color:var(--ifab-fg);cursor:pointer;
  transition:border-color var(--ifab-t) var(--ifab-ease),
             color var(--ifab-t) var(--ifab-ease);}
.ifab-form .gform-theme--framework .gform_button_select_files:hover,
.ifab-form .gform_wrapper .gform_button_select_files:hover{
  border-color:var(--ifab-accent);color:var(--ifab-accent-hi);background:var(--ifab-ink-3);}


/* Required marker and descriptions, which Orbital colours from its own tokens. */
.ifab-form .gfield_required,
.ifab-form .gform-theme--framework .gfield_required{color:var(--ifab-accent);}

/* The select-files control is `button.button.gform_button_select_files` inside
   the drop area, and Orbital styles it through a longer selector than the one
   above, so it stayed blue. Chaining both of its classes and its container
   outranks that without reaching for !important. */
.ifab-form .gform-theme--framework .gform_drop_area button.gform_button_select_files.button,
.ifab-form .gform_wrapper .gform_drop_area button.gform_button_select_files.button{
  padding:9px 18px;border:1px solid var(--ifab-line-2);
  border-radius:var(--ifab-r-sm);background:var(--ifab-ink-3);
  font-family:var(--ifab-ff);font-size:clamp(13px,1vw,14px);font-weight:500;
  color:var(--ifab-fg);cursor:pointer;
  transition:border-color var(--ifab-t) var(--ifab-ease),
             color var(--ifab-t) var(--ifab-ease);}
.ifab-form .gform-theme--framework .gform_drop_area button.gform_button_select_files.button:hover,
.ifab-form .gform_wrapper .gform_drop_area button.gform_button_select_files.button:hover{
  background:var(--ifab-ink-3);border-color:var(--ifab-accent);color:var(--ifab-accent-hi);}

/* The drop-area icon paints itself, not through `color`, so setting color on
   the svg did nothing. Both paint properties have to be named. */

/* Checkboxes, drawn rather than tinted. accent-color only colours the CHECKED
   state, so unchecked boxes stayed stark white on a near-black panel - the one
   thing on the form still shouting "default". appearance:none hands the box
   over, and the tick is a mask so it inherits the accent. */
.ifab-form .gchoice input[type="checkbox"],
.ifab-form .gform-theme--framework .gchoice input[type="checkbox"]{
  appearance:none;-webkit-appearance:none;
  flex:0 0 18px;width:18px;height:18px;margin:1px 0 0;padding:0;
  background:var(--ifab-ink);
  border:1px solid var(--ifab-line-2);border-radius:4px;
  cursor:pointer;position:relative;
  transition:border-color var(--ifab-t) var(--ifab-ease),
             background-color var(--ifab-t) var(--ifab-ease);}
.ifab-form .gchoice input[type="checkbox"]:hover{border-color:var(--ifab-accent);}
.ifab-form .gchoice input[type="checkbox"]:checked{
  background:var(--ifab-accent);border-color:var(--ifab-accent);}
.ifab-form .gchoice input[type="checkbox"]:checked::after{
  content:"";position:absolute;inset:0;background:#14100C;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E") center/13px no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E") center/13px no-repeat;}
.ifab-form .gchoice input[type="checkbox"]:focus-visible{
  outline:2px solid var(--ifab-accent-hi);outline-offset:2px;}

/* The phone field's country selector is a native <select>; without this it
   renders as a light control in the middle of a dark form. */
.ifab-form .ginput_container_phone select,
.ifab-form .gform-theme--framework select{
  background:var(--ifab-ink);color:var(--ifab-fg);
  border:1px solid var(--ifab-line-2);border-radius:var(--ifab-r-sm);}

/* The drop-area icon is an icon-font glyph on ::before, not an <svg> - which is
   why styling `svg` inside the drop area changed nothing. It takes its colour
   from `color`, so that is all that needs setting. */
.ifab-form .gform_drop_area::before,
.ifab-form .gform-theme--framework .gform_drop_area::before{color:var(--ifab-accent);}

/* Gravity Forms sizes a textarea from its rows attribute, which at size "large"
   is far taller than this form needs - it dominated the panel. Height is set
   here instead, and the theme class is chained so it outranks that. */
.ifab-form .gform-theme--framework textarea,
.ifab-form .gform_wrapper textarea{
  min-height:0;height:118px;resize:vertical;}

/* The asterisk sits tight to the label rather than floating as a separate word. */
.ifab-form .gfield_required,
.ifab-form .gform-theme--framework .gfield_required{
  margin-left:2px;color:var(--ifab-accent);font-weight:500;}

/* Submit spans the panel. It is the only action in the form, so a button sized
   to its own text left a wide empty strip under a full-width field stack. */
.ifab-form .gform-theme--framework .gform_button.button,
.ifab-form .gform_wrapper .gform_footer .gform_button{width:100%;}
.ifab-form .gform_footer{display:block;}

/* ============================================================
   FAQ + CONTACT, second pass
   ============================================================ */

/* 60 / 40, with the gap taken out of the track sizes rather than added to them,
   so the columns really are 60 and 40 of the container. */
.ifab-faq-grid{display:grid;
  grid-template-columns:minmax(0,60fr) minmax(0,40fr);
  gap:clamp(32px,4vw,64px);align-items:start;}
@media (max-width:900px){.ifab-faq-grid{grid-template-columns:1fr;gap:36px;}}

.ifab-faq-media{position:relative;overflow:hidden;border-radius:var(--ifab-r);
  background:var(--ifab-paper-2);}
/* The image column matches the height of the questions beside it rather than
   setting its own; on one column it falls back to its natural ratio. */
.ifab-faq-media{height:100%;min-height:340px;}
.ifab-faq-img{display:block;width:100%;height:100%;object-fit:cover;
  filter:grayscale(.3) contrast(1.04);}
@media (max-width:900px){
  .ifab-faq-media{height:auto;min-height:0;aspect-ratio:16/10;}
}

/* ---------- FAQ on the light band ---------- */
.ifab-light .ifab-faqs{border-top:1px solid var(--ifab-line-inv);}
.ifab-light .ifab-faq{border-bottom:1px solid var(--ifab-line-inv);}
.ifab-light .ifab-faq-q{color:var(--ifab-fg-inv);}
.ifab-light .ifab-faq-q:hover{color:var(--ifab-accent-ink);}
.ifab-light .ifab-faq-a{color:var(--ifab-muted-inv);}

/* Chevron rather than a plus/minus, rotating to point up when open. One
   element, one transform - the two-bar sign needed two and could not rotate
   cleanly. */
.ifab-faq-q::before{content:"";position:absolute;right:6px;top:50%;
  width:17px;height:17px;margin-top:-8.5px;
  background-color:var(--ifab-accent);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 9l7 7 7-7'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 9l7 7 7-7'/%3E%3C/svg%3E") center/contain no-repeat;
  transform:rotate(0deg);
  transition:transform var(--ifab-t) var(--ifab-ease),
             background-color var(--ifab-t) var(--ifab-ease);}
.ifab-faq-q::after{content:none;}
.ifab-faq[open] .ifab-faq-q::before{transform:rotate(180deg);}
.ifab-light .ifab-faq-q::before{background-color:var(--ifab-accent-ink);}

/* The answer's height is animated by menu.js, which measures scrollHeight, so
   the margin has to sit outside the animated box or the close jumps. */
.ifab-faq-a{overflow:hidden;}

@media (prefers-reduced-motion:reduce){
  .ifab-faq-q::before{transition:none;}
}

/* ---------- contact: icons, socials, sticky ---------- */
/* The left column holds still while the form scrolls past it. It is shorter
   than the form at every width where they sit side by side, so there is always
   something for it to hold against. */
@media (min-width:901px){
  .ifab-cx-side{position:sticky;top:calc(76px + 28px);}
}

.ifab-cx-item{position:relative;padding-left:32px;}
.ifab-cx-item::before{content:"";position:absolute;left:0;top:3px;
  width:19px;height:19px;background-color:var(--ifab-accent);
  -webkit-mask:var(--ifab-cxi) center/contain no-repeat;
  mask:var(--ifab-cxi) center/contain no-repeat;}
.ifab-cx-email{--ifab-cxi:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.5' y='4.5' width='19' height='15' rx='2.5'/%3E%3Cpath d='M3.5 7l8.5 6 8.5-6'/%3E%3C/svg%3E");}
.ifab-cx-phone{--ifab-cxi:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.5 3.5H4.8A1.8 1.8 0 0 0 3 5.4c0 8.4 7.2 15.6 15.6 15.6a1.8 1.8 0 0 0 1.9-1.8v-2.7l-4.4-1.6-2 2a14.6 14.6 0 0 1-6-6l2-2z'/%3E%3C/svg%3E");}
.ifab-cx-loc{--ifab-cxi:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 21.5s7-6.1 7-11.2a7 7 0 1 0-14 0c0 5.1 7 11.2 7 11.2z'/%3E%3Ccircle cx='12' cy='10' r='2.6'/%3E%3C/svg%3E");}

.ifab-cx-soc{display:flex;align-items:center;gap:12px;margin-top:26px;
  padding-top:24px;border-top:1px solid var(--ifab-line);}
.ifab-cx-soc .ifab-soc-wrap{display:inline-flex;}
/* Qualified with the anchor: .ifab-cx a (0,1,1) would otherwise outrank a bare
   .ifab-soc (0,1,0) and leave these grey, exactly as happened in the footer. */
.ifab-cx-soc a.ifab-soc{display:inline-flex;align-items:center;justify-content:center;
  width:38px;height:38px;border-radius:50%;
  border:1px solid var(--ifab-line-2);color:var(--ifab-muted);
  text-decoration:none;
  transition:color var(--ifab-t) var(--ifab-ease),
             border-color var(--ifab-t) var(--ifab-ease);}
.ifab-cx-soc a.ifab-soc:hover,.ifab-cx-soc a.ifab-soc:focus-visible{
  color:var(--ifab-accent-hi);border-color:var(--ifab-accent);}
.ifab-cx-soc a.ifab-soc svg{width:18px;height:18px;display:block;}

/* ============================================================
   MOBILE CORRECTIONS
   ============================================================ */

/* The capability row's accent tick sits at left:0, which is fine beside a
   three-column row but lands on top of the text once the row stacks. The row
   divider above each cluster already separates them at this width. */
@media (max-width:1080px){
  .ifab-cap::before{display:none;}
  .ifab-cap-head{padding-left:0;}
}

/* Quote band: centred once it stacks. Left-aligned copy under a centred
   heading read as two different decisions in the same block. */
@media (max-width:880px){
  .ifab-quote-in{text-align:center;}
  .ifab-quote-in .ifab-lede,.ifab-quote-in .ifab-body{margin-left:auto;margin-right:auto;}
  .ifab-quote-side{padding-left:0;}
  .ifab-quote-side .ifab-btns{justify-content:center;}
}

/* Footer bar: the credit line and the policy link swap, so the policy link
   leads, and both centre. justify-content alone would not reorder them. */
@media (max-width:600px){
  .ifab-ftr-bar{flex-direction:column;justify-content:center;text-align:center;gap:12px;}
  .ifab-ftr-bar > *:first-child{order:2;}
  .ifab-ftr-bar > *:last-child{order:1;}
}

/* ============================================================
   CONTACT ON THE LIGHT BAND (contact page)
   The markup is identical to the home section, so the colours are flipped by
   redefining tokens on the section rather than by writing a second set of
   rules. Everything inside - the detail rows, the socials, the whole Gravity
   Forms panel - already reads these, so nothing can drift out of step.
   ============================================================ */
.ifab-light .ifab-cx{
  --ifab-fg:var(--ifab-fg-inv);
  --ifab-muted:var(--ifab-muted-inv);
  --ifab-dim:var(--ifab-muted-inv);
  --ifab-line:var(--ifab-line-inv);
  --ifab-line-2:rgba(12,14,17,.20);
}
/* The form stays a DARK panel on the light page, so it re-asserts the tokens
   the section just inverted. Without this its labels inherit the paper ink and
   go dark on a dark panel - which is exactly what happened when the light form
   treatment was removed and this was not put back in its place. */
.ifab-light .ifab-form{
  --ifab-fg:#F2F4F6;
  --ifab-muted:#AEB6BD;
  --ifab-dim:#79828B;
  --ifab-line:rgba(255,255,255,.09);
  --ifab-line-2:rgba(255,255,255,.17);
}

/* Copper on paper is the darker ink variant. Inside the dark panel it is left
   alone, so the form keeps the standard accent. */
.ifab-light .ifab-cx .ifab-eyebrow{color:var(--ifab-accent-ink);}
.ifab-light .ifab-cx .ifab-cx-item::before{background-color:var(--ifab-accent-ink);}
.ifab-light .ifab-cx-soc a.ifab-soc:hover,
.ifab-light .ifab-cx-soc a.ifab-soc:focus-visible{
  color:var(--ifab-accent-ink);border-color:var(--ifab-accent-ink);}
.ifab-light .ifab-cx-v:hover,.ifab-light a.ifab-cx-v:focus-visible{color:var(--ifab-accent-ink);}

/* The contact page has one section and no hero, so it needs breathing room
   under the sticky header. */
.ifab-light.ifab-sec#contact{padding-top:calc(var(--ifab-pad) + 20px);}

/* ============================================================
   PROJECTS GALLERY
   CSS multi-column, not grid. Grid cannot do masonry without either fixed row
   spans or JavaScript measuring every tile; columns gives real masonry with
   neither, and degrades to a single readable column with no extra rules.
   break-inside:avoid is what stops a tile being sliced across two columns.
   ============================================================ */
.ifab-gal{column-count:4;column-gap:clamp(12px,1.4vw,18px);}
@media (max-width:1200px){.ifab-gal{column-count:3;}}
@media (max-width:900px) {.ifab-gal{column-count:2;}}
@media (max-width:560px) {.ifab-gal{column-count:1;}}

.ifab-gal-item{break-inside:avoid;-webkit-column-break-inside:avoid;
  margin:0 0 clamp(12px,1.4vw,18px);
  position:relative;overflow:hidden;border-radius:var(--ifab-r);
  background:var(--ifab-ink-2);}
/* The tile ratio is set here, not by the image, so the masonry has a rhythm
   instead of ten near-identical 16:9 blocks. Real photography drops straight in. */
.ifab-gal-item.r-tall{aspect-ratio:4/5;}
.ifab-gal-item.r-wide{aspect-ratio:3/2;}
.ifab-gal-item.r-sq  {aspect-ratio:1/1;}

.ifab-gal-img{display:block;width:100%;height:100%;object-fit:cover;
  filter:grayscale(.35) contrast(1.04);
  transition:transform .5s var(--ifab-ease),filter var(--ifab-t) var(--ifab-ease);}
.ifab-gal-item:hover .ifab-gal-img{transform:scale(1.04);filter:grayscale(0) contrast(1.04);}
@media (prefers-reduced-motion:reduce){
  .ifab-gal-img{transition:none;}
  .ifab-gal-item:hover .ifab-gal-img{transform:none;}
}

/* ============================================================
   404
   Part 12.2: centred, "404" as the main heading, one light line, and a
   single button back to home.
   ============================================================ */
.ifab-404{min-height:clamp(420px,58vh,620px);justify-content:center;}
/* min(), not a bare 60ch. This class shares its element with .ifab-wrap,
   whose max-width:100% it would otherwise override - which made the block
   wider than the screen on every phone width. */
.ifab-404-in{text-align:center;max-width:min(60ch,100%);
  margin-left:auto;margin-right:auto;}
.ifab-404-n{margin:0 0 18px;font-family:var(--ifab-ff-head);
  font-size:clamp(76px,14vw,180px);line-height:.92;font-weight:500;
  letter-spacing:-.04em;color:var(--ifab-fg);}
.ifab-404 .ifab-lede{margin-left:auto;margin-right:auto;margin-bottom:34px;}
.ifab-404-btns{justify-content:center;}

/* ============================================================
   ABOUT
   Two new blocks only. The principles reuse .ifab-appr and the headings reuse
   .ifab-head, so the page inherits every fix those have already had rather
   than starting a second set of rules that would drift.
   ============================================================ */

/* ---------- the story: text beside a picture ---------- */
.ifab-ab{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
  gap:clamp(32px,4.5vw,72px);align-items:center;}
@media (max-width:900px){.ifab-ab{grid-template-columns:1fr;gap:36px;}}

.ifab-ab-text .ifab-body + .ifab-body{margin-top:18px;}
.ifab-ab-text .ifab-body{max-width:60ch;}

.ifab-ab-media{overflow:hidden;border-radius:var(--ifab-r);
  background:var(--ifab-paper-2);}
/* Portrait on desktop so it holds the column beside the copy, and a calmer
   landscape once it stacks and has the full width to itself. The clip itself is
   4:5, so the wide crop is the browser trimming top and bottom, not stretching.

   No grayscale here, unlike the stills elsewhere: this is the client's own
   machine running, which is the thing they asked to have seen, and a filter on
   thirty frames a second is work the compositor does for the whole time the
   section is on screen. */
.ifab-ab-vid{display:block;width:100%;height:100%;object-fit:cover;
  aspect-ratio:4/5;background:var(--ifab-paper-2);}
@media (max-width:900px){.ifab-ab-vid{aspect-ratio:16/10;}}

/* Asked for less motion, the clip is never started and never fetched (see
   autoplay-video.js). What stays is the poster, which is its own first frame,
   so the section looks the way it would have looked a moment before playing
   rather than empty. */
@media (prefers-reduced-motion:reduce){
  .ifab-ab-vid{object-fit:cover;}
}

/* The story CTAs sit clear of the last paragraph rather than reading as part
   of it, and wrap rather than overflow once the column narrows. */
.ifab-ab-btns{margin-top:clamp(26px,2.6vw,34px);}

/* ---------- the closing CTA ---------- */
.ifab-abcta-btns{justify-content:center;}

/* ============================================================
   ABOUT, second pass
   ============================================================ */

/* The story image never fades. The reveal is right for text, but measured at
   0.86 opacity while the block is entering the viewport - and a washed-out
   photograph reads as a loading fault, not as motion. The text keeps its rise. */
.ifab-ab-media.ifab-rise{animation:none;transform:none;}



/* ============================================================
   AT A GLANCE
   A ruled band of four columns on a solid panel - no photograph behind it.
   Two earlier versions were rejected: icon-topped cards read as a second copy
   of the principles section below, and a spec sheet laid over a photo left the
   type small and hard to read against the image. Here the value leads at
   heading size, the label sits under it in the accent, the detail follows in grey,
   and a hairline grid is the only ornament.
   ============================================================ */
.ifab-glance{background:var(--ifab-ink-2);}
.ifab-glance-row{display:grid;grid-template-columns:repeat(4,1fr);
  border-top:1px solid var(--ifab-line-2);}
.ifab-glance-cell{padding:clamp(26px,2.6vw,36px) clamp(20px,2vw,30px);
  border-right:1px solid var(--ifab-line-2);}
.ifab-glance-cell:last-child{border-right:0;}
/* The band spans the content column exactly, so the first value lines up with
   the heading above it instead of sitting a padding-width to its right. */
.ifab-glance-row > .ifab-glance-cell:first-child{padding-left:0;}
.ifab-glance-row > .ifab-glance-cell:last-child{padding-right:0;}

.ifab-glance-v{display:block;font-family:var(--ifab-ff-head);font-weight:500;
  letter-spacing:-.022em;color:var(--ifab-fg);line-height:1.05;
  font-size:clamp(28px,3.2vw,44px);}
.ifab-glance-l{display:block;margin-top:10px;font-family:var(--ifab-mono);
  font-size:clamp(12px,.85vw,12px);line-height:1.5;font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ifab-accent);}
.ifab-glance-d{display:block;margin-top:8px;
  font-size:clamp(13px,1vw,14.5px);line-height:1.55;color:var(--ifab-muted);}

/* Four columns, then two, then one. At the two-column step the right-hand
   border is dropped from the second cell of each pair and a top rule added to
   the second row, so the grid stays closed with no stray edges. */
@media (max-width:900px){
  .ifab-glance-row{grid-template-columns:1fr 1fr;}
  .ifab-glance-cell:nth-child(2n){border-right:0;}
  .ifab-glance-cell:nth-child(n+3){border-top:1px solid var(--ifab-line-2);}
  .ifab-glance-row > .ifab-glance-cell:nth-child(2n){padding-right:0;}
  .ifab-glance-row > .ifab-glance-cell:nth-child(2n+1){padding-left:0;}
}
@media (max-width:520px){
  .ifab-glance-row{grid-template-columns:1fr;}
  .ifab-glance-cell{border-right:0;padding-left:0;padding-right:0;}
  .ifab-glance-cell + .ifab-glance-cell{border-top:1px solid var(--ifab-line-2);}
}

/* ============================================================
   SERVICES
   The single service page and the services listing. Both are rendered from
   fields by infinity/service-single and infinity/service-archive, so these
   rules are the only place the design lives - there is no stored markup that
   could drift out of step with them.

   Prefixed ifab-srv- rather than ifab-svc-, because .ifab-svc and .ifab-svc-img
   are already taken by the technical drawings in the home capability rows.
   ============================================================ */

/* ---------- breadcrumb ---------- */
/* Above the heading rather than beside it. A service sits two levels deep,
   which is exactly where a visitor needs telling where they are. */
.ifab-crumb{display:flex;flex-wrap:wrap;align-items:center;gap:9px;
  margin-bottom:clamp(22px,2.2vw,32px);
  font-family:var(--ifab-mono);font-size:clamp(12px,.85vw,12px);
  letter-spacing:.1em;text-transform:uppercase;}
.ifab-crumb-l{color:var(--ifab-muted);text-decoration:none;transition:color .2s ease;}
.ifab-crumb-l:hover,.ifab-crumb-l:focus-visible{color:var(--ifab-accent);}
.ifab-crumb-s{color:var(--ifab-dim);}
.ifab-crumb-c{color:var(--ifab-accent);}

/* ---------- single: the page header ---------- */
/* A compact header, not a split hero. The hero carried the featured photograph
   beside the title and did not earn its height: it left a tall column of empty
   black under two lines of text, and the industry stock photographs do not
   describe the individual service well enough to justify the space. The image
   still does its work on the listing card and as the og:image, where it is
   doing a job the text cannot.

   The band is tightened top and bottom so the tabs begin close to the fold. */
.ifab-srv-hero{padding-top:clamp(30px,3vw,44px);padding-bottom:clamp(28px,2.8vw,40px);}
.ifab-srv-head{max-width:70ch;}
.ifab-srv-head .ifab-h2{margin:0 0 clamp(12px,1.2vw,16px);}
.ifab-srv-head .ifab-lede{margin:0;max-width:58ch;}

/* ============================================================
   SERVICE PAGE: two columns, tabbed body, sticky enquiry sidebar
   The structure comes from a reference the client sent: article on the left,
   a get-in-touch card on the right. The structure is all that is borrowed
   (Part 4). The reference was a rounded teal-and-red SaaS layout; this is the
   site's own black, green and hairline system, with the tabs rendered as a
   ruled bar rather than pills, and the form card squared off to match every
   other panel on the site.
   ============================================================ */

.ifab-srv-layout{display:grid;
  grid-template-columns:minmax(0,1fr) clamp(300px,26vw,360px);
  gap:clamp(30px,3.4vw,58px);align-items:start;}
/* The sidebar drops under the article before it gets too narrow to hold a form
   field comfortably - a 300px column of inputs beside a shrinking article is
   worse than the two stacked. */
@media (max-width:1000px){
  .ifab-srv-layout{grid-template-columns:1fr;gap:clamp(34px,4vw,48px);}
}

/* Sticky only where there is room to scroll past it, and never on a touch
   layout where it would eat the viewport.

   The aside has to stretch to the full height of the grid row for this to do
   anything. With the row set to align-items:start the column shrank to its own
   content, so the sticky child had exactly zero pixels to travel and simply
   scrolled away with the page - measured, before this line existed: parent
   height 700, child height 700, travel 0. */
/* A grid child does not shrink below its content by default, so a long
   unbroken string in the article could push the column wider than its track.
   min-width:0 lets it shrink and wrap instead. */
.ifab-srv-content{min-width:0;}
.ifab-srv-side{align-self:stretch;}
.ifab-srv-sticky{display:flex;flex-direction:column;gap:clamp(16px,1.6vw,22px);}
@media (min-width:1001px){
  /* The aside above stretches to the full row height, which is what gives this
     somewhere to travel; without it the column shrinks to its content and a
     sticky child has nowhere to go. */
  .ifab-srv-sticky{position:sticky;top:50px;}
}

/* ---------- the body sections ---------- */
/* Plain sections, one after another, each with its own heading. An earlier
   pass put these behind tabs; stacking them means the whole service is on the
   page at once, which is also what balances the column against the sidebar
   beside it - a single tab panel left most of the left column empty. */
.ifab-srv-block + .ifab-srv-block{margin-top:clamp(34px,3.4vw,52px);}
/* Bronze, and larger, so the section titles carry the page rather than sitting
   at the same weight as the body. A short accent bar above each one marks the
   start of a section at a glance, which the full-width hairline alone did not:
   the hairline separates, the bar announces. */
.ifab-srv-block-h{position:relative;margin:0 0 clamp(18px,1.8vw,24px);
  font-family:var(--ifab-ff-head);font-weight:500;letter-spacing:-.012em;
  line-height:1.22;color:var(--ifab-accent-ink);
  font-size:clamp(21px,1.95vw,26px);}
/* A hairline over each heading after the first, so the sections read as
   separate pieces of the page rather than one long run of text. */
.ifab-srv-block + .ifab-srv-block .ifab-srv-block-h{
  padding-top:clamp(30px,3vw,42px);
  border-top:1px solid var(--ifab-line-inv);}
/* The accent bar sits on the hairline for sections that have one, and at the
   top of the block for the first one, so every title is marked the same way. */
.ifab-srv-block-h::before{content:"";position:absolute;left:0;top:0;
  width:34px;height:3px;background:var(--ifab-accent);}
/* Sat just above the text, inside the block padding. A negative offset put it
   on the far side of the hairline, where it read as a mark ending the previous
   section rather than one opening this title. */
.ifab-srv-block + .ifab-srv-block .ifab-srv-block-h::before{
  top:calc(clamp(30px,3vw,42px) - 15px);}

/* ---------- the article ---------- */
.ifab-srv-body p{font-size:clamp(15.5px,1.2vw,17.5px);line-height:1.7;
  color:var(--ifab-muted-inv);margin:0 0 1.15em;max-width:66ch;}
.ifab-srv-body p:last-child{margin-bottom:0;}

.ifab-srv-list{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:1fr 1fr;gap:clamp(13px,1.2vw,18px) clamp(22px,2.4vw,34px);}
@media (max-width:680px){.ifab-srv-list{grid-template-columns:1fr;}}
.ifab-srv-li{list-style:none;margin:0;padding:0;
  display:flex;align-items:flex-start;gap:12px;
  font-size:clamp(15px,1.15vw,16.5px);line-height:1.5;color:var(--ifab-fg-inv);}
/* Tick drawn in CSS, as everywhere else on the site, so each item stays one
   plain line of editable text in the back end. */
.ifab-srv-li::before{content:"";width:15px;height:15px;flex:0 0 15px;margin-top:.3em;
  background-color:var(--ifab-accent-ink);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5l5 5 11-11'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5l5 5 11-11'/%3E%3C/svg%3E") center/contain no-repeat;}

/* ---------- stages, as a ruled list rather than the dark band ---------- */
.ifab-srv-steps{list-style:none;margin:0;padding:0;
  border-top:1px solid var(--ifab-line-inv);}
/* The last stage drops its rule: the next section heading already draws one,
   and the two together left an empty ruled band between the sections. */
.ifab-srv-steps .ifab-srv-step:last-child{border-bottom:0;}
.ifab-srv-step{list-style:none;display:grid;
  grid-template-columns:auto minmax(0,1fr);
  column-gap:clamp(16px,1.8vw,26px);
  padding:clamp(18px,1.8vw,24px) 0;
  border-bottom:1px solid var(--ifab-line-inv);}
.ifab-srv-n{font-family:var(--ifab-mono);font-size:clamp(12px,.85vw,12px);
  font-weight:500;letter-spacing:.16em;line-height:1.7;
  color:var(--ifab-accent-ink);}
.ifab-srv-step-in{display:block;}
.ifab-srv-step-t{display:block;font-family:var(--ifab-ff-head);font-weight:500;
  font-size:clamp(16.5px,1.35vw,19px);line-height:1.3;letter-spacing:-.008em;
  color:var(--ifab-fg-inv);}
.ifab-srv-step-d{display:block;margin-top:6px;
  font-size:clamp(14.5px,1.1vw,16px);line-height:1.6;color:var(--ifab-muted-inv);}

.ifab-srv-foot{display:flex;flex-wrap:wrap;gap:clamp(10px,1vw,14px);
  margin-top:clamp(32px,3.2vw,44px);}

/* ---------- sidebar: every service ---------- */
.ifab-srv-side-nav{background:#FFFFFF;border:1px solid var(--ifab-line-inv);
  border-radius:var(--ifab-r);padding:clamp(20px,2vw,26px);}
/* display:block because this is a span now, not a heading. */
.ifab-srv-side-h{display:block;margin:0 0 clamp(14px,1.4vw,18px);
  font-family:var(--ifab-mono);font-size:clamp(12px,.85vw,12px);font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ifab-accent-ink);}

.ifab-srv-side-l{list-style:none;margin:0;padding:0;}
.ifab-srv-side-i{list-style:none;margin:0;
  border-bottom:1px solid var(--ifab-line-inv);}
.ifab-srv-side-i:last-child{border-bottom:0;}
/* One property drives both the row padding and the length of the marker on the
   current item, so the two cannot drift apart when the spacing is tuned. */
.ifab-srv-side-l{--srv-row-y:clamp(15px,1.5vw,19px);}
.ifab-srv-side-a{display:block;padding:var(--srv-row-y) 0;text-decoration:none;
  font-size:clamp(14px,1.08vw,15.5px);line-height:1.45;color:var(--ifab-fg-inv);
  transition:color .2s ease,padding-left .2s ease;}
.ifab-srv-side-i:first-child .ifab-srv-side-a{padding-top:0;}
.ifab-srv-side-i:last-child .ifab-srv-side-a{padding-bottom:0;}
a.ifab-srv-side-a:hover,a.ifab-srv-side-a:focus-visible{
  color:var(--ifab-accent-ink);padding-left:5px;}
/* The page you are on is marked rather than linked, so the list says where you
   are as well as where you can go. A link to the current page is a dead click. */
.ifab-srv-side-i.is-on{position:relative;}
.ifab-srv-side-i.is-on .ifab-srv-side-a{color:var(--ifab-accent-ink);font-weight:500;}
.ifab-srv-side-i.is-on .ifab-srv-side-a::before{content:"";position:absolute;
  left:calc(clamp(20px,2vw,26px) * -1);
  top:var(--srv-row-y);bottom:var(--srv-row-y);
  width:2px;background:var(--ifab-accent-ink);}
.ifab-srv-side-i.is-on:first-child .ifab-srv-side-a::before{top:0;}
.ifab-srv-side-i.is-on:last-child .ifab-srv-side-a::before{bottom:0;}

/* ---------- sidebar: the call to action ---------- */
/* Photograph on ::before, scrim on ::after, content above both - the same
   construction as the footer and the quote band, so the site has one way of
   putting type over a picture rather than three.

   The scrim is heavier than it looks like it needs to be. Contrast has to hold
   against the brightest part of the composited image, not its average, and the
   sparks in this frame are close to white. */
.ifab-srv-side-cta{position:relative;isolation:isolate;overflow:hidden;
  border-radius:var(--ifab-r);border:1px solid var(--ifab-line-2);}
.ifab-srv-side-cta::before{content:"";position:absolute;inset:0;z-index:0;
  background:center/cover no-repeat;
  filter:grayscale(.4) contrast(1.05);}
.ifab-srv-side-cta.ifab-bg-in::before{
  background-image:url("../img/quote-bg.jpg");
  background-image:image-set(url("../img/quote-bg.webp") type("image/webp"),url("../img/quote-bg.jpg") type("image/jpeg"));}
.ifab-srv-side-cta::after{content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(160deg,
    rgba(8,8,8,.80) 0%,
    rgba(8,8,8,.76) 52%,
    rgba(8,8,8,.90) 100%);}
.ifab-srv-side-cta-in{position:relative;z-index:2;
  padding:clamp(22px,2.2vw,28px);}
.ifab-srv-side-cta-e{margin:0 0 10px;
  font-family:var(--ifab-mono);font-size:clamp(12px,.85vw,12px);font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ifab-accent-hi);}
.ifab-srv-side-cta-t{margin:0 0 10px;
  font-family:var(--ifab-ff-head);font-weight:500;letter-spacing:-.012em;
  line-height:1.25;color:var(--ifab-fg);font-size:clamp(19px,1.65vw,22px);}
.ifab-srv-side-cta-d{margin:0 0 clamp(18px,1.8vw,24px);
  font-size:clamp(13.5px,1.02vw,14.5px);line-height:1.55;color:#D7DCE1;}
.ifab-srv-side-cta-b{width:100%;justify-content:center;}

@media (prefers-reduced-motion:reduce){
  .ifab-srv-side-a{transition:none;}
}

/* ---------- service page: common questions ---------- */
/* Same construction as the FAQs on the home and About pages - a <details> per
   question, so the answers are in the page, findable with browser search, and
   working with no JavaScript. The colours are the light-panel set, because this
   list sits inside the article column rather than on the dark ground. */
.ifab-srv-faqs{border-top:1px solid var(--ifab-line-inv);}
.ifab-srv-faq{border-bottom:1px solid var(--ifab-line-inv);}
.ifab-srv-faq-q{position:relative;display:block;cursor:pointer;list-style:none;
  padding:clamp(16px,1.5vw,21px) 44px clamp(16px,1.5vw,21px) 0;
  font-family:var(--ifab-ff-head);font-weight:500;letter-spacing:-.006em;
  font-size:clamp(15.5px,1.25vw,17.5px);line-height:1.4;color:var(--ifab-fg-inv);
  transition:color .2s ease;}
/* Safari draws its own triangle on summary and ignores list-style there. */
.ifab-srv-faq-q::-webkit-details-marker{display:none;}
.ifab-srv-faq-q:hover{color:var(--ifab-accent-ink);}
.ifab-srv-faq-q:focus-visible{outline:2px solid var(--ifab-accent-ink);outline-offset:2px;}

/* A plus that becomes a minus. Two bars, and the upright one is the only thing
   that moves, so there is nothing to rotate and nothing to get stuck halfway. */
.ifab-srv-faq-q::before,
.ifab-srv-faq-q::after{content:"";position:absolute;right:6px;
  background:var(--ifab-accent-ink);}
.ifab-srv-faq-q::before{top:50%;width:15px;height:2px;margin-top:-1px;}
.ifab-srv-faq-q::after{top:50%;right:12.5px;width:2px;height:15px;margin-top:-7.5px;
  transition:transform .25s var(--ifab-ease),opacity .25s var(--ifab-ease);
  transform-origin:center;}
.ifab-srv-faq[open] .ifab-srv-faq-q::after{transform:scaleY(0);opacity:0;}
.ifab-srv-faq[open] .ifab-srv-faq-q{color:var(--ifab-accent-ink);}

.ifab-srv-faq-a{padding:0 44px clamp(18px,1.7vw,24px) 0;}
.ifab-srv-faq-a p{margin:0;max-width:64ch;
  font-size:clamp(14.5px,1.12vw,16px);line-height:1.65;color:var(--ifab-muted-inv);}

@media (prefers-reduced-motion:reduce){
  .ifab-srv-faq-q,
  .ifab-srv-faq-q::after{transition:none;}
}

/* ============================================================
   INDUSTRIES SERVED
   Alternating picture-and-text rows, one per sector.

   Deliberately not the card grid the home page uses for the same six sectors.
   That grid links here, and repeating it would make this page read as the same
   section again at a different URL. Rows give each sector room to say what the
   work actually is, which a card cannot.
   ============================================================ */

.ifab-indr-set{display:flex;flex-direction:column;
  gap:clamp(44px,5vw,88px);}

.ifab-indr{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(26px,3.4vw,60px);align-items:center;}
/* Every other row puts the picture on the right. Done by ordering the columns
   rather than by reordering the markup, so the reading order stays name then
   description whichever side the photograph is on. */
.ifab-indr-flip .ifab-indr-media{order:2;}
.ifab-indr-flip .ifab-indr-text{order:1;}
@media (max-width:860px){
  .ifab-indr{grid-template-columns:1fr;gap:clamp(20px,2.4vw,28px);}
  /* Stacked, the picture always leads: an alternating order reads as a mistake
     once the two columns become one. */
  .ifab-indr-flip .ifab-indr-media{order:0;}
  .ifab-indr-flip .ifab-indr-text{order:0;}
}

.ifab-indr-media{position:relative;border-radius:var(--ifab-r);overflow:hidden;
  border:1px solid var(--ifab-line-inv);}
.ifab-indr-img{display:block;width:100%;height:auto;aspect-ratio:16/10;
  object-fit:cover;filter:grayscale(.32) contrast(1.04);
  transition:filter .4s ease;}
.ifab-indr:hover .ifab-indr-img{filter:grayscale(.08) contrast(1.06);}
/* The photographs never fade in. The reveal is right for text, but a washed-out
   picture reads as a loading fault rather than as motion. */
.ifab-indr-media.ifab-rise{animation:none;opacity:1;transform:none;}

.ifab-indr-n{margin:0 0 10px;font-family:var(--ifab-mono);
  font-size:clamp(12px,.85vw,12px);line-height:1;font-weight:500;
  letter-spacing:.16em;color:var(--ifab-accent-ink);}
.ifab-indr-text .ifab-h3{margin:0 0 clamp(10px,1vw,14px);
  font-size:clamp(22px,2.1vw,28px);letter-spacing:-.014em;}
.ifab-indr-lede{margin:0 0 clamp(12px,1.2vw,16px);
  font-size:clamp(15.5px,1.25vw,17.5px);line-height:1.55;font-weight:400;
  color:var(--ifab-fg-inv);max-width:46ch;}
.ifab-indr-text .ifab-body{margin:0 0 clamp(18px,1.8vw,24px);max-width:52ch;}

.ifab-indr-list{list-style:none;margin:0;padding:0;display:flex;
  flex-direction:column;gap:clamp(9px,.9vw,12px);
  padding-top:clamp(16px,1.6vw,22px);
  border-top:1px solid var(--ifab-line-inv);}
.ifab-indr-li{list-style:none;margin:0;padding:0;
  display:flex;align-items:flex-start;gap:11px;
  font-size:clamp(14px,1.08vw,15.5px);line-height:1.5;color:var(--ifab-muted-inv);}
/* The same tick used everywhere else on the site, drawn in CSS so each item
   stays one plain line of editable text. */
.ifab-indr-li::before{content:"";width:14px;height:14px;flex:0 0 14px;margin-top:.3em;
  background-color:var(--ifab-accent-ink);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5l5 5 11-11'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5l5 5 11-11'/%3E%3C/svg%3E") center/contain no-repeat;}

@media (prefers-reduced-motion:reduce){
  .ifab-indr-img{transition:none;}
}

/* ---------- the service card, used by the Services listing ---------- */
/* Lost once already: these rules sat between the anchors of an unrelated edit
   to the sidebar and were cut with it, which left the listing as full-width
   images and underlined blue links. The markup never stopped using them. */
.ifab-srv-grid{display:grid;grid-template-columns:repeat(3,1fr);
  gap:clamp(18px,1.8vw,26px);}
@media (max-width:900px){.ifab-srv-grid{grid-template-columns:1fr 1fr;gap:18px;}}
@media (max-width:600px){.ifab-srv-grid{grid-template-columns:1fr;gap:16px;}}

.ifab-srv-card{display:flex;flex-direction:column;text-decoration:none;
  background:var(--ifab-ink-2);border:1px solid var(--ifab-line);
  border-radius:var(--ifab-r);overflow:hidden;
  transition:border-color .25s ease,transform .25s ease;}
.ifab-srv-card:hover,.ifab-srv-card:focus-visible{
  border-color:var(--ifab-line-2);transform:translateY(-3px);}
.ifab-light .ifab-srv-card{background:#FFFFFF;border-color:var(--ifab-line-inv);}

.ifab-srv-card-media{display:block;overflow:hidden;background:var(--ifab-ink-3);}
.ifab-srv-card-img{display:block;width:100%;height:auto;aspect-ratio:16/10;
  object-fit:cover;filter:grayscale(.35) contrast(1.04);
  transition:transform .4s ease,filter .4s ease;}
.ifab-srv-card:hover .ifab-srv-card-img{transform:scale(1.04);filter:grayscale(.1) contrast(1.06);}

.ifab-srv-card-in{display:flex;flex-direction:column;flex:1 1 auto;
  padding:clamp(20px,2vw,28px);}
.ifab-srv-card-t{display:block;font-family:var(--ifab-ff-head);font-weight:500;
  letter-spacing:-.008em;line-height:1.25;color:var(--ifab-fg);
  font-size:clamp(18px,1.5vw,21px);}
.ifab-light .ifab-srv-card-t{color:var(--ifab-fg-inv);}
.ifab-srv-card-d{display:block;margin-top:10px;
  font-size:clamp(14px,1.05vw,15.5px);line-height:1.55;color:var(--ifab-muted);}
.ifab-light .ifab-srv-card-d{color:var(--ifab-muted-inv);}
/* Pushed to the bottom, so the arrow line sits level across a row of cards
   whose summaries run to different lengths. */
.ifab-srv-card-go{display:inline-flex;align-items:center;gap:8px;
  margin-top:auto;padding-top:clamp(16px,1.6vw,22px);
  font-family:var(--ifab-mono);font-size:clamp(12px,.85vw,12px);
  letter-spacing:.14em;text-transform:uppercase;color:var(--ifab-accent);}
.ifab-light .ifab-srv-card-go{color:var(--ifab-accent-ink);}
.ifab-srv-card-go::after{content:"";width:15px;height:9px;
  background-color:currentColor;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 14' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 7h21M16 1l6 6-6 6'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 14' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 7h21M16 1l6 6-6 6'/%3E%3C/svg%3E") center/contain no-repeat;
  transition:transform .25s ease;}
.ifab-srv-card:hover .ifab-srv-card-go::after{transform:translateX(4px);}

/* ---------- the services listing closing CTA ---------- */
.ifab-srv-cta .ifab-abcta-btns{justify-content:center;}

@media (prefers-reduced-motion:reduce){
  .ifab-srv-card,.ifab-srv-card-img,.ifab-srv-card-go::after{transition:none;}
}

/* ---------- home: recent work, now that there are photographs ---------- */
/* These tiles were dashed placeholders reading "Project photo" while the client
   had none. The frame is kept - same grid, same ratio, same radius - so the
   section did not change shape when the pictures arrived, only its contents.
   The caption stays because it says what the work is, which the photograph on
   its own does not. */
.ifab-work-card{position:relative;margin:0;border-radius:var(--ifab-r);
  overflow:hidden;border:1px solid var(--ifab-line);background:var(--ifab-ink-2);}
.ifab-work-img{display:block;width:100%;height:auto;aspect-ratio:4/3;
  object-fit:cover;filter:grayscale(.3) contrast(1.04);
  transition:transform .45s var(--ifab-ease),filter .45s var(--ifab-ease);}
.ifab-work-card:hover .ifab-work-img{transform:scale(1.04);filter:grayscale(.05) contrast(1.06);}
/* The caption sits on the picture rather than under it, so a row of tiles keeps
   one clean bottom edge whatever the captions run to. */
.ifab-work-cap{position:absolute;left:0;right:0;bottom:0;
  padding:clamp(28px,3vw,40px) clamp(14px,1.4vw,18px) clamp(12px,1.2vw,16px);
  font-family:var(--ifab-mono);font-size:clamp(12px,.85vw,12px);
  letter-spacing:.12em;text-transform:uppercase;color:var(--ifab-fg);
  background:linear-gradient(180deg,rgba(8,8,8,0) 0%,rgba(8,8,8,.86) 62%,rgba(8,8,8,.94) 100%);}

@media (prefers-reduced-motion:reduce){
  .ifab-work-img{transition:none;}
}

/* ---------- single service: the featured image, leading the column ---------- */
/* Sized to the article column rather than run full width. A full-bleed band was
   tried and rejected; this keeps the picture on the same measure as the copy it
   introduces, and leaves the sidebar beside it untouched. */
.ifab-srv-lead-media{margin:0 0 clamp(28px,2.8vw,40px);
  border-radius:var(--ifab-r);overflow:hidden;
  border:1px solid var(--ifab-line-inv);background:var(--ifab-paper-2);}
.ifab-srv-lead-img{display:block;width:100%;height:auto;aspect-ratio:16/9;
  object-fit:cover;filter:grayscale(.28) contrast(1.04);}
/* Never faded in. The reveal suits text, but a washed-out photograph at the top
   of the page reads as a loading fault rather than as motion. */
.ifab-srv-lead-media.ifab-rise{animation:none;opacity:1;transform:none;}

/* The line that closes the projects gallery. Centred under the grid and held to
   a readable measure, so it reads as a note on the work above rather than as a
   caption belonging to whichever photograph happens to sit at the bottom of the
   last column. */
.ifab-gal-note{margin:clamp(28px,3vw,44px) auto 0;max-width:62ch;text-align:center;
  font-size:clamp(15px,1.05vw,17px);line-height:1.65;color:var(--ifab-muted);}
.ifab-light .ifab-gal-note{color:var(--ifab-muted-inv);}
.ifab-gal-note a{color:inherit;text-decoration:underline;
  text-underline-offset:3px;text-decoration-thickness:1px;}
.ifab-gal-note a:hover{color:var(--ifab-accent-ink);}


/* ============================================================
   GRAVITY FORMS ICONS
   The plugin draws its small glyphs from an icon font it fetches
   itself. This form shows two of them, so they are drawn as SVG
   masks instead and the font is never requested. See the note in
   the deploy scratch: it was also rendering the tick in Gravity
   Forms blue on a site with no blue in it.
   ============================================================ */

/* Nothing may reference the family, or the browser fetches the file for one
   glyph. Everything that would have used it is either replaced below or has
   its glyph blanked, so no character box is left showing. */
.gform_wrapper,.gform-theme--framework,.gform-theme--foundation{
  --gf-icon-font-family:inherit;}

.gform_wrapper input.gfield-choice-input::before,
.gform_wrapper .gform_drop_area::before{
  content:"" !important;
  font-family:inherit !important;
  background-color:currentColor;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
  /* The glyph used to give these their height. An empty string gives none, so
     the box collapsed to zero and the mask had nothing to paint into. */
  display:block;}

/* The tick, in the site's green rather than the plugin's blue. */
.gform_wrapper input.gfield-choice-input::before{
  width:14px;height:14px;
  color:var(--ifab-accent);
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 12.6l5.2 5.2L19.5 7'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 12.6l5.2 5.2L19.5 7'/%3E%3C/svg%3E");}

/* The drop area's arrow. The box is the full width of the field and the mask is
   square, so contain sizes it to the height and centres it. */
.gform_wrapper .gform_drop_area::before{
  height:36px;
  color:var(--ifab-accent);
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.2 18.4A4.6 4.6 0 0 1 6.6 9.3a6.1 6.1 0 0 1 11.6-.7 3.95 3.95 0 0 1-.2 9.8'/%3E%3Cpath d='M12 21.2V10.4'/%3E%3Cpath d='M8.4 14L12 10.4l3.6 3.6'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.2 18.4A4.6 4.6 0 0 1 6.6 9.3a6.1 6.1 0 0 1 11.6-.7 3.95 3.95 0 0 1-.2 9.8'/%3E%3Cpath d='M12 21.2V10.4'/%3E%3Cpath d='M8.4 14L12 10.4l3.6 3.6'/%3E%3C/svg%3E");}

/* Anything else the plugin would have drawn from the font: no glyph rather than
   an empty character box. The messages beside them are text and stay. */
.gform_wrapper .gform-icon:not(.gform_drop_area):not(.gfield-choice-input)::before{
  content:"" !important;font-family:inherit !important;}
