/* =========================================================================
   Hubit Creative Agency — static stylesheet
   Ported 1:1 from the React build (src/index.css + src/styles/Home.css)
   Only deviations from the original: heading tag selectors (h1 -> h2/h3 for
   a clean, SEO-friendly document outline) and the new animation classes
   that replace the `motion` library (gated behind `html.js`).
   ========================================================================= */

/* ------------------------------ 1. RESET ------------------------------- */
/* NOTE: the original project resets margins/padding only (no border-box).
   border-box is added below because several cards set a width/height AND
   padding, which overflowed their container (see .testimonial-box). */
* {
  margin: 0;
  padding: 0;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

:root {
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --brand: #be181c;
  --brand-dark: #b50000;
  --brand-line: #8c090a;
  --brand-accent: #fe7477;
  --ink: #2b2b2b;
  --muted: #6b6b6b;

  /* FIX: the stylesheet used `"Poppins", serif` as the fallback, so if the
     webfont failed the whole page fell back to Times. Poppins is a geometric
     sans, so the fallback chain has to end in sans-serif too. */
  --font-sans: "Poppins", "Segoe UI", Roboto, "Helvetica Neue", Arial,
    "Noto Sans", sans-serif;

  /* fluid rhythm: 160px on desktop, ~64px on a 375px phone */
  --space-section: clamp(4rem, 9vw, 10rem);
  --space-section-sm: clamp(3rem, 7vw, 8rem);
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  font-family: var(--font-sans);
  color: var(--ink);
  background: #fff;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Long service names ("BRAND IDENTITY & MERCHANDISING") must never force a
   horizontal scrollbar on narrow phones. */
h1,
h2,
h3,
h4 {
  overflow-wrap: break-word;
  line-height: 1.2;
}

::selection {
  color: #ffffff;
  background-color: #b50000;
}

[id] {
  scroll-margin-top: 2rem;
}

/* Accessibility helpers -------------------------------------------------- */
.sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: -100px;
  left: 1rem;
  z-index: 2000;
  padding: 0.75rem 1.25rem;
  background: #fff;
  color: var(--brand);
  font-weight: 600;
  border-radius: 0 0 8px 8px;
  text-decoration: none;
  transition: top 0.2s ease;
}

.skip-link:focus {
  top: 0;
}

/* FIX: a plain white outline is invisible on the white cards and on the
   white "Portfolio" button. The dark outer ring keeps it legible on both the
   red hero and the white sections. */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid #ffffff;
  outline-offset: 2px;
  box-shadow: 0 0 0 5px #1f1f1f;
  border-radius: 4px;
}

/* =========================================================================
   2. HERO  (React: .page1)
   ========================================================================= */
/* The three hero shapes drift independently, so the hero never looks
   choreographed. Two things stop them falling into step:

   1. Durations that share no common factor (13.7 / 9.3 / 18.3s), so they only
      line up again after a very long time.
   2. A negative animation-delay each, so on first paint they are already
      mid-move at different points instead of all starting from rest.

   What must NOT be used to de-sync them: irregular keyframe spacing, or
   lopsided easing curves. Both make a single motion lurch — an earlier
   version had four uneven stops and cubic-bezier(0.6, 0.04, 0.98, 0.34), and
   the square visibly surged, stalled, then surged again. Every set below is
   therefore 0 / 50 / 100 (two equal segments) with a plain S-curve, which
   keeps the velocity smooth; the shapes differ only in duration, phase,
   amplitude and direction. 0% == 100% keeps the loop seamless. */

@keyframes moveCircle {
  0%   { transform: translate(0, 0); }
  50%  { transform: translate(2.8vw, -2.2vh); }
  100% { transform: translate(0, 0); }
}

@keyframes moveTriangle {
  0%   { transform: translate(0, 0) rotate(0deg); }
  50%  { transform: translate(2.4vw, 6.5vh) rotate(11deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

/* The third hero shape: an outlined square, sitting above the triangle.
   It has to be a real element rather than another pseudo-element, because the
   circle and the triangle already use .page1::after and .page1::before.

     circle   top-right   (.page1::after)
     triangle centre      (.page1::before)
     square   above the triangle

   z-index 1 — below the content (3) and the MG artwork (2) — so it can never
   cover the headline or the buttons. */
.hero-shape-square {
  position: absolute;
  z-index: 1;
  top: 13%;
  left: 50%;
  width: clamp(5rem, 10vw, 10rem);
  height: clamp(5rem, 10vw, 10rem);
  border: clamp(2rem, 5vw, 3rem) solid pink;
  opacity: 0.2;
  pointer-events: none;
  animation: moveSquare 18.3s cubic-bezier(0.48, 0, 0.52, 1) -11.7s infinite alternate;
}

/* see the note above: a third distinct duration and phase, but a plain
   S-curve easing so the motion itself stays smooth */
@keyframes moveSquare {
  0%   { transform: translate(0, 0) rotate(0deg); }
  50%  { transform: translate(-1.6vw, -2.4vh) rotate(-7deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

/* On phones the two shapes collided into one blob. The cause is the minimums,
   not the positions: `5rem` + a `2rem` border on each side makes every shape
   144px wide, which is 45% of a 320px screen. Two 144px shapes cannot both
   fit, so no amount of nudging `left` would have separated them.

   Both are scaled down together below 768px (the circle is original design,
   but at 45% of the viewport width it had stopped being decoration and become
   structure), and the square is pulled left to clear the circle. Desktop
   above 768px is untouched. */
@media (max-width: 768px) {
  .page1::after,
  .hero-shape-square {
    width: clamp(3rem, 15vw, 5rem);
    height: clamp(3rem, 15vw, 5rem);
    border-width: clamp(1rem, 3.5vw, 1.5rem);
  }

  .hero-shape-square {
    left: 30%;
    top: 11%;
  }
}

/* (a fourth shape was tried here and removed — it read as a second circle) */

/* The hero was declared twice in the source stylesheet and the second block
   (`height: 80vh`) silently killed the first (`height: 80dvh`), so mobile
   browsers got a viewport that jumps when the URL bar hides. 80vh is kept as
   the fallback for browsers without dvh support, then dvh wins. */
.page1 {
  overflow: hidden;
  position: relative;
  display: block;
  width: 100%;
  height: 80vh;
  height: 80dvh;
  background-color: #be181c;
}

.page1::after {
  width: clamp(5rem, 10vw, 10rem);
  content: "";
  border-radius: 50%;
  z-index: 1;
  height: clamp(5rem, 10vw, 10rem);
  position: absolute;
  border: clamp(2rem, 5vw, 3rem) solid pink;
  opacity: 0.2;
  top: 10%;
  right: 10%;
  animation: moveCircle 13.7s cubic-bezier(0.45, 0.05, 0.55, 0.95) -3.2s infinite alternate;}

.page1::before {
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  border-left: clamp(5rem, 7vw, 7rem) solid transparent;
  border-right: clamp(5rem, 7vw, 7rem) solid transparent;
  border-bottom: clamp(8rem, 12vw, 12rem) solid pink;
  opacity: 0.2;
  top: 50%;
  left: 50%;
  rotate: 40deg;
  /* a plain S-curve: no overshoot, no long flat spot, so the triangle glides */
  animation: moveTriangle 9.3s cubic-bezier(0.42, 0, 0.58, 1) -6.1s infinite alternate;
}

.page1waveimg {
  width: auto;
  height: auto;
  position: relative;
  top: -1px;
  display: block;
}

/* .mainheadingmagnet, .mainheadingmagnet2, .mainheading, .mainheading2 and
   .kuchbhi were carried over from an older version of the page — no element
   uses them any more, so they have been dropped. */

.page1 > .maincontaint {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  margin: 0px 8dvw 0px 8dvw;
  justify-content: center;
  height: 90%;
}

.page1 > .maincontaint > h1 {
  font-family: var(--font-sans);
  font-weight: 700;
  color: white;
  font-size: clamp(1.7rem, 5vw, 3rem);
  line-height: 1.15;
  text-wrap: balance;
}

.page1 > .maincontaint > h1 > span {
  position: relative;
}

.page1 > .maincontaint > p {
  font-family: var(--font-sans);
  color: white;
  margin-top: 10px;
  /* FIX: this was clamp(0.2vw, 3.4vw, 1.3rem) — the 0.2vw floor is a typo, so
     the tagline rendered at 10.9px on a 320px phone. */
  font-size: clamp(0.95rem, 3.4vw, 1.3rem);
  text-wrap: pretty;
}

.btncontainer {
  margin-top: clamp(28px, 5vw, 50px);
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}

/* FIX: on mobile the two buttons stacked at a fixed 8rem width and were left
   aligned, which looked broken. Let them fill the column instead. */
@media (max-width: 768px) {
  .btncontainer {
    flex-direction: column;
    align-items: stretch;
  }

  .page1 > .maincontaint > .btncontainer > .btn1,
  .page1 > .maincontaint > .btncontainer > .btn2 {
    width: auto;
    max-width: 20rem;
  }
}

/* The original nested an <a> inside a <button> and set the anchor's colour
   explicitly, so `button:hover { color: white }` never reached the label:
   the Portfolio button turned dark-red-on-dark-red and the text vanished.
   The buttons are now anchors themselves, so the colour inherits. */
.page1 > .maincontaint > .btncontainer > .btn1,
.page1 > .maincontaint > .btncontainer > .btn2 {
  font-size: clamp(1rem, 2.5vw, 1.2rem);
  font-weight: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  height: clamp(2.6rem, 5vw, 3rem);
  width: clamp(8rem, 20vw, 12rem);
  text-align: center;
  text-decoration: none;
  border-radius: 30px;
  padding: 0.5rem 1rem;
  cursor: pointer;
  transition: background-color 0.3s ease, color 0.3s ease, transform 0.3s ease;
  font-family: var(--font-sans);
}

.page1 > .maincontaint > .btncontainer > .btn1 {
  color: var(--brand);
  background-color: white;
  border: 2px solid white;
}

.page1 > .maincontaint > .btncontainer > .btn2 {
  color: white;
  border: 2px solid white;
  background-color: transparent;
}

.page1 > .maincontaint > .btncontainer > .btn1:hover,
.page1 > .maincontaint > .btncontainer > .btn2:hover {
  color: white;
  background-color: var(--brand-dark);
  transform: scale(1.05);
}

.page1 > .contactinfo {
  display: flex;
  position: relative;
  z-index: 4;
  margin: 0px 8dvw 0px 8dvw;
  justify-content: space-between;
  align-items: center;
  height: 8%;
}

/* FIX: the floor was 35rem (560px), so on every screen under ~2800px the
   preferred 20vw could never apply and the graphic was 560px wide — wider
   than a 375px phone. The fluid middle term now actually does something. */
.topleftmg {
  width: clamp(9rem, 38vw, 50rem);
  height: auto;
  position: absolute;
  z-index: 2;
  top: 0;
  left: 0;
  pointer-events: none;
  animation:
    mgSlideInTop 0.6s ease-out 0.15s backwards,
    mgFloatTop 7s ease-in-out 0.75s infinite alternate;
}

.bottomrightmg {
  width: clamp(7rem, 22vw, 50rem);
  height: auto;
  position: absolute;
  z-index: 2;
  bottom: 0;
  right: 0;
  pointer-events: none;
  animation:
    mgSlideInBottom 0.6s ease-out 0.15s backwards,
    mgFloatBottom 7s ease-in-out 0.75s infinite alternate;
}

/* replaces the <motion.img> keyframes of the React build.
   `backwards` (not `both`) on the slide-in: the float animation, declared
   second, already owns top/left from 0.75s on, and without forwards-fill the
   element falls back to its own top/left so the two never fight. */
@keyframes mgSlideInTop {
  from { top: -50%; left: -50%; }
  to   { top: 0;    left: 0;    }
}
@keyframes mgFloatTop {
  from { top: 0;    left: 0;    }
  to   { top: -2%;  left: -2%;  }
}
@keyframes mgSlideInBottom {
  from { bottom: -50%; right: -50%; }
  to   { bottom: 0;     right: 0;     }
}
@keyframes mgFloatBottom {
  from { bottom: 0;    right: 0;    }
  to   { bottom: -2%;  right: -2%;  }
}

.page1 > .contactinfo > .gmail {
  width: 80px;
  flex: 0 0 auto;
}

/* FIX: `height: 100%` stretched the 262x92 logo to the height of the 8%-tall
   contact bar (~50px), squashing it from 2.85:1 down to 1.6:1. */
.page1 > .contactinfo > .gmail > img {
  width: 100%;
  height: auto;
  display: block;
}

/* the old `.gmail > p` rule (clamp down to 9.6px) was dead — .gmail only ever
   contained the logo image, so it has been removed rather than "fixed" */

.page1 > .line {
  position: relative;
  z-index: 4;
  height: 2px;
  background-color: #8c090a;
}

.page1 > .contactinfo > .othercontact {
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  gap: 10px;
}

.page1 > .contactinfo > .othercontact a {
  display: inline-flex;
  line-height: 0;
  /* `fill="currentColor"` resolves against the nearest ancestor's colour,
     which here is the anchor — and the UA's own `a { color: -webkit-link }`
     is what it found. So all three brand marks rendered in link blue on the
     red bar while the `|` separators beside them, which inherit from the nav
     and have no UA rule, came out white. */
  color: inherit;
}

/* =========================================================================
   3. SERVICES  (React: .page2)

   Redesigned from a uniform 3x3 of white cards into a bento, because the
   old grid had no focal point, and the nine emoji icons rendered differently
   on every OS. What changed and why:

     - Bento instead of 3x3. The wide cards are the primary services, so the
       eye lands somewhere instead of scanning wallpaper.
     - Nine hand-drawn SVG line icons replace the emoji. One stroke weight,
       one grid, currentColor — consistent everywhere.
     - Glass cards on a deep red gradient rather than flat white blocks, so
       the section has depth instead of maximum contrast.
     - The descriptions and "Get a Quote" CTAs were already in the original
       servicesData, they just were never rendered. They are visible now.
     - One stretched link per card makes the whole tile clickable while
       keeping a single focusable element, and :focus-within gives keyboard
       and touch users the same treatment as :hover.
   ========================================================================= */
.page2 {
  position: relative;
  isolation: isolate;
  font-family: var(--font-sans);
  margin-top: var(--space-section-sm);
  padding: clamp(3.5rem, 7vw, 6rem) clamp(1rem, 4vw, 3rem) clamp(4rem, 8vw, 7rem);
  overflow: hidden;
  background:
    radial-gradient(115% 75% at 50% -12%, #d62127 0%, rgba(214, 33, 39, 0) 62%),
    linear-gradient(180deg, #be181c 0%, #a4131a 62%, #971116 100%);
}

/* a slow drifting light so the red is not a flat slab of colour */
.page2::before {
  content: "";
  position: absolute;
  inset: -20%;
  z-index: -1;
  background:
    radial-gradient(26% 38% at 18% 22%, rgba(255, 255, 255, 0.11), transparent 70%),
    radial-gradient(24% 34% at 84% 74%, rgba(0, 0, 0, 0.2), transparent 70%);
  animation: servicesGlow 24s ease-in-out infinite alternate;
}

@keyframes servicesGlow {
  0%   { transform: translate3d(-2%, -1%, 0) scale(1); }
  50%  { transform: translate3d(2%, 2%, 0) scale(1.07); }
  100% { transform: translate3d(-2%, -1%, 0) scale(1); }
}

/* --- the tool wall --------------------------------------------------------
   The software and languages the work is done in, drifting behind the bento.

   It lives in the slot `.page2::before` already uses: `z-index: -1` inside a
   section that sets `isolation: isolate`, so it stays behind the content, cannot
   escape the section, and never joins the page's own stacking context. The
   `::before` glow keeps its own -1 and paints first, being generated as the
   first child.

   The tiles are CLEAR glass (`backdrop-filter: blur(3px)`), so a mark passing
   behind one still reads as a mark. That is the whole reason the field can work
   at all, and it is why the field goes *behind* the grid rather than over it —
   over the tiles the marks would sit on the copy instead of under it. The tiles
   used to be frosted at 14px, which turned every mark behind them into a
   formless wash; see the comment on `.services-box`.

   Nothing here is announced or indexed: aria-hidden, focusable="false", and the
   <title> elements were stripped from the source files (a <title> is still text
   in the DOM, and these do not link anywhere).

   --x/--y/--w/--rot/--dx/--dy/--drot/--dur/--delay/--o are all per-mark and live
   in the markup, so the whole field can be re-scattered from one place without
   touching this rule. */
.toolwall {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  /* the tiles below are <a>; nothing decorative may ever take a click */
  pointer-events: none;
  user-select: none;
  color: #ffffff;
}

.toolwall-mark {
  position: absolute;
  top: var(--y);
  left: var(--x);
  width: var(--w);
  height: auto;
  fill: currentColor;
  /* Low enough to read as texture rather than as a list of tools, and set for
     CLEAR glass: the tiles are now blur(3px) over a 4.8% fill, so this is the
     opacity a mark actually arrives at on screen. 0.08 is the ceiling that still
     holds the tile description at 4.63:1 with a mark at full strength behind it
     — 0.10 needs the text pushed to 0.94, and 0.12 fails AA outright. */
  opacity: calc(0.08 * var(--o));
  transform: rotate(var(--rot));
  /* transform-only, so the drift never triggers layout or paint. No will-change:
     these are nine elements on screen for a few seconds each, and permanent
     layer promotion would cost more than the animation saves. */
  animation: toolDrift var(--dur, 30s) ease-in-out var(--delay, 0s) infinite alternate;
}

/* Every mark gets its own duration AND a negative delay, so the field never
   pulses in unison: nine marks sharing one period read as a single moving blob
   instead of as depth. */
@keyframes toolDrift {
  from { transform: translate3d(0, 0, 0) rotate(var(--rot)); }
  to   { transform: translate3d(var(--dx), var(--dy), 0) rotate(calc(var(--rot) + var(--drot))); }
}

.page2-eyebrow {
  display: flex;
  justify-content: center;
  margin: 0 0 1rem;
}

.page2-eyebrow > span {
  display: inline-block;
  padding: 0.4rem 0.95rem;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.9);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.page2-heading {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: clamp(2rem, 5vw, 3.5rem);
  text-align: center;
  color: white;
  text-transform: none;
  letter-spacing: -0.02em;
  line-height: 1.05;
  margin: 0 auto;
  max-width: 20ch;
  text-wrap: balance;
}

.page2-lede {
  text-align: center;
  color: rgba(255, 255, 255, 0.82);
  font-size: clamp(1rem, 2vw, 1.3rem);
  line-height: 1.6;
  margin: 1.25rem auto 0;
  max-width: 54ch;
  text-wrap: pretty;
}

.services-container {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(0.75rem, 1.4vw, 1.15rem);
  max-width: 1240px;
  margin: clamp(2.5rem, 5vw, 4rem) auto 0;
}

.services-box {
  position: relative;
  isolation: isolate;
  /* REQUIRED: the shine layer below is the full size of the card and slides
     ±100% sideways. Without this it was not clipped, so a translucent white
     band travelled across the red section background outside the tile. */
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: clamp(0.85rem, 1.4vw, 1.15rem);
  grid-column: span 1;
  padding: clamp(1.4rem, 2.2vw, 2rem);
  border-radius: 20px;
  /* CLEAR glass, not frosted — 3px, not 14px. Frosting does not soften the
     tool-wall marks behind the tiles, it erases them: measured on a rendered
     page, a white mark at 0.14 behind a 14px blur is a formless smudge, and the
     only opacity at which it reads as a logo (0.40) is a white blur that washes
     out this tile's own copy. The tile still reads as glass from its border and
     its inset highlight, and the border goes UP to 0.18 to keep describing the
     surface now that so little fill is doing it. */
  background: linear-gradient(158deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.025));
  border: 1px solid rgba(255, 255, 255, 0.18);
  backdrop-filter: blur(3px) saturate(1.05);
  -webkit-backdrop-filter: blur(3px) saturate(1.05);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 18px 40px -24px rgba(0, 0, 0, 0.65);
  transition:
    transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-color 0.45s ease,
    background 0.45s ease,
    box-shadow 0.45s ease;
}

/* The primary services get a wider tile and a slightly brighter glass, so the
   grid has a focal point rather than nine identical boxes.

   The step is 0.085/0.035 against the normal tile's 0.07/0.025 — far smaller
   than the old 0.19/0.07, because clear glass needs a low fill to let the marks
   through. The separation is kept at roughly the same 1.4x proportion so the
   hierarchy survives, and 0.085 is the ceiling that still holds the description
   at 4.63:1 with a mark at full strength behind it. */
.services-box.is-wide {
  grid-column: span 2;
  background: linear-gradient(158deg, rgba(255, 255, 255, 0.085), rgba(255, 255, 255, 0.035));
  padding: clamp(1.6rem, 2.6vw, 2.4rem);
}

/* A light sweep across the tile on hover.
   The layer is exactly the size of the card, so translateX(-100%) parks it
   fully outside and translateX(100%) parks it fully outside the other side.
   With overflow:hidden on the card it is invisible at rest and clipped to
   the rounded corners while it sweeps — without that clip it was sliding a
   translucent white band across the red background outside the tile. */
.services-box::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(115deg, transparent 18%, rgba(255, 255, 255, 0.4) 50%, transparent 82%);
  transform: translateX(-100%);
  transition: transform 0.75s cubic-bezier(0.22, 0.61, 0.36, 1);
  pointer-events: none;
}

.services-box:hover,
.services-box:focus-within {
  transform: translateY(-6px);
  border-color: rgba(255, 255, 255, 0.44);
  background: linear-gradient(158deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.1));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.32),
    0 30px 60px -26px rgba(0, 0, 0, 0.7);
}

.services-box:hover::before,
.services-box:focus-within::before {
  transform: translateX(100%);
}

/* editorial numbering — cheap, and it makes nine tiles read as a set */
.services-index {
  position: absolute;
  top: clamp(1rem, 1.8vw, 1.5rem);
  right: clamp(1.1rem, 2vw, 1.7rem);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: rgba(255, 255, 255, 0.4);
  font-variant-numeric: tabular-nums;
  transition: color 0.45s ease, transform 0.45s ease;
}

.services-box:hover .services-index,
.services-box:focus-within .services-index {
  color: rgba(255, 255, 255, 0.9);
  transform: translateY(-2px);
}

.upper-section {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.94);
  color: #b0141a;
  box-shadow: 0 10px 24px -14px rgba(0, 0, 0, 0.75);
  transition:
    transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-radius 0.5s cubic-bezier(0.2, 0.8, 0.2, 1),
    background 0.45s ease;
}

.services-box.is-wide .upper-section {
  width: 68px;
  height: 68px;
  border-radius: 20px;
}

/* the squircle turns into a circle on hover */
.services-box:hover .upper-section,
.services-box:focus-within .upper-section {
  transform: translateY(-2px) rotate(-7deg) scale(1.06);
  border-radius: 50%;
  background: #fff;
}

.services-icon {
  display: block;
  width: 28px;
  height: 28px;
}

.services-box.is-wide .services-icon {
  width: 34px;
  height: 34px;
}

.lower-section {
  display: flex;
  flex-direction: column;
  text-align: left;
  padding: 0;
  gap: 0.55rem;
}

.lower-section h3 {
  font-size: clamp(1.05rem, 1.5vw, 1.3rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.25;
  margin: 0;
  color: #fff;
  text-transform: none;
  text-wrap: balance;
}

.services-box.is-wide .lower-section h3 {
  font-size: clamp(1.25rem, 2.1vw, 1.7rem);
}

/* always visible — dimmed rather than collapsed, so revealing it on hover
   cannot reflow the grid, and touch/keyboard users are not missing anything */
.services-desc {
  font-size: 0.94rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.62);
  margin: 0;
  opacity: 0.72;
  transform: translateY(3px);
  transition: opacity 0.45s ease, transform 0.45s ease, color 0.45s ease;
  text-wrap: pretty;
}

.services-box:hover .services-desc,
.services-box:focus-within .services-desc {
  opacity: 1;
  transform: none;
  color: rgba(255, 255, 255, 0.88);
}

.services-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: auto;
  padding-top: 0.35rem;
  font-size: 0.86rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: rgba(255, 255, 255, 0.9);
  transition: color 0.4s ease, gap 0.4s ease;
}

.services-arrow {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.services-box:hover .services-cta,
.services-box:focus-within .services-cta {
  gap: 0.7rem;
}

.services-box:hover .services-arrow,
.services-box:focus-within .services-arrow {
  transform: translateX(4px);
}

/* one focusable element per card, stretched over the whole tile */
.services-link {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  text-decoration: none;
}

.services-link:focus-visible {
  outline: 3px solid #fff;
  outline-offset: -3px;
}

.services-link:focus-visible ~ * {
  border-color: rgba(255, 255, 255, 0.44);
}

@media (max-width: 1024px) {
  .services-container {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .services-box.is-wide {
    grid-column: span 2;
  }
}

@media (max-width: 640px) {
  .services-container {
    grid-template-columns: 1fr;
  }

  .services-box.is-wide {
    grid-column: span 1;
  }

  .services-box.is-wide .upper-section {
    width: 56px;
    height: 56px;
    border-radius: 16px;
  }

  .services-box.is-wide .lower-section h3 {
    font-size: clamp(1.05rem, 4.6vw, 1.3rem);
  }

  .services-box:hover {
    transform: none;
  }

  /* A phone gets its OWN scatter — and only its own scatter. The tile material
     and the mark opacity are global; what has to differ here is POSITION, and
     for an arithmetic reason: the section is ~1470px tall on desktop and ~2700px
     in one column, so the desktop percentages land somewhere else entirely —
     y 15% is 220px on one and 405px on the other. Reusing them put four marks
     in the first 400px, including one 73% of the screen wide directly behind the
     heading, and left the middle 1900px bare.

     Five marks, alternating edges, from y 16% to 90% so there is something at
     every scroll depth, and nothing at all in y 0-9% — the only band on a phone
     with no tile in it, and where the heading and lede are. Indices 3, 5, 6 and
     8 are off here. */
  .toolwall-mark {
    left: var(--mleft);
    top: var(--mtop);
    width: var(--mw);
  }

  .toolwall-mark:nth-child(3),
  .toolwall-mark:nth-child(5),
  .toolwall-mark:nth-child(6),
  .toolwall-mark:nth-child(8) {
    display: none;
  }
}

/* no hover on touch: keep the tile flat and the copy at full strength */
@media (hover: none) {
  .services-desc {
    opacity: 1;
    transform: none;
    /* 0.92, not 0.82 — and this is the state that has to be readable at rest.
       There is no hover to reveal it, and a tool-wall mark is now visible
       through the clear tile: at 0.82 the worst case (a mark at full strength
       directly under the text) measured 4.04:1 and failed AA, at 0.92 it is
       4.63:1 on the brighter wide tile. */
    color: rgba(255, 255, 255, 0.92);
  }

  .services-box::before {
    display: none;
  }
}

/* =========================================================================
   4. ABOUT / WHY CHOOSE US  (React: .page3)
   ========================================================================= */
.page3 {
  margin-top: var(--space-section);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px;
}

.text-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 50%;
  flex: 1 1 400px;
}

.text-container > h2 {
  font-family: var(--font-sans);
  font-weight: 700;
  color: var(--brand);
  font-size: clamp(1.7rem, 5vw, 3rem);
  text-wrap: balance;
}

.text-container > p {
  font-family: var(--font-sans);
  margin-top: 10px;
  font-size: clamp(1.05rem, 4vw, 1.4rem);
  max-width: 62ch;
  text-wrap: pretty;
}

.margin {
  margin: 0px 8dvw 0px 8dvw;
}

.image-container2 {
  position: relative;
  z-index: 2;
  width: fit-content;
  height: fit-content;
}

.image-container {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 50%;
  flex: 1 1 300px;
}

/* FIX: this rule set no size, so the piece fell through to the generic
   `.image-container2 > img` rule below (width: 24vw / max-width: 9rem), which
   is sized for the MAIN bulb. That stretched the 51x34 piece to 144x96 - a 2.8x
   upscale, so it dwarfed the bulb and read as a blurry blob.
   It is now sized relative to the bulb it floats next to: ~68% keeps the
   natural 51:78 proportion of the two source files, and 6rem stops it
   growing past what the asset can resolve.
   top/left are percentages of the wrapper (which shrink-wraps the bulb), so
   the piece keeps the same relationship to the bulb at every screen size.
   It was parked at -45%/-25%, which left the piece floating clear of the
   bulb with a visible gap. It now sits at -22%/-14%: roughly two thirds of
   the piece rests over the bulb's top-left shoulder, so it reads as a piece
   hovering next to it rather than either far away or buried in it.
   left is kept proportional to the width so that growing the piece does not
   also increase how buried it looks. */
.image-container2 > .puzlimg {
  position: absolute;
  top: -14%;
  left: -22%;
  width: 68%;
  height: auto;
  max-width: 6rem;
  transform: translate3d(0, 0, 0) rotate(0deg);
  will-change: transform;
  animation: puzzle 6s ease-in-out infinite;
}

/* the main bulb illustration */
.image-container2 > img:not(.puzlimg) {
  width: 24vw;
  height: auto;
  max-width: 9rem;
}

/* FIX: `.image-container :nth-child(1)` was meant to size the bulb image, but
   the first child of .image-container is the .image-container2 *wrapper* div.
   It clamped that wrapper to 6rem while the image inside it is 9rem, so the
   bulb hung 24px off-centre. The wrapper now shrink-wraps the image and the
   bubble is sized on its own. */
.image-container2 {
  width: fit-content;
  max-width: 100%;
}

.circel-bg {
  height: 16rem;
  width: 16rem;
  position: absolute;
  animation: borderRadiusAnimation 3s infinite ease-in-out;
  background-color: var(--brand-accent);
  border-radius: 50%;
}

/* A gentle drift, animated on `transform` only.
   - The old version animated top/left, which are layout-triggering: the
     browser had to recalculate layout on every frame instead of just
     compositing one layer, which is what made the float look uneven.
   - 0% now matches the element's own position, so there is no jump on load.
   - 0% and 100% are identical, so the loop has no seam.
   - % translate scales with the piece, so the drift looks the same on a
     375px phone and a 1920px monitor.
   - the slight counter-rotation is what sells it as "floating" rather than
     "sliding". */
@keyframes puzzle {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg); }
  50%  { transform: translate3d(-7%, -16%, 0) rotate(-5deg); }
  100% { transform: translate3d(0, 0, 0) rotate(0deg); }
}

@keyframes borderRadiusAnimation {
  0% {
    border-radius: 149px 200px 168px 200px;
    -moz-border-radius: 149px 200px 168px 200px;
    -webkit-border-radius: 149px 200px 168px 200px;
    border: 0px solid #000000;
  }
  50% {
    border-radius: 166px 148px 200px 152px;
    -moz-border-radius: 166px 148px 200px 152px;
    -webkit-border-radius: 166px 148px 200px 152px;
    border: 0px solid #000000;
  }
  100% {
    border-radius: 149px 200px 168px 200px;
    -moz-border-radius: 149px 200px 168px 200px;
    -webkit-border-radius: 149px 200px 168px 200px;
    border: 0px solid #000000;
  }
}

@media (max-width: 600px) {
  .circel-bg {
    height: 13rem;
    width: 13rem;
  }
}

@media (max-width: 450px) {
  .circel-bg {
    height: 10rem;
    width: 10rem;
    position: absolute;
    border-radius: 50%;
  }
}

/* =========================================================================
   4b. WHY CHOOSE US  (rebuilt as a connected diagram)

   The previous version was a column flex with a fixed height, so any card
   that did not fit was pushed onto a new flex line: the three cards ended up
   as two columns with the third floating alone, inside a morphing blob that
   was mostly empty red.

   The three cards are now deliberate nodes in a three-column track, joined by
   lines that draw themselves in and carry a dot along them — which makes the
   section's own subject, "Connections", something you can actually see. The
   grid gap and the connector length share one variable, so the lines always
   meet the next card exactly at any width.

   NOTE: the ABOUT half of section 4 (.page3, .text-container,
   .image-container*, .circel-bg, @keyframes puzzle, @keyframes
   borderRadiusAnimation) lives above this block and must not be removed.
   ========================================================================= */
.why-choose-us {
  margin-top: var(--space-section);
}

.why-choose-us-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

.why-choose-us-header {
  text-align: center;
}

.why-choose-us-header > h2 {
  font-family: var(--font-sans);
  color: var(--brand-dark);
  font-size: clamp(1.7rem, 5vw, 3rem);
  margin-bottom: 10px;
  text-wrap: balance;
}

.why-choose-us-header p {
  font-family: var(--font-sans);
  font-size: clamp(1.05rem, 4vw, 1.4rem);
  color: #000;
  max-width: 60ch;
  margin: 0 auto 30px;
  text-wrap: pretty;
}

/* --- the red stage ------------------------------------------------------
   The blob used to be a free-floating shape with the cards dropped on top of
   it, which is why it read as mostly empty. It is now a full-width organic
   panel that the cards sit inside, so the red is a frame rather than
   islands. */
.wcu-stage {
  position: relative;
  width: 100%;
  --wcu-gap: clamp(2rem, 5.5vw, 5.5rem);
  /* the vertical padding has to clear the riser, because a node translated up
     by --wcu-riser overhangs the top of the grid row, and this panel has
     `overflow: hidden` — too little padding and the corner of the first card
     gets sheared off. Checked: at 901px the padding is 54px against a 27px
     riser, and at 1920px it is 80px against 40px. */
  padding: clamp(2.75rem, 6vw, 5rem) clamp(1.25rem, 3.5vw, 3rem);
  border-radius: clamp(2rem, 5vw, 3.5rem) / clamp(2rem, 5vw, 3.5rem);
  overflow: hidden;
  background:
    radial-gradient(110% 90% at 50% -20%, #d62127 0%, rgba(214, 33, 39, 0) 65%),
    linear-gradient(180deg, #be181c 0%, #a4131a 70%, #971116 100%);
  animation: wcuMorph 18s ease-in-out infinite alternate;
}

/* slower and shallower than the old blob: it is a backdrop now, and a
   violent morph behind text is distracting */
@keyframes wcuMorph {
  0%   { border-radius: clamp(2rem, 5vw, 3.5rem) / clamp(2rem, 5vw, 3.5rem); }
  50%  { border-radius: clamp(2.75rem, 6.5vw, 4.5rem) / clamp(1.75rem, 4.5vw, 3rem); }
  100% { border-radius: clamp(1.75rem, 4.5vw, 3rem) / clamp(2.75rem, 6.5vw, 4.5rem); }
}

/* --- the riser and the zig-zag ------------------------------------------
   --wcu-riser is how far a node sits off the row's centre line, and
   --wcu-run is the centre-to-centre distance between two NEIGHBOURING nodes,
   which is twice the riser because they alternate. Every wire derives its
   height from --wcu-run, so the geometry cannot drift out of sync with the
   layout: change the riser and the wires follow. */
.wcu-track {
  --wcu-riser: clamp(1.5rem, 3vw, 2.5rem);
  --wcu-run: calc(2 * var(--wcu-riser));

  /* --- the entrance timeline --------------------------------------------
     node 1, node 2, then the line between them, then node 3, then its line.

     A wire is drawn only once BOTH of the nodes it joins have landed, not
     straight after its own. That is what reads as "these two are connected".
     Leading with its own node instead put the line on screen 0.55s before the
     second box arrived — fine on desktop, where the wire is off to the side
     and clearly points at the next card, but on mobile it hangs in the gap
     below card 1 pointing at empty space, and the box it belongs to then
     arrived underneath it. Two of the three nodes, not one, must exist first.

     --reveal-dur MUST match the 0.55s in `.js .reveal.is-visible`, and
     --wire-dur MUST match the wcuDraw duration, because both are what the
     next beat is measured from. --wcu-beat is the overlap between a beat and
     the next, so the sequence flows instead of stopping dead.

     All five delays are derived from those three numbers and live here, not as
     inline styles in the markup. An inline --reveal-delay beats the
     stylesheet, so a literal left in the HTML would silently re-break this. */
  --reveal-dur: 0.55s;
  --wire-dur: 0.6s;
  --wcu-beat: 0.3s;

  /* node 2 overlaps node 1's landing by one beat */
  --wcu-t-node-2: calc(var(--reveal-dur) - var(--wcu-beat));
  /* wire 1 starts the instant node 2 has landed */
  --wcu-t-wire-1: calc(var(--wcu-t-node-2) + var(--reveal-dur));
  /* node 3 overlaps wire 1's draw by one beat */
  --wcu-t-node-3: calc(var(--wcu-t-wire-1) + var(--wire-dur) - var(--wcu-beat));
  /* wire 2 starts the instant node 3 has landed */
  --wcu-t-wire-2: calc(var(--wcu-t-node-3) + var(--reveal-dur));

  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--wcu-gap);
  align-items: stretch;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The zig-zag uses the `translate` PROPERTY, never `transform`.
 *
 * They are separate properties and they compose — the used transform is
 * translate, then rotate, then scale, then transform — so both can be live at
 * once. That is the whole reason for the choice: `.js .reveal` and
 * `.js .reveal.is-visible` both set `transform: translateY(...)` on this
 * element, and the hover lift does too. Putting the zig-zag on `transform`
 * would mean whichever rule wrote last simply won, and the reveal would
 * flatten the zig-zag every time the section scrolled into view. */
.why-choose-us-card:nth-child(odd) {
  translate: 0 calc(-1 * var(--wcu-riser));
}

.why-choose-us-card:nth-child(even) {
  translate: 0 var(--wcu-riser);
}

/* the three node delays, keyed off the timeline above rather than off
   :nth-child(odd)/(even) — those are about the zig-zag, and mixing the two
   concerns in one rule is how they drifted apart before */
.why-choose-us-card:nth-child(1) { --reveal-delay: 0s; }
.why-choose-us-card:nth-child(2) { --reveal-delay: var(--wcu-t-node-2); }
.why-choose-us-card:nth-child(3) { --reveal-delay: var(--wcu-t-node-3); }

/* --- the nodes --------------------------------------------------------- */
.why-choose-us-card {
  position: relative;
  isolation: isolate;
  /* NOT `overflow: hidden`. The .wcu-link wire is a child positioned at
     `left: 100%` — it has to escape sideways into the gap between nodes, and
     `overflow: hidden` would clip the whole thing away, leaving three cards
     with nothing joining them. The light sweep below is clipped by the
     unmoving .wcu-sheen wrapper instead. */
  overflow: visible;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(0.85rem, 1.6vw, 1.2rem);
  padding: clamp(1.5rem, 2.6vw, 2.25rem) clamp(1.25rem, 2vw, 1.75rem);
  border-radius: 18px;
  background: linear-gradient(158deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.06));
  border: 1px solid rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(12px) saturate(1.15);
  -webkit-backdrop-filter: blur(12px) saturate(1.15);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 18px 40px -26px rgba(0, 0, 0, 0.7);
  text-align: center;
  transition:
    transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-color 0.45s ease,
    background 0.45s ease,
    box-shadow 0.45s ease;
}

/* --- the light sweep ----------------------------------------------------
 * A two-element construction, and the split matters.
 *
 * The card itself MUST stay `overflow: visible`, because the wire is a child
 * positioned at `left: 100%` and has to escape sideways into the gap. So the
 * card cannot be the thing that clips.
 *
 * The clipping therefore lives on .wcu-sheen, a static wrapper that never
 * moves, and the sweep is its ::before. That is the whole point: a clip and a
 * transform on the SAME element do not compose the way you would hope.
 * `clip-path` clips in the element's own local coordinate space, so it is
 * applied BEFORE the transform and then travels with it — a
 * `clip-path: inset(0 round 18px)` layer at `translateX(-100%)` is a
 * perfectly round-cornered rectangle sitting entirely OUTSIDE the card, which
 * is exactly the hard-edged light block that showed up over the red stage.
 * `overflow: hidden` on an unmoving ancestor clips in the card's space and
 * behaves the way the services tiles already do. */
.why-choose-us-card .wcu-sheen {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
}

.why-choose-us-card .wcu-sheen::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 20%, rgba(255, 255, 255, 0.24) 50%, transparent 80%);
  transform: translateX(-100%);
  transition: transform 0.9s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.why-choose-us-card:hover,
.why-choose-us-card:focus-within {
  /* 4px, not the usual 6px. The wire is a CHILD of this card, so it rides
     along with the lift and its far end lands that far off the next node's
     centre. 4px is inside the radius of the 7px end cap, so the wire still
     reads as plugged in; 6px was starting to look like a break. */
  transform: translateY(-4px);
  border-color: rgba(255, 255, 255, 0.46);
  background: linear-gradient(158deg, rgba(255, 255, 255, 0.26), rgba(255, 255, 255, 0.1));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.34),
    0 30px 60px -28px rgba(0, 0, 0, 0.75);
}

.why-choose-us-card:hover .wcu-sheen::before,
.why-choose-us-card:focus-within .wcu-sheen::before {
  transform: translateX(0);
}

/* editorial numbering, matching .services-index */
.wcu-index {
  position: absolute;
  top: clamp(0.85rem, 1.6vw, 1.25rem);
  right: clamp(0.95rem, 1.8vw, 1.4rem);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: rgba(255, 255, 255, 0.42);
  font-variant-numeric: tabular-nums;
  transition: color 0.45s ease, transform 0.45s ease;
}

.why-choose-us-card:hover .wcu-index,
.why-choose-us-card:focus-within .wcu-index {
  color: rgba(255, 255, 255, 0.9);
  transform: translateY(-2px);
}

/* the icon tile: squircle that becomes a circle on hover, exactly like
   .upper-section in the services bento */
.why-choose-us-card .upper-section {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  flex: 0 0 auto;
  border-radius: 15px;
  background: rgba(255, 255, 255, 0.94);
  color: #b0141a;
  box-shadow: 0 10px 22px -14px rgba(0, 0, 0, 0.75);
  transition:
    transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-radius 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.why-choose-us-card:hover .upper-section,
.why-choose-us-card:focus-within .upper-section {
  transform: translateY(-2px) rotate(-7deg) scale(1.06);
  border-radius: 50%;
}

.wcu-icon {
  display: block;
  width: 26px;
  height: 26px;
}

.wcu-body {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.card-title {
  font-family: var(--font-sans);
  font-size: clamp(1.15rem, 1.7vw, 1.5rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: #fff;
  margin: 0;
  text-wrap: balance;
}

.card-description {
  font-family: var(--font-sans);
  font-size: 0.95rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.68);
  margin: 0;
  max-width: 34ch;
  text-wrap: pretty;
}

/* =========================================================================
   THE WIRES — a lasso selection, Photoshop style

   One per gap, so two for three nodes. Each is an inline <svg> sized to the
   gap by --wcu-gap and to the vertical hop by --wcu-run, with
   viewBox="0 0 100 100" and preserveAspectRatio="none". The shape is written
   once in a 100x100 space and stretched to whatever the real box turns out to
   be, which is what lets one path serve both orientations: on desktop the box
   is wide and short-ish and the curve reads as an S; on mobile the box is 2px
   wide, the x control points land at 0.76px and 1.24px, and the same curve
   collapses into a straight vertical line.

   Two attributes are load-bearing:
     pathLength="1"               normalises the path length, so the dash maths
                                   is identical whatever the box measures
     vector-effect="non-scaling-stroke"
                                   preserveAspectRatio="none" scales x and y
                                   by different factors, and a scaled stroke
                                   visibly changes width along its length.
                                   This pins it to a flat 2.5px.
   ========================================================================= */
.wcu-link {
  position: absolute;
  left: 100%;
  top: 50%;
  width: var(--wcu-gap);
  height: var(--wcu-run);
  pointer-events: none;
  /* the reveal delay is keyed off :nth-child below rather than inline, so the
     two wires can be staggered independently of the cards */
  /* which corner of the box each end cap sits in, flipped when the wire runs
     upward. Both caps are centred on their corner with translate(-50%,-50%). */
  --cap-a-x: 0%;
  --cap-a-y: 0%;
  --cap-b-x: 100%;
  --cap-b-y: 100%;
}

/* Node 1 sits high and node 2 sits low, so the box hanging off node 1's own
   centre line and running one --wcu-run DOWN already lands on node 2's centre.
   Node 2 sits low and node 3 sits high, so the box has to be pulled UP by a
   full run, and the caps swap corners to match. */
.why-choose-us-card:nth-child(even) .wcu-link {
  margin-top: calc(-1 * var(--wcu-run));
  --cap-a-y: 100%;
  --cap-b-y: 0%;
}

/* Each wire starts the instant its FAR node has landed, so both of the boxes
   it joins already exist when the line appears. Written as "the far node's
   delay + --reveal-dur" rather than as a literal so the ordering holds if the
   beat above is retuned. */
.why-choose-us-card:nth-child(1) .wcu-link {
  --reveal-delay: var(--wcu-t-wire-1);
}

.why-choose-us-card:nth-child(2) .wcu-link {
  --reveal-delay: var(--wcu-t-wire-2);
}

.wcu-wire {
  display: block;
  width: 100%;
  height: 100%;
  /* the svg root clips to its viewport by default, which would shave the
     round linecaps off both ends of the wire */
  overflow: visible;
}

.wcu-wire path {
  fill: none;
  stroke-linecap: round;
}

/* Pass one: a solid wire that draws itself in from nothing. */
.wcu-wire-draw {
  stroke: rgba(255, 255, 255, 0.8);
  stroke-width: 2.5;
  /* dasharray 1 against pathLength 1 covers the whole path, so an offset of 1
     is entirely hidden and 0 is entirely drawn */
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

/* Pass two: the marching ants. Hidden until the draw hands over to them, and
   with no JS or with reduced motion they never appear, leaving the solid
   wire above as the resting state. */
.wcu-wire-ants {
  stroke: rgba(255, 255, 255, 0.95);
  stroke-width: 2.5;
  /* dash + gap must add up to the distance travelled in one march step, or
     the loop will visibly jump when it wraps */
  stroke-dasharray: 0.05 0.035;
  stroke-dashoffset: 0;
  opacity: 0;
}

/* --- the lasso itself ----------------------------------------------------
   The draw-in and the hand-off are two animations on one element because
   they drive two different properties, so they cannot collide. `backwards` on
   the first holds the wire hidden through its delay; `forwards` on the second
   keeps it handed over afterwards. Both resting states are declared on the
   base rules above, so with JS off the solid wire is simply there.

   Gated on `.is-visible`, which is the whole point. Keyed on `.js` alone the
   animation started the moment the stylesheet applied — i.e. on page load,
   while the section was still below the fold — so by the time you scrolled to
   it the draw and the hand-off had both finished and you only ever saw the
   finished lasso. The wires are descendants of a node, so the group's
   leader putting .is-visible on all three starts both wires in the right
   place in the stagger. */
.js .why-choose-us-card.is-visible .wcu-wire-draw {
  animation:
    wcuDraw 0.6s cubic-bezier(0.22, 0.61, 0.36, 1) var(--reveal-delay, 0s) backwards,
    wcuHandOff 0.4s ease calc(var(--reveal-delay, 0s) + 0.6s) forwards;
}
@keyframes wcuDraw {
  from { stroke-dashoffset: 1; }
}

@keyframes wcuHandOff {
  to { opacity: 0; }
}

.js .why-choose-us-card.is-visible .wcu-wire-ants {
  animation:
    wcuMarch 1.15s linear infinite,
    wcuLasso 0.35s ease calc(var(--reveal-delay, 0s) + 0.35s) forwards;
}

/* one full dash+gap cycle per step, so the crawl is seamless */
@keyframes wcuMarch {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: -0.085; }
}

@keyframes wcuLasso {
  to { opacity: 1; }
}

/* the end caps, marking where a wire plugs into a node */
.wcu-link::before,
.wcu-link::after {
  content: "";
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.12);
  transform: translate(-50%, -50%);
}

.wcu-link::before { left: var(--cap-a-x); top: var(--cap-a-y); }
.wcu-link::after  { left: var(--cap-b-x); top: var(--cap-b-y); }

/* --- the caps arrive WITH the wire, not with the card --------------------
   The caps are pseudo-elements of .wcu-link, and .wcu-link itself never had
   an animation — so they simply appeared the moment the card's opacity
   animated away from 0, half a second before the line they belong to started
   drawing. On desktop that is invisible, because both caps sit on a card edge
   and read as part of the card. Stacked, the far cap is a lone dot floating
   in the gap with no line attached to it.

   So they are gated on the same .is-visible as the wire, and the far one
   waits for the draw to actually reach it: wire delay + --wire-dur. That is
   the moment the line arrives, so the connection visibly completes.

   No explicit opacity: 0 in the base rule. These are inside the card, so
   `.js .reveal { opacity: 0 }` already hides them before the card lands; the
   base state stays visible, which is what no-JS and reduced-motion get. The
   `backwards` fill holds each cap at opacity 0 through its own delay. */
.js .why-choose-us-card.is-visible .wcu-link::before {
  animation: wcuCapIn 0.3s ease var(--reveal-delay, 0s) backwards;
}

.js .why-choose-us-card.is-visible .wcu-link::after {
  animation: wcuCapIn 0.3s ease calc(var(--reveal-delay, 0s) + var(--wire-dur)) backwards;
}

@keyframes wcuCapIn {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.35); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

/* --- responsive --------------------------------------------------------
   A zig-zag needs a horizontal run to happen across. Stacked in one column
   there is none, so the nodes sit flat and the wires run straight down. */
@media (max-width: 900px) {
  .wcu-track {
    grid-template-columns: 1fr;
    gap: clamp(3.5rem, 9vw, 5rem);
  }

  .why-choose-us-card:nth-child(odd),
  .why-choose-us-card:nth-child(even) {
    translate: none;
  }

  .wcu-link,
  .why-choose-us-card:nth-child(even) .wcu-link {
    left: 50%;
    top: 100%;
    width: 2px;
    height: clamp(3.5rem, 9vw, 5rem);
    margin-top: 0;
    margin-left: -1px;
    --cap-a-x: 50%;
    --cap-a-y: 0%;
    --cap-b-x: 50%;
    --cap-b-y: 100%;
  }

  /* the boxes are 2px wide now, so the shared S-curve has collapsed to a
     straight vertical line on its own — no second shape needed.

     The wire delays are deliberately NOT overridden here. The entrance
     sequence is node-then-wire in both layouts, and overriding them to the
     same value on mobile was what made the lines arrive before the boxes. */
}

/* --- reduced motion ---------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .wcu-stage,
  .wcu-wire-draw,
  .wcu-wire-ants,
  .wcu-link::before,
  .wcu-link::after {
    animation: none !important;
  }

  /* with the animations off, .wcu-wire-draw falls back to its declared
     stroke-dashoffset: 0, i.e. a solid wire, and .wcu-wire-ants falls back to
     its declared opacity: 0. The connection is still fully drawn — it just
     does not crawl. The caps have no declared opacity, so they are simply
     there, which is right: a static dot at each end of a static line. */
  .why-choose-us-card:hover {
    transform: none;
  }
}

/* =========================================================================
   5. REVIEWS + STATS  (React: .page5-reviews)
   ========================================================================= */
.page5-reviews {
  margin-top: var(--space-section);
}

.heading-text-container {
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}

.heading-text-container > h2 {
  font-family: var(--font-sans);
  font-weight: 700;
  color: var(--brand);
  font-size: clamp(1.7rem, 5vw, 3rem);
  text-wrap: balance;
}

.heading-text-container > p {
  font-family: var(--font-sans);
  /* FIX: was clamp(0.2vw, 3.4vw, 1.3rem) — 10.9px on a 320px phone. */
  font-size: clamp(0.95rem, 3.4vw, 1.3rem);
  color: rgb(48, 45, 45);
  max-width: 60ch;
  text-wrap: pretty;
}

.heading-text-container > div {
  margin-bottom: clamp(2.5rem, 6vw, 5rem);
  margin-top: 1rem;
  width: 6rem;
  border-radius: 20px;
  /* 1px all but disappears on retina screens */
  height: 2px;
  background-color: rgb(48, 45, 45);
}

.testimonial-box-container {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  width: 100%;
}

.testimonial-box {
  border-radius: 0.6rem;
  font-family: var(--font-sans);
  width: 500px;
  box-shadow: 2px 2px 30px rgba(0, 0, 0, 0.1);
  background-color: #ffffff;
  padding: 20px;
  margin: 15px;
  /* FIX: pointer cursor on a card that is not clickable */
  cursor: default;
}

.profile-img {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  overflow: hidden;
  margin-right: 10px;
  flex: 0 0 auto;
}

.profile-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

.profile {
  display: flex;
  align-items: center;
  min-width: 0;
}

.name-user {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.name-user strong {
  color: #3d3d3d;
  font-size: 1.1rem;
  letter-spacing: 0.5px;
}

.name-user span {
  /* FIX: #979797 on white is 2.9:1 — below the 4.5:1 minimum. */
  color: #6b6b6b;
  font-size: 0.875rem;
}

.reviews {
  color: #f9d71c;
  /* flex stops the inline-star line box from adding a descender gap, which
     pushed the five stars ~4px above the vertical centre of .box-top */
  display: flex;
  flex: 0 0 auto;
  margin-left: 10px;
}

.box-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.client-comment p {
  /* FIX: 0.9rem (14.4px) with no line-height made long testimonials a grey
     wall of text. */
  font-size: 0.95rem;
  line-height: 1.7;
  color: #4b4b4b;
  text-wrap: pretty;
}

.testimonial-box:hover {
  transform: translateY(-10px);
  box-shadow: 2px 2px 30px rgba(0, 0, 0, 0.14);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

@media (max-width: 1060px) {
  .testimonial-box {
    width: 45%;
    padding: 10px;
  }
}

@media (max-width: 790px) {
  .testimonial-box {
    /* FIX: `width: 100%` plus the 15px side margins pushed the card 30px past
       the viewport and created a horizontal scrollbar on every phone. */
    width: calc(100% - 30px);
  }
}

@media (max-width: 340px) {
  .box-top {
    flex-wrap: wrap;
    margin-bottom: 10px;
  }
  .reviews {
    margin-top: 10px;
    margin-left: 0;
  }
}

.counter-container {
  position: relative;
  z-index: -2;
  margin-top: var(--space-section);
  background-image: url("../assets/hand-shake.jpg");
  background-position: center center;
  background-size: cover;
  background-attachment: fixed;
}

/* FIX: `background-attachment: fixed` is ignored on iOS Safari and causes
   visible jank everywhere else on touch devices. */
@media (max-width: 768px), (hover: none) {
  .counter-container {
    background-attachment: scroll;
  }
}

.overlay {
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  background-color: var(--brand);
  opacity: 0.6;
}

.counter-container2 {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
}

.counters {
  flex: 1 1 200px;
  text-align: center;
  margin-top: 4rem;
  margin-bottom: 4rem;
  padding: 0 10px;
}

.counters > .counter-number {
  font-family: var(--font-sans);
  font-weight: 700;
  color: white;
  font-size: clamp(3rem, 6.3vw, 5rem);
  line-height: 1.05;
}

.overlay-svg {
  position: relative;
  top: -2px;
}

.overlay-svg svg {
  display: block;
  width: 100%;
  height: auto;
}

.counters > .counter-label {
  font-family: var(--font-sans);
  font-size: clamp(1rem, 3.8vw, 2rem);
  /* FIX: rgb(197,197,197) over the red-tinted photo was ~3:1 */
  color: rgb(226, 226, 226);
}

/* =========================================================================
   6. PACKAGES  (React: .page6)
   ========================================================================= */
.page6 {
  font-family: var(--font-sans);
  margin-top: var(--space-section);
}

.pricing-table-container {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  padding: 20px;
}

.pricing-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  max-width: 1200px;
}

@media (min-width: 768px) {
  .pricing-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.pricing-card {
  color: white;
  background: var(--brand);
  border-radius: 20px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
  padding: 30px;
  text-align: center;
  border: none;
  transition: transform 0.3s, box-shadow 0.3s;
  position: relative;
  overflow: hidden;
}

/* FIX: `transform` was declared twice — `scale(1.1)` was dead code and a 10%
   scale would also have pushed the card out of the grid. */
.pricing-card:hover {
  transform: translateY(-8px) scale(1.02);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}

.pricing-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(to bottom right, rgba(255, 69, 58, 0.1), rgba(255, 159, 128, 0.1));
  z-index: 0;
}

.pricing-card * {
  position: relative;
  z-index: 1;
}

.pricing-title {
  font-size: 1.8rem;
  font-weight: 300;
  margin-bottom: 10px;
  color: white;
  letter-spacing: 0.5px;
}

.pricing-price {
  font-size: clamp(2.4rem, 6vw, 3.2rem);
  color: white;
  margin: 10px 0;
  font-weight: 700;
  line-height: 1.1;
}

.pricing-duration {
  color: white;
  font-size: 1rem;
  margin-bottom: 20px;
}

.pricing-features {
  list-style: none;
  padding: 0;
  margin: 20px 0;
  font-size: 1rem;
  text-align: left;
}

.pricing-features li {
  margin: 12px 0;
  color: white;
}

.unavailable {
  color: #f3c9c9;
  text-decoration: line-through;
}

.pricing-note {
  color: #ffe3e3;
  font-size: 0.95rem;
  margin: 15px 0;
}

.pricing-button {
  display: inline-block;
  background: white;
  color: var(--brand);
  text-decoration: none;
  padding: 15px 30px;
  border-radius: 50px;
  border: none;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: 1.1rem;
  font-weight: bold;
  transition: background 0.3s, color 0.3s, transform 0.3s;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
}

.pricing-button:hover {
  background: var(--brand-dark);
  color: white;
  transform: scale(1.05);
  box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
}

.pricing-button:focus-visible {
  outline: 3px solid #ffffff;
  outline-offset: 2px;
  box-shadow: 0 0 0 5px #1f1f1f;
}

/* =========================================================================
   7. FOOTER + FLOATING WHATSAPP
   ========================================================================= */
.footer-container {
  font-family: var(--font-sans);
  margin-top: 5rem;
  background-color: #b50000;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  height: 7vh;
  text-align: center;
}

.whatsapp-button {
  position: fixed;
  /* 30px glyph + 10px padding all round. The box was declared 30x30 *with*
     10px of padding and a global border-box, so the content box was 10x10 and
     the 30px SVG inside it was crushed to 10x30 — the mark rendered as a
     narrow sliver in the middle of the circle. */
  width: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50px;
  bottom: 20px;
  right: 20px;
  z-index: 1000;
  background-color: #25d366;
  border-radius: 50%;
  padding: 10px;
  box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
  transition: transform 0.3s ease;
}

.whatsapp-button:hover {
  transform: scale(1.1);
}

/* =========================================================================
   8. MOTION LAYER  —  plain-JS replacement for the `motion` library
   Everything below only runs when JS is available (`html.js`), so all copy
   stays visible/indexable in the raw HTML.

   TECHNIQUE: the entrance animations use a `from`-only @keyframes plus
   `animation-fill-mode: backwards`. The element's own declared style is the
   "resting" state, so once the animation ends the hover rules take over
   again. With the previous `forwards`/`both` fill, the animation kept
   owning `transform` forever and silently killed every card hover effect.
   ========================================================================= */

/* --- staggered letter-by-letter headline (hero) ------------------------ */
.js [data-split] > .char {
  animation: charIn var(--split-duration, 0.5s) ease var(--char-delay, 0s) backwards;
}

@keyframes charIn {
  from { opacity: 0; transform: translateX(-10px); }
}

/* --- fade/slide-up on scroll (whileInView) ----------------------------- */
.js .reveal {
  opacity: 0;
  transform: translateY(var(--reveal-y, 50px));
  /* the reveal itself is an animation; this transition is only for :hover */
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.js .reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
  animation: revealUp 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) var(--reveal-delay, 0s) backwards;
}

@keyframes revealUp {
  from {
    opacity: 0;
    transform: translateY(var(--reveal-y, 50px));
  }
}

/* FIX: `.js .reveal.is-visible` (0,3,0) out-specified `.why-choose-us-card:hover`
   and `.testimonial-box:hover` (0,2,0), so none of the card hover lifts ever
   rendered. These re-declare them at matching specificity. */
/* FIX: `.js .reveal.is-visible` (0,3,0) out-specified `.services-box:hover`
   (0,2,0), so the services card never lifted either — the same trap as the
   three rules below. */
.js .services-box.is-visible:hover,
.js .services-box.is-visible:focus-within {
  transform: translateY(-6px);
}

/* The card lifts 4px, matching `.why-choose-us-card:hover` above. It used to
   say -10px, left over from the old card design, so the two rules disagreed
   about how far the card travelled — and the higher value was chosen, so the
   card jumped 10px while the sweep and the border implied 6px. The `.wcu-link`
   wire is a CHILD of the card, so it rides along with the lift; 4px keeps its
   far end inside the radius of the 7px end cap, where 10px read as a break. */
.js .why-choose-us-card.is-visible:hover {
  transform: translateY(-4px);
}

.js .testimonial-box.is-visible:hover {
  transform: translateY(-10px);
}

.js .pricing-card.is-visible:hover {
  transform: translateY(-8px) scale(1.02);
}

/* --- hero buttons (delayed fade-in, like <motion.button delay={3}>) ----- */
.js .btncontainer > .btn1,
.js .btncontainer > .btn2 {
  animation: btnIn 0.35s ease-in var(--btn-delay, 1s) backwards;
}

@keyframes btnIn {
  from { opacity: 0; transform: translateY(5px); }
}

/* --- counter digits ----------------------------------------------------- */
.js [data-count-to] {
  font-variant-numeric: tabular-nums;
}


/* =========================================================================
   11. AWARD-WARDING PASS
   Four additions, all sharing one motion language: slow, tactile, precise.
   Deliberately NOT added, because each one costs more than it returns here:
     - a preloader        this site is 227 KB and paints instantly; a loader
                          would be theatre that delays the LCP element
     - a custom cursor    hijacks the OS pointer and is useless on touch
     - horizontal scroll  breaks document order, so it hurts the SEO this
                          build exists for
   ========================================================================= */

/* --- 1. reading-progress indicator ------------------------------------ */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 900;
  width: 100%;
  height: 3px;
  pointer-events: none;
}

.scroll-progress > span {
  display: block;
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, #d62127, #fe7477 60%, #ffd54a);
  transform: scaleX(0);
  transform-origin: 0 50%;
  /* driven by main.js via --progress; inert and invisible without JS */
  transform: scaleX(var(--progress, 0));
  will-change: transform;
}

/* --- 2. client marquee ------------------------------------------------ */
.client-strip {
  margin-top: clamp(3rem, 6vw, 5rem);
  padding: clamp(1.5rem, 3vw, 2.5rem) 0;
  background: #fff;
  border-top: 1px solid rgba(190, 24, 28, 0.1);
  border-bottom: 1px solid rgba(190, 24, 28, 0.1);
  overflow: hidden;
}

.client-strip-label {
  text-align: center;
  margin: 0 0 clamp(1rem, 2vw, 1.5rem);
}

.client-strip-label > span {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #8a8a8a;
  padding: 0 0.5rem;
}

.marquee {
  display: flex;
  width: max-content;
  animation: marqueeSlide 42s linear infinite;
}

.marquee-track {
  display: flex;
  align-items: center;
  gap: clamp(1.5rem, 3vw, 2.75rem);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* translating exactly half the container = one full track, so the loop has
   no seam */
@keyframes marqueeSlide {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

.marquee-item {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: clamp(1.05rem, 2.1vw, 1.6rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  color: #2b2b2b;
  opacity: 0.42;
  transition: opacity 0.35s ease, color 0.35s ease;
}

.marquee:hover .marquee-item {
  opacity: 0.62;
}

.marquee-dot {
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-left: clamp(1.5rem, 3vw, 2.75rem);
  border-radius: 50%;
  background: var(--brand);
  opacity: 0.5;
}

/* pausing is not optional for a perpetual animation */
.marquee:hover,
.marquee:focus-within {
  animation-play-state: paused;
}

/* --- 3. magnetic primary actions ---------------------------------------
   main.js writes --mx/--my in px. The hover scale composes with the pull
   instead of overwriting it, which is why the transforms below are written
   in full rather than left to a :hover rule. */
.page1 > .maincontaint > .btncontainer > .btn1,
.page1 > .maincontaint > .btncontainer > .btn2,
.whatsapp-button {
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
}

.page1 > .maincontaint > .btncontainer > .btn1:hover,
.page1 > .maincontaint > .btncontainer > .btn2:hover {
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0) scale(1.05);
}

.whatsapp-button:hover {
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0) scale(1.1);
}

/* --- 4. grain ----------------------------------------------------------
   A fixed fractal-noise tile at ~2.5%. Invisible on text, but it stops the
   large flat areas of red and white reading as vector-smooth. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: 0.025;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

/* =========================================================================
   12. THE SAME FOUR, DEGRADED SAFELY
   ========================================================================= */

/* No pointer of that kind, or the user asked for less motion: everything
   settles into a static, readable end state. */
@media (hover: none), (prefers-reduced-motion: reduce) {
  .marquee {
    animation: none !important;
    width: auto;
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 0.75rem;
  }

  /* the duplicate only exists to drive the loop */
  .marquee-track--dup {
    display: none;
  }

  .marquee-item {
    opacity: 0.62;
  }

  .scroll-progress {
    display: none;
  }

  .page1 > .maincontaint > .btncontainer > .btn1,
  .page1 > .maincontaint > .btncontainer > .btn2,
  .whatsapp-button {
    transform: none !important;
  }

  body::before {
    opacity: 0.02;
  }
}

@media print {
  .scroll-progress,
  .marquee-track--dup {
    display: none !important;
  }

  body::before {
    display: none !important;
  }

  .marquee {
    animation: none !important;
    width: auto;
    flex-wrap: wrap;
    justify-content: center;
  }
}

/* =========================================================================
   9. REDUCED MOTION
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }

  /* the entrances are animations, so they must be switched off, not just
     fast-forwarded — otherwise a 1ms `backwards` fill would still apply the
     hidden `from` state for a frame */
  .js [data-split] > .char,
  .js .reveal,
  .js .reveal.is-visible,
  .js .btncontainer > .btn1,
  .js .btncontainer > .btn2 {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  .topleftmg,
  .bottomrightmg {
    animation: none !important;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
  }

  /* The tool wall is an endless decorative loop, so it is stopped outright
     rather than fast-forwarded: `alternate` has no meaningful finished frame,
     and a 1ms fill would leave the marks parked at the `from` transform instead
     of at their declared --rot, which is a visibly different angle. */
  .toolwall-mark {
    animation: none !important;
    transform: rotate(var(--rot)) !important;
  }

  /* endless decorative loops — stopped outright rather than fast-forwarded,
     so they do not sit frozen on an arbitrary frame */
  .image-container2 > .puzlimg,
  .circel-bg,
  .wcu-stage,
  .page1::before,
  .page1::after,
  .hero-shape-square,
  .page2::before {
    animation: none !important;
  }

  /* the connectors still draw themselves in, but as an instant state change
     rather than an animated wipe. The draw is a `from`-only keyframe with a
     backwards fill on a path whose resting stroke-dashoffset is 0, so
     removing the animation shows the finished wire rather than hiding it.
     This one rule covers the whole lasso handoff, because both halves live on
     .wcu-wire-draw / .wcu-wire-ants. */
  .wcu-wire-draw,
  .wcu-wire-ants {
    animation: none !important;
  }

  /* the hover light sweep slides a layer 100% of the card's width across it,
     which is movement, not just a colour change. Snap it instead. The resting
     state is unchanged, so the card still lights up on hover — instantly. */
  .wcu-sheen::before {
    transition: none !important;
  }

  .image-container2 > .puzlimg {
    transform: none !important;
  }

  /* hover lifts are pure decoration */
  .why-choose-us-card:hover,
  .testimonial-box:hover,
  .pricing-card:hover,
  .pricing-button:hover,
  .whatsapp-button:hover {
    transform: none !important;
  }
}

/* =========================================================================
   10. PRINT
   ========================================================================= */
@media print {
  .whatsapp-button,
  .skip-link,
  .topleftmg,
  .bottomrightmg,
  /* pure ornament, and it would print as a field of grey smudges behind the
     copy — on paper the red section has no glow and no depth to sit in */
  .toolwall {
    display: none !important;
  }

  .counter-container {
    background: none;
  }
}
