@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400..700&display=swap");

/* ============================================================
   0. THE SITEWIDE WEBFONT ------------------------------------
   Preet's call 2026-09-04: "go with urbanist sitewide". Until
   today this @import lived only in service.css, so the six
   service pages rendered in Urbanist while every other page
   silently fell back to Arial, and to Times wherever Instrument
   Serif was asked for. Two fonts, one site.

   It MUST be the first rule in this file. A CSS @import is
   ignored once any other rule has been seen.

   Urbanist is requested as a VARIABLE range: one file of
   27,752 B, replacing the four static weight files service.css
   used to pull, 111,008 B together. The range is 400..700 and
   not wider on purpose: the five webflow-core rules asking for
   weight 300 clamp to 400, exactly as they did against the
   static set, so the six service pages do not shift a pixel.
   service.css no longer imports anything -- this is the only
   place the site asks for a font.
   ============================================================ */

/* ============================================================
   enquad.en3.support — site-wide overrides (global.css)
   Loaded on EVERY page, after the Webflow CSS and BEFORE the
   per-page file, so a page file can still win by cascade order.
   Home this file to chrome that is shared by every page:
   the header template (#358) and the footer template (#420).
   ============================================================ */

/* 1. FOOTER BLOBS ------------------------------------------
   The footer's two decorative shapes used to be styled only by
   home.css (§7 / §7b), because the footer lived inside the home
   page's HTML widget. The footer is one global template now, so
   its blob treatment has to be global too — otherwise the home
   page footer reads as a soft glow and every other page's reads
   as a hard-edged graphic.

   Size pin: the <img> tags carry no width/height, so they render
   at the source file's intrinsic size. Pinning the box to the
   original 632/686 footprint makes render size independent of
   whatever resolution the asset happens to be.
   Soften: fade + desaturate + blur so it reads as a faint radial
   glow rather than a solid shape. Values match home.css §7b. */

/* 1b. HELP-SHAPE SCOPE
   2026-08-06: un-scoped from .footer to site-wide. help-shape-1/2.avif
   are now Enquad's own brand blobs (solid + saturated) and are used in
   the home page body, the 6 service page bodies and the footer. The
   soften treatment has to follow the asset, not the location. */
.help-shape-1{ width: 632px; height: auto; }
.help-shape-2{ width: 686px; height: auto; }
.help-shape-1,
.help-shape-2{
  opacity: 0.45;
  filter: saturate(0.95) brightness(1.22) blur(16px);
}

/* 2. CONTACT PAGE — trim the footer's CTA block --------------
   The global footer is two stacked blocks:
     .footer-box       -> "Have a project in mind?" + phone/email
                          + the 4-field contact form
     .footer-box._03   -> address, socials, copyright, links
   The Contact page already has that exact form in its own body,
   so it shipped with only the second block. One global template
   would put a duplicate form directly under the page's own —
   hide the CTA block there instead of forking the template.
   Scoped to the page id so it cannot leak to another page. */
body.page-id-105 .footer .footer-box:not(._03){
  display: none;
}

/* 3. HEADER LOGO — site-wide --------------------------------
   The logo <img> carries no width/height attributes and is
   loading="lazy". Its only size rule lived in home.css
   (height:var(--enq-logo-h)), which is enqueued for page 10 only,
   so on the other 7 pages the image had no intrinsic size, laid
   out 0x0, and the lazy loader never fired — no logo at all, at
   any width. The header is one global template (#358) now, so its
   sizing belongs here. Values match home.css so the two agree. */
.navbar .navbar-brand img.logo{
  height: 44px;
  width: auto;
  max-width: none;
  object-fit: contain;
  /* The source is 2250x538 and the tag has no width/height, so
     before it loads the box is 0 wide — and a zero-area image is
     never intersected, so loading="lazy" never fires it: the logo
     could not load because it had no size, and had no size because
     it had not loaded. aspect-ratio breaks the deadlock by giving
     the box a definite width up front, and removes the layout
     shift when the file arrives. */
  aspect-ratio: 2250 / 538;
}

/* .navbar-brand ships as width:23.33% with justify-content:center,
   and .header-left as 33.33%. At 44px tall the logo is 184px wide —
   always wider than that brand box — so it overhangs it on both
   sides. Above roughly 1340px the .container inset absorbs the
   overhang and it looks fine; below that the left edge is clipped
   off-screen (measured x = -24 at a 1265px viewport). Shrink-wrap
   both boxes and start them at the left edge. .container-navbar is
   justify-content:space-between, so brand-left / menu-right holds
   with no further rule. */
.header-left{
  width: auto;
  flex: 0 0 auto;
}
.navbar-brand,
.navbar-brand.w--current{
  width: auto;
  justify-content: flex-start;
}
/* .content ships with auto side margins, so it centres itself in
   whatever space .header-left leaves rather than sitting at the right
   edge — and it drifted 112px further inward once the brand box above
   was shrink-wrapped. home.css already pinned it right for page 10;
   the header is one global template now, so the pin belongs here. */
.container-navbar .content{
  margin-left: auto;
  margin-right: 0;
}

/* 4. MOBILE HEADER — keep the brand -------------------------
   Stock Webflow hides .header-left below 768px, so phones got a
   bare "Menu" pill and no branding. Show it and let both boxes
   shrink-wrap the image: the stock percentage widths (33.33% on
   .header-left, 40% on .navbar-brand at <=479) are far narrower
   than the logo at 34px tall (2250x538 source => ~142px wide),
   which would push it out of its own box. .container-navbar is
   justify-content:space-between, so brand-left / menu-right holds
   without any extra rule. */
@media screen and (max-width: 767px){
  .header-left{ display: flex; }
  .navbar .navbar-brand img.logo{ height: 34px; }
}

/* 5. PHONE GUTTERS ------------------------------------------
   Every section shell drops to 10px side padding at <=479px —
   and every phone is 360-430px CSS wide, so the whole site read
   as text jammed against the screen edge. 20px matches what the
   template already uses at <=991/<=767, so this is the missing
   step of its own rhythm rather than a new value. box-sizing is
   border-box site-wide, so widening padding cannot cause overflow. */
@media screen and (max-width: 479px){
  .hero, .patner-section, .features-section, .service-section,
  .portfolio, .portfolio-section, .choose-section, .testimonials-section,
  .project-section, .insights-section, .insights-section-03,
  .contact-section, .service-single, .bolg-details, .footer-box{
    padding-left: 20px;
    padding-right: 20px;
  }
}
/* .features-section is the one section still on 10px in the
   480-767 band; bring it in line with its neighbours. */
@media screen and (max-width: 767px){
  .features-section{ padding-left: 20px; padding-right: 20px; }
  /* and the header band, so the logo lines up with the content under
     it (.navbar ships 1rem below 768 and 10px below 480). */
  .navbar{ padding-left: 20px; padding-right: 20px; }
}

/* 6. HERO ALIGNMENT ON MOBILE -------------------------------
   .hero-left is width:80% at every breakpoint (home.css already
   overrides it to 100% at >=992px). On a phone that started the
   headline 47px in while the tag pills directly below it started
   at the 10px gutter — two different left edges in one block. */
@media screen and (max-width: 991px){
  .hero-left{
    width: 100%;
    /* it also ships align-items:center below 992, which centres the
       shrink-wrapped heading boxes while their text stays left-aligned
       — so the headline still sat 15px in from the pills below it. */
    align-items: flex-start;
  }
}

/* 7. TOUCH TARGETS + WRAP SAFETY ----------------------------
   Form inputs render 38px tall, under the 44px touch minimum.
   overflow-wrap is insurance: long unbroken strings (a URL or an
   address dropped into a heading later) would otherwise be the
   one thing that can still force a sideways scroll. */
@media screen and (max-width: 767px){
  .form-input{ min-height: 46px; }
  h1, h2, h3, h4, h5, h6, p{ overflow-wrap: break-word; }
}

/* 8. STICKY HEADER -- the two bits Elementor has no control for ---
   Sticky itself is NOT here. It lives on the header template (#358),
   root container d1e197d: Advanced > Motion Effects > Sticky = Top,
   sticky_on desktop/tablet/mobile, offset 0, z-index 999. The
   translucent white ground and the 0 1px 24px shadow are Elementor
   container settings too (Background: Classic, plus Box Shadow), so
   all 8 pages get an identical bar from one edit in one template.
   It used to be position:sticky in home.css section 14, scoped to
   .elementor-10 -- which is exactly why it only stuck on the home page.

   a) backdrop-filter has no Elementor control, and the frosted blur is
      what keeps the dark logo legible over the dark portfolio and
      testimonial sections. One declaration, id-independent. NOTE the
      descendant selector: Elementor sticky wraps the element in a
      .elementor-sticky__spacer on activation, so a child combinator
      would stop matching the moment the bar sticks.
   b) .pagtitle-shap / .pagtitle-shap-2 are decorative img children of
      .navbar, absolutely positioned by the Webflow sheet (inset:0 auto
      auto 4% / 14%). .navbar is 84px tall with overflow:visible, so the
      art spills far below the bar; once the header is fixed it rides
      down the page and washes over every section. Was home.css section
      17, i.e. home only -- global now that every page sticks.
      Do NOT use .navbar{overflow:hidden} instead: .w-nav-overlay (the
      mobile menu) is a sibling child and would get clipped. */
.elementor-358 .elementor-element-d1e197d{
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}
.pagtitle-shap,
.pagtitle-shap-2{
  display: none;
}

/* 9. ELEMENTOR DEVICE-MODE PROBE -- restores responsive JS ---
   Every Elementor Pro feature that is device-conditional asks
   elementorFrontend.getCurrentDeviceMode(), which is literally
       getComputedStyle(span#elementor-device-mode, :after).content
   and nothing else. Those content rules ship in elementor-frontend.css,
   which functions.php Nuclear Mode deregisters -- so the call returned
   the string none on every viewport, and any handler comparing it
   against desktop/tablet/mobile silently did nothing.
   That is why the sticky header did not activate even with sticky:top
   set on the container: sticky_on is [desktop,tablet,mobile] and none
   is in none of them.
   Values match this install: mobile <=767, tablet <=1024, desktop above
   (elementorFrontend.config.responsive.activeBreakpoints, defaults, no
   custom breakpoints). If breakpoints are ever customised in Site
   Settings, these two numbers must follow.
   The span is 0-content and .elementor-screen-only is unstyled here
   (same dequeue), so it is clipped explicitly -- otherwise the word
   desktop prints at the bottom of every page. */
#elementor-device-mode{
  position: absolute;
  top: -10000em;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}
#elementor-device-mode:after{ content: 'desktop'; }
@media screen and (max-width: 1024px){
  #elementor-device-mode:after{ content: 'tablet'; }
}
@media screen and (max-width: 767px){
  #elementor-device-mode:after{ content: 'mobile'; }
}


/* ---------------------------------------------------------------
   10. HEADER MENU PILL -- solid brand navy   (2026-08-04)
   The Menu pill shipped its colour as a gradient background IMAGE
   (uploads/nexostudio/images/btn.avif, 1426 B) on .hamburger-wrap;
   .hamburger.js-hover itself is transparent, so recolouring the
   button means killing that image, not overriding a colour.
   Kept on .hamburger-wrap so the 63px radius / 43px height already
   on the wrap keep clipping it.
   Safe from IX2: the ix2 chunk targets .hamburger.js-hover._03 only
   (a variant this markup does not carry) and has ZERO references to
   .hamburger-wrap, so nothing animates this background. js-hover is
   a dead class here too -- neither webflow-script file mentions it.
   Site-wide, because the header is template #358 on all 8 pages.
   White .menu-text and the two white lines stay as-is: 17.0:1 on
   #0f054d, and the pill reads clearly against the frosted white
   sticky bar from section 8. */
.navbar .hamburger .hamburger-wrap{
  background-image: none;
  background-color: #0f054d;
}
/* ---------------------------------------------------------------
   11. HEADER MENU -- full-screen overlay rebuilt as a DROPDOWN
                                                    (2026-08-05)
   Stock: .global-menu was a 100vh absolute overlay inside .navbar,
   opened by Webflow IX2 lists a-5/a-6 (Menu Open / Menu Close),
   which slid two black half-curtains (.global-menu-shap-left and
   -right) across the screen, plus a-3/a-4 for the burger-to-X. All
   of that bound to data-w-id 71730281-... on .hamburger, and every
   .global-menu__item ALSO fired a-6 + a-4 on click. Those six
   attributes are now data-enq-w-id in template 358, so IX2 registers
   no event on the header menu at all and writes no inline style
   here. The .container-navbar w-id (slideInTop / slideInBottom
   preset) is deliberately left bound -- that is the header entrance
   animation. a-3..a-6 have exactly one other trigger in the whole
   IX2 chunk (e-696, element a9fc7156-...), and that element exists
   only in post revisions, never on a rendered page -- checked.
   Open/close now lives in custom-js/global.js, which toggles
   .enq-menu-open on .navbar.

   Everything below beats the Webflow rules on specificity alone
   (.navbar .global-menu = 0,2,0 vs .global-menu = 0,1,0), which is
   what lets it win inside webflow.css media queries too, since a
   media query adds no specificity. No !important anywhere.

   ANCHORING. .global-menu stays exactly where it is in the DOM, a
   direct child of .navbar (position:relative, z-index:9999). It is
   deliberately NOT moved inside .container-navbar: that element
   carries the slideIn preset, so IX2 writes inline transform and
   opacity onto it, and a panel nested there would inherit both. To
   line its right edge up with the Menu pill it re-creates the
   geometry of .container instead -- stretched across the padding box
   of .navbar, max-width 1340px (= .container 1300 + 2 x .navbar
   padding 20) with margin-inline:auto and a matching padding-inline.
   Below 1340 the max-width stops binding and the padding alone
   reproduces the inset, so the two boxes agree at every width.
   .container is overflow:visible at every breakpoint, and no
   ancestor clips, so nothing cuts the panel off.
*/
.navbar .global-menu{
  position: absolute;
  top: calc(100% + var(--enq-menu-gap, 10px));
  left: 0;
  right: 0;
  bottom: auto;
  width: auto;
  height: auto;
  max-width: 1340px;
  margin-inline: auto;
  padding-inline: 20px;
  box-sizing: border-box;
  background-color: transparent;
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
  z-index: 1000;

  /* Closed state. visibility, not display, is what takes it out of
     the a11y tree and out of hit-testing while still allowing a
     transition; the delay holds it visible for the length of the
     fade and then drops it in one step. */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .2s ease, visibility 0s linear .2s;
}
.navbar.enq-menu-open .global-menu{
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* The layer is the full 1340px rail but only its right-hand 264px is
   ever painted, so it must NEVER take pointer events -- otherwise the
   invisible strip beside the panel eats clicks meant for the page
   underneath, and the outside-click close treats them as clicks
   inside the menu. Hit-testing belongs to the panel alone; auto on a
   child of a none parent re-enables it for exactly that subtree. */
.navbar.enq-menu-open .global-menu__wrap{ pointer-events: auto; }

/* The two black half-curtains only ever made sense full-screen.
   Left in the markup so this is reversible from CSS alone. */
.navbar .global-menu-shap-left,
.navbar .global-menu-shap-right{ display: none; }

/* THE PANEL. .global-menu__wrap already wraps the five links, so it
   becomes the card and no markup is added. Brand navy #0f054d is the
   same value as the Menu pill in section 10, so the panel reads as
   that pill opening rather than as a new surface. Radius 20px: the
   vocabulary of this site is the 63px pill, which on a 264px block
   would read as a lozenge, so this is the card-sized member of the
   same family. Stock here is 52px/700 with 40px items -- all gone.
   The scale/translate entrance hangs off the top-right corner, i.e.
   the Menu pill, so it grows out of the control that opened it. */
.navbar .global-menu__wrap{
  width: 264px;
  max-width: 100%;
  max-height: calc(100vh - 120px);
  overflow-y: auto;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 2px;
  padding: 10px;
  background-color: #0f054d;
  border-radius: 20px;
  box-shadow: 0 18px 40px rgba(15, 5, 77, .22), 0 2px 6px rgba(20, 24, 31, .10);
  color: var(--white);
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
  transform: translateY(-8px) scale(.98);
  transform-origin: 100% 0;
  transition: transform .28s cubic-bezier(.2, .8, .25, 1);
}
.navbar.enq-menu-open .global-menu__wrap{ transform: none; }

/* Items. 16px/500 matches .menu-text on the pill exactly, so the
   open menu is the pill label repeated down the panel. The hover
   chip is a rounded rectangle rather than an underline because
   every other interactive surface on this site is a filled shape. */
.navbar .global-menu__item{
  display: block;
  margin: 0;
  padding: 12px 16px;
  border-radius: 12px;
  background-color: transparent;
  color: var(--white);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: .01em;
  text-decoration: none;
  opacity: 0;
  transform: translateY(-4px);
  transition: background-color .18s ease, color .18s ease, opacity .22s ease, transform .22s cubic-bezier(.2, .8, .25, 1);
}
.navbar .global-menu__item:hover,
.navbar .global-menu__item:focus-visible{
  color: var(--white);
  background-color: rgba(255, 255, 255, .10);
}
.navbar .global-menu__item:focus-visible{
  outline: 2px solid rgba(255, 255, 255, .55);
  outline-offset: 1px;
}
/* Webflow marks the page you are on with .w--current. */
.navbar .global-menu__item.w--current{ background-color: rgba(255, 255, 255, .14); }

/* Stagger. The delay is applied only to the two entrance properties
   (3rd and 4th in the transition list above) so hover stays instant
   once the panel has settled. */
.navbar.enq-menu-open .global-menu__item{
  opacity: 1;
  transform: none;
  transition-delay: 0s, 0s, var(--enq-stagger, 0s), var(--enq-stagger, 0s);
}
.navbar.enq-menu-open .global-menu__item:nth-child(1){ --enq-stagger: .04s; }
.navbar.enq-menu-open .global-menu__item:nth-child(2){ --enq-stagger: .08s; }
.navbar.enq-menu-open .global-menu__item:nth-child(3){ --enq-stagger: .12s; }
.navbar.enq-menu-open .global-menu__item:nth-child(4){ --enq-stagger: .16s; }
.navbar.enq-menu-open .global-menu__item:nth-child(5){ --enq-stagger: .20s; }

/* BURGER TO X, replacing IX2 lists a-3/a-4 in CSS. Geometry, since
   the numbers are not guessable: .btn-2 is 21x21, column, centred,
   holding .line-top (2px + 6px margin-bottom) over .line-bottom
   (2px, 60% wide, right-aligned by align-items:flex-end). That is a
   10px stack in 21px, so the lines sit at y 6.5 and y 14.5 and the
   midpoint is 10.5 -- hence exactly 4px of travel each, towards each
   other. A 21px line at 45deg needs ~4.4px outside the box on each
   side, so .btn-2 has to stop hiding its overflow. .hamburger-wrap
   has a 63px radius but no overflow:hidden, so nothing clips there.
   Safe to transform: after the data-w-id rename no IX2 list can
   fire on these lines, and there is no !important here, so nothing
   is frozen the way a transform:none !important would freeze it. */
.navbar .hamburger .btn-2{ overflow: visible; }
.navbar .hamburger .line-top,
.navbar .hamburger .line-bottom{
  transform-origin: 50% 50%;
  transition: width .24s ease, transform .3s cubic-bezier(.2, .8, .25, 1);
}
.navbar.enq-menu-open .hamburger .line-top{ transform: translateY(4px) rotate(45deg); }
.navbar.enq-menu-open .hamburger .line-bottom{ width: 100%; transform: translateY(-4px) rotate(-45deg); }

/* The pill is a div with role=button + tabindex=0 (kept a div on
   purpose: swapping it for a real <button> would have meant
   re-balancing the tag tree inside a live template for no styling
   gain). Keyboard activation is handled in custom-js/global.js. */
.navbar .hamburger{
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
}
.navbar .hamburger:focus-visible{
  outline: 2px solid #0f054d;
  outline-offset: 3px;
  border-radius: 63px;
}

/* Breakpoints. padding-inline has to track .navbar padding exactly or
   the panel edge stops agreeing with the pill -- and that value is a
   flat 20px at every width, NOT the stock Webflow 20 / 1rem / 10px:
   section 5 above already overrides .navbar to 20px below 768 so the
   logo lines up with the content beneath it. So the base rule covers
   all three bands and there is nothing to restate here. Measured at
   390 with the stock values in place, the panel sat at x=10 while the
   logo sat at x=20 -- that is the bug this comment exists to prevent.
   Below 768 the panel goes full width of the inset instead: at 390px
   a 264px card leaves an odd sliver, and the pill is close enough to
   the edge that a full-width sheet reads as one gesture. */
@media screen and (max-width: 767px){
  .navbar .global-menu{ --enq-menu-gap: 8px; }
  .navbar .global-menu__wrap{ width: 100%; }
}

/* Reduced motion: the panel still opens and closes, it just stops
   moving. Nothing here is a teardown -- unlike the tag marquee,
   a still dropdown loses no reachability. */
@media (prefers-reduced-motion: reduce){
  .navbar .global-menu,
  .navbar .global-menu__wrap,
  .navbar .global-menu__item,
  .navbar .hamburger .line-top,
  .navbar .hamburger .line-bottom{
    transition-duration: .01ms;
  }
  .navbar.enq-menu-open .global-menu__item{ transition-delay: 0s; }
  .navbar .global-menu__wrap{ transform: none; }
  .navbar .global-menu__item{ opacity: 1; transform: none; }
}


/* 12. FOOTER BACKGROUND IMAGE -------------------------------
   Preet supplied 001.jpg (19850x5830, 17.2 MB JPEG) and asked
   for it as the footer background. Baked to
   uploads/assets/bg/footer.avif -- 3840x1128, 42.9 KB.

   Painted on .footer-box._03 -- the LOWER footer row ONLY: the
   one holding the address, the social icons and the copyright
   line. It first went on section.footer, i.e. the whole #420
   template, which put the art behind the contact/form row as
   well; on 2026-08-06 Preet asked for it in the bottom section
   only, so the form row above the divider is plain white again.
   Still template #420, so still every page, still one rule.

   Sizing: _03 is a fraction of the full footer height (the
   1585x1165 at vw 1600 quoted in the old note was the WHOLE
   footer box), and the art is 3.4:1, so cover crops the sides
   far less here than it did across the full box -- this row is
   much closer to the aspect of the art. The 3840 px source
   still keeps the crop sharp: cover scales it to about the
   footer width at desktop, a downscale, not an upscale.

   Left alone on purpose: section.footer keeps background-color:
   var(--white) and overflow: hidden from webflow.css, so white
   is now the ground for the form row and still the matte and
   404 fallback under the art; .footer-box._03 keeps its
   z-index: 2 and its border-bottom-style: none. Only the
   background-image longhands are set here. */
.footer .footer-box._03{
  background-image: url(/wp-content/uploads/assets/bg/footer.avif);
  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;
}

/* ============================================================
   13.  PAGE LOADER  --  added 2026-08-06
   ------------------------------------------------------------
   Markup is injected on wp_body_open by the sandbox loader
   (enq-custom-css-loader.php, priority 1). Assets:
     /uploads/assets/video/loader-v1.mp4         20456 B
     /uploads/assets/video/loader-still-v1.avif   2767 B
   Clip is 448x336, 2.767s. It fades to white at the tail so the
   loop point is white-to-white and never jump-cuts back to the
   blank first frame.

   FILENAMES ARE VERSIONED ON PURPOSE. Video and images here
   serve max-age=315360000 with no ?ver=, so replacing one in
   place is invisible to any browser that already has it. Bump
   to -v2 rather than overwriting.

   THE FAILSAFE ANIMATION BELOW IS LOAD-BEARING. It hides this
   overlay at 8s with no JavaScript involved at all, and it is
   the only thing standing between a script error and a white
   screen nobody can click through. Do not remove it. Do not
   let it animate opacity either: a running animation outranks
   the transition the script drives, so it owns visibility and
   pointer-events only, and opacity is left to the script.
   ============================================================ */
#enq-loader{
  position: fixed;
  inset: 0;
  z-index: 2147483000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 32px;
  background: #fff;
  opacity: 1;
  transition: opacity .45s ease;
  animation-name: enq-loader-failsafe;
  animation-duration: 8s;
  animation-timing-function: step-end;
  animation-fill-mode: forwards;
}
#enq-loader.is-out{
  opacity: 0;
  pointer-events: none;
}
@keyframes enq-loader-failsafe{
  from{ visibility: visible; pointer-events: auto; }
  to  { visibility: hidden;  pointer-events: none; }
}

#enq-loader .enq-loader__stage{
  position: relative;
  width: min(224px, 46vw);
  aspect-ratio: 4 / 3;
}
#enq-loader .enq-loader__vid{
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Still fallback, for autoplay blocked (iOS Low Power Mode is
   the one that has bitten this site before), a decode failure,
   or reduced motion. Declared as a background image so the AVIF
   is only ever fetched when this state actually applies -- in
   the normal path it costs nothing. Safe from Elementor's
   lazy-load background-image:none rule, which is scoped to
   .e-con descendants; this overlay is a direct child of body. */
#enq-loader.is-still .enq-loader__vid{ display: none; }
#enq-loader.is-still .enq-loader__stage{
  background-image: url(/wp-content/uploads/assets/video/loader-still-v1.avif);
  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: contain;
}

/* The bar is the reason a settled logo never reads as a hang --
   it keeps moving for as long as the overlay is up. */
#enq-loader .enq-loader__bar{
  position: relative;
  overflow: hidden;
  width: min(170px, 38vw);
  height: 2px;
  border-radius: 2px;
  background: rgba(15, 5, 77, .12);
}
#enq-loader .enq-loader__bar i{
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 38%;
  border-radius: 2px;
  background: #0f054d;
  animation-name: enq-loader-sweep;
  animation-duration: 1.15s;
  animation-timing-function: cubic-bezier(.65, .05, .36, 1);
  animation-iteration-count: infinite;
}
@keyframes enq-loader-sweep{
  from{ transform: translateX(-105%); }
  to  { transform: translateX(370%); }
}

/* Longhands throughout, per house rule: the shorthand would
   reset animation-iteration-count here and the bar would run
   exactly once. */
@media (prefers-reduced-motion: reduce){
  #enq-loader{ transition: none; }
  #enq-loader .enq-loader__bar i{
    width: 100%;
    animation-name: enq-loader-pulse;
    animation-duration: 1.6s;
    animation-timing-function: ease-in-out;
  }
  @keyframes enq-loader-pulse{
    from, to{ opacity: .2; }
    50%     { opacity: .85; }
  }
}

/* ============================================================
   14.  FOOTER CONTACT + FORM ROW  --  added 2026-08-07
   ------------------------------------------------------------
   Scope: .footer .footer-box:not(._03) -- the UPPER footer row,
   the one holding the two display headings, the phone/email pair
   and the enquiry form. Live on 7 pages; section 2 above hides it
   on /contact, which carries its own copy of the same form.

   Why this section exists. Section 12 moved the footer artwork
   down onto ._03 on 2026-08-06, which left this row as plain
   unbranded white. Two real defects were sitting in it as well:

     1. The contact details were a flat four-item column: icon
        anchor, then the number on its own line, then icon anchor,
        then the address on its own line. The icon and its value
        were two separate links to the same href on two different
        lines, and no label anywhere said which value was which --
        you had to decode a 20px glyph.
     2. .form-input:focus in webflow-core.css sets
        border-bottom-color to var(--input-border), the value the
        border already has, so focusing a field changed nothing on
        screen. Keyboard and screen-magnifier users had no cue.

   Design language is lifted from the frosted-card idiom the site
   already uses on .choose-item, .testimonials-item and the
   .contact-info-item block on /contact: translucent white,
   backdrop blur, 16px radius, a hairline in --border-color-five
   and the navy-tinted soft shadow --info-shadow. The brand navy
   #0f054d is the same value section 10 uses for the header menu
   pill -- that pill and this submit button are the two primary
   actions on any page, so they share one colour.

   IX2 WARNING. Every element in this row carries a
   SCROLL_INTO_VIEW -> slideInBottom entrance, ELEMENT-bound by
   data-w-id and staggered 200/300/400/500ms across the headings
   and the two contact links, then on through the four fields and
   the button (events e-159 .. e-175). IX2 therefore owns the
   inline transform AND the inline opacity on .call-link, on every
   .form-input and on .message. Nothing in this section may set
   either property on those elements. The submit hover lift uses
   the translate LONGHAND, which composes with transform rather
   than replacing it; a transform here would be overwritten
   mid-entrance, and a transform:none would freeze the entrance
   solid the way it once did on the project fan.
   ============================================================ */

/* THE ROW GROUND. A whisper of navy over the white that
   section.footer still supplies. It has to stay this faint: both
   help-shape blobs are positioned at the top of .footer-wrap,
   i.e. directly behind this row, and .footer-box sits above them
   at z-index 2, so anything close to opaque here would cover the
   brand glow. A background-COLOR rather than a gradient on
   purpose -- Elementor lazy-loading forces background-image:none
   on everything below the 2nd container until .e-lazyloaded
   lands, which is why the ._03 artwork only paints on scroll. A
   colour is immune to that and paints on the first frame. */
.footer .footer-box:not(._03){
  background-color: rgba(15, 5, 77, .028);
}

/* COLUMN SPLIT. Webflow ships .footer-item at 95% and
   .footer-item-left at 50%, i.e. 145% into a flex row so both
   shrink -- which left the form column about 368px wide once its
   80px inner padding came off, too narrow for a panel. 62/38
   sums to 100%, so nothing shrinks and the widths are the widths.
   Both selectors are reached THROUGH .footer-box:not(._03),
   because ._03 reuses the same two base classes for the address
   row and that row must not move. */
@media screen and (min-width: 992px){
  .footer .footer-box:not(._03) .footer-item{
    width: 62%;
    padding-right: 56px;
    /* The column rule was doing the same job the panel border
       now does, 40px away from it. Two hairlines that close
       together read as a mistake rather than as structure. */
    border-right-style: none;
  }
  .footer .footer-box:not(._03) .footer-item-left{
    width: 38%;
    padding-left: 40px;
  }
}

/* CONTACT PAIR -- BOTH copies, the footer on all 8 pages and
   the /contact column (section 28 moved the scope, it is not
   duplicated). .link-wrap ships flex-flow:column with a 10px
   gap, which is what stacked the four unrelated lines. Row-wrap
   puts the two side by side where there is room and stacks them
   where there is not. overflow:hidden is left alone -- that is
   what clips the slideInBottom entrance. */
:is(.footer, .contact-item-two-left) .link-wrap{
  grid-column-gap: 14px;
  grid-row-gap: 14px;
  flex-flow: row wrap;
  align-items: stretch;
  width: 100%;
  padding-top: 8px;
}

/* Each row is now ONE anchor wrapping icon plus label plus value,
   so it is a single link and a single tap target instead of two
   links pointing at the same href. Pill language borrowed from
   .hero-tag-btn, squared off to 14px because it holds two lines.
   flex-basis 240px is the break point: two fit above roughly
   540px of column, one below. */
:is(.footer, .contact-item-two-left) .link-wrap .call-link{
  grid-column-gap: 14px;
  flex: 1 1 240px;
  align-items: center;
  border: 1px solid var(--border-color-five);
  border-radius: 14px;
  background-color: rgba(255, 255, 255, .55);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  padding: 13px 20px 13px 13px;
  transition: background-color .22s ease, border-color .22s ease, box-shadow .22s ease;
}
:is(.footer, .contact-item-two-left) .link-wrap .call-link:hover{
  background-color: rgba(255, 255, 255, .9);
  border-color: rgba(15, 5, 77, .18);
  box-shadow: 6px 6px 22px 0 var(--info-shadow);
}
/* Drawn inset because .link-wrap is overflow:hidden, so a
   positive outline-offset would be clipped on the outer edge. */
:is(.footer, .contact-item-two-left) .link-wrap .call-link:focus-visible{
  outline: 2px solid #0f054d;
  outline-offset: -3px;
}

/* Icon plate. 46px sits between the 40px social circles in the
   row below and the 64px .icon-wrap the template uses on
   /contact, so it reads as part of the same family as both. */
:is(.footer, .contact-item-two-left) .link-wrap .enq-contact-ico{
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background-color: var(--icon-bg);
  transition: background-color .22s ease;
}
:is(.footer, .contact-item-two-left) .link-wrap .enq-contact-ico img{
  display: block;
  width: 20px;
  height: auto;
  transition: filter .22s ease;
}
/* call.svg and email.svg are img tags, so their fill cannot be
   reached from CSS. Both are black duotone -- one solid path plus
   one at 0.4 opacity -- and brightness(0) invert(1) maps that to
   white on the navy plate with the duotone still intact. */
:is(.footer, .contact-item-two-left) .link-wrap .call-link:hover .enq-contact-ico{
  background-color: #0f054d;
}
:is(.footer, .contact-item-two-left) .link-wrap .call-link:hover .enq-contact-ico img{
  filter: brightness(0) invert(1);
}

:is(.footer, .contact-item-two-left) .link-wrap .enq-contact-txt{
  display: flex;
  flex-flow: column;
  grid-row-gap: 3px;
  min-width: 0;
}
/* The labels are the actual fix. Until now nothing on the page
   said which of the two values was a phone number and which was
   an address. */
:is(.footer, .contact-item-two-left) .link-wrap .enq-contact-label{
  color: var(--nexo-text);
  opacity: .6;
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: .08em;
  text-transform: uppercase;
}
/* .number ships 22px/400 as a p tag. It is a span now, because it
   lives inside the anchor, so block display and the wrapping
   behaviour have to be restated. 20px because it no longer
   carries the row on its own -- it has a label above it. */
:is(.footer, .contact-item-two-left) .link-wrap .number{
  display: block;
  font-size: 20px;
  font-weight: 500;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

/* Phones: the template centres this whole column at 479 and
   below, so the rows centre as a group while their own contents
   stay left-aligned against the icon plate. */
@media screen and (max-width: 479px){
  :is(.footer, .contact-item-two-left) .link-wrap{
    flex-flow: column;
    align-items: stretch;
  }
  :is(.footer, .contact-item-two-left) .link-wrap .call-link{
    /* flex-basis is the HEIGHT once the container turns into a
       column, so the 240px width basis above has to be dropped
       here or every row renders 240px tall. */
    flex: 0 0 auto;
    justify-content: flex-start;
    width: 100%;
    max-width: 340px;
    margin-left: auto;
    margin-right: auto;
  }
}

/* THE FORM PANEL. Deliberately NOT scoped to .footer: /contact
   carries a second copy of this exact form in its page body, on
   the same .footer-form / .form-input / .message classes. Scoping
   the panel to the footer would have restyled the form on seven
   pages and left the eighth on the Webflow default. */
.footer-form{
  border: 1px solid var(--border-color-five);
  border-radius: 16px;
  background-color: rgba(255, 255, 255, .62);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  box-shadow: 8.817px 8.817px 35.267px 0 var(--info-shadow);
  padding: 32px;
  /* .w-form ships margin: 0 0 15px. */
  margin-bottom: 0;
}
@media screen and (max-width: 479px){
  .footer-form{
    border-radius: 14px;
    padding: 24px 20px;
  }
}

/* FIELDS. Webflow ships these bottom-border-only and fully
   transparent, which is close to invisible on a translucent
   panel. One language for all four: filled box, 12px radius,
   hairline border. 52px clears the 44px touch minimum at every
   breakpoint, so it also supersedes the 46px floor section 7
   sets below 768. */
.footer-form .form-input{
  border: 1px solid var(--input-border);
  border-radius: 12px;
  background-color: #fff;
  min-height: 52px;
  margin-bottom: 14px;
  padding: 14px 16px;
  font-size: 16px;
  font-weight: 500;
  color: var(--black);
  transition: border-color .18s ease, box-shadow .18s ease;
}
/* ._03 is the textarea. Webflow pins it to height:98px; a
   min-height instead lets it be dragged without the box
   fighting back. */
.footer-form .form-input._03{
  height: auto;
  min-height: 116px;
  resize: vertical;
}
.footer-form .form-input::placeholder{
  color: var(--nexo-text);
  opacity: .72;
}
/* THE FOCUS STATE -- see the header note. webflow-core sets
   border-bottom-color to the colour the border already is, so
   there was no visible focus anywhere in either form. */
.footer-form .form-input:focus{
  border-color: #0f054d;
  box-shadow: 0 0 0 3px rgba(15, 5, 77, .13);
  outline: none;
}
/* Native validation feedback, but only once something has been
   typed, so the required fields are not red on first paint. */
.footer-form .form-input:not(:placeholder-shown):invalid{
  border-color: #c2384a;
}

/* SUBMIT. Full width because it is the only action in the panel,
   and navy because that is the brand action colour section 10
   already established on the header pill. translate, not
   transform -- see the IX2 warning at the top. */
.footer-form .message{
  background-color: #0f054d;
  border-radius: 63px;
  width: 100%;
  min-height: 54px;
  margin-top: 4px;
  padding: 16px 30px;
  font-size: 17px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color .2s ease, box-shadow .2s ease, translate .2s ease;
}
/* ⚠⚠⚠ THE ONE CTA THAT CANNOT DO D-023's WIPE, AND WHY.
   .message is an <input type="submit"> -- a replaced element -- and no browser
   renders ::before or ::after on one. It gets the identical rule (2px, #e01820,
   bottom edge, .22s) as an INSET SHADOW, so it fades in where the other six
   wipe in from the left. At rest and at full hover the two are identical; only
   the arrival differs.
   ⚠ The fix, if that ever matters: make the submit a <button type="submit">,
   which means regenerating form-src (gen.js) and reshipping BOTH form copies,
   template 420 and page 105. That is a markup change to a live lead form and is
   Preet's call, not a session's.
   ⛔ The old hover -- #180b66, a drop shadow and a 2px lift -- is gone with the
   other five. It was a THIRD hover navy, after #1a0b73 and the base. */
.footer-form .message:hover,
.footer-form .message:focus-visible{
  box-shadow: inset 0 -2px 0 #e01820;
}
.footer-form .message:focus-visible{
  outline: 3px solid rgba(15, 5, 77, .35);
  outline-offset: 3px;
}

/* Success and error bars. Neither one fires today -- the form
   still has no working handler, see CTA-PLAN section 1 -- so
   these two rules exist only so that they match the panel if and
   when a handler lands. */
.footer-form .success-message,
.footer-form .error-message{
  border-radius: 12px;
  padding: 16px 20px;
  font-size: 15px;
  font-weight: 500;
}

@media (prefers-reduced-motion: reduce){
  :is(.footer, .contact-item-two-left) .link-wrap .call-link,
  :is(.footer, .contact-item-two-left) .link-wrap .enq-contact-ico,
  :is(.footer, .contact-item-two-left) .link-wrap .enq-contact-ico img,
  .footer-form .form-input,
  .footer-form .message{
    transition: none;
  }
  /* ⛔ the ":hover{translate:0 0}" that used to be here is gone with the lift
     it cancelled -- see the submit block above. */
}

/* ============================================================
   15.  WHATSAPP FLOATING CTA  --  added 2026-08-07
   ------------------------------------------------------------
   Markup is injected on wp_body_open by the sandbox loader
   (enq-custom-css-loader.php, priority 2 -- right behind the
   page loader at priority 1). One anchor holding one inline
   SVG: no image request, no extra stylesheet, nothing to load
   before the button can paint.

   WHY IT REPLACES THE ARROW. Preet asked for WhatsApp in the
   bottom-right corner instead of the back-to-top arrow. That
   arrow was never site-wide: it lives inside each page body's
   HTML widget and only two of the eight pages ever carried it
   (home and /contact -- the six service pages had nothing in
   that corner at all). Injecting from the loader instead puts
   the button on all eight pages without touching a single
   _elementor_data blob.

   HIDDEN WITH display, NOT opacity. IX2 action list a-39,
   titled Back-To-Top, is a SCROLL_PROGRESS list that writes
   inline opacity onto .back-to-top on every scroll frame -- 0
   at 0% scroll, 1 from 50% on. An opacity rule here would be
   arguing with a value the engine rewrites continuously.
   display is a property a-39 never touches, so it wins cleanly
   and the now-pointless list just animates a hidden node. The
   markup is left in place on purpose: putting the arrow back is
   deleting the one rule below, not a rollback on two blobs.

   Z-INDEX 900 sits deliberately between two things -- below
   .navbar (9999, so an open mobile menu covers this) and far
   below #enq-loader (2147483000, so the page loader does too).
   ============================================================ */
.back-to-top{
  display: none;
}

#enq-wa{
  position: fixed;
  right: 18px;
  bottom: 18px;
  /* Lifts clear of the iPhone home indicator when the document is
     served viewport-fit=cover. env() resolves to 0 otherwise, so
     this is the same flat 18px everywhere else. */
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  z-index: 900;

  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;

  background-color: #25d366;
  color: #fff;
  box-shadow:
    0 10px 24px rgba(18, 140, 70, .34),
    0 2px 6px rgba(15, 5, 77, .14);

  text-decoration: none;
  -webkit-tap-highlight-color: transparent;

  transition:
    background-color .2s ease,
    box-shadow .2s ease,
    translate .2s ease;

  /* Longhands, never the animation shorthand -- the shorthand also
     resets animation-duration to 0s and would pin this at its end
     keyframe. The delay holds the button back until the page
     loader has had its chance to clear, so it does not fade in
     underneath the overlay and then reappear. */
  animation-name: enq-wa-in;
  animation-duration: .45s;
  animation-timing-function: cubic-bezier(.2, .7, .3, 1);
  animation-delay: .7s;
  /* backwards, NOT both, and that is a cascade rule rather than a
     preference -- the same one that keeps the page loader failsafe in
     section 13 off opacity. A filling animation outranks every normal
     author declaration on the properties it touches, so a forwards
     fill would hold opacity at 1 forever and the menu-open rule below
     could never fade this out; measured, it lost silently and only
     visibility still worked. backwards fills the 0.7s delay with the
     from keyframe and then lets go, which looks identical and hands
     opacity and scale back to the cascade. */
  animation-fill-mode: backwards;
}

#enq-wa svg{
  display: block;
  width: 30px;
  height: 30px;
  fill: currentColor;
  pointer-events: none;
}

#enq-wa:hover{
  background-color: #1eb257;
  box-shadow:
    0 14px 30px rgba(18, 140, 70, .42),
    0 2px 6px rgba(15, 5, 77, .16);
  /* translate longhand, not transform: it composes rather than
     replacing, which is the house rule everywhere on this site. */
  translate: 0 -2px;
}

#enq-wa:focus-visible{
  outline: 3px solid #0f054d;
  outline-offset: 3px;
}

#enq-wa:active{
  translate: 0 0;
}

@keyframes enq-wa-in{
  from{ opacity: 0; scale: .8; }
  to  { opacity: 1; scale: 1; }
}

/* The open mobile menu panel lands in the same corner region. This
   button is a direct child of body printed BEFORE .navbar, so no
   sibling combinator can reach it from the open menu -- :has() on
   the shared ancestor is the only selector that can. Browsers
   without :has() simply keep the button visible, which is the old
   behaviour, not a broken one. */
body:has(.navbar.enq-menu-open) #enq-wa{
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .2s ease, visibility 0s linear .2s;
}

@media (max-width: 767px){
  #enq-wa{
    right: 16px;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  }
}

@media (prefers-reduced-motion: reduce){
  #enq-wa{
    animation-name: none;
    transition: none;
  }
  #enq-wa:hover,
  #enq-wa:active{
    translate: 0 0;
  }
}

/* ============================================================
   16. ENQUIRY FORM -- THE SERVICE / TIMELINE SELECTS
   Added 2026-08-07 with the field rebuild. Both copies of the
   form gained a required "What do you need?" select; /contact
   also gained an optional Timeline select plus Company and
   Country/timezone text inputs, which need no rules of their own
   (they are plain .form-input and section 14 already covers
   them).

   Scoped to .footer-form like everything in section 14 -- that
   wrapper is on BOTH copies of the form, footer template 420 and
   page 105, so one rule reaches both. Writing element+class
   (select.form-input) is deliberate: it outranks section 14's
   plain .form-input rules wherever the two disagree, with no
   !important anywhere.

   IX2 WARNING, same as section 14. Each new select carries a
   data-w-id shared with the field above it, so the entrance
   engine owns its inline opacity AND transform. Nothing here may
   set either -- which is why the muted "nothing chosen yet"
   state below is a colour swap and not an opacity.
   ============================================================ */

.footer-form select.form-input{
  -webkit-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%230f054d' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 12px 8px;
  padding-right: 44px;
  cursor: pointer;
}

/* Placeholder tone. A select has no ::placeholder, so the "no
   choice yet" state is found with :has() -- the empty-value first
   option is implicitly :checked until the user picks something.
   One rule therefore covers the REQUIRED service select and the
   OPTIONAL timeline select; :invalid would have caught only the
   required one. Browsers without :has() render the prompt in full
   --black, which is cosmetic only. */
.footer-form select.form-input:has(option[value=""]:checked){
  color: var(--nexo-text);
}

/* Section 14 turns a field's border red once it has been touched
   and is still invalid, gated behind :not(:placeholder-shown). A
   select NEVER matches :placeholder-shown, so :not() matched it
   from the first paint and the required service select rendered
   red before anyone had touched the form. Element+class beats the
   section 14 selector, so this puts the normal border back and
   leaves validation to the native message on submit. */
.footer-form select.form-input:not(:placeholder-shown):invalid{
  border-color: var(--input-border);
}

/* The open dropdown list is painted by the OS and ignores the
   muted colour above -- without this the options inherit it and
   the whole list reads greyed-out/disabled. */
.footer-form select.form-input option{
  color: var(--black);
}

/* THE CHEVRON HAS TO SURVIVE ELEMENTOR'S LAZY-LOAD.
   Elementor prints two inline @media blocks (max-height 1024px and
   640px) that set
       background-image: none !important
   on .e-con.e-parent:nth-of-type(n+3):not(.e-lazyloaded) AND on
   every descendant of it, until the container scrolls into view and
   picks up .e-lazyloaded. The footer sits past that threshold on all
   seven pages that carry the footer form, so the chevron declared
   above was being erased on first paint -- leaving a select with
   appearance:none and therefore NO arrow of any kind, i.e. a
   dropdown that looks exactly like a text box. On /contact the form
   is high enough in the page that it never hit this, which is why
   the two copies disagreed.

   Elementor's selector scores (0,5,0) and carries !important, so
   out-specifying it is the only way through: .form-input is repeated
   to reach (0,5,1). That repetition is load-bearing, not a typo --
   drop one and the chevron goes away again below the fold.

   This is the same trap section 12 notes for the footer artwork: a
   background-COLOUR is immune to lazy-load, a background-IMAGE is
   not. Section 12 could live with painting on scroll because it is
   decoration. Here the image IS the affordance, so it cannot wait. */
.footer-form select.form-input.form-input.form-input.form-input{
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%230f054d' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
}


/* ============================================================
   17.  UNIFIED ENQUIRY FORM -- TWO COLUMNS  --  added 2026-08-07
   ------------------------------------------------------------
   Preet asked for one identical form everywhere, laid out in two
   columns, with Country as a dropdown feeding the phone field's
   dial code. Sections 14 and 16 already style this form; this
   section only changes its LAYOUT and adds the two new controls.
   It is APPENDED rather than merged into 14 so that 14's
   substr($s,0,27659) rollback stays a valid prefix.

   (a) THE COLUMN SWAP. Section 14 gave the contact column 62% and
   the form column 38%. Measured at vw 1425 that left the form
   panel 454px wide and its fields 388px, so two columns would
   have been 187px each -- narrower than the words inside them.
   Swapping the two widths hands the form the 806px column
   (fields ~692px, so 2 x ~339px) and the contact chips the 494px
   one, where they stack instead of sitting side by side.

   Reached through .footer-box:not(._03) for the reason section 14
   documents: ._03 reuses .footer-item / .footer-item-left for the
   address row, which must not move. Verify ._03 still measures
   ~843/457 after any change here.

   (b) WHY CONTAINER QUERIES AND NOT MEDIA QUERIES. The same form
   sits in a ~758px box in the footer and a 540px box on /contact
   AT THE SAME VIEWPORT. A viewport media query cannot tell those
   apart, so the column count is driven by the panel's own inline
   size instead. This is also what makes "the same form
   everywhere" true rather than approximate -- one rule, and each
   copy resolves it against the space it actually has.

   !! IX2 still owns inline transform AND inline opacity on every
   .form-input and on .message, both via data-w-id. Nothing in
   this section may set either property. Same warning as 14/16.
   (It owned the consent label too until that was removed on
   2026-08-07; section 18's group is the same situation.)
   ============================================================ */

/* (a) */
@media screen and (min-width: 992px){
  .footer .footer-box:not(._03) .footer-item{
    width: 38%;
    padding-right: 48px;
  }
  .footer .footer-box:not(._03) .footer-item-left{
    width: 62%;
    padding-left: 48px;
  }
}

/* (b) The panel is the query container. inline-size implies
   contain:layout style inline-size -- not paint, so nothing is
   clipped, and the panel's width still comes from its flex
   parent rather than from its contents. */
.footer-form{
  container-type: inline-size;
  container-name: enqform;
}

/* One column is the baseline, so a browser without container
   query support gets today's stacked form and nothing worse.
   row-gap replaces the 14px margin-bottom section 14 puts on
   each field; the child combinator outranks it. */
.footer-form .form{
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: 14px;
  row-gap: 14px;
}
.footer-form .form > .form-input{
  margin-bottom: 0;
}
/* Section 14 adds 4px above the submit for breathing room. The
   grid's row-gap now does that job, and 14 + 4 reads as a wonky
   rhythm next to the even gaps above it. */
.footer-form .form > .message{
  margin-top: 0;
}

/* 460px is the threshold because two 14px-padded fields plus the
   14px gap need about that much before either one gets too
   narrow to read a typed email address in. */
@container enqform (min-width: 460px){
  .footer-form .form{
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

/* The website field, the services group, the message and the
   submit always span the row. The services group because its
   labels are the longest strings in the form and it is the field
   that routes the lead. */
.footer-form .form > .enq-full{
  grid-column: 1 / -1;
}

/* The .enq-consent block that used to sit here was removed with
   the consent checkbox on 2026-08-07 (Preet's ask). Section 18's
   .enq-service rules were written as a deliberate copy of it and
   are now the only checkbox styling in the form -- so if consent
   ever comes back, copy them back rather than re-deriving. */

/* HONEYPOT. The clip/1px pattern rather than left:-9999px, which
   can widen a container, or display:none, which the better bots
   skip. tabindex="-1" and aria-hidden are on the element itself,
   so no keyboard or screen reader ever reaches it. */
.footer-form .enq-hp{
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The three prompts are now disabled="" so they cannot be picked. The
   option rule in section 16 paints EVERY option --black, which beats the
   UA sheet's grey for :disabled -- so an unpickable prompt would have
   rendered exactly like a real answer in the open OS list. Adding the
   pseudo-class outranks that rule with no !important. Also greys the
   country list's ---------- divider, which is correct. */
.footer-form select.form-input option:disabled{
  color: var(--nexo-text);
}


/* ============================================================
   18.  SERVICES CHECKBOX GROUP + COMPANY WEBSITE -- 2026-08-07
   ------------------------------------------------------------
   Preet: "a potential client can want to select multiple
   services". The Service <select> became seven checkboxes. They
   are not <select multiple> because a multi-select needs
   ctrl/cmd-click to ADD, and a plain click on any option drops
   every other choice -- on the field that routes the lead, one
   stray click loses the answer silently.

   Company website needs no rules of its own: it is a plain
   .form-input .enq-full, so section 14 styles it and section 17
   spans it across the row.

   !! The group div carries a data-w-id copied from the row
   above, so IX2 owns its inline transform AND inline opacity.
   Nothing in this section may set either -- same warning as
   14/16/17.
   ============================================================ */

/* The group is itself a 2-column grid at the same 460px
   container threshold section 17 uses, so the checkboxes track
   the fields above them instead of inventing a second rhythm.
   Seven items over two columns is four rows; one column below
   460px, same as the form. */
.footer-form .enq-services{
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: 14px;
  row-gap: 10px;
}
@container enqform (min-width: 460px){
  .footer-form .enq-services{
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

/* The group's own caption. It spans both columns so it reads as
   a heading for the block rather than as an eighth option. */
.footer-form .enq-services-label{
  grid-column: 1 / -1;
  color: var(--nexo-text);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.45;
}

/* These were written as a copy of section 17's .enq-consent so
   the two checkbox controls would read as one family. The consent
   checkbox was removed on 2026-08-07 and 17's rules went with it,
   so THIS IS NOW THE ONLY CHECKBOX STYLING IN THE FORM -- if a
   consent box ever comes back, copy these rather than re-derive. */
.footer-form .enq-service{
  display: flex;
  grid-column-gap: 10px;
  align-items: flex-start;
  cursor: pointer;
  color: var(--nexo-text);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.45;
}
.footer-form .enq-service input[type="checkbox"]{
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  accent-color: #0f054d;
  cursor: pointer;
}
.footer-form .enq-service input[type="checkbox"]:focus-visible{
  outline: 2px solid #0f054d;
  outline-offset: 2px;
}


/* ============================================================
   19.  SUBMIT RESULT -- success / failure  --  added 2026-08-08
   ------------------------------------------------------------
   Until now these two blocks were dead markup: the Webflow
   runtime owned this form's submit and posted it to the
   template author's account, so nothing on this site ever
   revealed them. CTA-PLAN section 1 gave the form a real
   handler, and global.js now toggles .w-form-done /
   .w-form-fail itself -- which means their stock Webflow look
   (grey #ddd panel, centred, 20px) is suddenly visible, inside
   a frosted panel it was never designed for.

   This section is only about how the ANSWER looks. Sections
   14/16/17/18 style the form itself and are untouched.

   !! Appended, never merged into 17 or 18. Every earlier
   section carries a substr($s,0,N) rollback and a byte
   inserted above shifts all of them (RULES.md -- the
   consent-removal offset lesson).

   !! Nothing here may set opacity or transform: IX2 owns both
   inline on .form-input and .message via data-w-id. These two
   blocks carry no data-w-id, so they are safe -- but the rule
   still applies to anything added beside them.
   ============================================================ */

.footer-form .w-form-done,
.footer-form .w-form-fail{
  border-radius: 12px;
  padding: 22px 24px;
  margin: 0;
  text-align: left;
  font-size: 15px;
  line-height: 1.55;
}

/* Success reads as an answer, not a system notice: the brand
   navy the submit button already uses, at a tint that sits on
   the frosted panel without fighting it. */
.footer-form .w-form-done{
  background-color: rgba(15, 5, 77, .055);
  border: 1px solid rgba(15, 5, 77, .12);
  color: #0f054d;
}

.footer-form .w-form-done .thank{
  font-weight: 600;
}

/* Failure keeps Webflow's warm red but loses the flat pink
   slab, and gains the same shape as everything else here. */
.footer-form .w-form-fail{
  background-color: rgba(190, 30, 45, .06);
  border: 1px solid rgba(190, 30, 45, .18);
  color: #8c1622;
  margin-top: 12px;
}


/* ---------------------------------------------------------------
   20. HEADER CTA + HERO CTA, AND THE MENU INVERTED TO WHITE
                                                    (2026-08-08)
   Preet's call. Three parts that only make sense together:
     - one navy #0f054d / white CTA component, used twice: in the
       header (template 358, so all 8 pages) and in the homepage
       hero,
     - the Menu pill and its dropdown panel inverted to white with
       #0f054d text, so the CTA is the ONLY navy surface in the
       header and reads unambiguously as the primary action,
     - the pill drops its "Menu" label below 768 and becomes a
       square icon button. That is not decoration: at 390px the
       row is logo (~142px) + CTA + pill, and the 121px labelled
       pill is what pushes it over the gutter.

   The hero half REVERSES home.css section 15, which removed the
   stock label+arrow button on the grounds that the header menu
   already carried the CTA. There is a real header CTA now, and
   Preet wants one in the hero too. It is NOT the old .hero-right
   column revived: the hero was rebuilt around a full-width left
   block (home.css 15 pins .hero-left to 100% at >=992), so the
   button goes inside .hero-left under the headline, where the
   layout already wants it. The .hero-right block is deleted from
   page 10's markup in the same pass, along with its IX2 hover
   pair e / e-2 on data-w-id 31f89dd4-... (MOUSE_OVER / MOUSE_OUT
   on the arrow, appliesTo ELEMENT, so deleting the element just
   leaves the events with no target -- other pages keep their own).
--------------------------------------------------------------- */

/* --- 20a. The CTA itself. One component, two sizes. ---------
   inline-flex so it shrink-wraps: .hero-left is a column flex with
   align-items:flex-start, where a block child would instead stretch
   to its widest descendant. Radius 63px is the pill value already
   used by .button-one and .hamburger-wrap, so this joins the family
   rather than introducing a shape. Hover moves background, shadow
   and the translate LONGHAND -- never opacity, and never the
   transform shorthand, so nothing here can collide with an IX2
   inline style if one of these ever ends up inside an animated
   subtree. */
.enq-cta{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  box-sizing: border-box;
  border: 0;
  border-radius: 63px;
  background-color: #0f054d;
  color: #fff;
  /* Urbanist by NAME, not inherit. Inherit was written when every
     ancestor on the site was Urbanist; that stopped being true once
     the hero container picked up webflow-core's bare ._02 rule, which
     sets Instrument Serif on the whole block. .enq-cta--hero then
     inherited a SERIF -- the only serif button on the site -- at
     weight 600, which Instrument Serif does not have, so the browser
     synthesised the bold. A button is structural text: Urbanist
     everywhere, and never dependent on where it is placed. */
  font-family: "IBM Plex Sans", sans-serif;
  font-weight: 600;
  line-height: 1;
  letter-spacing: .01em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  /* the accent rule below is absolutely positioned to this box */
  position: relative;
  -webkit-tap-highlight-color: transparent;
}
/* ---------- ONE HOVER FOR EVERY CTA ON THE SITE (D-023, 2026-09-04) --------
   A 2px accent rule wipes in along the bottom edge and NOTHING ELSE MOVES.
   Preet picked it as option F off mockups/cta-hover-src/cta-hover.html, where
   he was shown that the site's seven CTAs hovered in six different ways.

   ⛔ GONE ON PURPOSE, do not restore any of them on their own:
     - this rule's own background-color #1a0b73
     - .enq-cta--header's shadow + 1px lift          (section 20b)
     - .hero-left .enq-cta--hero's shadow + 2px lift (section 20f)
     - .enq-cta--project's tint + 2px lift           (home.css section 24)
   The base transition went with them: nothing on the element animates now,
   only the ::after does. .enq-cta--hero keeps its RESTING shadow.

   ⚠⚠ THE SAME RULE IS IN FIVE FILES and nothing links them: this file,
   home.css, mockups/service-src/v4-mock.html (.cta), build-c2.mjs
   (.cx-close__cta) and mockups/blog-src/blog-single.css (.enqb-btn).
   Change one, change all five. #e01820 is the brand red, sampled off
   brand/logo.avif -- the same value as service.css --accent, capabilities.css
   --cx-accent and blog-single.css --enqb-accent. It is written as a literal
   here because global.css has no accent token and a var() that fails to
   resolve kills the whole declaration in silence.

   ⛔ ::after, NEVER a background-image. Elementor's lazy-load rule sets
   background-image:none !important on everything inside the 4th top-level
   container and below until it scrolls into view -- the same reason the
   service pages draw their tick instead of using a data URI.

   ⛔ scale, NOT transform. Longhand composes with an IX2 inline transform
   instead of being wiped by it; the same house rule as translate above. */
.enq-cta::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background-color: #e01820;
  scale: 0 1;
  transform-origin: left center;
  transition: scale .22s ease;
}
.enq-cta:hover::after,
.enq-cta:focus-visible::after{
  scale: 1 1;
}
/* ⚠⚠⚠ THIS ONE DECLARATION IS LOAD-BEARING AND LOOKS LIKE DEAD CODE.
   hello-elementor/reset.css ships a{color:#c36} and a:active,a:hover{color:#336}
   as BARE ELEMENT RULES. a:hover is (0,1,1) and .enq-cta is (0,1,0), so it wins
   on specificity no matter what the load order is: delete the line below and
   every navy CTA on the site turns its label slate blue the moment a pointer
   touches it. .enq-cta--project repeats it with its own colour in home.css for
   exactly the same reason. Same shape as the three traps in TRAPS-BUILD. */
.enq-cta:hover,
.enq-cta:focus-visible{
  color: #fff;
}
.enq-cta:focus-visible{
  outline: 2px solid #0f054d;
  outline-offset: 3px;
}
/* The arrow ships fill="white" at 13x13 (assets/icon/button-arrow.svg),
   which is why it needs no recolouring on navy. Explicit width/height
   because .socile-icon taught us what an unsized inline SVG does. */
.enq-cta__icon{
  flex: 0 0 auto;
  display: block;
  width: 13px;
  height: 13px;
}

/* --- 20b. Header row: the CTA sits left of the Menu pill ----
   .content ships width:33.33% (50% at <=767, 60% at <=479) with
   justify-content:flex-end -- a percentage box sized for a single
   control. With two in there that box either wastes space or clips,
   so shrink-wrap it exactly the way section 3 shrink-wrapped
   .header-left. .container-navbar is justify-content:space-between,
   so brand-left / actions-right still holds with no further rule.
   Specificity: .container-navbar .content is (0,2,0) against
   webflow-core's (0,1,0), and a media query adds none, so this wins
   in all three bands. */
.container-navbar .content{
  width: auto;
  flex: 0 0 auto;
  gap: 12px;
}
.enq-cta--header{
  height: 43px;
  padding: 0 22px;
  font-size: 15px;
}
/* ⛔ The shadow + 1px lift that used to be here went with D-023. The header
   pill hovers exactly like every other CTA now: the accent rule in 20a. */

/* --- 20c. The Menu pill, inverted --------------------------
   Section 10 killed the stock gradient background IMAGE and put
   #0f054d on .hamburger-wrap; this keeps the image dead and swaps
   the fill. The ring is an INSET box-shadow, not a border:
   .hamburger-wrap is height:43px width:100% inside a 121x43
   .hamburger, so a real border would change the box. .menu-text,
   .line-top and .line-bottom all ship var(--white) -- all three
   have to move together or the control disappears into its own
   fill. Safe to restyle: the six IX2 data-w-id attrs on this
   subtree were renamed in section 11, so nothing writes inline
   colour here. */
.navbar .hamburger .hamburger-wrap{
  background-image: none;
  background-color: #fff;
  box-shadow: inset 0 0 0 1.5px rgba(15, 5, 77, .18);
  transition: background-color .2s ease, box-shadow .2s ease;
}
.navbar .hamburger:hover .hamburger-wrap{
  background-color: #f4f2fb;
  box-shadow: inset 0 0 0 1.5px rgba(15, 5, 77, .34);
}
.navbar .hamburger .menu-text{ color: #0f054d; }
.navbar .hamburger .line-top,
.navbar .hamburger .line-bottom{ background-color: #0f054d; }

/* --- 20d. The dropdown panel, inverted ---------------------
   Section 11 painted the panel #0f054d so it read as the Menu pill
   opening. The pill is white now, so the panel follows it. White on
   a white page needs its own edge: the hairline is again an inset
   shadow, composed into the SAME box-shadow as the drop shadow --
   two box-shadow declarations would not merge, the later would win
   outright. Every selector below repeats section 11's exactly, so
   these win on source order alone, no !important. */
.navbar .global-menu__wrap{
  background-color: #fff;
  box-shadow: inset 0 0 0 1px rgba(15, 5, 77, .12),
              0 18px 40px rgba(15, 5, 77, .16),
              0 2px 6px rgba(20, 24, 31, .08);
  color: #0f054d;
}
.navbar .global-menu__item{ color: #0f054d; }
.navbar .global-menu__item:hover,
.navbar .global-menu__item:focus-visible{
  color: #0f054d;
  background-color: rgba(15, 5, 77, .07);
}
.navbar .global-menu__item:focus-visible{
  outline: 2px solid rgba(15, 5, 77, .45);
  outline-offset: 1px;
}
.navbar .global-menu__item.w--current{ background-color: rgba(15, 5, 77, .10); }

/* --- 20e. Icon-only pill on phones -------------------------
   The label is the only reason the pill is 121px wide. Below 768
   it becomes a 44px square target and the two lines ARE the
   control -- the div already carries role=button and
   aria-label="Menu", so hiding the text costs nothing to a screen
   reader. The burger-to-X geometry in section 11 is deliberately
   untouched: .btn-2 stays 21x21 and the 4px travel still lands on
   the 10.5px midpoint. grid-column-gap and column-gap are both
   zeroed because .hamburger-wrap ships the legacy alias. */
@media screen and (max-width: 767px){
  .navbar .hamburger.js-hover{ width: 44px; }
  .navbar .hamburger .hamburger-wrap{
    height: 44px;
    grid-column-gap: 0;
    column-gap: 0;
  }
  .navbar .hamburger .menu-text{ display: none; }
  .container-navbar .content{ gap: 10px; }
  .enq-cta--header{ height: 44px; padding: 0 16px; font-size: 14px; }
}
/* 360px-class phones: logo 142 + CTA + 44 pill + gutters is the
   tightest row on the site. Two steps of padding buy ~24px. */
@media screen and (max-width: 374px){
  .enq-cta--header{ padding: 0 13px; font-size: 13px; }
  .container-navbar .content{ gap: 8px; }
}

/* --- 20f. Hero CTA -----------------------------------------
   .hero-left is a column flex, align-items:flex-start, 30px gap,
   so the button needs no margin at all -- the gap places it under
   the heading block and inline-flex keeps its left edge on the
   headline's. Bigger than the header pill because it is the only
   action on the first screen. It sits on home.css section 12's
   white readability scrim (72% white at the left edge), so navy on
   near-white: no ring needed, unlike the pill on the frosted bar.

   The entrance is animation-fill-mode BACKWARDS, and the element
   carries NO data-w-id. Both for the same reason: this is the
   primary CTA and it must not be able to end up stuck invisible.
   `both` would leave the animation owning opacity and translate
   for the life of the page, outranking every later declaration
   (the bug section 15's WhatsApp button already paid for), and an
   IX2 entrance that fails to resolve leaves opacity:0 behind.
   Longhands throughout, per house rule. */
.hero-left .enq-cta--hero{
  height: 58px;
  padding: 0 30px;
  font-size: 17px;
  box-shadow: 0 10px 26px rgba(15, 5, 77, .22);
  animation-name: enq-cta-in;
  animation-duration: .5s;
  animation-timing-function: cubic-bezier(.2, .8, .25, 1);
  animation-delay: .45s;
  animation-fill-mode: backwards;
}
/* ⛔ The bigger shadow + 2px lift that used to be here went with D-023. The
   RESTING shadow above stays -- it is what lifts the primary action off the
   hero art, and it is not a hover state. */
@keyframes enq-cta-in{
  from{ opacity: 0; translate: 0 14px; }
}
@media screen and (max-width: 767px){
  .hero-left .enq-cta--hero{ height: 52px; padding: 0 24px; font-size: 16px; }
}

/* --- 20g. Reduced motion -----------------------------------
   Nothing here is a teardown: the CTA keeps every state, it just
   stops moving between them. */
@media (prefers-reduced-motion: reduce){
  /* ⚠ the accent rule is the only thing that moves now, and a transition on
     an element cannot reach its own pseudo-element -- this has to name it. */
  .enq-cta::after{ transition: none; }
  .hero-left .enq-cta--hero{ animation-name: none; }
}

/* ============================================================
   21. GROUND SEQUENCE -- THE PAGE TINT (site-wide)
   2026-08-10. Preet picked "variant B" out of mockups/bg-variants.html
   at FULL tint strength with the blue channel enriched.

   The tint is brand navy #0f054d composited over white at 10%,
   then nudged -2 red / -2 green / +3 blue. Navy at a low alpha
   goes grey before it goes blue; the nudge is what keeps it
   reading as a colour rather than as dirt.
     255 - 240*.10 = 231  -2 = 229  e5
     255 - 250*.10 = 230  -2 = 228  e4
     255 - 178*.10 = 237  +3 = 240  f0
   It replaces #f0f0f0, so it is a like-for-like swap of the
   DEFAULT ground on all 8 pages: everything that was grey is now
   tinted, and nothing that was explicitly white moves. On the six
   service pages that is .choose-section, on /contact it is
   .contact-section; both ship background-color unset.

   Done by re-declaring webflow's own --body-bg rather than by
   painting body, so the four other consumers of that token move
   with it: .project-img-1/2/3 (the removed CTA fan, whose 4px
   borders are meant to read as the page showing through the photo
   stack) and .insights-item-img-04 (the parked blog block). Both
   are out of the DOM today; if either is ever restored it lands on
   the new ground with no further edit. The 5th consumer,
   .color-item.pastel-blue, is a style-guide swatch on no page.

   The FOOTER is the other half of variant B: it drops webflow's
   var(--white) and inherits the tint, so the page closes on the
   ground colour instead of on a third white slab. Nothing else in
   the footer is touched. The ._03 artwork still covers its own row
   at background-size:cover, section 14's rgba(15,5,77,.028) row
   wash now composites to about #dfdeeb over the tint instead of
   near-white, and the form panel's rgba(255,255,255,.62) reads as
   a soft white card rather than white-on-white.

   background-COLOUR only, deliberately: Elementor's inline
   lazy-load rule forces background-image:none on every .e-con
   descendant until its observer fires (RULES -> Images), so a
   ground built out of gradients would flicker in on scroll.

   Rollback: substr($s,0,68532).
   ============================================================ */
:root{
  --enq-ground: #e5e4f0;
  --body-bg: var(--enq-ground);
}

/* The white here was webflow's, and the comment in section 12 that
   calls it "the matte and 404 fallback under the art" still holds --
   the matte is just tinted now. transparent rather than a second
   copy of the value, so the ground has one source of truth. */
.footer{
  background-color: transparent;
}

/* ============================================================
   22. CTA -> THE ENQUIRY FORM ON THE SAME PAGE (site-wide)
   2026-08-11. Preet: "link all cta buttons to form residing on
   that same page." The three .enq-cta buttons -- header (all 8
   pages), hero and project section (home) -- used to navigate
   to /contact. They now carry href="#email-form", and all 8
   pages already have that form: the footer copy on seven, and
   on /contact the page's OWN copy, which sits first in the
   document there, so getElementById and a fragment navigation
   both resolve to the visible one and not the hidden footer
   twin.

   scroll-margin-top is THE source of truth for where an anchor
   lands, on both paths: the browser reads it on a real fragment
   navigation (someone opening /#email-form cold, or anyone with
   JS off), and the third IIFE in custom-js/global.js reads it
   back with getComputedStyle so a click lands in exactly the
   same place. That IIFE exists because Webflow's own smooth
   scroll drives window.scroll() with a raw number and never
   reads this property -- and because it subtracts nothing for
   our sticky header: its offset selector is "header,
   body > .header, body > .w-nav", and although this site does
   have a <header>, Elementor makes the CONTAINER INSIDE it
   position:fixed and leaves the <header> itself static, so
   Webflow measures 0 and the target lands under an 84px bar.

   140 = the 84px header + 24px of air + the 33px of panel
   padding between the .footer-form edge and the <form> that
   actually carries the id.
   ============================================================ */
#email-form{
  scroll-margin-top: 140px;
}

/* The click handler parks focus on the panel so a keyboard user
   is not thrown back to the top of the tab order. Same idea as
   Webflow's own .wf-force-outline-none: the ring belongs on the
   field they land in, not around a 700px box. */
.footer-form[tabindex="-1"]:focus{
  outline: none;
}


/* ============================================================
   23. THE HEADER MENU'S TWO HASH LINKS (site-wide)
   2026-08-11. /#Services and /#Portfolio had the same problem
   the CTAs did, on both of their paths: clicked FROM another
   page they are a real navigation and the browser's own
   fragment scroll ignores the sticky bar; clicked ON home they
   are Webflow's, which also ignores it.

   The two values differ because they were measured, not
   assumed. Neither section is padded the same way:

     #Services   padding-top 0, first ink ("Our Services") only
                 7px in -- landing flush buries the heading 77px
                 behind the bar. 84 + 24 of air.
     #Portfolio  ships 100px of its own top padding, first ink
                 107px in, so it only needs the bar cleared;
                 adding a gap on top of that would open ~130px
                 of dead space above the eyebrow.

   #hero is deliberately absent: that id is on .navbar itself,
   the target of the back-to-top arrow that global.css section
   15 retired. It is viewport-pinned, so it has no document
   position to scroll to at all -- see the guard in global.js.
   ============================================================ */
#Services{
  scroll-margin-top: 108px;
}
#Portfolio{
  scroll-margin-top: 84px;
}

/* Same reasoning as .footer-form above: the handler parks focus
   on whatever it scrolled to, and a ring around a whole section
   is noise. */
#Services[tabindex="-1"]:focus,
#Portfolio[tabindex="-1"]:focus{
  outline: none;
}


/* ============================================================
   24. HELP-SHAPE BLOBS ON A NON-WHITE GROUND -----------------
   2026-08-11, Preet: "remove the blue help shape avif from
   sections that have bg blue/non white. center the red help
   shape avif in those sections."

   WHICH BLOB IS WHICH -- the filenames say nothing, so this is
   the record. Sampled from the AVIFs themselves with GD (every
   4th pixel, alpha < 100): help-shape-1.avif averages #efa2a5
   = RED, help-shape-2.avif averages #a1abe6 = BLUE.

   WHICH SECTIONS QUALIFY. Only four places on the whole site
   carry the pair, and exactly one of them is white:
     .features-section      home             transparent -> tint
     .choose-section        home + 6 service transparent -> tint
     .footer                all 8 pages      transparent -> tint
     .testimonials-section  home             #fff <- LEFT ALONE
   The tint is S21's --enq-ground #e5e4f0, navy at 10% over
   white, so a blue blob on it is mud. The navy .project-section
   carries no blobs at all, which is why it is not listed.
   => .testimonials-section is deliberately absent from every
   selector below. Both of its blobs stay exactly as shipped.

   CENTRING IS HORIZONTAL ONLY -- Preet's call over dead-centre;
   the red blob keeps webflow-core's top:40px anchor. left:50%
   is exact because every wrapper these images are positioned
   against (.features-row, the .container, .footer-wrap) is
   itself centred in its section: measured 633 against a 633
   section midpoint on all three at 1280. The translate(-50%)
   that makes 50% mean "centre" is webflow-core's own and is
   left in place, not restated.

   right:auto is belt-and-braces. left + right + a non-auto
   width is over-constrained so right:75% is already ignored in
   ltr -- but that width only exists because of S1's size pin,
   so spelling it out keeps this rule true if the pin ever goes.
   ============================================================ */
.features-section .help-shape-2,
.choose-section .help-shape-2,
.footer .help-shape-2{
  display: none;
}

/* The footer's red blob is reached through ._02 on purpose.
   webflow-core ships .help-shape-1._02{left:35%} at (0,2,0), so
   a plain .footer .help-shape-1 would only TIE it and the
   footer glow would ride on source order alone; (0,3,0) settles
   it. The two body sections have no such variant rule, so
   (0,2,0) over webflow-core's bare (0,1,0) is already decisive
   there. */
.features-section .help-shape-1,
.choose-section .help-shape-1,
.footer .help-shape-1._02{
  left: 50%;
  right: auto;
}

/* ---------------------------------------------------------------
   25. DESKTOP NAVBAR -- the links come out of the Menu pill
                                                    (2026-08-11)
   Above 992 the four links sit in the bar itself and Services opens
   a two-column card; the pill is hidden. Below 992 NOTHING changes:
   .enq-nav is display:none by default and only this media query
   turns it on, so the phone header, the icon-only pill at <=767 and
   section 11's dropdown are all untouched. That is also why section
   20b's warning about a third control in .content does not bite --
   at 390 there is still no third control.

   Deliberately does NOT re-declare .hamburger-wrap, .global-menu or
   .global-menu__wrap. Section 20 overrides sections 10 and 11 to
   white on SOURCE ORDER alone with no !important, so anything down
   here that re-touched those selectors would silently take the navy
   back. Hiding .hamburger (the outer 121x43 box) is a different
   selector and cannot do that.

   Every value is section 11's or section 20's, so this reads as the
   same control opened out rather than as a new one: items 16/500
   #0f054d, the rgba(15,5,77,.07) hover chip, the .10 current chip,
   radius 12; the card is section 20d's white panel exactly -- radius
   20, the inset hairline composed into the SAME box-shadow as the
   drop shadow -- and it hangs 10px below the bar, which is the
   --enq-menu-gap the pill's own dropdown uses.

   ANCHORING. The card is 600px wide, far wider than the Services
   link, so it is positioned against the whole .enq-nav and
   right-aligned with Contact; hung off the link it would dangle from
   the middle of the bar. That is why .enq-nav is the positioned
   ancestor and .enq-nav__group is deliberately static.
   --------------------------------------------------------------- */
.navbar .enq-nav{ display: none; }

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

  /* The pill is the <=991 control now. */
  .navbar .hamburger{ display: none; }

  .navbar .enq-nav{
    position: relative;
    display: flex;
    align-items: center;
    gap: 2px;
  }
  .navbar .enq-nav__group{ display: flex; align-items: center; }

  .navbar .enq-nav__link{
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: 43px;
    padding: 0 14px;
    border-radius: 12px;
    background-color: transparent;
    color: #0f054d;
    font-family: inherit;
    font-size: 16px;
    font-weight: 500;
    line-height: 1;
    letter-spacing: .01em;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color .18s ease, color .18s ease;
  }
  .navbar .enq-nav__link:hover,
  .navbar .enq-nav__link:focus-visible{
    color: #0f054d;
    background-color: rgba(15, 5, 77, .07);
  }
  .navbar .enq-nav__link:focus-visible{
    outline: 2px solid rgba(15, 5, 77, .45);
    outline-offset: 1px;
  }
  .navbar .enq-nav__link.w--current{ background-color: rgba(15, 5, 77, .10); }

  .navbar .enq-nav__chev{
    flex: 0 0 auto;
    width: 11px;
    height: 11px;
    transition: transform .2s ease;
  }
  .navbar .enq-nav__group.enq-open .enq-nav__chev{ transform: rotate(180deg); }

  /* THE CARD. Closed with visibility rather than display, the same
     way section 11 does it: it can still transition, and it is still
     out of the a11y tree and out of hit-testing while closed. */
  .navbar .enq-nav__panel{
    position: absolute;
    top: calc(100% + 30px);
    right: 0;
    left: auto;
    width: 600px;
    padding: 14px;
    box-sizing: border-box;
    background-color: #fff;
    border-radius: 20px;
    box-shadow: inset 0 0 0 1px rgba(15, 5, 77, .12),
                0 18px 40px rgba(15, 5, 77, .16),
                0 2px 6px rgba(20, 24, 31, .08);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-8px) scale(.98);
    transform-origin: 100% 0;
    transition: opacity .2s ease,
                transform .28s cubic-bezier(.2, .8, .25, 1),
                visibility 0s linear .2s;
    z-index: 1000;
  }
  .navbar .enq-nav__group.enq-open .enq-nav__panel{
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: none;
    transition-delay: 0s;
  }
  /* The 10px gap under the bar has to be crossable by the pointer or
     the card closes on the way to it. */
  .navbar .enq-nav__panel::before{
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: -32px;
    height: 32px;
  }

  .navbar .enq-nav__grid{
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2px;
  }
  .navbar .enq-nav__card{
    display: block;
    padding: 12px 14px;
    border-radius: 12px;
    background-color: transparent;
    color: #0f054d;
    text-decoration: none;
    transition: background-color .18s ease;
  }
  .navbar .enq-nav__card:hover,
  .navbar .enq-nav__card:focus-visible{ background-color: rgba(15, 5, 77, .07); }
  .navbar .enq-nav__card:focus-visible{
    outline: 2px solid rgba(15, 5, 77, .45);
    outline-offset: 1px;
  }
  .navbar .enq-nav__card.w--current{ background-color: rgba(15, 5, 77, .10); }
  .navbar .enq-nav__card strong{
    display: block;
    margin-bottom: 4px;
    color: #0f054d;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.2;
  }
  /* Body ink on this site is rgb(44,44,44); .78 of it keeps the
     descriptor secondary to the service name. em purely because the
     row needs a second inline element and a <p> inside an <a> is a
     block. */
  .navbar .enq-nav__card em{
    display: block;
    color: rgba(44, 44, 44, .78);
    font-size: 12.5px;
    font-style: normal;
    font-weight: 500;
    line-height: 1.45;
  }

  .navbar .enq-nav__sep{
    height: 1px;
    margin: 8px 6px;
    background-color: rgba(15, 5, 77, .10);
  }
  .navbar .enq-nav__foot{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 2px 4px 0;
  }
  .navbar .enq-nav__all{
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border-radius: 12px;
    color: #0f054d;
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
    transition: background-color .18s ease;
  }
  .navbar .enq-nav__all:hover,
  .navbar .enq-nav__all:focus-visible{ background-color: rgba(15, 5, 77, .07); }
  .navbar .enq-nav__all:focus-visible{
    outline: 2px solid rgba(15, 5, 77, .45);
    outline-offset: 1px;
  }
  .navbar .enq-nav__all svg{ flex: 0 0 auto; width: 12px; height: 12px; }

  /* The quote button is the .enq-cta component from section 20, not
     a new shape -- only the size differs. */
  .navbar .enq-nav__quote{
    height: 40px;
    padding: 0 18px;
    font-size: 14px;
  }
}

@media screen and (min-width: 992px) and (prefers-reduced-motion: reduce){
  .navbar .enq-nav__link,
  .navbar .enq-nav__card,
  .navbar .enq-nav__all,
  .navbar .enq-nav__chev,
  .navbar .enq-nav__panel{ transition-duration: .01ms; }
  .navbar .enq-nav__panel{ transform: none; }
}


/* ---------------------------------------------------------------
   26. SHARP CORNERS  (2026-08-22)
   Preet: "make all cta, buttons, navbar selected item, section
   borders sharp/unrounded", then scoped by his own answers to
   cards/panels + form fields as well. There is no rounded SECTION
   on this site -- every section is full-bleed -- so "section
   borders" is the 16px card and panel chrome.

   DELIBERATELY LEFT ROUNDED, his call:
     - photography and media   .portfolio-img-*, .teaser-img,
                               .service-img, .insights-item-img-*,
                               .service-single-*, .caucasian-img
     - anything circular       .patner-img, .testimonials-img,
                               .enq-contact-ico, .choose-icon-wrap,
                               #enq-wa, .play-link, .enq-dots
     - the label pills         .section-title / .section-title-text
                               (42px) and .hero-tag-btn (50px). The
                               second is named -btn but it is a
                               <div><p> tag chip in the portfolio
                               teaser, not a control.

   EVERY rule below declares border-radius and nothing else. That
   matters for .hamburger-wrap and .global-menu__wrap: section 20
   beats sections 10/11 on source order to invert those from navy
   to white, so anything appended here that also declared a
   background or a colour would silently take the navy back. One
   property cannot. Do not grow these rules.

   No !important anywhere -- everything wins on specificity or on
   source order, the same way the rest of this file does.
   --------------------------------------------------------------- */

/* --- 26a. CTAs and buttons ------------------------------------- */
.enq-cta{ border-radius: 0; }

/* The enquiry-form submit. .message is the <input type="submit">
   in both copies of the form, not a textarea -- see template 420.
   Its 30px of side padding was never radius-dependent, so the
   label does not move. */
.footer-form .message{ border-radius: 0; }

/* The header Menu pill, <=991 only (section 25 hides it above).
   THREE copies of 63px have to move together or the shape and its
   focus ring disagree: webflow-core paints the inner .hamburger-wrap
   (0,1,0) and the outer .hamburger.js-hover box (0,2,0), and
   section 8 above repeats it on the focus ring -- an outline
   follows border-radius, so a square pill would keep a lozenge
   ring. .hamburger-wrap is a CHILD of .hamburger, hence (0,3,0). */
.navbar .hamburger .hamburger-wrap,
.navbar .hamburger.js-hover,
.navbar .hamburger:focus-visible{ border-radius: 0; }

/* Footer phone/email chips. A link with a fill, a border and a
   hover state -- a button by the only test that matters. Its
   focus ring is drawn inset, so it follows this to 0 too. */
:is(.footer, .contact-item-two-left) .link-wrap .call-link{ border-radius: 0; }

/* --- 26b. Navbar ----------------------------------------------- */
/* .enq-nav__link.w--current is the "selected item" he pointed at.
   The rest of the component follows it -- a square current chip
   inside a 20px panel is worse than either end of the choice.
   Section 25 declares these inside @media(min-width:992px), which
   adds NO specificity, so these base rules beat it on source
   order. Below 992 the whole nav is display:none anyway. */
.navbar .enq-nav__link,
.navbar .enq-nav__card,
.navbar .enq-nav__all,
.navbar .enq-nav__panel{ border-radius: 0; }

/* Same again for the <=991 menu panel and its items (sections
   10/11 + 20). Radius only -- see the note at the top. */
.navbar .global-menu__wrap,
.navbar .global-menu__item{ border-radius: 0; }

/* --- 26c. Cards and panels (his "section borders") -------------- */
/* webflow-core gives bare .features-item two CORNER longhands
   (top-left + bottom-left, 12px) and gives the .features-item-1 /
   ._02 variants a 16px shorthand, so all four variants are listed
   and the shorthand below flattens all four corners in each case.
   home.css section 29 re-rounds these at (0,3,0)/(0,4,0) on the
   homepage, which nothing in this file can reach -- that copy is
   answered in home.css section 36. */
.features-item,
.features-item.features-item-1,
.features-item._02,
.features-item.features-item-1._02{ border-radius: 0; }

/* Why Choose Us -- home + all six service pages. */
.choose-item{ border-radius: 0; }

/* Testimonials. Preet's earlier call that these keep their
   rgba(255,255,255,.53) fill is untouched; only the corner moves. */
.testimonials-item{ border-radius: 0; }

/* The blog/insights cards. DORMANT, not dead: the whole
   <section class="insights-section"> is COMMENTED OUT in page
   10's markup, so this matches 0 elements today. Kept anyway --
   un-commenting one line is all it takes to bring it back, and
   the alternative is a lone rounded block on a square page.
   Its images (.insights-item-img-*, 7px) stay rounded. */
.insights-item,
.insights-item-02,
.insights-item-03,
.insights-item-three{ border-radius: 0; }

/* /contact info cards. */
.contact-info-item,
.contact-item-two{ border-radius: 0; }

/* The enquiry panel itself. Two rules above set it -- 16px base
   and 14px at <=479 -- both at (0,1,0), so this one beats both on
   source order at every width. */
.footer-form{ border-radius: 0; }

/* --- 26d. Form fields ------------------------------------------ */
/* .form-input carries the three <select>s and the textarea too
   (section 16), so one rule covers every field in the panel.
   Nothing in section 16's higher-specificity select rules touches
   border-radius, so (0,2,0) is enough here. */
.footer-form .form-input{ border-radius: 0; }
.footer-form .success-message,
.footer-form .error-message,
.footer-form .w-form-done,
.footer-form .w-form-fail{ border-radius: 0; }
/* --- end 26 --- */

/* === 27. STICKY HEADER, NATIVE -- REPLACES ELEMENTOR PRO'S (2026-08-22) ===
   Pro implements sticky by taking the container position:fixed and
   CLONING the whole header into an .elementor-sticky__spacer to hold
   the 84px it just vacated. Every page therefore shipped two complete
   copies of the header -- two .navbar, two logos, six imgs, a duplicate
   data-id d1e197d -- which is why both nav IIFEs in global.js have to
   skip anything inside a spacer. Native sticky needs no spacer: the
   element never leaves the flow, so one copy does both jobs.
   Section 8's opening note is now historical -- sticky IS here.

   a) The selector sits on the TOP-LEVEL CONTAINER, not on <header>.
      Sticky travels only inside its PARENT's box, and <header>'s whole
      ancestor chain is 84px tall until .elementor (10305px on home).
      The first element whose parent is tall enough is the top-level
      .e-con -- and its id is per-page (fcd78a7 on 10, 2a55374 on 105,
      a07001b on 148, 7ac0001 on 490), so :has() names it by what it
      contains instead of by id. A browser without :has() drops the
      rule and the bar simply scrolls away; nothing else moves.
   b) NOT position:fixed on <header>. webflow-runtime.js at 34661
      offsets every in-page anchor by
        fixed===n.css(position) ? n.outerHeight() : 0
      measured on <header>, and sections 22/23 + global.js IIFE 3 are
      all built on that term reading 0. sticky is not fixed, so it
      still reads 0 and every anchor lands on the same pixel.
   c) z-index is restated because 999 used to arrive from Pro's
      sticky.min.css rule on .e-con.elementor-sticky--active, and that
      sheet no longer loads at all. post-358.css still emits
      --z-index:999 but on the INNER container, so it cannot reach up
      here.
   Nothing on this site declares position on .e-con, so (0,3,1) wins
   with no !important. */
.elementor > .e-con:has(header.elementor-358){
  position: sticky;
  top: 0;
  z-index: 999;
}
/* --- end 27 --- */

/* ==========================================================
   28. /contact -- the page's own copy of the contact pair
   ----------------------------------------------------------
   a) SCOPE. Section 14 built the chip pair for the footer and
      scoped every rule to .footer, because at the time /contact
      still carried the old flat markup with the template's fake
      values. It carries the same markup now, so 14's selectors
      moved to :is(.footer, .contact-item-two-left) rather than
      being duplicated here. Same reasoning as 14's field rules,
      which were never scoped at all: one component, two hosts,
      and a duplicate set is a change-one-change-both trap.
      :is() rather than a bare .link-wrap because a bare one is
      (0,1,0) and would only TIE with webflow-core's own
      .link-wrap rule -- that tie rides on source order, which
      is exactly the failure this file has been bitten by
      before. :is() keeps the (0,2,0) the rules had.

   b) THE SPLIT. webflow-core gives both columns width:50%,
      which flex then shrinks to 540/540 inside the 1100px
      container. 540, minus 64 of panel padding and 16 of column
      gap, leaves the form two 230px tracks -- against 339px for
      the identical form in the footer, at the same viewport.
      38/62 is the footer's own split (section 17), so this is
      not a new number: it puts the tracks at 296px and the text
      column at 408px. 408 is deliberately under the 240px
      flex-basis pair in 14 (240 + 14 + 240 = 494), so the two
      chips stack in this column and sit side by side in the
      wider footer, off one rule.
      calc() takes half the 20px gap off each side so the two
      still add up to the container exactly.
      Percentages, not pixels, so nothing overflows between 992
      and 1300 where the container is still shrinking.
   ========================================================== */
@media screen and (min-width: 992px){
  .contact-item-container > .contact-item-two-left{
    flex: 0 0 auto;
    width: calc(38% - 10px);
    /* 190px was sized for the four stacked lines the old markup
       had. Against a two-chip pair it is just a void. */
    gap: 40px;
  }
  .contact-item-container > .contact-item-two-right{
    flex: 0 0 auto;
    width: calc(62% - 10px);
  }
  /* The headline is two h5s meant to read as two lines. At 48px
     the first one measures 504px and a 408px column breaks it into
     three, orphaning "mind?". 38px measures 399px, so the pair keeps
     the shape the block was written for. Scoped to this column and
     to the desktop band -- below 992 the template already steps the
     size down and the column is full width. */
  .contact-item-two-left .contact-title{
    font-size: 38px;
    line-height: 1.12;
  }

  /* c) THE FOUR INFO CARDS ABOVE THE BAND. .contact-item is a flex
     row shipping align-items:center, so the one card whose value
     takes two lines -- the address, at every width -- grows in
     BOTH directions and reads as a different-sized card rather
     than a taller one: 272 against 249, top 184 against 196.
     stretch levels all four to the tallest. Each card is already
     a column flex with justify-content:center, so its own icon
     and text stay centred and nothing inside moves.
     Scoped to the child of .contact-row because a bare
     .contact-item only TIES with webflow-core at (0,1,0).
     Below 992 that row is a GRID, where stretch is already the
     default, so this band is the whole fix. */
  .contact-row > .contact-item{
    align-items: stretch;
  }
  /* And TOP-align each card's own contents rather than leaving
     them centred. Equal-height cards with centred contents put
     the address card's icon and label 13px above the other
     three, which trades one visible mismatch for another; from
     the 55px top padding all four icons, labels and values sit
     on the same lines and the second address line simply hangs
     into the space the stretch created. */
  .contact-row .contact-info-item{
    justify-content: flex-start;
  }
}
/* --- end 28 --- */

/* ============================================================
   29. IBM PLEX SANS -- THE DISPLAY CORRECTIONS ---------------
   Preet, 2026-09-05: "remove the cursive font from entire
   website" then "go with ibm plex sans". Swapping the family
   itself was four token values and five declarations. THIS
   section is the part that is not a find-and-replace, and it
   is the part to read before touching any of it again.

   Instrument Serif was not only the accent word. It was the
   DISPLAY face: .hero-heading rendered at 160px and 108px on
   the homepage and .footer-title at 96px on every page, all at
   weight 400. A serif display face sets optically smaller and
   lighter than a sans at the same numbers, so carrying those
   values straight across gives an oversized, underweight
   headline that also wraps differently. Sizes come down, weight
   goes up. Nothing else in this section.
   ============================================================ */

/* 29a. The two display headings that were serif. */
.hero-heading{
  font-size: 132px;
  font-weight: 600;
  letter-spacing: -.03em;
}
.hero-heading._03{
  font-size: 92px;
}
.footer-title{
  font-size: 64px;
  font-weight: 600;
  letter-spacing: -.025em;
}

/* 29b. The accent word, sitewide.
   An italic serif word inside an otherwise sans heading was the
   site's signature. IBM Plex Sans does ship a real italic, but
   an italic sans is not what was asked for -- the brief was to
   remove the cursive. So the accent keeps its position in the
   sentence and carries WEIGHT instead of slope.

   These selectors mirror webflow-core's own, so nothing gains a
   weight that was not already an italic serif before this pass.
   The four service-page accents and the blog's are fixed in
   service.css and blog-single.css instead, because both load
   after this file and would otherwise win on source order. */
._02,
.subtitle._02,
.zenith._02,
.heading-h3-italic,
.testimonial-name{
  font-style: normal;
  font-weight: 700;
}
/* --- end 29 --- */

/* ==========================================================
   30. /contact -- THE OFFICE MAP                (2026-09-08)
   ----------------------------------------------------------
   Preet: "add a map in contact page." One band, appended as
   the THIRD child of .contact-row rather than as a sibling of
   it, so it inherits that column flex's own 100px row gap and
   introduces no spacing value of its own.

   a) WHAT THE PIN IS, AND WHY IT IS NOT A GUESS. The embed is
      queried by loc-eq-pune's street_address VERBATIM, never by
      a latitude and longitude worked out here -- D-016 forbids
      supplying a fact ourselves, and a coordinate is a fact.
      Four query forms were rendered and compared before this
      one was picked (full address / plus code alone / building
      name / name + code); all four land on the same building,
      and Google's own record for the building reads back
      "GQXC+89H Link Road, 46, Sus - Pashan Rd, Baner Annex,
      Baner, Pune, Maharashtra 411045, India" -- matching the
      row on the plus code, the road, the area, the city, the
      state and the postcode. That is the cross-check.
   ⚠⚠ THE ADDRESS FORM IS SHIPPED BECAUSE IT IS THE ONLY ONE
      GOOGLE DRAWS WITHOUT A PLACE CARD. The three name-bearing
      forms open an overlay reading "Orbisoul 46 Downtown" with
      "4.5 (47)" under it -- Google's rating of the BUILDING,
      which on Enquad's own contact page reads as Enquad's. That
      is a number on the page nobody here can substantiate, i.e.
      exactly what D-016 exists to keep off. The address form
      draws a bare marker and no card. ⛔ Do not "improve" this
      query by naming the building: the label comes back and so
      does the rating.

   b) THE SURFACE. Same three declarations as .contact-item-two
      and .contact-info-item -- var(--info-item-bg), the same
      shadow, and z-index 2 -- so it joins the family of cards
      already on the page instead of being a new kind of box.
      .contact-section is position:relative with a gradient
      ::after; without the z-index this band renders under it.
      radius 0 to match section 26c's sharp-corner sweep, which
      rules the site's own boxes.
      overflow:hidden is NOT for the radius: it stops the
      iframe's own white corners painting outside the shadowed
      box.

   c) THE FRAME. display:block because an iframe is inline by
      default and would otherwise sit on a text baseline with a
      4px strip of card showing under it. loading="lazy" is on
      the element, not here: the map is the last thing on the
      page and most visitors never scroll to it, so it should
      not cost them ~1.5MB of Google's script on load.

   d) THE DIRECTIONS LINK. Secondary on purpose -- the primary
      action on this page is the form's navy "Get a Free Quote"
      and a second navy pill beside it would compete. So it is
      the inverse: white ground, navy 1px edge, navy label.
      It still takes D-023's hover -- the 2px #e01820 rule
      wiping in along the bottom edge from the left, .22s ease,
      on an ::after scaled 0 1 -> 1 1, and nothing else moving.
      This is a SIXTH copy of that rule but not a sixth FILE:
      20a's warning list already names global.css, so its count
      is still right. Change it there, change it here.
      ::after and not a background-image, for the same reason
      as section 20a: Elementor's lazy-load sets
      background-image:none !important below the 4th top-level
      container until it scrolls in.
   ⚠⚠⚠ THE :hover COLOUR DECLARATION IS LOAD-BEARING AND LOOKS
      LIKE DEAD CODE. hello-elementor/reset.css ships
      a:active,a:hover{color:#336} as a BARE ELEMENT RULE at
      (0,1,1); .enq-map__go is (0,1,0) and loses on specificity
      whatever the load order. Delete it and the label turns
      slate blue the moment a pointer touches it. Same trap as
      section 20a and TRAPS.md.
   ========================================================== */
.enq-map{
  position: relative;
  z-index: 2;
  background-color: var(--info-item-bg);
  box-shadow: 8.817px 8.817px 35.267px 0 var(--info-shadow);
  border-radius: 0;
  overflow: hidden;
}
.enq-map__frame{
  display: block;
  width: 100%;
  height: 420px;
  border: 0;
}
.enq-map__bar{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 18px 40px;
  padding: 26px 32px;
}
.enq-map__addr{
  margin: 0;
  max-width: 640px;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 15px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--text-color-three);
}
.enq-map__go{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  position: relative;
  flex: 0 0 auto;
  padding: 14px 26px;
  border: 1px solid #0f054d;
  border-radius: 0;
  background-color: #fff;
  color: #0f054d;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: .01em;
  text-decoration: none;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}
.enq-map__go::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background-color: #e01820;
  scale: 0 1;
  transform-origin: left center;
  transition: scale .22s ease;
}
.enq-map__go:hover::after,
.enq-map__go:focus-visible::after{
  scale: 1 1;
}
.enq-map__go:hover,
.enq-map__go:focus-visible{
  color: #0f054d;
}
.enq-map__go:focus-visible{
  outline: 2px solid #0f054d;
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce){
  .enq-map__go::after{ transition: none; }
}
/* Phones: the card loses its side padding to the gutter rules
   in section 5, and a 420px map on a 390px screen is most of a
   viewport. The bar stacks and the link goes full width so it
   is a real target rather than a 26px-padded stub. */
@media screen and (max-width: 767px){
  .enq-map__frame{ height: 300px; }
  .enq-map__bar{ padding: 22px; gap: 16px; }
  .enq-map__go{ width: 100%; }
}
/* --- end 30 --- */
