/* ============================================================
   enquad.en3.support — Homepage custom overrides (home.css)
   Loaded AFTER the Webflow CSS via the sandbox enqueue snippet.
   Layout-only. No !important on transform/opacity, so Webflow
   IX2 + GSAP animations stay intact.
   Tune the numbers below — each is one line.
   ============================================================ */

:root{
  --enq-logo-h: 44px;          /* logo height, desktop  (tune to taste) */
  --enq-logo-h-mobile: 34px;   /* logo height, mobile */
  --enq-pill-minw: 150px;      /* equal min-width for hero tag pills */
  --enq-btn-gap: 16px;         /* gap between "Let's Talk" and the arrow */
  --enq-svc-blur: 3px;         /* blur on the non-focused service rows */
  --enq-svc-dim: 0.5;          /* opacity of the non-focused service rows */
  --enq-svc-scale: 1.02;       /* how much the focused row pops out */
  --enq-clogo-h: 34px;         /* client logo height in the marquee */
  --enq-clogo-gap: 56px;       /* horizontal gap between client logos */
  --enq-clogo-dur: 20s;        /* one full marquee lap on phones (<= 479px) */
}

/* 1. LOGO ---------------------------------------------------- */
.navbar .navbar-brand img.logo{
  height: var(--enq-logo-h);
  width: auto;
  max-width: none;      /* parent is shrink-to-fit; 100% would clamp & crop */
  object-fit: contain;  /* beat any global img{object-fit:cover} */
}

/* 2. MENU BUTTON — pin the "Menu" pill to the navbar's right edge.
   Base CSS gives .content margin:auto on both sides (leftover from a
   3-item navbar), which CENTERED the pill mid-bar. margin-left:auto
   absorbs the free space so it sits flush right; the logo stays left. */
.container-navbar .content{
  margin-left: auto;
  margin-right: 0;
}

/* 3. HERO TAG PILLS — even width, centered text ------------- */
.hero-tag-wrap .hero-tag-btn{
  min-width: var(--enq-pill-minw);
  display: flex;
  justify-content: center;
  align-items: center;
  flex: 0 0 auto;            /* keep size in the sliding marquee row */
}
.hero-tag-wrap .hero-tag-btn .hero-tag-btn-text{
  text-align: center;
  white-space: nowrap;
}

/* 4. "LET'S TALK" + ARROW — one row, spaced ---------------- */
/* .btn ships as display:block, so the label stacked above the arrow. */
.hero-right .btn{
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--enq-btn-gap);
}

/* 5. SERVICES — hover to focus one row, blur the rest ------
   Hover-capable pointers only (no effect on touch / while scrolling).
   The pop-up images were removed at source in the Webflow IX2
   bundle (interactions a-44/a-45 emptied); this replaces them. */

/* The dead pop-up <img class=service-img> is still in the DOM: absolute,
   height:318px, opacity:0, offset UPWARD over the row above, pointer-events
   auto — so it was catching the cursor and you had to hover ABOVE a card to
   trigger it. Make it transparent to the pointer so hover hits the real card. */
.service-item-wrap-02 .service-img{ pointer-events: none; }

@media (hover: hover) and (pointer: fine){
  .service-item-wrap-02 .service-collection{
    transition: filter .18s ease, opacity .18s ease, transform .18s ease;
  }
  /* Blur/dim the OTHER rows, but ONLY while a card is genuinely hovered.
     :has() means nothing fires until the cursor is actually on a card
     (no whole-section flash, no dead-zone between rows). */
  .service-item-wrap-02:has(.service-collection:hover) .service-collection:not(:hover){
    filter: blur(var(--enq-svc-blur));
    opacity: var(--enq-svc-dim);
  }
  /* the row under the cursor: lifted and in front (stays sharp) */
  .service-item-wrap-02 .service-collection:hover{
    transform: scale(var(--enq-svc-scale));
    position: relative;
    z-index: 30;
  }
  .service-item-wrap-02:hover .service-collection:hover .service-collection-item-box{
    box-shadow: 0 20px 50px rgba(0,0,0,.28);
  }
}

/* 6. CLIENT LOGOS - marquee strip under the hero -----------
   Full-colour client logos (pre-trimmed to a uniform 120px master).
   One common height + even spacing; object-fit:contain beats any
   global img{object-fit:cover}; flex:0 0 auto stops them squashing
   in the Webflow marquee row. */
.patner-section .client-wrap{
  flex: 0 0 auto;              /* dont let the marquee copy shrink */
  width: max-content;          /* size each copy to its 7 logos so the two copies dont overlap (was pinned 720px -> overlap = "sticking") */
  justify-content: flex-start;
  align-items: center;
  gap: var(--enq-clogo-gap);   /* even space between logos */
}
.patner-section .client-img{
  height: var(--enq-clogo-h);
  width: auto;
  max-width: none;
  object-fit: contain;
  margin: 0;                  /* spacing now comes from the wrap gap */
  flex: 0 0 auto;
}

/* ============================================================
   MOBILE  (≤ 767px)
   ============================================================ */
@media screen and (max-width: 767px){
  .navbar .navbar-brand img.logo{ height: var(--enq-logo-h-mobile); }
  .hero-right .btn{ gap: 12px; justify-content: center; }
  .hero-tag-wrap .hero-tag-btn{ min-width: 120px; }
  .patner-section{ --enq-clogo-h: 26px; }
  .patner-section .client-img{ margin: 0 20px; }
  /* no focus/blur on small screens */
  .service-item-wrap-02 .service-collection{ filter: none !important; opacity: 1 !important; transform: none !important; }
}

/* 7. PERF - lighter service strip bg: AVIF 1.8KB replaces 247KB PNG (same image, url override; loads after Webflow CSS so it wins) */
.service-bg{ background-image: url(/wp-content/uploads/assets/bg/services-strip.avif); }


/* 6b. CLIENT LOGO OPTICAL BALANCE ---------------------------
   All logos share one image height, but heavy/dense marks read
   bigger. Measured ink density (opaque px / box): bitts 67%,
   en3 60%, berwick 45% vs ~30% for the rest. Shrink the heavy
   ones by a factor of the base height so perceived weight matches;
   calc(var * f) keeps it proportional on mobile too. */
.patner-section .client-img[src$="bitts.avif"]  { height: calc(var(--enq-clogo-h) * 0.76); }
.patner-section .client-img[src$="en3.avif"]    { height: calc(var(--enq-clogo-h) * 0.80); }
.patner-section .client-img[src$="berwick.avif"]{ height: calc(var(--enq-clogo-h) * 0.94); }

/* 6c. MARQUEE SEAM ----------------------------------------
   The two .client-wrap copies live in .ticket-wrap, whose base
   Webflow rule is justify-content:space-between + width:100%.
   Since each copy (7 logos) overflows the container, space-between
   pulls copy-2 back over copy-1 -> the seam (en3 -> bitts) OVERLAPPED
   by ~78px. Lay the copies back-to-back and give the seam the same
   gap as between logos so spacing is uniform all the way round. */
.patner-section .ticket-wrap{
  justify-content: flex-start;
  gap: var(--enq-clogo-gap);
}

/* 6d. RELEASE WRAP WIDTH CAP -------------------------------
   Base Webflow .client-wrap has max-width:1375px + min-width:100%,
   so width:max-content still cant grow past 1375 while 7 logos
   need ~1455 -> the last logo (en3) overflowed the wrap by ~80px
   and collided into copy-2. Release both caps so max-content wins. */
.patner-section .client-wrap{
  max-width: none;
  min-width: 0;
}


/* 7. HELP-SHAPE SIZE PIN ------------------------------------
   The help-shape images have NO explicit width/height, so they
   render at the source files intrinsic pixel size. The AVIFs were
   632x623 / 686x649; the PNG replacements are ~2784px -> they blow
   up. Pin the display box to the original AVIF footprint so render
   size is independent of source resolution. aspect-ratio+height:auto
   keeps proportional scaling when base max-width:100% caps width. */
.help-shape-1{ width:632px; height:auto; }
.help-shape-2{ width:686px; height:auto; }

/* 7b. HELP-SHAPE SOFTEN -> match target AVIF glow ----------
   Brand PNGs are solid/hard-edged/full-opacity; target is a faint
   radial glow. Fade + desaturate + blur to read as a soft glow. */
.help-shape-1, .help-shape-2{ opacity:0.45; filter:saturate(0.95) brightness(1.22) blur(16px); }


/* 8. SERVICE CARDS CLICKABLE --------------------------------
   Each .service-collection-item-box now has a first-child overlay
   <a.service-card-link> covering the whole card so the entire card
   is one click target to its service page. Box gets position:relative
   so the absolute overlay anchors to it; z-index sits above card art
   but the hover blur/scale (section 5) still works on the row. */
.service-collection-item-box{ position: relative; }
.service-card-link{ position:absolute; inset:0; z-index:6; cursor:pointer; display:block; }

/* 9. INSIGHTS TOP GAP -----------------------------------------
   Reference has a pricing-section above insights that supplies its
   top separation; insights itself is pt:0. We removed pricing, so
   insights.pt:0 left its title flush to the section top ("stuck up").
   Give it top padding matching the other content sections. */
.insights-section{ padding-top:100px; }
@media screen and (max-width:991px){ .insights-section{ padding-top:70px; } }
@media screen and (max-width:767px){ .insights-section{ padding-top:50px; } }


/* ===== 10. HERO SEO H1 (small keyword line above display headline) ===== */
.hero-heading-seo{
  display: inline-flex;
  align-items: center;
  gap: 14px;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 17px;
  line-height: 1.4;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #14181f;
  margin: 0 0 20px 0;
  max-width: 640px;
}
/* Dash: removed 2026-08-03, RESTORED same day at Preet's request.
   Tradeoff (measured): the 48px rule + 14px gap set the kicker TEXT at
   x=124.4 while the display lines start at x=62.4 -- so the dash, not the
   H1 text, is what lines up with 'Enquire with' / 'ENQUAD'. */
.hero-heading-seo::before{
  content: '';
  flex: 0 0 auto;
  width: 48px;
  height: 2px;
  background: #14181f;
}
@media screen and (max-width:767px){
  .hero-heading-seo{ font-size: 13px; letter-spacing: 0.12em; gap: 10px; margin-bottom: 12px; max-width: 320px; }
  .hero-heading-seo::before{ width: 26px; }
}


/* ===== 11. HERO DISPLAY HEADING -- ALL CAPS, zoomed-out scale =====
   Preet, 2026-09-04: fix the ENQUAD weight. This section used to say
   "restore bold" and set 700, which was honest when the site loaded no
   webfont and this line rendered in Arial, a face with a real bold. Since
   the sitewide font shipped it is Instrument Serif, and Google ships that
   at 400 ONLY -- so 700 was the browser smearing the regular outline to
   fake a bold. There is no heavier Instrument Serif to load: 400 is the
   whole family, and it is also what the line above it already uses. */
.hero-heading-wrap .hero-heading._03{
  font-size: clamp(40px, 9vw, 108px);
  line-height: 0.98;
  font-weight: 400;
  text-transform: none;
  letter-spacing: -0.03em;
  margin: 0;
}
.hero-heading-wrap .hero-heading:not(._03){
  font-size: clamp(58px, 13.5vw, 160px);
  line-height: 0.98;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: -0.04em;
  margin: 0;
}

/* ===== 12. HERO READABILITY SCRIM (light veil behind text, fades to right) ===== */
.hero-video-bg::after{
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(90deg, rgba(255,255,255,0.72) 0%, rgba(255,255,255,0.5) 28%, rgba(255,255,255,0.14) 54%, rgba(255,255,255,0) 72%);
}


/* ===== 13. HERO VERTICAL FIT (lift bottom-anchored content into view + zoom-out breathing room) ===== */
@media screen and (min-width:992px){
  .hero-row{ padding-bottom: 104px; }
}


/* ===== 14. STICKY HEADER -- MOVED OUT OF CSS ----------------
   The header is sticky on ALL 8 pages now, and it is done in
   Elementor, not here: header template #358, root container
   d1e197d, Advanced > Motion Effects > Sticky = Top (sticky_on
   desktop/tablet/mobile, offset 0), plus z-index 999, Background
   Classic rgba(255,255,255,0.88) and Box Shadow 0 1px 24px
   rgba(20,24,31,0.06) on the same container.
   What used to live here was .elementor-10 > .elementor-element-fcd78a7
   { position:sticky }, i.e. the HOME PAGE WRAPPER -- which is the whole
   reason the bar stuck on the home page and nowhere else. Putting it on
   the template container instead makes one edit cover every page.
   Only backdrop-filter stayed in CSS (no Elementor control); it moved to
   global.css section 8, together with the .pagtitle-shap blob hide that
   used to be section 17 below.
   Frosted at ALL scroll positions, deliberately: a scroll-driven fade-in
   was tried and removed (the ScrollTimeline resolved inactive on this
   page, currentTime null, so the bar sat on its base style anyway).
   Elementor can do that natively via sticky_effects_offset if it is ever
   wanted again. */


/* ===== 15. HERO CTA REMOVED ---------------------------------
   The hero label + arrow button are dropped; the same CTA already
   lives in the header menu and the contact band. .hero-row is a
   centred flex row, so hiding .hero-right alone would re-centre the
   80%-wide .hero-left and shove the headline ~130px right -- give
   the left column the full row instead. */
.hero-right{ display: none; }
@media screen and (min-width: 992px){
  .hero-row{ justify-content: flex-start; }
  .hero-left{ width: 100%; }
}


/* ===== 16. WHY-CHOOSE-US HEADING ALIGNMENT ------------------
   The heading is split across two <h4 class=subtitle _04> lines.
   Base CSS gives ._04 text-align:left + max-width:579px while the
   parent .section-title-box is a column flex with align-items:center
   -- so each line shrink-wraps and is centred on its own, landing
   their left edges 77px apart, and line 2 wraps mid-phrase at the
   579px cap. Give the box one measure, stretch both lines to it and
   centre the text, matching Who We Help / Insights. */
.choose-section .section-title-box{
  align-items: stretch;
  width: 100%;
  max-width: 680px;
}
.choose-section .section-title-box .subtitle._04{
  max-width: none;
  text-align: center;
}
@media screen and (max-width: 991px){
  .choose-section .section-title-box{ max-width: 560px; }
}
@media screen and (max-width: 767px){
  .choose-section .section-title-box{ max-width: 420px; }
}
@media screen and (max-width: 479px){
  .choose-section .section-title-box{ max-width: 320px; }
}


/* 17. Navbar decoration blobs -- moved to global.css section 8.
   .pagtitle-shap / .pagtitle-shap-2 had to be hidden site-wide once
   every page got a sticky header, not just this one. */


/* ---------------------------------------------------------------
   18. CLIENT LOGO MARQUEE ON PHONES (<= 479px)
   Dead below 480px for two independent reasons:
     a) the Webflow IX2 loop that drives it (event e-11 -> action
        list a-11 "Ticket": CHILDREN .client-wrap, x 0 -> -100% over
        20s) is registered for mediaQueries main/medium/small only.
        "tiny" (<= 479px) is absent, so it never starts on a phone.
        Verified live at 390px: no a-11 instance in the ix2 store.
     b) the Webflow sheet hides the second copy at that width
        (.client-wrap._03{display:none}), and a marquee needs two
        identical tracks to loop.
   So one 1472px track sat inside a 350px overflow:hidden mask with
   nothing moving it: only the first ~3 logos were ever reachable,
   the other 4 were permanently clipped.

   Fix is CSS-only and scoped to <= 479px so it cannot collide with
   the IX2 loop that still drives 480px and up. Compositor-driven
   transform, no JS, no extra requests.

   Travel is -100% MINUS one gap, not just -100%: the two copies are
   separated by .ticket-wrap column-gap (56px, set in 6c), so moving
   exactly one copy width would land copy 2 one gap right of where
   copy 1 started and hitch on every lap. IX2 uses a bare -100% and
   has that hitch; this does not.
   --------------------------------------------------------------- */
@media screen and (max-width: 479px){
  .patner-section .client-wrap._03{ display: flex; }   /* second track back */
  .patner-section .client-wrap{
    animation: enq-clogo-marquee var(--enq-clogo-dur) linear infinite;
    will-change: transform;
  }
}

@keyframes enq-clogo-marquee{
  from{ transform: translate3d(0, 0, 0); }
  to  { transform: translate3d(calc(-100% - var(--enq-clogo-gap)), 0, 0); }
}

/* Honour the OS reduce-motion switch: park the strip. Both copies
   stay laid out, so it degrades to the static row it was before. */
@media screen and (max-width: 479px) and (prefers-reduced-motion: reduce){
  .patner-section .client-wrap{ animation: none; }
}

/* -- S19  "Ready to start your project" image fan (.project-section) --------
   Brief 1: bigger centre card, and the two side cards revealed COMPLETELY (each
   used to sit 169px under the centre at vw 1440 -- exactly half of it hidden).
   Brief 2: and KEEP the scroll animation, which the first pass killed.

   THE ANIMATION. IX2 action list a-18 "Project Secroll" (event e-125,
   SCROLLING_IN_VIEW, mediaQueries ["main"] only, i.e. >=992) drives:
       scroll 10%   ._01 x = -100px    ._03 x = +100px
       scroll 80%   ._01 x = -190px    ._03 x = +190px
   so the side cards DRIFT 90px further out as the pinned section scrolls, and
   the -100px baseline is written INLINE as the initial state at page load.

   *** Do NOT read a-18 as inert because actionItemGroups is empty. ***
   A continuous (scroll-driven) list keeps its keyframes in
   continuousParameterGroups; actionItemGroups is for timed lists and is empty
   here by design. The first pass misread that field, concluded nothing animated
   these cards, and used transform:none !important to drop the inline baseline
   -- which also outranks every frame IX2 writes, so the animation froze solid.

   HOW THE BASELINE IS CANCELLED NOW. The translate property is a separate
   longhand that composes with transform (translate is applied first), so IX2
   keeps sole ownership of transform and nothing here needs !important:
       ._01 translate:  100px  vs IX2 -100px  -> net 0 at scroll 10%
       ._03 translate: -100px  vs IX2 +100px  -> net 0 at scroll 10%
   Verified live at vw 1000: layout x 204.3, rendered x 104.3 at keyframe 10.
   So the fan sits fully revealed the moment the section is entered, and the
   remaining 90px reads as an outward fan-out -- the original design intent.

   WHY THE >=992 FAN IS TRIMMED RELATIVE TO THE 768-991 ONE. That leftover 90px
   of drift is FIXED px sitting on a percentage layout, so it eats page margin
   that does not scale with the viewport. At vw 1000 the untrimmed fan leaves
   only 79px beside it -- 11px short. 21% / 2.5% / 32.5% leaves 104.3px there,
   so the drift lands with 14.3px to spare, and 88.4px to spare at vw 1440.
   Measured at BOTH keyframes, horizontal overflow 0.

   PERCENTAGES, NOT PX -- still the whole trick. Everything in the stock fan is
   fixed px (300px sides, a -250px tuck, the +/-100px translate), so the moment
   the sides come out the composition is a FIXED ~1160px and punches straight
   through the container on smaller desktops (measured: 75px of page overflow at
   vw 1024 on a px-based attempt). .project-item-wrap IS the .container
   (width:100%, max-width:1300px), so percentages of it scale the whole fan.

   ROTATION BLEED SETS THE MINIMUM GAP. .project-img-1/3 carry
   rotate(+/-3deg) skew(-/+3deg), which renders each side image 1.1268x its own
   box width -- ~17px past each edge at a 273px box. gap 2.5% = 32.5px at the
   1300px container, so 15.2px of that is real daylight. Any spacing math on
   this fan must budget the bleed, never the box.

   WIDTH GOES ON .project-item._02, NOT ON .project-img-2. The centre card has no
   width in any sheet, so it is shrink-to-fit from its image; a percentage on the
   IMG would resolve against a parent whose width depends on that same image. As
   a flex item, ._02 resolves against the wrap. Deterministic.

   <=991 KEEPS transform:none. a-18 is registered for "main" only, so no frame is
   ever written below 992 -- but the inline baseline survives a desktop-to-narrow
   resize, so it is killed explicitly there. Nothing to animate, nothing to break.

   Scoped >=768: Webflow stacks the fan into a column at <=767 (flex-flow:column,
   gap:20px, width:100% on every part) and that must keep winning. gap:2.8% would
   also set ROW gap, which is the other reason it stays >=768.

   VERTICAL COST, accepted: the centre is portrait 1400x1902, so extra width buys
   height -- 385 -> 422.5px wide is 520 -> 571px tall. .project-stiky is
   position:sticky top:0 and was already 996px, taller than a laptop viewport, so
   its bottom is cropped mid-pin; that crop goes 96px -> 147px at vh 900 (it was
   156px on the wider first pass). It resolves at the end of the pinned range,
   exactly as before. The only reclaimable space is the 90px gap between the
   title block and the fan; left alone (rhythm matches the Webflow reference).
   -------------------------------------------------------------------------- */
@media screen and (min-width: 768px){
  .project-item-wrap{ gap: 2.8%; }

  .project-item._01,
  .project-item._03{
    max-width: 23%;
    margin-left: 0;
    margin-right: 0;
  }

  .project-item._02{ width: 33%; }

  .project-img-2{ width: 100%; max-width: 100%; height: auto; }
}

/* a-18 never runs below 992 -- drop any inline baseline left behind by a resize. */
@media screen and (max-width: 991px){
  .project-item._01,
  .project-item._03{ transform: none !important; }
}

/* 992+: a-18 is live. Cancel its -100px baseline with translate, and trim the
   fan so the remaining 90px of outward drift fits without overflow. */
@media screen and (min-width: 992px){
  .project-item-wrap{ gap: 2.5%; }

  .project-item._01,
  .project-item._03{ max-width: 21%; }

  .project-item._02{ width: 32.5%; }

  .project-item._01{ translate:  100px 0; }
  .project-item._03{ translate: -100px 0; }
}


/* =========================================================================
   SECTION 20  PORTFOLIO TEASER CATEGORY PILLS
   .teaser-section .hero-tag-btn._03  (9 pills, 3 per project card)
   -------------------------------------------------------------------------
   SYMPTOM: the pills were not a set. Card 02's row measured 47px tall against
   36px on cards 01 and 03, and one of its labels sat in a different size and
   weight. Widths and gaps also tracked label length.

   CAUSE 1 (markup, fixed in _elementor_data on 2026-08-04, not here): the
   "organic growth" pill had no <p class="hero-tag-btn-text _02"> inside it,
   only a bare text node. Bare text inherits the section body type
   (18px / 400 / 27px line-height) instead of the pill type (16px / 600 /
   16px), which made that pill 27+20 = 47px tall. The row is a flex container
   at the default align-items:stretch, so BOTH siblings stretched to 47px as
   well. One missing tag moved an entire row. Also stripped two stray &nbsp;
   and normalised label case (organic growth -> Organic Growth, Brand
   building -> Brand Building).

   CAUSE 2 (CSS, fixed below): pill height was derived entirely from the text
   line box, and .hero-tag-btn-wrap is really a leftover marquee track:
   min-width/max-width 100%, overflow:hidden, flex-wrap:nowrap, children at
   flex-shrink 1. That plumbing belongs to IX2 list a-7 "Tag Scroll"
   (TRANSFORM_MOVE x 0 -> 100% over 20s, looping) which is bound by event e-7
   to a specific hero element id that does not exist on this page. Verified
   live: 0 a-7 instances in the ix2 store. So on the teaser it does nothing
   except make pills squeeze and then clip as labels get longer.

   FIX: give the pill its own box (fixed min-height, centred content) and its
   own type, so height stops tracking the text; stop pills shrinking; let the
   row wrap instead of squeezing. Typography is declared on the PILL, not only
   on the inner <p>, so a pill authored without the <p> wrapper still renders
   identically and Cause 1 cannot come back.

   Left alone deliberately: the wrap's overflow:hidden and min/max-width:100%
   (harmless once the row wraps), and justify-content, so the template's own
   centring at <=767 still applies.
   ========================================================================= */

.teaser-section .hero-tag-btn-wrap._03{
  flex-wrap: wrap;           /* long labels move to a 2nd line, never squeeze */
  align-items: flex-start;   /* a rogue tall pill can no longer inflate a row */
  align-content: flex-start;
  /* row-gap intentionally NOT set: the template's own grid-row-gap already
     tracks the column gap (10px, 20px at <=479), so wrapped rows stay
     symmetric with the horizontal spacing at every breakpoint. */
}

.teaser-section .hero-tag-btn._03{
  flex: 0 0 auto;            /* was 0 1 auto: pills shrank as text grew */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 36px;          /* the height cards 01 and 03 already had */
  padding: 0 16px;           /* vertical padding replaced by min-height */
  max-width: 100%;
  white-space: nowrap;

  font-family: var(--urbanist);
  color: var(--black);
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
}

.teaser-section .hero-tag-btn._03 .hero-tag-btn-text{
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
  color: inherit;
  margin: 0;
  min-width: 0;              /* an over-long label ellipsises inside its own */
  overflow: hidden;          /* pill instead of blowing out the text column  */
  text-overflow: ellipsis;
}


/* =============================================================================
   21. PORTFOLIO TEASER - TAG PILLS SLIDE INSTEAD OF WRAPPING     (2026-08-04)

   Section 20 above made an over-long tag row WRAP to a second line. The ask
   changed: more tags are coming, and they should slide as a loop instead.

   Structure is built by custom-js/home.js at runtime (two identical tracks in
   the wrap) so the Elementor markup keeps ONE copy of each pill - see that file
   for why, and for the fits/overflows and reduced-motion rules.

   Section 20 stays exactly as it is. Its flex-wrap:wrap is now the no-JS
   fallback: without this marquee an over-long row still shows every tag on a
   second line rather than clipping it behind overflow:hidden.
   ========================================================================== */

/* THE RULE THAT MAKES [does it overflow?] ANSWERABLE AT ALL - do not drop it.
   .teaser-wrap (the card's whole text column) is shrink-to-fit, so it sizes to
   its widest child. Without this, adding tags does not overflow the row, it
   WIDENS THE CARD - up to whatever space is going - and the marquee can never
   trigger. Measured: 6 pills grew card 01's column from 426 to 445px and the
   row still counted as fitting. Size containment takes the pills out of the
   parent's intrinsic width, so the row is 100% of a column sized by the
   headline / subtitle / body copy, and extra tags overflow instead of stretch.

   Zero visual change at today's 3 tags per card - verified identical widths
   before and after (426.09 / 425 / 445.3, all three driven by their subtitle).
   It also stops a single long tag from widening a card, which section 20 could
   not prevent. Falls back to the old stretch-then-wrap on pre-2022 Safari. */
.teaser-section .hero-tag-btn-wrap._03{
  contain: inline-size;
}
.enq-tagmq-track{
  display: flex;
  flex: 0 0 auto;                       /* must NOT shrink to the wrap, or there is
                                           nothing to scroll */
  flex-wrap: nowrap;
  align-items: center;
  column-gap: var(--enq-tagmq-gap, 10px);   /* set inline by the JS from the wrap's
                                               own computed gap: 10px, 20px at <=479 */
}

.enq-tagmq-track .hero-tag-btn._03{
  max-width: none;    /* section 20 caps pills at 100%, which resolves against a
                         shrink-to-fit track here and can squeeze the last one.
                         Inside a marquee a pill always sizes to its own label. */
}

.teaser-section .hero-tag-btn-wrap._03.enq-tagmq-on{
  flex-wrap: nowrap;          /* only while the loop is live */
  justify-content: flex-start;/* the template centres this row at <=767; centring an
                                 OVERFLOWING row pushes its left edge off the card and
                                 the first tag is never seen */
  overflow: hidden;           /* the template already sets this - restated so the block
                                 stands alone if that rule is ever touched */
}

.enq-tagmq-on .enq-tagmq-track{
  animation-name: enq-tagmq-slide;
  animation-duration: var(--enq-tagmq-dur, 18s);   /* inline per row: constant px/s, so
                                                      a long row is not a fast row */
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform;
}

.enq-tagmq-on:hover .enq-tagmq-track{ animation-play-state: paused; }

/* Travel is one track PLUS one gap. A bare -100% lands copy 2 one gap to the
   right of copy 1's start and the loop visibly hitches every lap - the same bug
   IX2 ships in the client-logo marquee (section 18). */
@keyframes enq-tagmq-slide{
  from{ transform: translate3d(0, 0, 0); }
  to  { transform: translate3d(calc(-100% - var(--enq-tagmq-gap, 10px)), 0, 0); }
}

/* Belt and braces: the JS tears the marquee down entirely for reduced motion
   (pausing it would clip the later tags out of reach). This only matters if the
   class survives without the JS. */
@media (prefers-reduced-motion: reduce){
  .enq-tagmq-on .enq-tagmq-track{ animation: none; }
}


/* ===== 18. HERO HEADING BLOCK -- ONE LEFT AXIS AT 991px AND BELOW ------
   (2026-08-04) Measured bug at 390px: global.css (max-width:991px) gives
   .hero-left align-items:flex-start, so .hero-heading-wrap SHRINK-WRAPS
   instead of filling. Its width then came from the widest child -- the
   kicker, capped at max-width:320px in section 10 -- so the wrap was 320px
   inside a 350px container, pinned left: a 20px gutter on the left and 50px
   on the right. Webflow still sets .hero-heading{text-align:center} at
   max-width:991px, so the two display lines centred INSIDE that off-centre
   box (axis x=180 against a viewport centre of x=195) while the kicker
   stayed left-packed. Three lines, three axes.
   Preet chose flush-left, to match the 992px-and-up composition in section
   15, so this is the mobile/tablet counterpart of that section: give the
   wrap the full column and put every line on the same left edge.
   Verified 360 / 390 / 900: every box runs 20 to (vw minus 20), ink starts
   at x=20 on both display lines, the dash leads the kicker at x=20 with its
   text hanging at x=56, and scrollWidth equals clientWidth (no overflow).
   The 320px kicker cap in section 10 is inert now -- the wrap is identical
   with and without it -- so it was left alone. */
@media screen and (max-width:991px){
  .hero-heading-wrap{ width: 100%; text-align: left; }
  .hero-heading-wrap .hero-heading,
  .hero-heading-wrap .hero-heading._03{ text-align: left; }
}


/* ===== 19. HERO BLOCK BOTTOM-ANCHORED AT 991px AND BELOW ------------
   (2026-08-04) Webflow zeroes .hero-row padding-bottom at 991px and below
   and switches it to justify-content:center, so the heading block floated
   in the middle of the hero: measured at 390px it sat 127.8px above the
   hero bottom edge and 131.8px below the sticky header. Preet asked for it
   near the bottom edge, which is also what the desktop composition does
   (Webflow base is align-items:flex-end + padding-bottom:65px; section 13
   raises that to 104px at 992px and up).
   flex-end plus a 48px inset is the whole knob -- 48px is the number to
   change if it wants to sit higher or lower, and it is measured from the
   hero bottom edge, so it holds at both mobile hero heights (500px at
   479px and below, 650px from 480 to 991px).
   Left alone deliberately: the readability scrim in section 12 is a
   horizontal gradient (white at the left edge, transparent by 72%), so it
   veils the text column at ANY vertical position -- moving the block down
   does not cost contrast. */
@media screen and (max-width:991px){
  .hero-row{ justify-content: flex-end; padding-bottom: 48px; }
}

/* =========================================================================
   SECTION 21  CTA SECTION -- IMAGE FAN REMOVED (.project-section)
   -------------------------------------------------------------------------
   2026-08-06, at Preet request: the three project-fan images (cta-left /
   cta-main / cta-right) were removed from _elementor_data on page 10, so
   .project-item-wrap and all three .project-item cards no longer exist.
   Rollback row: postmeta _elementor_data_bak_20260806_ctafan
   (45942 B, md5 4514e59fa72899f49d68ad03b299274c).

   WHY THIS RULE IS REQUIRED, NOT COSMETIC.
   The section is a scroll-pinned block. webflow-core.css gives it, at
   >=992 only:
       .project-section { height: 310vh; }
       .project-wrap    { height: 300vh; padding-bottom: 100px; }
       .project-stiky   { position: sticky; top: 0; }
   Those three viewport-heights of scroll runway exist so the title stays
   pinned while IX2 list a-18 fans the side cards outward. With the fan
   gone there is nothing to reveal, so the runway renders as roughly three
   empty screens of white under a pinned heading. Collapsing to auto turns
   the block back into an ordinary short CTA: heading, paragraph, button.
   <=991 already sets height:auto in webflow-core.css, so it needs nothing.

   .project-stiky keeps position:sticky and is now inert -- with the wrap
   at auto height there is no scroll range for it to stick through. Left
   in place so restoring the fan is a single _elementor_data rollback with
   no CSS to undo.

   SECTION 19 ABOVE IS NOW DEAD but deliberately kept: it is the only
   record of how the fan was tuned (percentage widths, the translate
   longhand trick, the rotation bleed) and it costs nothing now that the
   elements are gone. If the fan is never coming back, 19 can be deleted
   wholesale along with this note.
   ========================================================================= */
@media screen and (min-width: 992px){
  .project-section{ height: auto; }
  .project-wrap{ height: auto; padding-bottom: 0; }
}


/* =========================================================================
   SECTION 22  TESTIMONIALS -- SHORTER PINNED SCROLL RUNWAY   (2026-08-06)
   -------------------------------------------------------------------------
   Requested by Preet: the testimonials block needed about three full
   screens of scrolling before its cards finished moving.

   Webflow builds that section as a 300vh spacer (.testimonials-box) with
   .testimonials-wrap sticky at top:100px, so the content pins while you
   scroll three viewport heights. IX2 a-24 (Testimonials Scroll) is a
   SCROLL_PROGRESS list whose range is section height + one viewport and
   which finishes at keyframe 70 -- measured 2,156 px of scroll at vh 720.

   Halving the spacer halves the pin and compresses the motion with it:
   same animation, same end state, roughly half the scrolling.

   MUST STAY SCOPED >= 992. Webflow already sets .testimonials-box
   { height:auto } at <= 991, and home.css loads after webflow-core.css,
   so an unscoped 150vh here would WIN against that rule and reintroduce
   a pin on tablet and mobile where there is none today.

   Reverse = delete this block; the Webflow 300vh takes over again.
   ========================================================================= */
@media screen and (min-width: 992px){
  .testimonials-box{ height: 150vh; }
}

/* ---------------------------------------------------------------
   23. TRUST LINE / CLIENT-LOGO SPLIT  (>= 992px)
   Brief (Preet, 2026-08-07, marked on a screenshot with a vertical
   line at ~20% of the container): the two trust lines must sit
   ENTIRELY left of that line, and the logo marquee must start there
   instead of at 41%.

   Webflow ships .patner-wrap as a flex row of
     .text-wrapper.left-contant-wrap { width:70%  }
     .ticket-wrap                    { width:100% }
   Both are flex:0 1 auto, so 170% of basis shrinks proportionally to
   41.18% / 58.82%: at a 1300px container the text column measured
   535.3px and the mask 764.7px, and the paragraphs (394.2 / 421.9px,
   capped by .patner-exar{max-width:447px}) ran ~160px past the mark.

   Pinned to a fixed measure, not a percentage: the copy needs a
   stable line length, and every px a narrower desktop takes off the
   container should come off the marquee, not the text. 260px = the
   marked line at the measured 1300px container. Each paragraph then
   sets as two balanced lines (~200-215px) and the strip gains 275px,
   about 1.5 more logos in view.

   MUST stay scoped >= 992px: Webflow cancels this layout at <=991
   (.patner-wrap{flex-flow:column} + .left-contant-wrap{width:100%})
   and home.css loads after webflow-core.css at higher specificity,
   so an unscoped rule here would beat that cancel and pin a 260px
   column on tablet and phone too.

   Knob: --enq-trust-col, one value. Reverse = delete this block.
   --------------------------------------------------------------- */
@media screen and (min-width: 992px){
  .patner-section{ --enq-trust-col: 260px; }

  .patner-section .text-wrapper.left-contant-wrap{
    flex: 0 0 var(--enq-trust-col);
    width: var(--enq-trust-col);
  }

  /* .text-wrapper is a column flex with align-items:flex-start, so a
     block child can shrink-wrap to its widest descendant instead of
     filling. Pin it, or the wrap width becomes unpredictable. */
  .patner-section .left-contant-wrap .letter-animation{ width: 100%; }

  /* Webflow caps each line at 447px, now WIDER than the column, so
     without this the lines would never re-wrap. */
  .patner-section .patner-exar{ max-width: 100%; }

  /* mask takes the remainder; min-width:0 lets a flex item shrink
     below its content width, which an overflow:hidden marquee needs. */
  .patner-section .ticket-wrap{ flex: 1 1 auto; min-width: 0; }
}

/* ---------------------------------------------------------------
   23b. TRUST LINE - BALANCED WRAP  (all widths)
   The copy is one paragraph in two spans (bold + rest) and the
   greedy line breaker fills line 1 to the last word that fits:
   at the 260px column that is `Trusted by teams in education,`
   (255.2px) leaving an ugly 95.1px orphan `retail & tech`.
   text-wrap:balance re-splits it evenly -> 167.1 / 183.1px.
   Measured, not assumed; `pretty` does nothing here (it only
   protects the LAST line, and 2 lines is already its minimum).

   Deliberately NOT scoped to a breakpoint: it is a 2-line label at
   every width and balancing helps at 350px too. Browsers cap
   balancing at a few lines and simply ignore it past that, and an
   engine without support falls back to the greedy break above -
   so there is no width where this can do harm.
   Reverse = delete this block.
   --------------------------------------------------------------- */
.patner-section .patner-exar{ text-wrap: balance; }

/* ==========================================================================
   SECTION 24  PORTFOLIO TEASER - STICKY PHOTO POSITION + CARD-LOCKED CROSSFADE
   .teaser-column-02 .p-teaser-img-wrap  /  .teaser-img._1 ._2 ._3   (>=992)
   Pairs with the "portfolio teaser photo lock" block in custom-js/home.js.
   --------------------------------------------------------------------------
   SYMPTOM (Preet, 2026-08-08): the photos are not centred, they stick to the
   top as you scroll, and the next picture starts fading in the moment you
   begin scrolling onto the next project.

   CAUSE 1 - POSITION. webflow-core.css pins .p-teaser-img-wrap at
   position:sticky; top:100px with a fixed height:520px. The sticky header is
   84px, so at 1440x900 the photo occupies 100-620px of a 900px viewport: 16px
   of clearance under the header and 280px of dead space below it. It reads as
   stuck to the top, not held in the middle.

   CAUSE 2 - TIMING. The crossfade was IX2 list a-43 (event e-662,
   SCROLLING_IN_VIEW, smoothing 95) driven continuously off the section's own
   scroll progress. Keyframes: 0 (._2=0, ._3=0), 33 (._1=1), 53 (._1=0),
   58 (._2=1), 76 (._2=0), 80 (._3=1), 100 (._3=1, a no-op tail). Because ._2
   and ._3 had no HOLD keyframe after 0, BOTH RAMPED LINEARLY UP FROM PROGRESS
   0. Measured against the real geometry (section 1632px, vh 900, continuous
   range = height + vh = 2532px; the three cards sit dead-centre at progress
   28.9 / 50.0 / 71.1):

       progress 50  - card 02 centred ->  ._1 0.15   ._2 0.86   ._3 0.63
       progress 71  - card 03 centred ->             ._2 0.27   ._3 0.89

   So at the exact moment a project was centred you were looking at three
   stacked photos at once, and the next one was already most of the way in
   before the current one had begun to leave. No photo was ever clean, which is
   both halves of the report.

   FIX. e-662 is retired by renaming .teaser-section's data-w-id to
   data-enq-w-id in _elementor_data - the same reversible trick as the header
   dropdown - so IX2 stops writing inline opacity/transform onto .teaser-img
   and the cascade owns them again. home.js then marks exactly one photo
   .enq-teaser-on: the one belonging to the card nearest the viewport centre.
   The dissolve is a plain CSS transition, so it is TIME-based rather than
   scroll-linked - nothing moves until you cross the half-way point between two
   projects, then it swaps in 0.5s and holds still.

   The blanket opacity:0 is gated behind [data-enq-teaser="on"], which only
   home.js ever sets. With JS off this file is inert and webflow-core's own
   baseline stands - ._1 opaque, ._2/._3 transparent - which is the correct
   static first frame.

   Scoped >=992 because webflow-core hides .p-teaser-img-wrap AND the whole
   .teaser-column-02 at <=991, where each project shows its own
   .teaser-img-tablet-down copy instead (and where ._2/._3 are forced back to
   opacity:1). Nothing in here may reach those.
   ========================================================================== */

@media screen and (min-width: 992px) {

  .teaser-column-02 .p-teaser-img-wrap {
    /* Mirrors of numbers that live elsewhere - if either moves, move it here:
       --enq-teaser-h   = webflow-core.css .p-teaser-img-wrap{height:520px}
       --enq-teaser-hdr = measured height of the sticky header at >=992. */
    --enq-teaser-h: 520px;
    --enq-teaser-hdr: 84px;

    /* Centre the frame in the band BELOW the header instead of jamming it
       under it: at vh 900 this resolves to 232px (was 100px), leaving 148px of
       air above and below. The max() is the short-viewport guard - under about
       604px of height there is no room to centre and the frame would slide up
       behind the header, so it falls back to clearing it by 12px. */
    top: max(
      calc(var(--enq-teaser-hdr) + 12px),
      calc(var(--enq-teaser-hdr) + (100vh - var(--enq-teaser-hdr) - var(--enq-teaser-h)) / 2)
    );
  }

  /* Both rules are gated on the attribute so they cannot fire before home.js
     has picked a winner - otherwise the frame would blank for a frame. */
  .p-teaser-img-wrap[data-enq-teaser="on"] .teaser-img {
    opacity: 0;
    transform: scale(1.04);     /* the outgoing photo eases back as it leaves */
    transition: opacity .5s ease, transform .8s ease;
  }

  .p-teaser-img-wrap[data-enq-teaser="on"] .teaser-img.enq-teaser-on {
    opacity: 1;
    transform: scale(1);
  }
}

/* The cross-dissolve is decoration. With Reduce Motion the swap is instant and
   nothing scales - the photo still tracks the project, it just cuts. */
@media screen and (min-width: 992px) and (prefers-reduced-motion: reduce) {

  .p-teaser-img-wrap[data-enq-teaser="on"] .teaser-img,
  .p-teaser-img-wrap[data-enq-teaser="on"] .teaser-img.enq-teaser-on {
    transition: none;
    transform: none;
  }
}

/* ===================================================================
   25 · TESTIMONIALS TOP ROW — stat, avatar line, decorative pull-quote
   -------------------------------------------------------------------
   Shipped 2026-08-08. The odometer that rolled to "145+" is gone from
   the markup (and with it IX2 a-14 and one [data-w-id]); the left
   column is now a static "100%" + "Client-Focused Approach". Two
   things follow from that.

   .counter-wrap was built as an ODOMETER WINDOW - height:60px with
   overflow:hidden and taller digit strips sliding behind it. A single
   static 64px/105% line is 67.2px tall, so that window would clip the
   glyphs by ~7px. The wrap is kept, because it is the flex item the
   stacked breakpoint centres; only its window behaviour is undone.

   The quote is the "decorative, clear to read" treatment. It is a
   system SERIF ITALIC, not a script face, and that is a constraint
   not a preference: this site has no @font-face anywhere, so a
   cursive stack resolves to Segoe Script on Windows, Snell Roundhand
   on Mac and NOTHING on Android. Georgia -> Times -> serif gives a
   real serif with a real italic on every platform, for zero bytes.

   The oversized opening quote is a TEXT GLYPH, never a background
   image. Elementor's lazy-load prints
   ".e-con.e-parent:nth-of-type(n+3):not(.e-lazyloaded) *
   {background-image:none!important}" inline in <head>, and the
   trailing * would erase a background ornament this far down the
   page (the same rule that once ate the select chevron). A glyph is
   immune to it. It also sits inside the paragraph's IX2 entrance, so
   the ornament fades in with the copy instead of ahead of it.

   Nothing here writes opacity or transform on an element IX2 owns -
   the .16 lives on a pseudo-element, which IX2 cannot address.

   ⚠ THIS SECTION STACKS AT 767, NOT 991. webflow-core flips
   .testimonials-top-item-wrap to flex-flow:column and gives
   .testimonials-top-item-right-exar text-align:center inside
   "screen and (max-width:767px)". Shipping the centred ornament at
   991 put it above LEFT-aligned copy for the whole 768-991 band,
   where the layout is still two columns. That band gets its own
   rule instead: same left gutter, trimmed for a ~397px column.
   =================================================================== */

.testimonials-top-item-left .counter-wrap{
  height: auto;
  overflow: visible;
}

.testimonials-top-item-left .counter{
  margin: 0;
}

.testimonials-top-item-left .counter-exar{
  margin-top: 8px;
}

/* Measured at 390: "Partnering with teams across the globe" broke as
   "...across the" / "globe" - a 39px orphan in a 242px box. Same fix
   and same reason as section 23b's trust line, scoped to this wrapper
   so it cannot reach .patner-section's copy. */
.patner-img-text-wrap .join-text,
.patner-img-text-wrap .patner-exar{
  text-wrap: balance;
}

.testimonials-top-item-right-exar{
  position: relative;
  padding-left: 52px;
  font-family: Georgia, 'Times New Roman', serif;
  font-style: italic;
  font-weight: 400;
  font-size: 21px;
  line-height: 1.72;
  letter-spacing: .002em;
  color: #1c1c22;
}

.testimonials-top-item-right-exar::before{
  content: '\201C';
  position: absolute;
  left: -4px;
  top: -26px;
  font-family: Georgia, 'Times New Roman', serif;
  font-style: normal;
  font-weight: 400;
  font-size: 118px;
  line-height: 1;
  color: #0f054d;
  opacity: .16;
  pointer-events: none;
}

/* 768-991: still two columns, still left-aligned, but each column is
   only ~397px - so the gutter and the ornament come down with it. */
@media (min-width: 768px) and (max-width: 991px){
  .testimonials-top-item-right-exar{
    padding-left: 38px;
    font-size: 19px;
    line-height: 1.7;
  }

  .testimonials-top-item-right-exar::before{
    left: -3px;
    top: -22px;
    font-size: 92px;
  }
}

/* <=767 webflow-core stacks the row and centres this paragraph, so
   the ornament leaves the gutter and sits directly above the copy. */
@media (max-width: 767px){
  .testimonials-top-item-right-exar{
    padding-left: 0;
    padding-top: 40px;
    font-size: 20px;
    line-height: 1.7;
  }

  .testimonials-top-item-right-exar::before{
    left: 50%;
    top: -18px;
    transform: translateX(-50%);
    font-size: 100px;
  }
}

@media (max-width: 479px){
  .testimonials-top-item-right-exar{
    padding-top: 34px;
    font-size: 18px;
    line-height: 1.68;
  }

  .testimonials-top-item-right-exar::before{
    top: -22px;
    font-size: 88px;
  }
}

/* ============================================================
   §26 — MOBILE REDESIGN OF 4 SECTIONS  (2026-08-10)
   Preet's picks: A1 compact rows · B4 swipe cards ·
   C4 four-up + one line · D1 swipe carousel.
   Live @390 before: features 1518 · service 1331 ·
   choose 1454 · testimonials 3120 = 7423 px.

   BREAKPOINT IS 767, deliberately: "on phone". 768-991 is
   left exactly as it was. Read RULES -> Testimonials top row
   before assuming 991 anywhere in this file.

   IX2 owns inline opacity AND transform on .features-item,
   .cloneable-area and everything inside the service cards.
   NOTHING in this section sets either property.
   ============================================================ */

/* .choose-short is new markup: one <p> inside each
   .c-card-front .choose-item-text-wrap on page 10. Phone-only,
   hidden everywhere else so 768+ is untouched. It REPLACED
   .choose-summary (one shared line under all four tiles), which
   is gone from the markup as of 2026-08-10. */
.choose-short{display:none}

@media (max-width:767px){

/* ---------- A1 · WHO WE HELP — compact rows ----------
   Was 4 glass cards, 274px each, to carry 4 sentences.
   Now one row per sector: 40px icon left, copy right,
   hairline between. Card chrome was doing nothing here. */
.features-item-wrap{display:flex;flex-direction:column;gap:0}
.features-item-wrap .features-item{
  flex-flow:row nowrap;
  align-items:flex-start;
  gap:15px;
  padding:17px 0;
  background:none;
  border:0;
  border-radius:0;
  box-shadow:none;
}
.features-item-wrap .features-item + .features-item{
  border-top:1px solid rgba(0,0,0,.13);
}
.features-item-wrap .features-img-box{flex:0 0 40px;width:40px;height:40px}
.features-item-wrap .features-text-wrap{flex:1 1 auto;gap:3px}
.features-item-wrap .features-item-title{
  font-size:17px;line-height:1.25;letter-spacing:-.01em;
}
.features-item-wrap .feature-exar{font-size:14px;line-height:1.5}

/* ---------- B4 · OUR SERVICES — swipe cards ----------
   Horizontal snap rail, one card at a time, next peeking.
   The 6 .service-img files are template stock in 5 clashing
   styles (and card-website.avif serves BOTH 05 and 06), so
   the art panel is a brand ground + the service number.
   display:none here + loading="lazy" in the markup is what
   actually stops the 166,383 B of invisible image fetches —
   display:none ALONE does not (RULES -> srcset). */
.service-item-wrap-02{
  display:flex;
  gap:14px;
  overflow-x:auto;
  overflow-y:hidden;
  scroll-snap-type:x mandatory;
  scroll-padding-inline-start:20px;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  margin:0 -20px;
  padding:2px 20px 6px;
}
.service-item-wrap-02::-webkit-scrollbar{display:none}
.service-item-wrap-02 > .service-collection{
  flex:0 0 280px;
  width:280px;
  scroll-snap-align:start;
}
.service-item-wrap-02 .service-collection-list,
.service-item-wrap-02 .service-collection-item{height:100%}
.service-item-wrap-02 .service-collection-item-box{
  height:100%;
  flex-flow:column nowrap;
  padding:0;
  border:1px solid rgba(255,255,255,.41);
  border-bottom:1px solid rgba(255,255,255,.41);
  border-radius:16px;
  overflow:hidden;
  background:rgba(255,255,255,.53);
  box-shadow:inset 0 4px 4px rgba(255,255,255,.2),0 4px 8px rgba(0,0,0,.11);
}
/* the two invisible assets */
.service-item-wrap-02 .service-img,
.service-item-wrap-02 .service-bg{display:none}

/* .service-collection-item-left holds the number + title:
   it becomes the coloured art panel. */
.service-item-wrap-02 .service-collection-item-left{
  display:flex;
  flex-flow:column nowrap;
  justify-content:space-between;
  gap:12px;
  min-height:104px;
  padding:14px 18px 16px;
  background:linear-gradient(150deg,#0f054d,#2a1a6e);
}
.service-item-wrap-02 .service-number{
  font-size:12px;line-height:1.2;letter-spacing:.08em;
  color:rgba(255,255,255,.62);
}
.service-item-wrap-02 .service-title{
  font-size:19px;line-height:1.2;letter-spacing:-.014em;color:#fff;
}
/* 3 coordinated grounds rotated across the 6 cards — navy,
   rose-plum and periwinkle-indigo, all derived from the two
   brand blobs. Never 4+ grounds. */
.service-item-wrap-02 > .service-collection:nth-child(3n+2) .service-collection-item-left{
  background:linear-gradient(150deg,#4d1030,#7d2350);
}
.service-item-wrap-02 > .service-collection:nth-child(3n+3) .service-collection-item-left{
  background:linear-gradient(150deg,#2e2a80,#5750b8);
}

.service-item-wrap-02 .service-collection-item-right{
  flex:1 1 auto;
  display:flex;
  flex-flow:column nowrap;
  gap:10px;
  padding:14px 18px 16px;
}
.service-item-wrap-02 .service-exar{
  font-size:13.5px;line-height:1.5;
  display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;
  overflow:hidden;
}
/* .service-arrow-02 is the hover twin. display, never opacity —
   IX2 owns opacity on these. */
.service-item-wrap-02 .service-arrow-02{display:none}
.service-item-wrap-02 .service-arrow{margin-top:auto}

/* ---------- C4 (v5) - WHY CHOOSE US - four-up, each tile
   carrying its own short line ----------
   .c-card-back is display:none UNSCOPED in webflow-core and
   nothing re-shows it, so the 4 long .choose-exar sentences are
   unreachable at EVERY width. Un-hiding them is not the fix -
   they are far too long for a 170px tile - so each front now
   carries a short <p class=choose-short> of its own, and the
   single shared .choose-summary line this replaced is deleted
   from the markup. Measured @390: 599 -> 584 px, and 4 of 4
   reasons are explained instead of 0 of 4. */
.c-flipping-cards-list{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:10px;
  align-items:stretch;
}
.c-flipping-cards-list .cloneable-area{
  display:flex;width:auto;height:auto;min-height:0;
}
.c-flipping-cards-list .c-flip-card,
.c-flipping-cards-list ._w-flip-card,
.c-flipping-cards-list .c-card-front{
  display:flex;flex:1 1 auto;width:100%;height:auto;min-height:0;
}
/* NO TAP-FLIP ON PHONE. Webflow ships e-456 MOUSE_CLICK +
   e-457 MOUSE_SECOND_CLICK bound BY CLASS to ._w-flip-card on
   breakpoints medium/small/tiny, to give touch what a mouse gets
   from e-454/e-455 (hover, main only). But .c-card-back is
   display:none in webflow-core, so on a phone a tap just spun the
   card and showed nothing. pointer-events is the scalpel: no
   pointer event can reach the element, so the click listeners
   never fire. IX2 is untouched, which is why the entrance still
   runs. HOME ONLY - home.css is enqueued for page 10 alone, and
   the 6 service pages MUST keep their tap-flip: their card backs
   carry copy a phone has no other way to read.
   Rollback: delete this one rule. */
.c-flipping-cards-list ._w-flip-card{pointer-events:none}

.c-flipping-cards-list .choose-item{
  flex:1 1 auto;
  width:100%;
  height:auto;
  min-height:118px;
  padding:16px 14px;
  gap:11px;
  align-items:flex-start;
  justify-content:flex-start;
}
/* webflow-core gives .choose-item-top-icon-wrap width:100% AND
   justify-content:flex-end, so the icon sits top-RIGHT on the live
   card. align-self cannot move it - the wrap is already full width.
   The flip has to be justify-content. */
.c-flipping-cards-list .choose-item-top-icon-wrap{
  width:100%;
  justify-content:flex-start;
}
.c-flipping-cards-list .choose-icon-wrap{
  width:36px;height:36px;padding:9px;
}
.c-flipping-cards-list .choose-icon{width:18px;height:18px}
.c-flipping-cards-list .choose-item-text-wrap{
  margin-top:auto;
  display:flex;
  flex-direction:column;
  gap:6px;
}
.c-flipping-cards-list .choose-item-text{
  margin:0;
  font-size:16px;line-height:1.2;letter-spacing:-.01em;
}
.choose-short{
  display:block;
  margin:0;
  font-size:13.5px;
  line-height:19px;
  font-weight:500;
  color:#2c2c2c;
}

/* ---------- D1 · TESTIMONIALS — swipe carousel ----------
   2349px of stacked cards -> one card at a time. The stat
   strip above it (.testimonials-top-item-wrap, the 08-08
   pass) is untouched. Card internals keep their live order
   (avatar, stars, quote, name), tightened — reordering to the
   mockup's face-at-the-bottom would need markup surgery for
   no reader benefit. Full quotes kept, nothing clamped.
   .testimonials-box's 150vh pin is >=992 only, so the rail
   leaves no runway behind. */
.testimonials-item-wrap{
  display:flex;
  grid-template-columns:none;
  gap:14px;
  /* Three inherited rules have to be cancelled here, and each one
     broke something measurable before it was:
     - justify-content:center (webflow-core, unscoped) centres an
       OVERFLOWING flex row, which pushes the first cards to negative
       x where no amount of scrolling can reach them. Measured: card
       01 at x=-418 with scrollLeft already 0.
     - align-items:center stops the cards stretching, so a 4-line
       quote and a 6-line quote sat at different heights in the rail.
     - the parent .testimonials-wrap is a COLUMN flex box with
       align-items:center, so the rail shrink-wrapped to 350px and
       the -20px margins had nothing to pull against. align-self is
       what makes the full-bleed work; the margins alone did not.
     The services rail needs none of this - .service-row is a plain
     flex column with normal alignment. */
  justify-content:flex-start;
  align-items:stretch;
  align-self:stretch;
  width:auto;
  max-width:none;
  overflow-x:auto;
  overflow-y:hidden;
  scroll-snap-type:x mandatory;
  scroll-padding-inline-start:20px;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  margin:0 -20px;
  padding:2px 20px 6px;
}
.testimonials-item-wrap::-webkit-scrollbar{display:none}
.testimonials-item-wrap .testimonials-item{
  flex:0 0 296px;
  width:296px;
  height:auto;
  scroll-snap-align:start;
  display:flex;
  flex-flow:column nowrap;
  gap:14px;
  padding:24px 22px;
}
.testimonials-item-wrap .testimonials-img{width:42px;height:42px}
.testimonials-item-wrap .testimonials-text-wrap{
  display:flex;flex-flow:column nowrap;gap:12px;
}
.testimonials-item-wrap .testimonials-exar{font-size:15px;line-height:1.6}
.testimonials-item-wrap .testimonial-name{font-size:20px;line-height:1.15}
.testimonials-item-wrap .testimonial-name-exar{font-size:12.5px;line-height:1.4}

/* ---------- shared rail dots (built by home.js) ---------- */
.enq-dots{
  display:flex;
  gap:6px;
  justify-content:center;
  margin-top:14px;
}
.enq-dots button{
  width:6px;height:6px;padding:0;border:0;border-radius:50%;
  background:rgba(0,0,0,.2);cursor:pointer;
  transition:width .2s ease,background-color .2s ease,border-radius .2s ease;
}
.enq-dots button[aria-current="true"]{
  width:20px;border-radius:3px;background:#0f054d;
}

}/* end max-width:767 */

/* The dots element only ever exists on phone, but belt and
   braces for the resize case before home.js tears it down. */
@media (min-width:768px){ .enq-dots{display:none} }

@media (prefers-reduced-motion:reduce){
  .enq-dots button{transition:none}
}


/* ---------- C4 (v5) FIX - equal-height tiles, phone only (2026-08-10)
   The 2x2 grid stretches .cloneable-area, so all four CELLS measure
   163px. But webflow-core sets align-items:center on EVERY flex level
   between the cell and the painted .choose-item, i.e. .cloneable-area
   then .c-flip-card then ._w-flip-card then .c-card-front. align-items
   is the CROSS axis, and on a row flex container that is the axis that
   carries height - the flex:1 1 auto declared above only governs the
   MAIN axis, so it never grew them. Result: the tile with the shortest
   copy (04 Always Available, a 1-line title and a 1-line .choose-short)
   painted 125px and sat centred in its 163px cell with 19px of air
   above and below it. Stretch has to be set at ALL FOUR levels - the
   first center left in the chain stops it.
   Measured @390 after: 4 tiles 170 x 163.4, section still 623px.
   Rollback: delete this block. ---------- */
@media (max-width:767px){
.c-flipping-cards-list .cloneable-area,
.c-flipping-cards-list .c-flip-card,
.c-flipping-cards-list ._w-flip-card,
.c-flipping-cards-list .c-card-front{align-items:stretch}
}

/* Text block sits at the TOP of the tile, under the icon.
   The C4 block above gives .choose-item-text-wrap margin-top:auto,
   which was harmless while every tile was exactly its content
   height. Now that the tiles stretch to a common 163px, auto
   pushed tile 04's copy to the floor and opened a 49px hole
   between its icon and its title while the other three kept 11px.
   Slack belongs at the bottom of the card, not in the middle of it.
   Measured @390: all four now icon then 11px then title, tile 04
   carries its 38px of slack below. Rollback: delete this block. */
@media (max-width:767px){
.c-flipping-cards-list .choose-item-text-wrap{margin-top:0}
}

/* ---------- B4 FIX - the numeral IS the graphic, phone only (2026-08-10)
   Preet: "what can we use instead of colours for our services
   section on phone" -> option 1, the big faint number.
   B4 painted .service-collection-item-left as a coloured art panel
   (navy / plum / periwinkle gradients rotated 3n+1/2/3) because the
   6 .service-img files are off-brand template stock. This removes
   the panel entirely: the card is the same pale glass as every other
   card on the page, .service-number becomes the graphic at 40px in
   navy at .16, and a hairline draws the edge the colour block used
   to draw. Title goes back to #000 to match every other card title.
   TWO reasons this scores the way it does:
   1. B4's nth-child(3n+2)/(3n+3) ground rules score (0,4,0), so a
      plain two-class override would LOSE on cards 2,3,5,6. The same
      three selectors are repeated here and win on source order
      alone - no !important anywhere, same as global.css S20.
   2. A gradient is a background-IMAGE, so Elementor's inline
      lazy-load rule (.e-con.e-parent:nth-of-type(n+3):not(.e-lazyloaded)
      * {background-image:none!important}, which matches at
      max-height:1024px i.e. every phone) could blank those grounds
      until the IntersectionObserver fires. color + border are immune.
   No opacity is set on anything here - IX2 owns inline opacity on
   these elements (RULES -> mobile snap rails).
   Rollback: delete this block, the colour grounds come straight back.
   ---------- */
@media (max-width:767px){
.service-item-wrap-02 > .service-collection .service-collection-item-left,
.service-item-wrap-02 > .service-collection:nth-child(3n+2) .service-collection-item-left,
.service-item-wrap-02 > .service-collection:nth-child(3n+3) .service-collection-item-left{
  background:none;
  min-height:0;
  justify-content:flex-start;
  gap:2px;
  padding:16px 18px 13px;
  border-bottom:1px solid rgba(15,5,77,.10);
}
.service-item-wrap-02 .service-number{
  font-size:40px;
  line-height:1;
  letter-spacing:-.02em;
  font-weight:600;
  color:rgba(15,5,77,.16);
}
.service-item-wrap-02 .service-title{color:#000}
}


/* ==========================================================
   SECTION 27  GROUND SEQUENCE -- THE HOMEPAGE BANDS
   2026-08-10. Pairs with global.css section 21, which retints the
   default page ground to --enq-ground (#e5e4f0) site-wide. This
   file carries only the bands specific to the home page. Full
   sequence, in DOM order:

     hero          white  <- here (fallback under the video)
     partners      white  <- here
     features      tint      inherited, no rule
     services      tint      inherited, no rule
     project CTA   navy   <- here, plus its type inverted below
     portfolio     white     already var(--white), no rule
     choose        tint      inherited, no rule
     testimonials  white  <- here, gradient dropped
     footer        tint      global.css section 21

   Variant B out of mockups/bg-variants.html: six ground changes,
   longest unbroken run about 2,100 px, and navy appears exactly
   ONCE so the CTA is the only dark stop on the page.

   .hero is painted even though an opaque 850px <video> covers all
   of it -- the colour is what shows if the video never plays, and
   on iOS Low Power Mode that is a real state (RULES -> Do NOT
   "fix" these).
   ---------- */
.hero{ background-color: #fff; }
.patner-section{ background-color: #fff; }

/* The linear-gradient(#fff, transparent 77%) existed only to blend
   the white above into the grey below. With explicit bands it is a
   smudge, so it goes and the section commits to white. The
   background-image longhand, not the shorthand, so nothing else on
   the box is reset. */
.testimonials-section{
  background-image: none;
  background-color: #fff;
}

/* --- 27b. The CTA on navy --------------------------------
   #0f054d is the same navy as the header CTA and the form submit,
   and this is now the only navy surface in the page body.

   Type has to be inverted or the section goes dark-on-dark: h3
   ships color:var(--black) and .section-title-exar inherits
   --nexo-text #2c2c2c.

   .button_text_two needs NO rule -- it already ships
   color:var(--white) with nothing painted behind it, so on the
   white section the words "Book a Free Consultation" were white on
   white and had never been visible. The navy ground is what makes
   them readable.

   .button-one is the arrow's pill, webflow's --theme-color #201e1e,
   which on navy reads as a smudge. It becomes a ghost -- a white
   wash inside the white ring it already carries (border-color
   #ffffff69) -- which keeps button-arrow.svg visible without a
   second asset, since that file is hard-coded fill="white".

   Nothing here sets opacity or transform: IX2 owns both inline on
   the h3, on .section-title-exar and on .project-btn
   (RULES -> Webflow IX2).

   NOT touched, and still a defect either way: IX2 "Btn Hover"
   (a / a-2) sizes .button_bg to width 100%, and .btn is unpositioned
   so that black fill escapes to .section-title-wrap._04 and wipes
   across the whole 401px title block on hover. Pre-existing on the
   white ground; on navy it is dark-on-dark instead of black over a
   black headline.
   ---------- */
.project-section{ background-color: #0f054d; }
.project-section h3.subtitle{ color: #fff; }
.project-section .section-title-exar{ color: rgba(255, 255, 255, .78); }
.project-section .button-one{ background-color: rgba(255, 255, 255, .12); }

/* --- 27c. The CTA button, rebuilt -------------------------
   2026-08-10, Preet: "fix the CTA button too".

   .btn landed from the Webflow export carrying only a font-family,
   so three things were wrong at once and the navy ground exposed
   the first of them:

     1. display:block stacked the label ABOVE the arrow, and the
        arrow's <a> then stretched to the label's width -- a 218x51
        bar with a 13px glyph floating in the middle of it.
     2. .btn was unpositioned, so .button_bg -- the black fill IX2
        wipes from width 0% to 100% on hover (action lists a / a-2,
        "Btn Hover", bound by CLASS) -- resolved against
        .section-title-wrap._04 instead and swept across the whole
        401px title block, headline included.
     3. .button_text_two ships color:var(--white) with nothing
        painted behind it, so on the old white section the words
        were invisible. 27b's navy ground is what fixed that one.

   This is the same unstacking .hero-right .btn got in section 4 of
   this file, plus the containing block and the pill geometry the
   export dropped. Result: label, 12px gap, the round arrow pill,
   all inside one pill-shaped box that the hover fill sweeps.
   padding-left is 28px because border-radius:63px clamps to half
   the 49px height, so 24.5px of the left end is curve -- less than
   that and the first letter sits in the round.

   The fill becomes rgba(255,255,255,.10) rather than webflow's
   var(--black): black on #0f054d is barely a change, and a white
   wash keeps both the white label and the white arrow readable as
   it passes under them. No stacking work is needed --
   .button_text_two (z-index 2) and .button-one (z-index 1) already
   outrank .button_bg (z-index 0); they only ever needed a shared
   containing block to stack against.

   overflow:hidden is what clips .button_bg's inset:0 auto -6% 0 /
   height:104% bleed to the pill shape.

   NOT fixed here, and it is the component's design, not this
   instance: only the arrow's <a> is a link, so the words are not
   clickable. .button-one is position:relative + overflow:hidden,
   so a pseudo-element cannot be stretched over the label from
   inside it -- that one needs markup.
   ---------- */
.project-section .btn{
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
  padding: 0 8px 0 28px;
  border-radius: 63px;
  position: relative;
  overflow: hidden;
}
.project-section .button_bg{
  background-color: rgba(255, 255, 255, .10);
}

/* ==========================================================
   SECTION 28  SECTION DIVIDERS  (2026-08-10)

   ONE hairline, at the only homepage seam where the ground
   does NOT change: features (tint) -> services (tint).
   Every other seam already changes ground colour (global.css
   section 21 + home.css section 27), and hero|partners is
   already broken by the hero video's own hard bottom edge,
   so neither of those gets a rule.

   Anchored INSIDE .features-section, which is already
   position:relative -- so this adds no positioning context,
   and being absolute it costs ZERO layout: nothing moves.

   Do NOT move it to .service-section::before with a negative
   top. .service-section is overflow:hidden and would clip the
   line away invisibly.

   background-COLOR, never a gradient. Elementor's inline
   lazy-load rule forces background-image:none on every .e-con
   descendant until its observer fires, so a gradient rule
   would flicker in on scroll. Same trap as the select chevron
   and the old B4 colour grounds.

   Offsets are measured to the PAINTED floor, not the text.
   .features-item are glass cards with 70px of inner bottom
   padding, so the visual floor is the card bottom, 71px below
   the last line of text. Measured:
     >= 992   gap 107px (cards 1821 -> heading 1928) -> 46px
     <= 991   gap  77px                              -> 31px
   Width tracks .container exactly: min(1300px, 100% - 40px).
   ========================================================== */
.features-section::after{
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 31px;
  margin-left: auto;
  margin-right: auto;
  width: min(1300px, calc(100% - 40px));
  height: 1px;
  background-color: rgba(15, 5, 77, .10);
  pointer-events: none;
}
@media (min-width: 992px){
  .features-section::after{
    bottom: 46px;
  }
}

/* ===========================================================
   29. WHITE CARDS - Who We Help / Our Services / Why Choose Us
   ------------------------------------------------------------
   The ground tint (global.css section 21, #e5e4f0) made every
   card on the page read lavender. webflow-core paints them from
   --testimonials-bg / --choose-item-bg, both #ffffff87 = white
   at 53%, which over #e5e4f0 computes to about #f2f1f8.
   Preet asked for white cards in these three sections.

   Testimonials keep the 53% glass - his explicit call, asked and
   answered - so this file must never grow a .testimonials-item
   background rule.

   Nothing here touches opacity, transform or backdrop-filter.
   IX2 owns inline opacity and transform on .features-item and
   .cloneable-area. backdrop-filter stays even though an opaque
   card no longer needs it: it makes the element a containing
   block for absolutely positioned children, so removing it can
   move them.
   =========================================================== */

/* Who We Help. Three of the four cards were #ffffff87 while the
   fourth (.features-item._02, no .features-item-1) was already
   solid #fff, so this also ends that mismatch.
   >=768 only: below that, section 26 turns these into rows with
   background:none and no border, and this must not override it. */
@media screen and (min-width:768px){
  .features-item-wrap .features-item,
  .features-item-wrap .features-item.features-item-1{
    background-color:#fff;
  }
}

/* Why Choose Us. A card at every width, and the same element in
   both layouts (4-up desktop, 2x2 phone), so this is unscoped.
   All 8 .choose-item live inside .c-flipping-cards-list, the 4
   painted fronts plus the 4 backs that .c-card-back keeps
   display:none. */
.c-flipping-cards-list .choose-item{
  background-color:#fff;
}

/* Our Services. UNSCOPED, every width - corrected 2026-08-10
   after Preet saw the result. Below 767 this repeats section
   26's own selector (rgba(255,255,255,.53)) and wins on source
   order alone, no !important. Above 767 it paints a fill that
   never existed: webflow-core gives .service-collection-item-box
   nothing but a dashed bottom border, so the 6 rows had been
   showing the tint straight through. */
.service-item-wrap-02 .service-collection-item-box{
  background-color:#fff;
}

/* The 6 rows are stacked flush with no gap, so above 767 the
   fill above reads as ONE white column the width of .container
   (1225 at 1280), not six cards. Preet asked for it to be
   rounded and shadowed like the others, so the BLOCK is the
   card: 16px and 0 4px 8px rgba(0,0,0,.11), both read straight
   off .choose-item / .features-item.

   Three things here are deliberate:

   1. The shadow goes on the WRAPPER, not on each row. Six
      shadows on flush-stacked rows would each cast onto the row
      below and draw a grey band at every divider.
   2. No overflow:hidden on the wrapper, so the corners have to
      be set on the first and last ROW instead. overflow:hidden
      would round them for free, but it would also clip the
      per-row hover lift that section 5 paints
      (0 20px 50px rgba(0,0,0,.28)) at the block edges.
   3. The wrapper carries the radius even though it has no
      background: an outer box-shadow follows the border-radius
      of the box it is cast from, so without it the shadow would
      be a sharp rectangle behind rounded corners.

   Specificity: webflow-core has .service-collection-item-box
   {border-radius:0} and a :hover repeat of it at (0,2,0). The
   two corner rules score (0,4,0), so the corners survive hover
   without !important.

   Left alone on purpose: the dashed border across the very top
   of row 1 and the bottom of row 6. It now curves with the
   corner instead of framing a rectangle, but the dashed rule is
   a page motif (.features-wrap carries the same one above the
   Who We Help block), so removing it is a design call, not a
   tidy-up. Two lines if he wants it gone. */
@media screen and (min-width:768px){
  .service-item-wrap-02{
    border-radius:16px;
    box-shadow:0 4px 8px 0 rgba(0,0,0,.11);
  }
  .service-item-wrap-02 > .service-collection:first-child .service-collection-item-box{
    border-top-left-radius:16px;
    border-top-right-radius:16px;
  }
  .service-item-wrap-02 > .service-collection:last-child .service-collection-item-box{
    border-bottom-left-radius:16px;
    border-bottom-right-radius:16px;
  }
}


/* ---------------------------------------------------------------
   30. CLIENT LOGO STRIP ON PHONES  (2026-08-11)
   Preet, from a 390px screenshot: the strip reads broken - one logo
   sliced clean through the middle at the mask edge, and the row
   sitting exactly on the white -> tint seam.

   Three independent causes, all measured live at 390:

   a) webflow-core ships `.patner-section{padding: 35px 10px 0}` at
      <= 479 ONLY (it is 40px in the <= 767 band and 50px at >= 992),
      so the 26px strip has ZERO air under it: section bottom 636.6
      == logo bottom 636.6, to the pixel. Harmless while the whole
      page was one grey; since global.css S21 / home.css S27 that
      floor IS the white -> tint colour change, so the logos now land
      on the seam. Match the 35px top.

   b) S6 sets `.client-img{margin:0}` with the comment "spacing now
      comes from the wrap gap", and then the <= 767 block re-adds
      `margin:0 20px`. Leftover from before 6c introduced the gap -
      the two stack, so phone pitch is 56 + 40 = 96px against
      desktop's 56px. Track 1523.8px in a 350px mask => exactly ONE
      logo fully in view and the next one cut through the middle.
      Dropping the margin takes the track to 1243.8 and puts TWO in
      view, the second ending 30px clear of the mask edge.

   c) The mask had no soft edge, so whatever is mid-cut reads as
      clipped rather than scrolling. It must be mask-image and NOT a
      background gradient: Elementor's inline lazy-load rule forces
      background-image:none on every .e-con descendant until its
      observer fires and would erase a gradient (same trap that ate
      the select chevron and S26's colour grounds); it does not touch
      mask-image.

   Marquee speed is held constant on purpose. Travel is track + one
   gap, so the narrower track at the old 20s would drop 79 -> 65 px/s
   and the strip would feel like it slowed down. 15.8s keeps the
   shipped feel. Knobs: --enq-clogo-dur, --enq-clogo-gap.
   Reverse = delete this block.
   --------------------------------------------------------------- */
@media screen and (max-width: 767px){
  /* kill the leftover per-logo margin; the wrap gap owns spacing */
  .patner-section .client-img{ margin: 0; }

  /* soft edges instead of a hard chop at both ends of the mask */
  .patner-section .ticket-wrap{
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 24px, #000 calc(100% - 24px), transparent 100%);
            mask-image: linear-gradient(to right, transparent 0, #000 24px, #000 calc(100% - 24px), transparent 100%);
  }
}

@media screen and (max-width: 479px){
  .patner-section{
    padding-bottom: 35px;        /* webflow ships 0 here; match the top */
    --enq-clogo-dur: 15.8s;      /* hold ~79 px/s on the shorter track */
  }
}

/* S18 parks the animation under reduce-motion, which left one long
   track sitting in a 350px mask with nothing moving it - most of the
   logos permanently unreachable (STATE carried this as a known
   defect). Give it the fallback it never had: a swipe rail. The
   second copy only exists to make the loop seamless, so it goes.
   Verified: scrollLeft reaches 1173.6 and the last logo (en3) comes
   fully into view. */
@media screen and (max-width: 479px) and (prefers-reduced-motion: reduce){
  .patner-section .ticket-wrap{ overflow-x: auto; scrollbar-width: none; }
  .patner-section .ticket-wrap::-webkit-scrollbar{ display: none; }
  .patner-section .client-wrap._03{ display: none; }
}

/* ===========================================================
   31. WHO WE HELP - white cards on phone
   ------------------------------------------------------------
   Section 26 (A1) turned these four cards into flush borderless
   rows at <=767. Preet asked for them white on phone, so they
   go back to reading as cards: the same 16px radius and the
   same 0 4px 8px rgba(0,0,0,.11) shadow the phone service
   cards and choose tiles already carry, with a 12px gap in
   place of the hairline divider.

   Three inconsistencies are normalised in the same block. All
   three are webflow-core rules that OUTSCORE section 26, not
   new design decisions - they were invisible on borderless
   rows and would be obvious the moment each row paints its
   own white box:

   1. .features-item.features-item-1._02 at <=767 sets
      padding-top/bottom:40px and scores (0,3,0) against
      section 26's (0,2,0), so row 2 painted 147px against
      ~100px on the other three. The selector list below
      reaches (0,4,0) so one padding value covers all four.
   2. At <=479 .features-text-wrap{align-items:center} (0,1,0)
      and .body-15px-regular.feature-exar{text-align:center}
      (0,2,0) both survived section 26, so every description
      sat centred in its column while its title hugged the
      left edge.
   3. .heading-h6.features-item-title._02 at <=479 is (0,3,0)
      and beat section 26's (0,2,0), so row 4 alone rendered
      20px and centred, wrapping to two lines.

   Nothing here touches opacity, transform or backdrop-filter -
   IX2 owns inline opacity and transform on .features-item.
   >=768 is untouched: section 29 keeps its own white rule
   there, and this whole block is scoped <=767.
   =========================================================== */
@media (max-width:767px){

.features-item-wrap{gap:12px}

.features-item-wrap .features-item,
.features-item-wrap .features-item.features-item-1,
.features-item-wrap .features-item._02,
.features-item-wrap .features-item.features-item-1._02{
  padding:16px 18px;
  background-color:#fff;
  border-radius:16px;
  box-shadow:0 4px 8px 0 rgba(0,0,0,.11);
}

/* The hairline divided flush rows. With a gap between separate
   cards it would draw across the top edge inside the rounded
   corner instead. */
.features-item-wrap .features-item + .features-item{
  border-top:0;
}

.features-item-wrap .features-text-wrap{align-items:flex-start}
.features-item-wrap .feature-exar{text-align:left}

.features-item-wrap .features-item-title._02{
  font-size:17px;
  line-height:1.25;
  letter-spacing:-.01em;
  text-align:left;
}

}


/* ===================================================
   32. CTA SECTION BUTTON -- white pill, navy label
   Shipped 2026-08-11. Preet: remove the arrow btn, put a
   btn with white bg and the same blue as the bg for the
   font.

   MARKUP CHANGED IN THE SAME PASS, because it had to be.
   The old control was the Webflow .btn composite: a
   <div class=button_text_two> label, an <a class=button-one>
   holding ONLY the arrow image, and a <div class=button_bg>
   that IX2 wiped across on hover. The arrow anchor was the
   only LINK in the whole control -- so removing the arrow
   and fixing the standing 'the words are not clickable'
   defect are one edit, not two. All three nodes are gone and
   .project-btn now holds a single
   <a class=enq-cta enq-cta--project>.

   Retired with them, site-wide, because this was the only
   .btn anywhere: IX2's class-bound a / a-2 'Btn Hover'
   (nothing carries .button_bg now) and the arrow anchor's
   own guid 31f89dd4-... . The wrapper guid 4fc921ea-... is
   KEPT on purpose -- it carries this block's IX2 entrance
   and sits on .project-btn, which is not being removed.

   Section 27c's .project-section .btn and .button_bg rules
   are now DEAD. Left in place deliberately: removing them is
   an in-place splice that would invalidate the recorded
   rollback offsets for 28, 29, 30 and 31, and they are the
   only surviving record of how that component was repaired.

   Colour is the brief read literally -- section 27 paints
   this section #0f054d, so the label is that exact value.

   SPECIFICITY: section 20a paints .enq-cta navy with a white
   label and its :hover repaints BOTH properties. These rules
   are (0,3,0) and (0,3,1), so they win outright; home.css
   also declares enq-custom-global as a dependency, so source
   order would do it too. No !important anywhere.
   =================================================== */

/* The anchor is inline-flex, so as the only child of a plain
   block it would sit on a line box and inherit the body's
   160% strut -- a few px of phantom descender under the
   pill. flex removes the strut. It does NOT touch opacity or
   transform, which IX2 owns on this element. */
.project-section .project-btn{
  display: flex;
  justify-content: center;
}

.project-section .enq-cta--project{
  height: 52px;
  padding: 0 32px;
  font-size: 17px;
  background-color: #fff;
  color: #0f054d;
}

/* ---------- D-023, 2026-09-04: ONE HOVER FOR EVERY CTA ON THE SITE.
   ⛔ The tint + 2px lift that used to be here is GONE. This button now hovers
   exactly like the other six: the accent rule on .enq-cta::after in global.css
   section 20a, which reaches this button because it is an .enq-cta.

   ✅ It is also the button the option was safest for. The old comment here
   recorded that .section-title-wrap._04 is overflow:hidden and this button is
   its LAST child, so its bottom edge is flush with that clip and a downward
   shadow would be cut off. The accent rule is INSIDE the button's own box, so
   the clip cannot reach it -- unlike a shadow, and unlike a lift, which only
   worked because it moved away from the edge.

   ⚠⚠⚠ WHAT IS LEFT BELOW IS NOT DEAD CODE, AND THE REASON HAS CHANGED.
   It used to repeat this colour because 20a's hover set #1a0b73 / #fff, and
   dropping either half flipped the button navy-on-navy. 20a sets no background
   any more, but it still sets color:#fff on :hover -- it has to, or
   hello-elementor/reset.css's bare a:active,a:hover{color:#336} at (0,1,1)
   beats .enq-cta at (0,1,0) and every navy CTA on the site turns slate blue.
   That white would reach THIS button too, and it is white on white. This rule
   is (0,2,1) and puts the navy back. Delete it and the label disappears. */
.project-section .enq-cta--project:hover,
.project-section .enq-cta--project:focus-visible{
  color: #0f054d;
}

/* Same clip problem, same answer: 20a's ring is
   outline-offset:3px, i.e. 3px BELOW the flush bottom edge,
   where overflow:hidden eats it. A negative offset draws the
   ring inside the pill instead, where nothing can clip it;
   outline follows border-radius, so it stays a pill. The
   colour has to move too -- 20a's ring is #0f054d, which is
   this section's own ground. */
.project-section .enq-cta--project:focus-visible{
  background-color: #e5e4f0;
  color: #0f054d;
  outline: 2px solid #0f054d;
  outline-offset: -4px;
}

@media screen and (max-width: 767px){
  .project-section .enq-cta--project{
    height: 48px;
    padding: 0 26px;
    font-size: 16px;
  }
}
/* ==========================================================
   §33 — TEAM BAND v4 (2026-08-20)
   v3's composition, plus a clip: a BLACK & WHITE video of the
   team walking in and assembling plays once inside the 16:9
   frame, and when it ends it hands back to the COLOUR still —
   and the group steps out of the frame.

   REPLACES §33 v3 WHOLESALE. home.css is truncated to 85287
   and this block appended:
       85287 / 0e001ffeac15ed0e2ab3cf8eae02636c
   §34 has not existed since v3. This is still the LAST section
   in the file.

   ⚠⚠ WHY THE FRAME CAN CUT THROUGH PEOPLE AND NOTHING BREAKS
   (unchanged from v3): the cut-out layer draws every person
   COMPLETE and sits on top, so the frame's edges only ever cut
   BACKGROUND. That is what frees the frame to be 16:9 while the
   group is 2.4:1, and why the layers must NEVER be offset or
   scaled against each other.

   ⚠⚠ THE HANDOFF IS ONLY INVISIBLE BECAUSE THE STILL *IS* THE
   VIDEO'S LAST FRAME. team-v4.avif is the clip's frame 160,
   cropped identically, in colour. Measured against the B&W
   clip's own last frame: SSIM 0.985 / PSNR 43.2 dB on luma.
   Re-cut the video without re-cutting the still from the SAME
   frame and the end of the clip will visibly jump.
   ⓘ Both come from crop=1524:774:86:263 of the 1660x1244 source.

   ⚠⚠ DEFAULT STATE IS THE FINISHED BAND, NOT THE VIDEO.
   Everything below is written so that with no JS, no autoplay
   permission, reduced-motion, or save-data, the band renders
   EXACTLY as v3 did — colour still, cut-out, drop-shadow. The
   video is an enhancement that adds itself; it can never
   subtract the band. iOS Low Power Mode refuses muted autoplay
   on this site (it already ate the hero video and the logo
   marquee once) and that path MUST land on the still.
   ========================================================== */

.enq-team{background-color:#fff;padding:50px 20px 110px}
.enq-team__inner{max-width:1300px;margin:0 auto}
.enq-team__head{display:flex;flex-direction:column;align-items:flex-start;gap:20px}
/* webflow-core's .subtitle is text-align:center + max-width:600px. This
   band uses the site's own LEFT-aligned header pattern, so both are
   overridden here. ⚠ Deliberately NOT wrapped in .section-title-box /
   -box-02 — both are overflow:hidden, which would make them scroll
   containers and kill any view() animation inside them. */
.enq-team__head .subtitle{text-align:left;max-width:760px;padding-bottom:0}

/* ---------- THE STAGE -------------------------------------
   The whole picture area, INCLUDING everything that spills out
   of the frame. Never clipped, never given a background.
   Its aspect is the asset's: 2000 x 1015. */
.enq-team__stage{position:relative;margin-top:46px;aspect-ratio:2000/1015}

/* ---------- THE FRAME -------------------------------------
   The 16:9 window onto the picture. Every number is a
   percentage of the stage, so the whole composition is one
   scale-invariant drawing — it holds at 1300 and at 350.
       frame  x 277  y 138  w 1846  h 1037   (of 2400 x 1218)
       1846 / 1037 = 1.780  ->  16:9
   ⚠⚠ overflow:CLIP, never hidden. `hidden` makes this a SCROLL
   CONTAINER, and animation-timeline:view() resolves against the
   nearest scrollport — the ken-burns INSIDE would then measure
   itself against a box that never scrolls and would never run.
   `clip` clips identically without creating one. Same trap as
   .page-wrapper below. */
.enq-team__frame{position:absolute;left:11.542%;top:11.330%;
  width:76.917%;height:85.140%;overflow:clip}

/* ---------- THE TWO PICTURE LAYERS ------------------------
   The still and the video are the SAME BOX, declared once so
   they can never drift apart. Both are bigger than the window
   and offset inside it so they line up EXACTLY with the
   full-stage cut-out layer.
   left = -277/1846, top = -138/1037, w = 2400/1846, h = 1218/1037.
   ⚠ These four numbers and the four above are one system — move
   the frame and all eight change together, or the cut-out slides
   off the picture.
   ⚠⚠ `max-width:none` IS LOAD-BEARING on the img, not tidying.
   It is deliberately WIDER than its own box (130% of the frame),
   and hello-elementor's reset.css ships img{max-width:100%},
   which silently clamps it back to the frame width. Nothing
   errors: the picture just squeezes to the window while the
   cut-out stays full-stage, and every person is drawn a few px
   off the copy of themselves underneath. Caught on live
   2026-08-20 by measuring the two layers, dw = -282.7 px.
   ⓘ The video is NOT hit by that rule — reset.css scopes it to
   img, and webflow-core's only global video rule is
   vertical-align:baseline;display:inline-block. max-width:none
   is set on both anyway so the pair stays one declaration, and
   display:block cancels that inline-block.
   ⚠⚠ EVERY ASSET IN THIS BOX MUST BE EXACTLY 2000:1015 (= 400:203).
   Both layers are object-fit:COVER in one box, so a file with a
   different aspect is not letterboxed — it is scaled up and
   cropped, and the two layers then disagree by that crop. The
   shipped sizes are the only even-dimension exact ones that are
   useful here: 2000x1015 and 1200x610 (still), 1600x812 and
   800x406 (clip). Measured against the box at 1280: all four
   crop 0.00px horizontally. A 1400x710 encode was built first
   and rejected — 1.971831 instead of 1.970443, which cropped the
   clip 0.42px per side against the still it has to dissolve into. */
.enq-team__bg,
.enq-team__vid{position:absolute;left:-15.006%;top:-13.307%;
  width:130.011%;height:117.454%;max-width:none;display:block;object-fit:cover}

/* The clip sits ON the still, in the same box, and is invisible
   until JS has it playing. Nothing preloads: the markup carries
   no src, only data-src, so a visitor who never reaches the band
   — or whose browser refuses the clip — pays ZERO bytes for it. */
.enq-team__vid{opacity:0;pointer-events:none;transition:opacity .45s ease}
.enq-team.is-rolling .enq-team__vid{opacity:1}

/* ---------- THE CUT-OUT -----------------------------------
   The still again, full stage size, with the background removed
   by an alpha mask.
   ⚠⚠ The drop-shadow is on the OUTER wrapper, not the img.
   Filters are applied BEFORE masking on the same element, so a
   shadow on the image itself would be cast by the un-masked
   rectangle and then masked away — a hard-edged box, exactly
   what this band is not for. On an ancestor it is cast by the
   already masked descendant and follows the people.
   ⚠⚠ WHY THERE ARE NOW *TWO* WRAPPERS. .enq-team__cutwrap owns
   the scroll-driven pop, which animates translate. The handoff
   needs its own move, and two scroll-driven animations cannot
   share one element — nor may the same property come from two
   sources. So the handoff lives on .enq-team__reveal in between.
   It must NOT be merged back into either neighbour, and ⛔ it
   must not be put on .enq-team__cut itself: that is the one
   element where a filter/mask ordering bug would surface as a
   hard-edged box. */
.enq-team__cutwrap{position:absolute;inset:0;
  filter:drop-shadow(0 14px 18px rgba(15,5,77,.18))}
.enq-team__cut{position:absolute;inset:0;width:100%;height:100%;
  display:block;object-fit:cover;
  -webkit-mask-image:url(/wp-content/uploads/assets/team/team-v4-mask.webp);
          mask-image:url(/wp-content/uploads/assets/team/team-v4-mask.webp);
  -webkit-mask-size:100% 100%;        mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat;      mask-repeat:no-repeat;
  -webkit-mask-position:center;       mask-position:center}
/* ⚠ The mask is an ALPHA mask, not a luminance one — CSS masks
   read the alpha channel by default. team-v4-mask.webp is
   therefore solid black with the matte in its alpha; a plain
   grey-scale file would be fully opaque and mask nothing.
   ⓘ If that file ever 404s the layer disappears entirely, and
   what is left is the framed still with no cut-out. */

/* ---------- THE HANDOFF -----------------------------------
   While the clip rolls, the group is IN the picture: the cut-out
   is gone and the frame's edges cut through everybody, because
   the only thing drawing them is the clipped layer underneath.
   When it ends the cut-out fades back and lifts, so the team
   steps out of the frame it was just inside.
   ⚠ The exit is FASTER than the return (.28s out, .55s back), and
   the return is delayed a beat so the clip has fully settled on
   its last frame before anything is drawn over it. */
.enq-team__reveal{position:absolute;inset:0;opacity:1;translate:0 0;
  transition:opacity .55s ease .12s,
             translate .55s cubic-bezier(.22,.7,.3,1) .12s}
.enq-team.is-rolling .enq-team__reveal{opacity:0;translate:0 18px;
  transition:opacity .28s ease, translate .28s ease}

@media screen and (max-width:991px){
  .enq-team{padding:40px 20px 80px}
  .enq-team__stage{margin-top:36px}
}
@media screen and (max-width:767px){
  .enq-team{padding:40px 20px 60px}
  .enq-team__stage{margin-top:28px}
  /* the shadow is a phone-tax: a large drop-shadow filter on a
     full-width image is the most expensive thing in this band */
  .enq-team__cutwrap{filter:drop-shadow(0 8px 12px rgba(15,5,77,.16))}
}

/* ⚠⚠ THE ONE RULE HERE THAT TOUCHES ANYTHING OUTSIDE THE BAND.
   webflow-core.css line 8176, inside @media screen and (max-width:479px),
   sets .page-wrapper{overflow:hidden} — and .page-wrapper is the direct
   parent of every section on the page. Same trap as above: below 480
   it is a scroll container, so the motion would sit frozen. Verified on
   live 2026-08-20: .page-wrapper computes visible at 1280 and the
   hidden rule exists ONLY in that <=479 block, so this changes nothing
   at any other width.
   ⚠ hidden establishes a block formatting context and clip does not —
   measure page height at 390 before/after, it must not move. */
@media screen and (max-width:479px){
  .page-wrapper{overflow:clip}
}

/* ==========================================================
   §33 motion — scroll-driven, CSS only, zero JS.
   The picture pushes back into its frame while the people rise
   out of it, so the two layers separate as you reach them.
   ⚠⚠ The from-state arrives ONLY via animation-fill-mode:both
   INSIDE @supports. Written as a plain rule, Firefox — which
   still has scroll-driven animations behind a flag — would get
   a permanently offset cut-out. As written it never parses the
   block and renders the finished band.
   ⚠ Longhands only. The animation shorthand resets
   animation-timeline to auto and pins it to its end keyframe.
   ⚠ translate/scale longhands, not transform — they compose
   instead of clobbering, which keeps this safe next to IX2.
   ⚠ Two scroll-driven animations cannot share one element, so
   each move gets its own: the FRAME's picture scales, the
   CUT-OUT's outer wrapper rises, the HEAD fades up.
   ⚠⚠ .enq-team__vid CARRIES THE SAME KEN-BURNS AS .enq-team__bg,
   from the same keyframes over the same range. They are two
   copies of one picture in one box: if only one of them scaled,
   the crossfade at either end of the clip would slide.
   ⚠ .enq-team__reveal is deliberately NOT in this list. It owns
   the handoff transition; giving it a scroll-driven animation as
   well would put translate on it from two sources.
   ⛔ Do not add an animation to .enq-team__cut itself — an
   ancestor already owns one, and an animated property on the
   masked image is the one place a filter/mask ordering bug
   would show up as a hard-edged box.
   ========================================================== */
@media (prefers-reduced-motion:no-preference){
@supports (animation-timeline:view()){

  @keyframes enq-team-ken { from{scale:1.06} to{scale:1.00} }
  @keyframes enq-team-pop { from{translate:0 26px} to{translate:0 0} }
  @keyframes enq-team-rise{ from{opacity:0; translate:0 34px}
                            to  {opacity:1; translate:0 0} }

  .enq-team__head,
  .enq-team__bg,
  .enq-team__vid,
  .enq-team__cutwrap{
    animation-duration:auto;          /* the timeline owns the clock */
    animation-fill-mode:both;         /* this, and only this, is the from-state */
    animation-timing-function:linear;
    animation-timeline:view();
  }
  /* ⚠ Stagger with animation-RANGE, never animation-delay — a delay is
     a time offset against a clock that does not exist here. */
  .enq-team__head   {animation-name:enq-team-rise; animation-range:entry 5% entry 78%}
  .enq-team__bg,
  .enq-team__vid    {animation-name:enq-team-ken;  animation-range:entry 10% cover 55%}
  .enq-team__cutwrap{animation-name:enq-team-pop;  animation-range:entry 10% cover 45%}
}}
/* --- end §33 --- */


/* ==========================================================
   SECTION 35  CORNER BLOBS ON THE WHITE BANDS
   2026-08-21, Preet: 'sections with blue background don't have
   any of those blobs / white [sections] get red or blue at
   random corners. both can be there if the section is long
   enough', and then 'leave out header and footer as it is'.

   NUMBERING: this is 35, not 34, on purpose. A section 34
   existed on 2026-08-20 (the team band's B+W lens + notch) and
   was deleted the same day; every note in STATE/RULES that says
   '34 no longer exists' stays true only if 34 is never reused.

   WHAT THE RULE MEANS HERE. Section 21 makes the default page
   ground the tint (--enq-ground #e5e4f0, navy at 10% over white)
   and section 27 paints the home page's own bands. So 'blue
   background' is BOTH the tint and the navy .project-section,
   and only these five sections on this page are actually white:
       .hero                white  850   video covers 100% of it
       .patner-section      white  158   logo marquee, too short
       .enq-team            white  974   -> one blob
       .portfolio           white 2063   -> two, it is long
       .testimonials-section white 1550  -> two, it is long
   .hero and .patner-section are deliberately left bare: an
   opaque 850px <video> covers every pixel of the hero, and a
   632px blob inside a 158px strip is a wash over the whole band,
   not a corner. Nothing here touches .features-section,
   .service-section, .choose-section or .project-section - the
   blobs came OUT of those in the same pass (markup), which is
   also why section 24's centring rules for the first two are now
   dead. They are kept: deleting them would move section 25's
   rollback offset.

   THE FOOTER IS EXPLICITLY OUT OF SCOPE - Preet, same message.
   It is on the tint like the sections above, and it keeps its
   red blob anyway. Do not 'finish the job' by removing it.

   TWO OR ONE: the threshold is 1200px of section height. Above
   it a single corner glow leaves the opposite end flat.

   HOW IT IS BUILT. The <img> stays a real element - a CSS
   background-image cannot be trusted below the second container
   (Elementor's lazy-load rule force-sets background-image:none
   until its observer fires) and this is the same reason the
   page-490 blooms are <img> too. Each one carries its original
   help-shape-1 / help-shape-2 class, so the size pin and the
   soften (section 1/1b of global.css, duplicated at 7/7b here)
   stay the single source of truth for the artwork; the classes
   below only place it.

   ISOLATION + z-index -1 rather than giving every section's
   content wrapper a z-index: .portfolio and .testimonials-section
   already ship position:relative;z-index:2 on their container,
   but .enq-team__inner is static, and making that relative would
   re-anchor whatever inside the team band is positioned against
   an ancestor above it. isolation:isolate keeps the -1 inside
   the section, above its own white background and below all of
   its content, and touches no descendant. Measured: 197
   descendants across the three sections, zero geometry changes.

   overflow:clip, NOT hidden. The corners are pushed a third of
   the way out of the box so only the falloff reads, and that
   overhang has to be clipped or the page scrolls sideways.
   clip is not a scroll container, so .testimonials-wrap's
   position:sticky survives it - verified pinned at the same
   offsets before and after. hidden would kill it.
   ---------- */
.enq-team,
.portfolio,
.testimonials-section{
  position: relative;
  isolation: isolate;
  overflow: clip;
}

/* max-width:none is load-bearing: the global img{max-width:100%}
   would otherwise clamp the blob to the section width and scale
   its height with it at every width below the blob's own. */
.enq-blob{
  position: absolute;
  z-index: -1;
  pointer-events: none;
  user-select: none;
  max-width: none;
}

/* left:auto / right:auto are not decoration. left + right + a
   fixed width over-constrains the box and in ltr the browser
   drops `right`, so a corner on the right edge only lands if
   `left` is released first (RULES -> help-shape blobs). */
.enq-blob--tl{ left: -200px;  right: auto;   top: -140px;    bottom: auto; }
.enq-blob--tr{ right: -200px; left: auto;    top: -140px;    bottom: auto; }
.enq-blob--bl{ left: -200px;  right: auto;   bottom: -160px; top: auto; }
.enq-blob--br{ right: -200px; left: auto;    bottom: -160px; top: auto; }

/* 991 is page 490's own bloom breakpoint (service.css), matched
   so the two pages shrink their glows at the same width. The
   two-class selector outscores the size pin in section 7. */
@media (max-width: 991px){
  .enq-blob.help-shape-1{ width: 460px; }
  .enq-blob.help-shape-2{ width: 500px; }
  .enq-blob--tl, .enq-blob--bl{ left: -150px; }
  .enq-blob--tr, .enq-blob--br{ right: -150px; }
  .enq-blob--tl, .enq-blob--tr{ top: -110px; }
  .enq-blob--bl, .enq-blob--br{ bottom: -120px; }
}
/* --- end 35 --- */


/* ---------------------------------------------------------------
   36. SHARP CORNERS  (2026-08-22)
   The homepage half of global.css section 26. Only the rules THIS
   file already owns are repeated here: section 29 rounds the Who
   We Help cards at (0,3,0)/(0,4,0) and the Our Services block at
   (0,4,0), and section 26 rounds the phone swipe cards -- none of
   which a (0,1,0)/(0,2,0) rule in global.css can reach.

   Both of those live inside media queries. A media query adds NO
   specificity, so these base rules beat their @media copies on
   source order alone, at every width.
   --------------------------------------------------------------- */

/* Who We Help. Four variant selectors because webflow-core ships
   ._02 rules at (0,3,0) for this component (RULES -> card
   surfaces) and section 29 already had to list all four. */
.features-item-wrap .features-item,
.features-item-wrap .features-item.features-item-1,
.features-item-wrap .features-item._02,
.features-item-wrap .features-item.features-item-1._02{ border-radius: 0; }

/* Our Services. The wrapper keeps carrying a radius even with no
   fill of its own, because an outer box-shadow follows the
   border-radius of the box it is cast from -- at 0 that shadow is
   now a sharp rectangle, which is the point. The first/last row
   corners exist only because the wrapper has no fill to round, so
   they go with it; the third selector is the <=767 swipe rail's
   own cards from section 26.
   NOT touched: the dashed hairlines above row 1 and below row 6.
   Those are a motif shared with .features-wrap, not corner chrome
   (RULES), and removing them is a separate design call.
   NOT added: overflow:hidden on the wrapper. It would round-and-
   clip for free and eat section 5's per-row hover lift (RULES). */
.service-item-wrap-02{ border-radius: 0; }
.service-item-wrap-02 .service-collection-item-box,
.service-item-wrap-02 > .service-collection:first-child .service-collection-item-box,
.service-item-wrap-02 > .service-collection:last-child .service-collection-item-box{ border-radius: 0; }
/* --- end 36 --- */

/* ===================================================================
   38. Founder quote band  (directly below the hero, page 10)
   Shipped 2026-08-26. It replaces the .enq-team photo band, which is
   commented out in _elementor_data on the same date.

   Layout is "B with C's quote": a navy panel starting 96px below the
   section top, with the cut-out portrait rising OUT of it, so the head
   clears the top edge. That overhang IS the treatment -- if the head
   ever sits inside the navy the section has silently broken.

   Every value is read off the live page: container 1300, navy #0f054d,
   chip = .section-title-text (14/700 upper, radius 42, pad 8 16 7),
   quote at the .subtitle section-heading size 58/64.

   Namespace is enqf-. enq- is taken (~48 live classes), enqb- is the
   blog, enqw- was the retired portfolio block. Asserted 0 occurrences
   of "enqf" in _elementor_data and in every custom sheet before this
   was written.

   ROLLBACK: truncate home.css to 105241 bytes AND roll page 10's
   _elementor_data to _elementor_data_bak_20260826_founder. Neither
   half is harmful alone -- the CSS is inert with no markup, and the
   markup unstyled is a plain portrait over a plain quote.
   =================================================================== */
.enqf{ position:relative; background:#fff; padding:100px 0; }
.enqf__wrap{ width:100%; max-width:1300px; margin:0 auto; }
.enqf__row{ position:relative; display:flex; align-items:stretch;
            min-height:430px; padding-top:96px; }
.enqf__panel{ position:absolute; left:0; right:0; top:96px; bottom:0;
              background:#0f054d; }
.enqf__inner{ position:relative; display:flex; align-items:flex-end;
              gap:64px; width:100%; padding:0 60px; }
.enqf__pic{ flex:0 0 460px; align-self:flex-end; }

/* max-width:none is load-bearing. The global img{max-width:100%} would
   otherwise clamp the portrait to its flex basis at narrow widths and
   pull the head back down inside the navy. */
.enqf__pic img{ display:block; width:460px; max-width:none; height:auto;
                margin-top:-96px; }

.enqf__txt{ flex:1; padding:0 0 60px; }
.enqf__chip{ display:inline-block; background:rgba(255,255,255,.14);
             color:#fff; font-weight:700; font-size:14px; line-height:1;
             text-transform:uppercase; padding:8px 16px 7px;
             border-radius:42px; }

/* webflow-core.css styles bare <blockquote> globally: 5px solid #e2e2e2
   rule, margin 0 0 10px, padding 10px 20px, 18/22 type. Every one of
   those is restated here rather than inherited -- the grey rule only
   read as white-on-navy by luck, and it is wanted, so it is declared. */
.enqf__q{ margin:22px 0 0; padding:2px 0 2px 26px;
          border-left:2px solid rgba(255,255,255,.55);
          color:#fff; font-size:58px; line-height:64px; font-weight:600;
          letter-spacing:-1px; }

/* ._02 is NOT globally the serif device -- it is scoped per component
   (.subtitle ._02 has it, .features-item-title ._02 does not), so this
   band has to declare its own. It renders as italic Arial: the site
   loads no webfonts at all. That is a known open item, not a bug here. */
.enqf__q ._02{ font-family:"IBM Plex Sans", sans-serif;
               font-style:normal; font-weight:700; }

.enqf__attr{ margin-top:30px; display:flex; align-items:center; gap:14px; }
.enqf__rule{ flex:none; width:38px; height:2px; background:#fff; }
.enqf__who{ margin:0; font-size:18px; line-height:1.35; font-weight:700;
            color:#fff; }
.enqf__role{ margin:0; font-size:16px; line-height:1.35; font-weight:400;
             color:rgba(255,255,255,.72); }

/* The site's own gutter below 1340 -- same breakpoint the team band's
   sizes attribute used (calc(100vw - 40px)). */
@media (max-width:1339px){ .enqf__wrap{ padding:0 20px; } }

/* 1200-1299 is the narrowest side-by-side band, and at 58px the quote
   takes one extra line there, which eats the overhang down to 46px.
   Dropping the type 8px puts the line count back and the overhang with
   it. This band exists ONLY to protect the overhang -- see the long
   note below the media queries. */
@media (min-width:1200px) and (max-width:1299px){
  .enqf__q{ font-size:48px; line-height:54px; letter-spacing:-.7px; }
}


/* ==== WHY THE COLUMNS STACK AT 1199 AND NOT AT 767 ==================
   In the SIDE-BY-SIDE layout the overhang is EMERGENT, NOT STRUCTURAL.
   The portrait is bottom-aligned so its crop stays hidden, which makes
      overhang = image height - text-column height
   and nothing pins it. As the viewport narrows the quote rewraps taller
   while the portrait gets shorter, so the overhang closes on its own:
   measured 96px at 1440, 60px at 1100, 8px at 992 and -21px at 768 --
   negative meaning the head had sunk fully inside the navy and the whole
   treatment was gone, with no error and nothing overflowing.
   So the stack MUST engage before the overhang reaches zero. 991 was
   tried first and was NOT far enough (8px at 992 is no overhang at all);
   1199 is the boundary that holds. In the stacked layout the overhang IS
   structural -- a column flex puts the portrait at the TOP of the box,
   so its negative margin pins the head a fixed 150px above the panel
   whatever the quote does underneath.
   ⚠⚠ IF THE QUOTE COPY IS EVER MADE LONGER, RE-MEASURE 1200-1340 FIRST.
   The side-by-side band is the half that degrades, it degrades silently,
   and it degrades from the narrow end. `mockups/shot/probe.mjs` reading
   (panel.top - img.top) is the check; it must stay comfortably positive.
   ==================================================================== */
@media (max-width:1199px){
  .enqf{ padding:70px 0; }
  .enqf__row{ min-height:0; padding-top:150px; }
  .enqf__panel{ top:150px; }
  .enqf__inner{ flex-direction:column; align-items:stretch;
                gap:0; padding:0 32px; }
  .enqf__pic{ flex:none; align-self:center; }
  .enqf__pic img{ width:300px; margin-top:-150px; }
  .enqf__txt{ padding:28px 0 44px; }
  .enqf__q{ margin:18px 0 0; padding-left:20px;
            font-size:34px; line-height:40px; letter-spacing:-.5px; }
  .enqf__attr{ margin-top:26px; }
}

@media (max-width:767px){
  .enqf{ padding:60px 0; }
  .enqf__inner{ padding:0 20px; }
  .enqf__pic img{ width:280px; }
  .enqf__txt{ padding:26px 0 38px; }
  .enqf__q{ padding-left:16px;
            font-size:28px; line-height:34px; letter-spacing:-.3px; }
  .enqf__attr{ margin-top:24px; }
}
/* --- end 38 --- */


/* ===================================================================
   SECTION 39 -- SECTION EYEBROW: NAVY RULE + CAPS (2026-08-26)

   Replaces the rounded uppercase pill on every homepage section head.
   Preet rejected that device sitewide ("these type of tags are all
   across the website. i dont like it") and picked option C out of
   mockups/tag-src/tag-variants.html: a 34x2 navy rule, a 14px gap,
   then the label at 13/700 uppercase with 1.9px tracking.

   The identical device ships in three other places in the same pass:
   service.css .sv-pill (built from mockups/service-src/v4-mock.html,
   never hand-edited), blog-single.css SS2 .enqb-pill, and .enqf__chip
   below. Change one, change all four.

   Why these selectors and not more: webflow-core.css declares
   .section-title-text at (0,1,0) and .section-title-text._02 at
   (0,2,0); home.css loads after it, so a plain rule at each of those
   specificities wins on source order alone. No !important needed and
   none used. The media-query copies in webflow-core add no
   specificity, so they are beaten too.

   inline-flex is inline-LEVEL, so the three centred heads
   (.section-title-wrap, align-items:center) still centre the device
   as a unit, and the one left-aligned head (.section-title-wrap-02)
   still left-aligns it. Nothing about the wrappers changes.

   .hero-tag-btn is deliberately NOT touched. It is named -btn, is not
   a button, and is a different device (16/600, not uppercase, 17 of
   them in the portfolio teaser). Preet has not been asked about those.

   ROLLBACK: truncate home.css to 111744 bytes
   (md5 ba43f8122ab158221389db3142c99e10). CSS only -- no markup
   changed anywhere in this pass, so there is nothing else to roll.
   =================================================================== */

.section-title{ border-radius:0; }

.section-title-text,
.section-title-text._02{
  display:inline-flex; align-items:center; gap:14px;
  background:transparent; background-color:transparent;
  border-radius:0; padding:0;
  font-size:13px; font-weight:700; line-height:1; letter-spacing:1.9px;
  text-transform:uppercase; color:rgba(0,0,0,.58); }

.section-title-text::before{
  content:''; flex:none; width:34px; height:2px; background:#0f054d; }

/* The founder band chip (SS38), on navy. The 16px margin-bottom is not
   styling -- it is exactly the height the old chip's padding used to
   contribute (8 + 14 + 7 = 29 against the new 13), and SS38's
   head-clears-the-navy overhang is EMERGENT (image height minus text
   column height) with nothing pinning it. Without this the text column
   shortens and the overhang silently grows. Keeping the column height
   identical means the whole SS38 geometry is untouched. */
.enqf__chip{
  display:inline-flex; align-items:center; gap:14px;
  background:transparent; border-radius:0; padding:0;
  font-size:13px; font-weight:700; line-height:1; letter-spacing:1.9px;
  text-transform:uppercase; color:rgba(255,255,255,.66);
  margin-bottom:16px; }

.enqf__chip::before{
  content:''; flex:none; width:34px; height:2px; background:#fff; }

/* ===================================================================
   40) FOUNDER BAND -- THE PORTRAIT IS A VIDEO, AND IT CARRIES ITS OWN
       ALPHA (2026-08-27, replaces the 08-26 CSS-mask version)

   ⛔⛔ THERE IS NO mask-image HERE ANY MORE, AND PUTTING ONE BACK
   REINTRODUCES THE BUG IT IS HERE TO FIX. The clip's silhouette now
   lives in the file, one matte per frame, from
   harshal-founder-v2.webm. -mask-v1/-v2/-v3.png are still on the
   server for rollback and nothing in any stylesheet references them.

   WHY THE STATIC MASK HAD TO GO. Over the 97 source frames he leans
   in and grows about 8%, so one stencil for all frames has only two
   settings and both are visible on the navy:

     too big   -> the clip's white backdrop leaks round hair, ear,
                  shoulder and forearm, and PULSES with the motion
                  (that was -mask-v1, his "white gaps");
     too small -> it slices him. -v2/-v3 intersected the stencil with
                  every frame's key, which IS the "too small" setting:
                  a pixel survived only if all 97 frames agreed, so at
                  the frames where he is bigger the top of his hair was
                  cut FLAT and the shoulder cut square. Invisible at
                  frame 0, which is the only frame anyone screenshots,
                  and obvious the moment it plays.

   Per-frame alpha has nothing to drift out of, so the erosion, the
   2px pull-in and the y=126..134 compromise band are all gone with it
   and he is no longer shaved. Measured on the same directional edge
   test (excess luma at the edge over navy vs the subject 4px inward,
   restricted to dark cloth, max / >120 / >80 across the clip):
   static -v3 189/269/501 -> per-frame alpha 126/2/112.

   ⚠⚠⚠ VP8, NOT VP9 -- CHROME REJECTS A VP9 ALPHA FILE OUTRIGHT
   (readyState 0, MediaError 4, blank). ffmpeg writes and reads one
   back perfectly, so it looks correct everywhere except the browser.
   canPlayType is no help: it says 'probably' for vp9 on the build
   that then refuses the file, because alpha is not part of a codec
   string. Rebuild only with mockups/founder-src/dewatermark.mjs.

   ⚠⚠ THE POSTER IS -v2 AND IT MUST STAY THE ALPHA ONE. -poster-v1 is
   frame 0 complete with its white backdrop, which was fine when the
   CSS mask cut the whole element, poster included. With no mask it
   would paint a white rectangle on the navy until the clip decodes.

   ⚠⚠⚠ THE WIDTHS ARE NOT THE STILL'S WIDTHS. DO NOT 'TIDY' THEM.
   The still is 920x986, the clip 612x640. Give the video the still's
   460px and its box comes out 33px SHORTER -- and section 38's
   overhang is `image height - text height`, so a shorter box silently
   eats it with nothing in the DOM to notice. 471 makes the box 492.5
   tall, i.e. the still's 493; the same arithmetic gives 307 stacked
   and 287 on phone.

   THE TEMPO IS NOT IN THIS FILE AND NOT IN THE CLIP. custom-js/home.js
   sets playbackRate 0.65, stretching the cycle from 8s to 12.3s.

   ROLLBACK: truncate home.css to 114853 bytes AND roll page 10's
   _elementor_data to _elementor_data_bak_20260827_prealpha, AND
   truncate custom-js/home.js to 21266 bytes. The CSS is inert with no
   <video> in the markup and the markup is an unsized clip without it,
   so no half is dangerous alone.
   =================================================================== */
.enqf__pic video{ display:block; width:471px; max-width:none; height:auto;
                  margin-top:-96px; }

.enqf__still{ display:none; }

@media (max-width:1199px){
  .enqf__pic video{ width:307px; margin-top:-150px; }
}

@media (max-width:767px){
  .enqf__pic video{ width:287px; }
}

/* ---- no-alpha fallback ----------------------------------------------
   home.js draws one frame to a canvas and reads the top-left pixel. If
   it comes back OPAQUE the browser decoded the clip but threw the alpha
   away (Safari), and a white rectangle is sitting on the navy -- so it
   puts .enqf--still on the wrapper and this swaps the flat cut-out back
   in. The same class is set when the clip fails to load at all.
   ⚠ THE STILL IS A BACKGROUND-IMAGE ON AN EMPTY SPAN, NOT AN <img>,
   AND THAT IS THE WHOLE POINT: an <img> in the markup is fetched even
   at display:none (proved on this site with the help-shape blobs;
   loading=lazy did not stop it either), so every visitor would pay
   100KB for a fallback almost none of them see. A background-image
   behind a class that JS only sets when it is needed is never fetched
   otherwise. Sizes are the still's own box, not the video's.
   -------------------------------------------------------------------- */
.enqf__pic.enqf--still video{ display:none; }
.enqf__pic.enqf--still .enqf__still{
  display:block; width:460px; height:493px; margin-top:-96px;
  background:url(/wp-content/uploads/assets/founder/harshal-w920.webp) 0 0/100% 100% no-repeat; }

@media (max-width:1199px){
  .enqf__pic.enqf--still .enqf__still{ width:300px; height:322px; margin-top:-150px; }
}

@media (max-width:767px){
  .enqf__pic.enqf--still .enqf__still{ width:280px; height:300px; }
}

/* ---- reduced motion -------------------------------------------------
   Handled in CSS, not JS, so it still works with scripting off.
   home.js reads the same query and skips both the rate and the probe.
   -------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce){
  .enqf__pic video{ display:none; }
  .enqf__still{ display:block; width:460px; height:493px; margin-top:-96px;
                background:url(/wp-content/uploads/assets/founder/harshal-w920.webp) 0 0/100% 100% no-repeat; }
}

@media (prefers-reduced-motion: reduce) and (max-width:1199px){
  .enqf__still{ width:300px; height:322px; margin-top:-150px; }
}

@media (prefers-reduced-motion: reduce) and (max-width:767px){
  .enqf__still{ width:280px; height:300px; }
}
/* --- end 40 --- */


/* ===== 41. PORTFOLIO NUMERALS -- NO SYNTHESISED OBLIQUE ==============
   Preet, 2026-09-04, same pass as section 11. webflow-core sets
   .number-text{font-style:italic;font-weight:700} on the outlined
   01/02/03. The 700 is real -- Urbanist is a variable face carrying
   400..700 -- but the ITALIC is not: the import buys no italic axis, so
   the browser skews the upright outline instead, and a hollow letter
   shows a skew far more than a solid one would.

   TWO classes on purpose. webflow-core's own .number-text is (0,1,0) and
   a bare .number-text here would only TIE with it, leaving the result
   riding stylesheet source order; .teaser-copy .number-text is (0,2,0)
   and wins outright. Nothing else on the site uses .number-text. */
.teaser-copy .number-text{ font-style: normal; }
/* --- end 41 --- */