/* ============================================================================
   Cart Gaze token overlay for the Aigocy template.
   Loaded AFTER styles.css. Nothing in the vendor stylesheet is edited, so the
   recolour is reversible and auditable: delete this file to see stock Aigocy.

   Palette and type are DESIGN.md verbatim. Aigocy's neutral ramp is warm-grey
   (zinc); Cart Gaze's ground is a cool near-white, so the ramp is re-cast to
   slate rather than merely tinted, otherwise the greys fight the background.
   ========================================================================= */

/* No @import for Google Fonts here: the CSP blocks it and next/font already
   self-hosts Lexend, Inter and JetBrains Mono. The rules at the foot of this file
   point the template at those variables instead. */

:root {
  /* --- "Practice green". Chosen from the 06-Sep measurement of 17 competitors,
         not from taste. Three facts drove it:

         1. NOBODY in the set uses green as a primary CTA. The accent census is
            orange (#FF6600), burnt orange (#E07538), teal (#0E6B6B), electric
            blue (#001AFF), pale blue (#A8C2E4), black. Green is open ground.
         2. That study's standing warning is "avoid mid-blue, it is the default
            nobody remembers". The first pass here led with #0369a1, which was
            drifting straight into it.
         3. Velaire owns warm-coral-on-near-black and was praised for it. Sibling
            brands under one parent must not converge, so Cart Gaze takes the
            light, green side of the split.

         Green also happens to carry both halves of the new positioning: health
         and money. The agency now sells RCM, so that is not a pun, it is the
         product.                                                            --- */

  --cg-ground:     #F7F9F6;  /* barely-green off-white; tinted ground is the modal pattern */
  --cg-surface:    #FFFFFF;
  --cg-ink:        #0C1A14;  /* near-black with a green cast, so type sits in the family */
  --cg-muted:      #5A6B60;
  --cg-edge:       rgba(12, 26, 20, .10);

  --cg-primary:    #0E7C5A;  /* emerald: the CTA colour nobody in the category owns */
  --cg-deep:       #064E3B;  /* forest: dark bands, hero plate */
  --cg-lift:       #34D399;  /* light mint: highlights on dark only */
  --cg-signal:     #B4832B;  /* restrained gold, numbers and money only, used sparingly */

  --cg-gradient:      linear-gradient(120deg, var(--cg-deep), var(--cg-primary) 58%, #10B981);
  --cg-gradient-dark: linear-gradient(160deg, #08301F 0%, var(--cg-deep) 60%, #0A6046 100%);

  /* Back-compat with the rules written below against the first pass. */
  --cg-background: var(--cg-ground);
  --cg-foreground: var(--cg-ink);
  --cg-accent:     var(--cg-primary);
  --cg-accent-2:   #10B981;
  --cg-accent-3:   var(--cg-lift);

  /* --- remap the template's own tokens --- */
  --brand:     var(--cg-primary);
  --primary:   var(--cg-ink);
  --secondary: var(--cg-muted);
  --body:      var(--cg-ground);

  /* Neutral ramp re-cast green-neutral so greys never fight the ground. */
  --neutral-50:  #F7F9F6;
  --neutral-100: #EEF2EC;
  --neutral-200: #DDE5DA;
  --neutral-300: #C3CFBF;
  --neutral-400: #98A79A;
  --neutral-500: #6E7F71;
  --neutral-600: #52604F;
  --neutral-700: #3A463A;
  --neutral-800: #222C24;
  --neutral-900: #0C1A14;
  --neutral-950: #05100B;

  --font_instrument: var(--font-urbanist), "Urbanist", ui-sans-serif, system-ui, sans-serif;
}

/* Type: ONE family, which is the template's own arrangement rather than ours.

   Qasim, 09-Sep, on the hero sub-paragraph: "the fonts should match with the
   template font like even on homepage, this does not match". He is right, and
   the cause is a split this file introduced. Stock Aigocy sets Urbanist on both
   its headings and its body copy — a single geometric family is most of why the
   template reads as one design. This overlay had been running Lexend on display
   and Inter on body, per DESIGN.md, so the hero headline and the sentence
   directly beneath it were visibly different typefaces.

   Urbanist is not available: the CSP blocks Google Fonts and next/font
   self-hosts Lexend, Inter and JetBrains Mono only. Lexend is the closest of the
   three — geometric-humanist, same warmth — so it takes the body role too.

   This file is loaded only on /v2-cartgaze-website, so the live site keeps the
   DESIGN.md split untouched. It is a change to the port, not to the brand. */
/* Scope the body face so it never touches the icon font — an unscoped
   `body *` silently swallows icomoon and every glyph turns into a box.

   Urbanist since 10-Sep, not Lexend. Lexend was the closest available substitute
   while Urbanist was believed unavailable; it is not unavailable, and the
   substitute is no longer needed. See the block further down for the reasoning. */
body,
body p, body span, body li, body a, body div, body td, body input, body button {
  font-family: var(--font-urbanist), "Urbanist", ui-sans-serif, system-ui, sans-serif;
}
.icon, [class^="icon-"], [class*=" icon-"], .icon::before, [class^="icon-"]::before, [class*=" icon-"]::before {
  font-family: "icomoon" !important;
}

h1, h2, h3, h4, h5, h6,
.heading-section *,
[class*="title"] {
  font-family: var(--font-urbanist), "Urbanist", ui-sans-serif, system-ui, sans-serif;
  letter-spacing: -0.025em;   /* the market's challenger band tightens display type */
}

/* Mono for eyebrows, chips and prices, per DESIGN.md. */
.price-value, .price-value * {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  letter-spacing: 0;
}

/* The brand gradient is the healthcare signal: blue → teal → green, and
   deliberately not the cyan→violet AI cliché. Apply it where Aigocy used a
   flat brand fill for emphasis. */
.text-gradient,
.hero-title span em,
.hero-title em {
  background: var(--cg-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  font-style: normal;
}

/* Aigocy's brand red appears as flat fills on buttons and accents. */
[class*="btn"][class*="primary"],
.btn-brand { background-color: var(--cg-accent); border-color: var(--cg-accent); }
[class*="btn"][class*="primary"]:hover,
.btn-brand:hover { background-color: var(--cg-accent-2); border-color: var(--cg-accent-2); }

a:hover { color: var(--cg-accent); }

/* Trust claims use the template's own .list-tags / .tags-item component rather
   than a bespoke row. Qasim, 08-Sep: use the theme's text styles, they are sized
   for reading. The only change is colour. */
.list-tags .tags-item {
  /* Stock Aigocy fills these from --neutral-700, i.e. a dark pill. Keep that
     shape language and fix the contrast the recolour broke: deep green ground,
     white type, at the theme's own 16px rather than a shrunken mono. */
  background-color: var(--cg-deep) !important;
  border-color: var(--cg-deep) !important;
  color: #fff !important;
}
.list-tags .tags-item:hover {
  background-color: var(--cg-primary) !important;
  border-color: var(--cg-primary) !important;
  color: #fff !important;
}

/* ---------------------------------------------------------------------------
   Three things in the template reach past the token layer. Found by reading
   computed styles in the browser, not by guessing.
   ------------------------------------------------------------------------ */

/* 1. .text-gradient-1 hardcodes a dark-grey ramp (#43484D → #292C2E). The
      headline is where the healthcare gradient has to read, so it is replaced
      rather than tinted. */
.text-gradient-1 {
  background: var(--cg-gradient) !important;
  -webkit-background-clip: text !important;
  background-clip: text !important;
  -webkit-text-fill-color: transparent !important;
}

/* 2. .hero-image is a raster (hero-1.jpg) carrying the template's orange. A
      photograph cannot be recoloured by tokens, so it is swapped for a soft
      brand wash: cool ground with the three accents held well below tint
      strength, so the headline keeps its contrast. */
.section-hero .hero-image {
  /* The generated wash replaces the hand-rolled gradient stack. It was briefed
     to leave its centre-left third almost empty, which is where the headline
     sits, so it can sit at full strength rather than being dimmed to survive. */
  background-image:
    linear-gradient(180deg, rgba(247, 249, 246, .55) 0%, rgba(247, 249, 246, .92) 100%),
    url("/v2/art/gen/hero-wash.webp") !important;
  background-size: cover !important;
  background-position: center !important;
}
.dark [data-v2] .section-hero .hero-image {
  /* Same art, read through a dark veil rather than regenerated: the wash is a
     light-lit render and would glare on #0b1512 at full strength. */
  background-image:
    linear-gradient(180deg, rgba(11, 21, 18, .82) 0%, rgba(11, 21, 18, .94) 100%),
    url("/v2/art/gen/hero-wash.webp") !important;
}

/* 3. Buttons: the template's dark pill is fine on a light ground, but the
      primary action should carry the brand. */
.tf-btn {
  background-color: var(--cg-accent) !important;
  border-color: var(--cg-accent) !important;
  color: #fff !important;
}
.tf-btn:hover { background-color: var(--cg-accent-2) !important; border-color: var(--cg-accent-2) !important; }

/* 4. Three more sites of the same stock orange (#EA2B16 → #FF3B26) that rules 1
      to 3 did not reach: the process-card icons, the centre feature plate and
      the checked switch. Missed originally because they are gradients on
      elements the earlier hunt did not visit; found by listing every icon whose
      glyph fails contrast against its own fill, which surfaced them as
      "white on orange" rather than as a colour audit.

      Not a dark-mode fix — they were orange in both themes. Same brand ramp as
      the buttons, so the accent stays one colour across the page. */
[data-v2] .process-card .icon,
[data-v2] .tf-switch-check:checked {
  background: linear-gradient(180deg, var(--cg-primary) 0%, var(--cg-accent-2) 100%);
}
[data-v2] .features-center {
  background:
    linear-gradient(180deg, var(--cg-primary) 0%, var(--cg-accent-2) 100%),
    radial-gradient(62.56% 62.56% at 28.14% -10.42%, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
}

/*    And the glow underneath each of them, which is the trap rule 3 already hit
      once: all three carry an eight-layer rgba(255, 59, 0, …) drop shadow, and a
      shadow is not a fill, so swapping the gradient left an orange halo on a
      green plate. Collapsed to the same two-layer green lift the hero box uses;
      the inset highlights are kept where the template had them, since they are
      what gives these plates their edge. */
[data-v2] .process-card .icon {
  box-shadow:
    inset 0 -3px 0 rgba(0, 0, 0, .06),
    inset 0 1px 0 rgba(255, 255, 255, .6),
    0 3px 6px rgba(15, 23, 42, .09),
    0 14px 34px rgba(14, 124, 90, .25);
}
[data-v2] .features-center {
  box-shadow:
    inset 0 -3px 0 rgba(0, 0, 0, .06),
    inset 0 1px 0 rgba(255, 255, 255, .6),
    0 3px 6px rgba(15, 23, 42, .09),
    0 14px 34px rgba(14, 124, 90, .25);
}
[data-v2] .tf-switch-check:checked {
  box-shadow:
    0 3px 6px rgba(15, 23, 42, .09),
    0 14px 34px rgba(14, 124, 90, .25);
}

/* ---------------------------------------------------------------------------
   Qasim, 08-Sep: "I don't like this colour of grey."
   The template leans on a warm mid-grey for every image placeholder and for
   the hero plate. On a cool #f8fafc ground that grey reads muddy and dead.
   Every flat grey surface is replaced with a brand wash instead, so the page
   carries blue/teal/green rather than sludge. Real artwork replaces these
   later; nothing here depends on the template's stock imagery.
   ------------------------------------------------------------------------ */

/* Any stock raster the template ships as a "photo" slot. */
.section-hero .hero-image,
[class*="-image"]:not(.footer-image):not(.hero-image),
[class*="thumbs"], [class*="thumb"] {
  background-color: transparent !important;
}

/* Give image blocks a branded plate rather than grey. */
.featured-works-item [class*="image"],
.section-featured-works [class*="image"],
[class*="card"] [class*="image"] {
  background-image: linear-gradient(135deg, rgba(3,105,161,.10), rgba(8,145,178,.12) 45%, rgba(16,185,129,.10)) !important;
  border: 1px solid var(--cg-edge);
  border-radius: 16px;
}

/* Kill the muddy mid-grey wherever it is set as a flat fill. */
[style*="#8EA4BB"], [style*="#8ea4bb"] { background: var(--cg-surface) !important; }

/* Surfaces read as paper, not concrete. */
.section-about-us, .section-services, .section-process,
.section-benefits, .section-features, .section-pricing, .section-faqs {
  background-color: var(--cg-background) !important;
}

/* The .box plate behind the hero shapes hardcodes the template's red
   (#EA2B16 → #FF3B26). Found by scanning computed styles in the hero, not by
   grepping the stylesheet — it is generated, so the literal is not obvious. */
.section-hero .title-icon .box {
  background-image: linear-gradient(160deg, var(--cg-accent) 0%, var(--cg-accent-2) 55%, var(--cg-accent-3) 100%) !important;
}

/* The hero plate also carries an orange glow as a second box-shadow layer
   (rgba(255,59,0,…)). It survived the background swap because it is a shadow,
   not a fill — found by dumping the .title-icon subtree in the browser. */
.section-hero .title-icon .box {
  box-shadow:
    0 3px 6px rgba(15, 23, 42, .09),
    0 24px 60px rgba(8, 145, 178, .28) !important;
}

/* ---------------------------------------------------------------------------
   Urbanist, which is the template's own face, self-hosted.

   Corrected 10-Sep-2026. This block said the CSP blocks Google Fonts so the
   template has to inherit ours. The first half is true; the conclusion was not.
   `next/font/google` downloads a font at BUILD time and serves it from
   /_next/static/media, which is exactly why `font-src 'self' data:` already
   works for Lexend and Inter. Urbanist needed adding, not permitting, and the v2
   layout now adds it.

   It also contradicted itself. The Lexend unification further up set one family
   on body and its children; this block then put Inter back on `body`, `input`,
   `button`, `select` and `textarea`, and being later it won. Measured: the demo
   renders Urbanist everywhere, the port rendered Inter on body and buttons and
   Lexend elsewhere -- three faces where the template has one. That is what
   Qasim kept seeing.

   One family, everywhere, as stock Aigocy does. Headings included: a single
   geometric family is most of why the template reads as one design.
   ------------------------------------------------------------------------ */
body, body input, body button, body select, body textarea,
h1, h2, h3, h4, h5, h6, .heading-title, .title, [class*="heading"] {
  font-family: var(--font-urbanist), "Urbanist", ui-sans-serif, system-ui, sans-serif;
}
.icon, [class^="icon-"], [class*=" icon-"],
.icon::before, [class^="icon-"]::before, [class*=" icon-"]::before {
  font-family: "icomoon" !important;
}

/* ---------------------------------------------------------------------------
   Stop the sideways wobble, at every width.
   .swiper-box-shadow runs margin: 0 -20px with padding: 0 20px so slide
   box-shadows can render past the container edge. That makes the element 40px
   wider than its parent, and wherever the container has less than 20px of slack
   the right edge escapes the viewport: measured at 8px on / and
   /ai-receptionist at 390px, and again at 1280px.

   The first attempt zeroed the margins below 768px, which fixed the phone and
   left the laptop. Clipping the parent is width-agnostic and costs nothing
   visible: the swiper already sets overflow-x: hidden on itself, so the shadows
   only ever render inside its own 20px padding. `clip` rather than `hidden`
   because hidden creates a scroll container, which is what breaks
   position: sticky and ScrollSmoother.
   ------------------------------------------------------------------------ */
.process-slide {
  overflow-x: clip;
}

/* ---------------------------------------------------------------------------
   The live homepage sections injected into the template page.
   Several carry a decorative glow on `-inset-8`, which makes the element 64px
   wider than its parent. On cartgaze.com an ancestor clips that; inside the
   template shell nothing did, and the homepage scrolled 7px sideways at 390px.
   Clipped here rather than on body or html, since overflow-x there is what
   breaks ScrollSmoother and sticky positioning.
   ------------------------------------------------------------------------ */
@media (max-width: 767px) {
  .v2-live-sections { overflow-x: hidden; }
}

/* ---------------------------------------------------------------------------
   Pricing card heading: let it wrap.
   The template's `.pricing-item .heading` is `display:flex` with
   `justify-content: space-between`, sized for a short price and a short button.
   Our real CTAs are longer ("Book a scoping call", "Run the 2-minute
   calculator") and our tier names longer still, so in a three-up row at 1024px
   the button was squeezed to 48px and its text ran 13px past the viewport.

   Measured at 390, 768, 1024, 1280 and 1440; only 1024 was breaking, which is
   why checking one width is not checking.
   ------------------------------------------------------------------------ */
.pricing-item .heading {
  flex-wrap: wrap;
  gap: 16px;
}
.pricing-item .heading .tf-btn {
  flex: 0 0 auto;
  max-width: 100%;
}

/* ---------------------------------------------------------------------------
   The dotted globe, in the dark section.

   This is the piece the original Aigocy demo has and the Envato zip stripped:
   a dot-built sphere curving away at the foot of the dark card. Generated to
   that brief and wired here rather than into a new element, because .box-black
   already IS the dark card and a background needs no markup change.

   `background-blend-mode: screen` is what makes it work. The render is a glow on
   pure black, and screen blending keeps black transparent while letting the lit
   dots add — so the sphere sits IN the card's colour instead of on a black
   rectangle over it. Without it the art would show its own background as a hard
   edged box, which is the usual way a raster betrays itself on a coloured panel.

   Anchored to the TOP, not the bottom, and that correction is worth keeping.
   The reference has the globe at the foot of a card, so `bottom` looked right —
   but .box-black here is not a card, it is a 3,198px section wrapping the whole
   team and awards block, and a percentage offset from its bottom put the art
   roughly 450px below the viewport, never once visible. Anchored to the top it
   sits behind the section's opening heading, where .light-box already throws its
   glow, so the two read as one light source.
   ------------------------------------------------------------------------ */
/* A pseudo-element, not the element's own background, and the reason is
   opacity. As a background layer the globe had no way to be dimmed — a
   background-image cannot take opacity, and `screen` at full strength put it
   straight through the three cards it is supposed to sit behind. On ::before it
   can be turned down to a backdrop, which is what it always needed to be. */
/* ---------------------------------------------------------------------------
   The benefit-block art needs its size pinned.

   .item-img-1..3 are the template's floating decorative images, positioned and
   animated (`rightleft`, `updown`) inside the block. The originals were ~230px
   plates, so the template never needed to constrain them. The generated
   replacements are 1376x768, and with the parent 611px wide they filled the
   whole card and sat on top of the copy — "Everything logged" and "Owned, not
   rented" were both behind a picture.

   Same intrinsic-size trap the hero tiles hit: art swapped into a slot the CSS
   sizes by assumption rather than by rule. Widths here match what the originals
   actually rendered at, read off the page rather than guessed.
   ------------------------------------------------------------------------ */
[data-v2] .benefits-design-inner .item-img-1,
[data-v2] .benefits-design-inner .item-img-2,
[data-v2] .benefits-design-inner .item-img-3,
[data-v2] .benefits-secure-inner img,
[data-v2] .services-image img {
  /* Rounded and edged because these renders carry their own off-white ground —
     an image model returns no alpha — so on a dark card they would otherwise be
     bare light rectangles. This is the treatment the live story strip already
     uses for light-lit art (.illus-stage): frame it and it reads as a deliberate
     illustration rather than a stray plate. */
  border-radius: 18px;
  border: 1px solid var(--edge);
  box-shadow: 0 10px 30px -18px rgba(2, 10, 7, .5);
}
[data-v2] .benefits-design-inner .item-img-1,
[data-v2] .benefits-design-inner .item-img-2,
[data-v2] .benefits-design-inner .item-img-3 {
  width: 190px;
  height: auto;
}
/* The template pulls the copy UP over this art: `.benefits-design .content` has
   `margin-top: -112px`, which reads well when the plates are 230x278 portraits
   standing above it. The replacements are landscape and half the height, so the
   same negative margin drops the heading straight onto them — "Owned, not
   rented" was sitting inside a picture.
   
   Reduced rather than removed: some overlap is the composition's character, and
   -32px keeps the plates and the copy interlocking without the text landing on
   an image. item-img-2 also moves clear of the text column. */
[data-v2] .benefits-design .content {
  margin-top: -32px;
}
[data-v2] .benefits-design .benefits-design-inner .item-img-2 {
  top: -34px;
  right: 250px;
}
[data-v2] .benefits-secure-inner img {
  width: 280px;
  height: auto;
  margin-inline: auto;
}
@media (max-width: 767px) {
  [data-v2] .benefits-design-inner .item-img-1,
  [data-v2] .benefits-design-inner .item-img-2,
  [data-v2] .benefits-design-inner .item-img-3 {
    width: 150px;
  }
  [data-v2] .benefits-secure-inner img { width: 210px; }
}

/* ---------------------------------------------------------------------------
   The globe band.

   A band of its own, because the globe had nowhere to go. Five placements inside
   .box-black and .section-statistic failed for the same underlying reason: both
   are dense multi-column blocks with no empty region a 680px sphere can occupy.
   The fix was never a different offset.

   The art is a background-image with `background-blend-mode: screen`, which is
   the arrangement that provably renders — the render is a glow on pure black, so
   screen keeps the black transparent and lets the lit dots add into the band's
   own colour. The earlier attempt used mix-blend-mode on a ::before, which
   blends against an isolated backdrop here and contributed nothing at any
   opacity.

   Copy sits left, art right, so the sphere has the empty half of the band to
   itself rather than sitting behind type.
   ------------------------------------------------------------------------ */
[data-v2] .section-globe-band {
  position: relative;
  margin: 0 16px;
  border-radius: 40px;
  overflow: hidden;
  background-color: #08120f;
  background-image: url("/v2/art/gen/globe-dots.webp");
  background-repeat: no-repeat;
  background-position: right -60px bottom -160px;
  background-size: 620px auto;
  background-blend-mode: screen;
}
[data-v2] .section-globe-band .globe-band-inner {
  padding: 92px 0 104px;
}
[data-v2] .section-globe-band .globe-band-copy {
  max-width: 560px;
}
[data-v2] .section-globe-band .heading-title {
  margin-bottom: 20px;
}
/* The eyebrow inherits --primary, which is the near-black page ink, and this
   band is near-black — it measured #0C1A14 on #08120F, i.e. invisible. It takes
   the accent here, which is also what the template's own dark sections do with
   their eyebrows. The dark pill this `.style-1` variant expects never applied
   because that background comes from --neutral-900, which the dark-mode ramp
   moves. */
[data-v2] .section-globe-band .heading-sub {
  color: var(--accent);
  background: none;
  box-shadow: none;
  padding-left: 16px;
  margin-bottom: 14px;
}
[data-v2] .section-globe-band .heading-sub::before {
  background: var(--accent);
}
[data-v2] .section-globe-band .text {
  color: rgba(232, 241, 236, 0.72);
  margin-bottom: 32px;
  max-width: 480px;
}

/* Under 991px the copy would sit on top of the sphere, so the art moves behind
   and drops back — the band still reads, and the type stays first. */
@media (max-width: 991px) {
  [data-v2] .section-globe-band {
    background-position: center bottom -180px;
    background-size: 520px auto;
  }
  [data-v2] .section-globe-band .globe-band-inner {
    padding: 64px 0 76px;
  }
  [data-v2] .section-globe-band .globe-band-copy {
    max-width: none;
  }
}

/* ---------------------------------------------------------------------------
   The dotted globe is NOT wired, and this records why so it is not retried blind.

   globe-dots.png is generated, good, and sitting in public/v2/art/gen. Five
   placements were tried inside the dark section and none worked:

     .box-black bottom  — the wrapper is 3,198px tall, not a card, so a
                          percentage offset put the art ~450px below the fold.
     .box-black top     — ran straight through the three service cards.
     .box-black mid     — same, dimmed; a smudge behind a card is not an
                          improvement on no art.
     .section-statistic — the block is 692px tall and two-column; a 680px sphere
                          has nowhere to sit that is not already occupied.
     …at 0.85 opacity   — still nothing. `mix-blend-mode: screen` on the
                          pseudo-element blends against an isolated backdrop
                          here, so the render contributes nothing regardless of
                          how bright it is. Opacity was never the variable.

   The honest read: this template's dark section is a dense two-column block and
   has no empty region large enough for a sphere. Wiring it needs a layout
   change — a dedicated band, or the "Based in…" style card the original Aigocy
   demo actually used — which is a design decision, not a CSS one.
   ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
   The header wordmark.

   Qasim: "the header or nav bar or idk whats its called, it should be there like
   in template too". The template's nav pill carries the brand as TYPE next to
   the mark — "Aigocy" set in the display face — and the port had only the robot
   icon, so the pill read as an unlabelled button rather than a masthead.

   The lockup matches the live site's own: "Cart" in the accent, "Gaze" in the
   ink. Hidden below 991px, where the template already collapses the nav to a
   drawer and the pill has no room for it.
   ------------------------------------------------------------------------ */
[data-v2] .logo-site {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* Both needed. The pill is a squeezed flex row, so without flex-shrink:0 the
     lockup compresses until the wordmark drops onto a second line under the
     mark; without nowrap the two halves of "Cart Gaze" can split. */
  flex: 0 0 auto;
  flex-wrap: nowrap;
  white-space: nowrap;
}
[data-v2] .logo-site .logo-wordmark {
  font-family: var(--font-urbanist), "Urbanist", ui-sans-serif, system-ui, sans-serif;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--foreground);
  white-space: nowrap;
}
[data-v2] .logo-site .logo-wordmark b {
  font-weight: 700;
  color: var(--accent);
}
@media (max-width: 991px) {
  [data-v2] .logo-site .logo-wordmark { display: none; }
}

/* ---------------------------------------------------------------------------
   Composed inner pages need to clear the header.

   The floating header is 116px tall and the template deliberately pulls the
   first section up underneath it, because on the homepage that section is the
   hero and its own 180px of padding absorbs the overlap. A composed inner page
   has no hero: it opens on `flat-spacing`, which is 80px, so its eyebrow landed
   inside the header band and the h1 sat hard against the nav pill.

   180px is the template's own answer -- `.section-hero.v1 .content-wrap` uses
   exactly that -- so inner pages get the same rhythm rather than a number picked
   to look right in one screenshot.

   `main > :first-child` is what scopes this to inner pages without a flag: on
   the homepage the first child of <main> is the header itself, so the rule does
   not match and the hero is left alone.
   ------------------------------------------------------------------------ */
[data-v2] main > div[class*="section-"]:first-child {
  padding-top: 180px;
}
@media (max-width: 991px) {
  [data-v2] main > div[class*="section-"]:first-child {
    padding-top: 132px;
  }
}

/* ---------------------------------------------------------------------------
   Drawn section art.

   The homepage image slots have now had three occupants. Stock Aigocy shipped
   flat #8EA4BB rectangles; generated hashed plates replaced them; then carousel
   story frames, which were composed 4:5 marketing slides being cropped into a
   1.93:1 card, so each showed a band of somebody else's advert — and because the
   template reuses four filenames across two sections, three of them appeared
   twice on one page.

   They are drawn per slot now, by scripts/v2/make-section-art.mjs, and inlined
   rather than served as files so every colour below resolves per theme. An <img>
   would bake one palette and need a second file for dark.
   ------------------------------------------------------------------------ */
[data-v2] {
  --art-panel: #ffffff;
  --art-edge: rgba(12, 26, 20, 0.10);
  --art-edge-strong: rgba(12, 26, 20, 0.22);
  --art-ink: #0c1a14;
  --art-accent: var(--accent);
  --art-on-accent: #ffffff;
}
.dark [data-v2] {
  /* The panel lifts off the ground rather than going pure black, so a drawn
     surface still reads as a surface. --art-ink inverts because in these scenes
     it is the abstracted type, not the page ink. */
  --art-panel: #16241f;
  --art-edge: rgba(232, 241, 236, 0.12);
  --art-edge-strong: rgba(232, 241, 236, 0.26);
  --art-ink: #e8f1ec;
  --art-accent: var(--accent);
  --art-on-accent: #04121f;
}

[data-v2] .v2-art,
[data-v2] .v2-art-img {
  display: block;
  width: 100%;
  height: 100%;
}

/* The four "Who We Serve" plates are rasters where the rest of this slot holds
   drawn SVG, so they need `object-fit` to crop rather than squash. `slice` was
   what the SVGs used (`preserveAspectRatio="xMidYMid slice"`), and `cover` is
   its raster equivalent -- same framing, same behaviour at every card width. */
[data-v2] .v2-art-img {
  object-fit: cover;
  object-position: center;
}

/* The card has no height of its own and its child is height:100%, so without a
   pinned ratio the box collapses to whatever the art's natural aspect is. That
   was invisible while the slot held a 1230x638 plate and grew every card to
   1539px tall the moment a portrait image went in. */
[data-v2] .featured-works-item .image {
  aspect-ratio: 1230 / 638;
  background: var(--art-ground, transparent);
}

/* ---------------------------------------------------------------------------
   Vendor lockups in the "stack we build on" marquee.

   Mark plus name, monochrome, inheriting the text colour so the strip follows
   the theme instead of being redrawn for it. Sixteen brand colours in a row is
   noise; a stack strip is supposed to read as one material.

   The mark sits in a fixed 26px box. Simple Icons are all on a 24x24 grid but
   their glyphs carry different visual weight, so the box is the constant and the
   eye does the rest — the alternative is per-logo optical tuning that a re-fetch
   would silently invalidate.
   ------------------------------------------------------------------------ */
[data-v2] .infiniteSlide_partner .v2-vendor {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
  font-weight: 600;
  font-size: 17px;
  line-height: 1;
  color: var(--secondary);
}
[data-v2] .infiniteSlide_partner .v2-vendor svg {
  flex: 0 0 26px;
  width: 26px;
  height: 26px;
  fill: currentColor;
}

/* The mark on an integrations card. Same monochrome treatment as the marquee,
   at card scale. The lettermark variant is deliberately a tile with letters in
   it rather than anything logo-shaped — it should read as "no mark available",
   not as a mark. */
[data-v2] .v2-vendor-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  color: var(--foreground);
}
[data-v2] .v2-vendor-mark svg {
  width: 28px;
  height: 28px;
  fill: currentColor;
}
[data-v2] .v2-vendor-mark.is-letters {
  border: 1px solid var(--edge);
  border-radius: 9px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--secondary);
}

/* The strip fades out at both ends, and the template paints that fade in a
   literal #EDECEC — its own idea of the page colour. It is wrong in light too
   (the ground here is #F7F9F6, so the fade was a faint grey smear against it)
   and on dark it was a light bar straight across the logos.

   Written as explicit stops per theme rather than `transparent`, because a
   gradient interpolating to rgba(0,0,0,0) greys out through the middle — the
   fade has to end on the ground colour at zero alpha, not on transparent black.
   `.section-partner` has no background of its own, so the ground is the token. */
[data-v2] .partner-wrap .infiniteslide_wrap::after {
  background: linear-gradient(90deg,
    #f7f9f6 0%, rgba(247, 249, 246, 0) 25%,
    rgba(247, 249, 246, 0) 75%, #f7f9f6 100%);
}
.dark [data-v2] .partner-wrap .infiniteslide_wrap::after {
  background: linear-gradient(90deg,
    #0b1512 0%, rgba(11, 21, 18, 0) 25%,
    rgba(11, 21, 18, 0) 75%, #0b1512 100%);
}

/* ---------------------------------------------------------------------------
   .text-neutral-200 and .text-neutral-300 are the wrong tool, everywhere.

   In stock Aigocy these are pale greys for captions sitting ON the near-black
   plates. The port uses them for ordinary secondary text on ordinary sections —
   footer address, step captions, the pricing footnote, category headings, the
   big step counters — about sixty call sites, nearly all of them in generated
   output (home-page.tsx, template-chrome.tsx) that must not be hand-edited.

   Measured across both themes, every single one fails WCAG AA: 1.53:1 in light
   (#C3CFBF on #F7F9F6) and 1.49:1 in dark. Not one is correctly placed, which
   is what makes remapping safe here — there is no dark-plate caption to break.
   The light-mode half of this predates dark mode and was not introduced by it.

   Remapped rather than fixed at the call sites because the call sites are
   regenerated. --secondary is the token that already means "muted body text"
   and it clears AA on both grounds: 5.53:1 light, 7.79:1 dark.
   ------------------------------------------------------------------------ */
[data-v2] .text-neutral-200,
[data-v2] .text-neutral-300 {
  color: var(--secondary) !important;
}

/* ---------------------------------------------------------------------------
   Dark mode for the template's own variables.

   Qasim, 09-Sep: the port has to carry light and dark like the live site.
   globals.css now defines the v2 token palette for `.dark [data-v2]`, but the
   template's stylesheet has a second, separate set of variables that our tokens
   never touched — `--primary` is the near-black used for every heading and most
   body copy (31 uses), `--secondary` the grey beneath it (10). Left alone, a dark
   page rendered black headings on a near-black ground.

   Only the ink moves. `--white` and `--black` keep their meanings, because a
   rule that says "white" means white; and the accent variables are already
   handled by the token overlay above.
   ------------------------------------------------------------------------ */
.dark [data-v2] {
  --primary: #e8f1ec;
  --secondary: #9ab5a7;
  --body: #0b1512;
}

/* The brand tokens at the top of this file, given their dark values.

   These are the roots of the whole graph — `--cg-background`, `--cg-foreground`
   and `--body` are all declared as `var(--cg-*)` — so a dark block that skipped
   them left every derived token pinned to its light value no matter what the
   theme said. That is what kept the page light: the seven `.section-*` rules
   below paint `var(--cg-background) !important`, which resolved to #F7F9F6
   through `--cg-ground` on a page whose body was already correctly #0b1512.
   Setting the roots here fixes the sections, the hero wash and the surfaces in
   one move rather than chasing each consumer. */
.dark [data-v2] {
  --cg-ground:  #0b1512;
  --cg-surface: #16241f;  /* white in light; on dark a plate lifted off the ground */
  --cg-ink:     #e8f1ec;
  --cg-muted:   #9ab5a7;
  --cg-edge:    rgba(232, 241, 236, .12);

  /* Restating the two derived tokens is not redundancy, and leaving them out is
     what made the first version of this block look like it had done nothing.

     `--cg-background: var(--cg-ground)` is declared up in `:root`. A custom
     property's `var()` is substituted where the property is DECLARED, not where
     it is used — so `--cg-background` resolves against `:root`'s `--cg-ground`
     and is already the literal #F7F9F6 by the time a section inherits it.
     Overriding `--cg-ground` on a div deep in the body cannot reach backwards
     and change it. Only the tokens whose declared value contains a `var()` need
     this; `--cg-surface` and `--cg-edge` above are literals, so overriding them
     here is enough. */
  --cg-background: #0b1512;
  --cg-foreground: #e8f1ec;

  /* Bootstrap ships its own `.text-muted`, and on these routes it wins over the
     app's utility of the same name — the forked pages carry both stylesheets.
     It reads --bs-secondary-color, which Bootstrap only re-themes under its own
     [data-bs-theme="dark"] attribute that this site never sets, so muted copy
     stayed rgba(33,37,41,.75) and measured 1.07:1 on the dark cards. Retheming
     the tokens rather than overriding the class fixes every Bootstrap utility
     that reads them, not just the one that happened to show up. */
  --bs-body-color: #e8f1ec;
  --bs-body-color-rgb: 232, 241, 236;
  --bs-secondary-color: rgba(232, 241, 236, 0.75);
  --bs-emphasis-color: #f2f7f4;

  /* Same trap, and the one that matters most visually: `--cg-gradient` is
     declared as `linear-gradient(120deg, var(--cg-deep), ...)`, so it is baked
     at :root and the hero display type kept starting on #064E3B — a deep forest
     green that all but vanishes on #0b1512. Restated on the lifted ramp, which
     is the same set of stops globals.css gives --v2-gradient in dark, so the
     two gradient systems agree instead of drifting. */
  --cg-gradient: linear-gradient(120deg, #10b981, #34d399 58%, #6ee7b7);
}

/* The other two gradient-text ramps never went through a token at all: stock
   Aigocy hardcodes near-blacks (#292C2E, #61666A) straight into the rules, so
   they survived every remap above and painted charcoal headings on the dark
   ground. `background-clip: text` means the `color` fix on headings cannot help
   them either — the glyphs are a window onto the background, not ink. These are
   most of the section headings on the page, so they are restated rather than
   left to the theme.

   Structure is preserved exactly, including gradient-2's fade from transparent;
   only the colours move to the light end of the ramp. */
.dark [data-v2] .text-gradient-2 {
  background-image: linear-gradient(360deg, rgba(232, 241, 236, 0) 0%, rgb(232, 241, 236) 100%) !important;
}
.dark [data-v2] .text-gradient-3 {
  background-image: linear-gradient(95.5deg, #9ab5a7 1.91%, #e8f1ec 43.92%) !important;
}

/* Fills the same job as body:has([data-v2]) in globals.css, for dark. The
   wrapper does not cover overscroll or any gap below the fold, so without this
   the page bounces against a light ground.

   The ink has to be set here too, and for the same reason the background does.
   styles.css says `body { color: var(--primary) }`, and `--primary` resolves at
   the BODY, which sits outside the `[data-v2]` div — so `.dark [data-v2]`
   cannot reach it, and every element that simply inherits its colour (the hero
   paragraph, the step descriptions, the timings) kept the light near-black and
   measured 1.04:1 on the dark ground. Setting the two tokens rather than
   `color` directly means everything below body that reads them follows. */
html.dark body:has([data-v2]) {
  background-color: #0b1512;
  --primary: #e8f1ec;
  --secondary: #9ab5a7;
}

/* Headings take `color: var(--black)` from the template's own reset, and --black
   has to keep meaning black for everything else that asks for it, so the ink is
   moved on the headings themselves rather than by redefining the variable. */
.dark [data-v2] :is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6) {
  color: var(--foreground);
}

/* ---------------------------------------------------------------------------
   The neutral scale, inverted.

   This is the piece that actually makes dark mode work. The template paints
   almost every surface, border and secondary text from `--neutral-*` — 72 uses
   across styles.css — and a light-to-dark ramp on a dark page gives you pale
   cards on a pale ground, which is what the first attempt produced: correct ink,
   invisible against everything behind it.

   Inverting the ramp fixes cards, borders and muted text in one move, because
   the template already uses it consistently. The hues carry a slight green cast
   rather than the template's cool grey, so the surfaces sit in the same family
   as the practice-green ground instead of reading blue against it.
   ------------------------------------------------------------------------ */
.dark [data-v2] {
  --neutral-50: #0b1512;
  --neutral-100: #121e19;
  --neutral-200: #1a2a23;
  --neutral-300: #24382f;
  --neutral-400: #9ab5a7;
  --neutral-500: #a8c0b3;
  --neutral-600: #bdd0c5;
  --neutral-700: #d2e0d7;
  --neutral-800: #e0ebe4;

  /* 900 and 950 are the exception, and inverting them was a real bug: they are
     not ink. Every one of the eleven `--neutral-900` uses and all four
     `--neutral-950` uses in styles.css is a background — `.box-black`, the
     `.team-item` plates, `.col-left`, `.box-quotes` — and none is a `color`.
     They are the template's deliberately near-black plates, so on a dark page
     they have to stay dark or the figure inverts: flipping them painted a
     1248x3194 white slab over `.box-black` and turned every team card white.
     They are lifted just off the ground instead, so a plate still reads as a
     plate against #0b1512 rather than disappearing into it. */
  --neutral-900: #17251f;
  --neutral-950: #101c17;
}

/* ---------------------------------------------------------------------------
   The three literals the token graph cannot reach.

   Everything above moves because it is expressed as a variable. These three are
   hardcoded colours — two in the template's own stylesheet, one in this file —
   so no amount of retokenising touches them, and each one paints a light slab
   on the dark page. Found by rendering the page in headless Chrome and listing
   every element over 40,000px2 whose computed background has a luminance above
   150, which is the only way any of these were going to surface.
   ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
   The nav pill has to read as a bar.

   Qasim, with the template's bar attached: "this bar isnt there on our one". It
   was there, but barely: the template sets rgba(255,255,255,.5) on it, which was
   fine over the old flat gradient and is not fine over the generated hero wash —
   at half opacity the green came straight through and the pill stopped reading
   as a separate object. In the template's own screenshot it is unmistakably a
   white bar floating above the art.

   Raised to .88 with a hairline and a soft lift, which is the template's look
   rather than a new one; the backdrop blur stays, so it still behaves like glass
   over whatever scrolls under it.
   ------------------------------------------------------------------------ */
[data-v2] .tf-header .header-inner {
  background: rgba(255, 255, 255, 0.88);
  border: 1px solid rgba(12, 26, 20, 0.06);
  box-shadow: 0 2px 6px rgba(12, 26, 20, .04), 0 16px 40px -24px rgba(12, 26, 20, .28);
}
.dark [data-v2] .tf-header .header-inner {
  background: rgba(18, 30, 25, 0.88);
  border-color: rgba(232, 241, 236, 0.10);
  box-shadow: inset 0 1px 0 rgba(154, 181, 167, .08), 0 16px 40px -24px rgba(0, 0, 0, .7);
}

/* The "Scroll for more" tab at the foot of the hero: #EDECEC. */
.dark [data-v2] .section-hero .scroll-more {
  background-color: #16241f;
}

/* The hero wash. Its own rule (rule 2 above) is `!important` and starts on a
   literal #ffffff, so it needs a matching `!important` here rather than a
   token. The three accent radials are held below tint strength and read the
   same either way, so only the base gradient is restated. */
.dark [data-v2] .section-hero .hero-image {
  background-image:
    radial-gradient(60% 55% at 78% 18%, rgba(52, 211, 153, .10), transparent 70%),
    radial-gradient(50% 50% at 15% 12%, rgba(8, 145, 178, .10), transparent 72%),
    radial-gradient(70% 60% at 50% 96%, rgba(16, 185, 129, .08), transparent 75%),
    linear-gradient(180deg, #142520 0%, var(--cg-background) 100%) !important;
}

/* The secondary button and the hero eyebrow pill.

   Both are `linear-gradient(0deg, #F5F5F5, #F5F5F5)` with a white inset
   highlight — a literal light plate, so the theme could not move them. On dark
   that left "See pricing" printing --primary (#e8f1ec) onto #F5F5F5, and the
   contrast sweep could not see it: the fill is a gradient, and gradients are
   exempted there because a ramp has no single colour to measure against. It was
   caught in a screenshot, which is the argument for still looking at the page.

   The insets are kept but inverted — a dark plate cannot show a white top edge
   or a black bottom one at those strengths. --brand is lifted on the eyebrow
   only, rather than globally, because it is a fill elsewhere. */
.dark [data-v2] .tf-btn-2,
.dark [data-v2] .section-hero .content-wrap .sub {
  background: linear-gradient(0deg, #16241f, #16241f);
  box-shadow:
    inset 0 -3px 0 rgba(0, 0, 0, .25),
    inset 0 1px 0 rgba(154, 181, 167, .14),
    0 3px 3px rgba(0, 0, 0, .2);
}
.dark [data-v2] .section-hero .content-wrap .sub {
  color: var(--accent);
}
.dark [data-v2] .section-hero .content-wrap .sub svg path {
  fill: var(--accent);
}

/* The check glyphs: 155 of them, and every one failed in dark.

   `.icon-check-solid` is an icomoon glyph drawn through `::before`, so it has no
   text node and the contrast sweep walked straight past it — it only surfaced
   when the ticks in a screenshot looked faintly red, which turned out to be
   subpixel fringing on a near-invisible black glyph. 151 of them take the
   template's hardcoded black and sat on dark plates at 1.4:1 and 1.02:1.

   The tick equals the body ink in stock Aigocy, so it is given the body ink
   here rather than a new colour; that keeps the port faithful and measures
   12.4:1 on the card plate.

   Almost all of them sit on their own --neutral-200 disc, which is dark here, so
   the body ink is right for them. The four exceptions are on the highlighted
   `.pricing-item.style-black` card, whose disc comes from --neutral-700 and so
   inverts to a light one while the glyph stays white — 1.37:1. Those get
   --neutral-50, the far end of the same ramp, so the disc and the glyph stay
   opposed in whichever direction the ramp points.

   The discriminator is the card variant, not the list class: both sets are in
   `ul.list-text.type-check`, so scoping to the list recoloured the 148 and left
   the 4 untouched, which measured worse than doing nothing.

   The selectors mirror the template's own `.list-text.type-check li .icon`
   rather than targeting `.icon-check-solid`. That rule is (0,3,1) and a tidier
   `.dark [data-v2] .icon-check-solid` is only (0,3,0), so it silently lost and
   the glyphs measured black again — the shape here is deliberate. */
.dark [data-v2] .icon-check-solid {
  color: var(--foreground);
}
.dark [data-v2] .list-text.type-check li .icon {
  color: var(--foreground);
}
.dark [data-v2] .pricing-item.style-black .list-text.type-check li .icon {
  color: var(--neutral-50);
}

/* ---------------------------------------------------------------------------
   The header has to stay on screen.

   Stock Aigocy declares `header { position: sticky; z-index: 100 }` and never
   gives it a `top`. A sticky element with `top: auto` never sticks — it sits in
   flow and scrolls away like a static one, which is what the header was doing:
   measured at -2500px after scrolling 2500px, on every route. Qasim reported it
   as "I don't see headers on top", and he was right.

   The live site's header is `position: fixed`, so this was a behaviour the port
   had quietly lost rather than a look the template intended. One inset value
   turns the template's own mechanism back on; `fixed` is not needed, and sticky
   keeps the header in flow so the layout below is unchanged.
   ------------------------------------------------------------------------ */
[data-v2] header.tf-header {
  top: 0;
}

/* The theme toggle. It was on the left rail, where the template's demo
   Configuration gear used to sit — which is also exactly where Next puts its
   dev-tools badge. `elementFromPoint` over the toggle returned `nextjs-portal`
   on all fifteen routes: the badge covered it, so it could not be clicked at
   all while reviewing locally. Qasim hit this before it was found here.

   Moved to the right rail, stacked above #goTop (fixed, bottom 40px, 38px
   tall), which reads as a deliberate utility column and is somewhere the dev
   badge will never be. */
.v2-theme-toggle {
  position: fixed;
  right: 20px;
  bottom: 92px;
  z-index: 1200;
}
@media (max-width: 991px) {
  .v2-theme-toggle { right: 14px; bottom: 86px; }
}

/* ============ the scroll reveal, removed ============ */
/* Qasim's call, 10-Sep-2026. This rule used to apply only under
   prefers-reduced-motion; it is unconditional now, and it is half of the fix.

   What the reveal cost. gsapAnimation.js opened every .effectFade element with
   `gsap.set(el, { autoAlpha: 0 })` — opacity 0 AND visibility: hidden — and
   released it on a ScrollTrigger at "top 95%". Before you scrolled, 37 of the
   homepage's 45 headings were out of the accessibility tree and out of
   find-in-page, not merely out of view: 3,650 of 13,083 characters readable at
   load with JS on, against 13,083 with JS off.

   Why the JS change alone was not enough, and this is the part worth reading.
   styles.css line 3101 ships `.effectFade { opacity: 0 }`, so the vendor
   stylesheet does the hiding and the script was the only thing that ever undid
   it. Neutralising scrollEffectFade() on its own left the port at opacity 0
   permanently — 109 of 109 .effectFade elements still hidden on the homepage,
   measured, which is how this was caught rather than shipped. The reveal had two
   authors and only one of them was JavaScript.

   !important stays. It was here to outrank GSAP's inline styles, which no longer
   exist; it now outranks the vendor rule regardless of load order, and it is the
   guarantee if the script ever comes back. */
.effectFade,
.effectFade.fadeUp,
.effectFade.fadeDown,
.effectFade.fadeLeft,
.effectFade.fadeRight,
.effectFade.fadeRotateX,
.effectFade.fadeZoom {
  visibility: visible !important;
  opacity: 1 !important;
  transform: none !important;
}

/* The agency-services cards lay their feature list out as a grid, and a grid item
   defaults to min-width: auto, so the check icon plus an unbreakable word set a
   floor wider than the column. At 1024px the col-lg-4 column is 119px of content
   and the implicit column measured 136px, which overflowed the card by 17px.
   min-width: 0 lets the item shrink and the text wrap, which is what it should
   have done. Scoped to this block so the template's own pricing lists are
   untouched. */
.v2-template-island .list-text {
  grid-template-columns: minmax(0, 1fr);
}

.v2-template-island .list-text > li {
  min-width: 0;
}

/* The agency-services cards borrow the template's .pricing-item, and two of its
   rules assume the template's own content rather than ours.

   `.pricing-item .top` is a row: [short label] | vertical rule | [short label].
   The template puts "Basic Plan" and "For startups" there. Ours carries a real
   service name and a sentence, so at three-up it rendered as two four-line
   columns with a hairline between them. Stacked, with the rule turned
   horizontal, it reads as a title and a standfirst, which is what it is.

   `.pricing-item .content > * { width: 50% }` expects two columns. Ours is one
   list, so every feature wrapped every three words and the list overflowed its
   card by 17px at 1024. Full width, one column.

   Scoped to the island so the template's own pricing cards, which do carry two
   short labels and two columns, are untouched. */
.v2-template-island .pricing-item .top {
  flex-direction: column;
  gap: 10px;
  align-items: flex-start;
}

.v2-template-island .pricing-item .top .line {
  width: 32px;
  height: 1px;
}

.v2-template-island .pricing-item .content > * {
  width: 100%;
}

/* `.pricing-item .heading` is a space-between row holding a short number and a
   short unit -- "$99" and "/ month". Two of ours read "Included" / "in every
   build; available standalone" and "from $8,000" / "project, scoped on a call",
   which do not fit on one line and pushed 75px past the card at 1024. Wrapping
   is the honest fix: the price and its qualifier belong together, and a
   qualifier that needs two lines should get two lines. */
.v2-template-island .pricing-item .heading,
.v2-template-island .pricing-item .heading > .d-flex {
  flex-wrap: wrap;
  min-width: 0;
}

/* And the qualifier gets its own line. Ours are sentences -- "quoted after a
   scoping call", "in every build; available standalone" -- not the "/ month" the
   template sizes this slot for, and a flex row cannot wrap a child narrower than
   its own longest word. */
.v2-template-island .pricing-item .heading .price-per {
  flex: 0 0 100%;
  max-width: 100%;
  min-width: 0;
}

/* The template sizes .price-number at 72px because its prices are "$99". Ours are
   phrases -- "Custom", "Scoped", "Included" -- and "Custom" alone measured 263px
   inside a 237px card at 1024, which is where the last of the card overflow was
   coming from. 34px is the template's own step down from its 40px breakpoint
   value, and it fits the longest string we publish. (The example list used to
   include "from $3,500"; the 10-Sep pricing decision removed every number from
   these cards, which only shortens the longest string this has to fit.) */
.v2-template-island .pricing-item .heading .price-number {
  font-size: 34px;
  line-height: 42px;
  overflow-wrap: anywhere;
}

/* ============ Bootstrap's .collapse vs Tailwind's .collapse ============ */
/* Two different meanings, one class name, and the collision made the FAQ
   unreadable. Bootstrap uses `.collapse` as a component class on the accordion
   panel; Tailwind generates `.collapse { visibility: collapse }` as a utility.
   Nothing in Bootstrap sets `visibility` on it, so Tailwind's rule was the only
   one and it won: opening any accordion row grew the panel to full height and
   showed nothing inside it. Reported 09-Sep with a screenshot of "What does it
   cost?" open over an empty box.

   `display` still does the hiding -- Bootstrap's own `.collapse:not(.show)` sets
   `display: none` -- so making these visible changes nothing about what is open.
   Scoped to [data-v2] because that wraps every page in the port and nothing else. */
[data-v2] .collapse,
[data-v2] .collapsing,
[data-v2] .accordion-collapse {
  visibility: visible;
}

/* ============ footer ============ */
/* The template's footer is one centred block over a mint wash, with a bottom bar
   carrying four links. We had put 37 links into two `.footer-links` flex-wrap
   rows inside that bottom bar, which is what made it read as a wall: "Industries"
   sat alone on its own line, "Veterinary Chiropractic" wrapped onto one row and
   "Optometry Podiatry" onto the next, and every link carried a permanent
   underline. The live footer is a four-column grid; this puts the port back to
   the same shape using Bootstrap's grid, which is the template's own. */

/* The wash is an absolutely-positioned image sized to its own aspect ratio, so it
   ended in the middle of the credential block with a hard horizontal edge. Cover
   the content block it sits behind and fade it out instead of cutting it. */
/* The mask goes on the <img>, not the wrapper. On the wrapper the child image
   still paints its own hard bottom edge, which is the horizontal line that was
   cutting across the footer just above the claim pills. */
footer .footer-image {
  height: 100%;
}

footer .footer-image img {
  -webkit-mask-image: linear-gradient(to bottom, #000 30%, transparent 88%);
  mask-image: linear-gradient(to bottom, #000 30%, transparent 88%);
}

/* The template leaves 125px under the centred block, which was fine when nothing
   followed it. With a link grid underneath it read as a hole. */
footer .footer-content {
  padding-bottom: 48px;
}

/* Four claims, four columns. `.tf-social-1` wraps, so at most desktop widths the
   fourth pill dropped to a second row on its own and read as an afterthought. */
@media (min-width: 992px) {
  .footer-claims {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
  }
  .footer-claims a {
    justify-content: space-between;
    padding: 14px 18px;
  }
}

footer .footer-nav {
  padding: 56px 0 8px;
  border-top: 1px solid var(--neutral-200, rgba(0, 0, 0, 0.08));
}

footer .footer-nav-title {
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--secondary);
  margin-bottom: 16px;
}

footer .footer-nav-list {
  list-style: none;
  margin: 0 0 32px;
  padding: 0;
  display: grid;
  gap: 10px;
}

/* Sixteen specialty links is the site's main internal-linking surface and has to
   stay whole. Two columns inside the one grid column keeps it from running down
   the page as a single strip. */
/* Multi-column rather than a two-column grid. A grid aligns rows, so one
   two-line entry ("OB-GYN & Women's Health") opened a gap opposite it; columns
   flow instead, which is what a list of sixteen wants. */
@media (min-width: 576px) {
  footer .footer-nav-list-2col {
    display: block;
    columns: 2;
    column-gap: 28px;
  }
  footer .footer-nav-list-2col li {
    break-inside: avoid;
    margin-bottom: 10px;
  }
}

/* No permanent underline. The template's own `link1` already animates one in on
   hover, which is the treatment every other link on the site uses. */
footer .footer-nav-list a {
  font-weight: 500;
  color: var(--primary);
}

footer .footer-address {
  font-style: normal;
  line-height: 1.7;
}

footer .footer-bottom {
  padding-top: 24px;
  padding-bottom: 32px;
}

footer .footer-bottom .coppy-rights {
  margin-bottom: 0;
  text-align: left;
}

@media (max-width: 991px) {
  footer .footer-nav {
    padding-top: 40px;
  }
  footer .footer-bottom .coppy-rights {
    text-align: center;
  }
}

/* The compliance glyphs. The slot was built for a wide partner logo, so it has no
   intrinsic size of its own; these are square. */
.v2-mark {
  width: 48px;
  height: 48px;
  display: block;
}

/* ============ reduced motion, part two: the live Reveal ============ */
/* Measured 10-Sep: with `prefers-reduced-motion: reduce` the port was BLANK
   above the fold on every forked page -- 13 of 13 headings and paragraphs
   invisible on /growth-leak-calculator, 14 of 14 on /pricing, and the same on
   /services, /work and the specialty pages. cartgaze.com renders 0 of them
   hidden, so this is the port's alone.

   The mechanism, and it is not a CSS bug. `Reveal` calls `useReducedMotion()`,
   which cannot know the user's setting during server rendering: the server emits
   framer-motion's `initial` state as an inline `style="opacity:0;transform:
   translateY(14px)"`, while a reduced-motion client renders a plain <div> with no
   style at all. React sees the mismatch, logs "some attributes of the server
   rendered HTML didn't match the client properties. This won't be patched up",
   and keeps the server's opacity: 0. On the live pages hydration recovers; in the
   port it does not, and the template's own jQuery/GSAP bundle mutating the DOM
   around hydration is the difference.

   reveal.tsx is a live component and is not ours to change, so the port pins the
   inline state instead. `!important` is doing real work: an inline declaration
   outranks every selector, and an !important rule in a stylesheet is the one
   thing that outranks it. Scoped to the reduced-motion query AND to
   .v2-live-content, so it can only ever affect a forked page belonging to someone
   who asked for less motion -- for whom "show me the content" is the whole point. */
@media (prefers-reduced-motion: reduce) {
  [data-v2] .v2-live-content [style*="opacity:0"],
  [data-v2] .v2-live-content [style*="opacity: 0"] {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ============================================================================
   THE THEME PASS
   ============================================================================
   Qasim, 10-Sep: "no v1 template be there, it should be in v2 and well shown."

   Measured first, because the complaint was "old fonts" and the fonts were never
   the problem: every page in the port renders Lexend, including the one he named.
   What differs is the type scale and the components. Three pages are built from
   template markup -- the homepage, /about, /integrations -- and carry 10 to 28
   template cards each with an h1 at 72-96px. The other twenty-one are forked live
   pages: zero template cards, zero template heading blocks, h1 at 36-54px, and
   `card` / `spotlight-card` / `card-raised` instead of `.features-item`.

   So this restyles rather than rewrites. Every rule below copies a value out of
   styles.css rather than inventing one, and all of it is scoped to
   `.v2-live-content`, which only ever wraps a forked page -- the template-native
   three cannot be touched by any of it.

   Rewriting twenty-one pages into template markup was the alternative. It would
   have thrown away the content parity the audit proved at zero across all sixteen
   specialty pages and seventeen other routes, and it is not reversible.
   ========================================================================= */

/* --- Cards. The template's own values, lifted from .features-item. --------- */
[data-v2-skin] .v2-live-content .card,
[data-v2-skin] .v2-live-content .card-raised,
[data-v2-skin] .v2-live-content .card-glow,
[data-v2-skin] .v2-live-content .spotlight-card {
  border-radius: 28px;
  background-color: var(--neutral-100);
  border-color: transparent;
  box-shadow:
    0px 7.77px 16px 0px rgba(0, 0, 0, 0.0588235294),
    0px 3px 3px 0px rgba(0, 0, 0, 0.1019607843),
    0px -8px 0px 0px rgba(0, 0, 0, 0.0509803922) inset,
    0px 4px 0px 0px rgba(255, 255, 255, 0.6) inset;
}

/* The inset highlight reads as a raised lip on a light ground and as a seam on a
   dark one, so the dark theme gets the same shape without it. */
[data-v2] [data-theme="dark"] .v2-live-content .card,
[data-theme="dark"] [data-v2-skin] .v2-live-content .card,
[data-theme="dark"] [data-v2-skin] .v2-live-content .card-raised,
[data-theme="dark"] [data-v2-skin] .v2-live-content .card-glow,
[data-theme="dark"] [data-v2-skin] .v2-live-content .spotlight-card {
  box-shadow:
    0px 7.77px 16px 0px rgba(0, 0, 0, 0.28),
    0px 3px 3px 0px rgba(0, 0, 0, 0.22);
}

/* --- The page heading. -----------------------------------------------------
   Only h1, and only the page's own. The template's 72px is the single loudest
   signal that you are looking at the new design, and a forked page has exactly
   one h1. h2 is deliberately left alone: pushing the template's 48px onto card
   headings was measured overflowing them -- a 48px h2 does not fit a 219px card,
   and /compare went 43px past the viewport at 768. */
[data-v2-skin] .v2-live-content h1 {
  font-size: 52px;
  line-height: 1.06;
  letter-spacing: -0.03em;
  font-weight: 600;
  /* The template's own headings are three or four words, so 72px sits on two
     lines. Live headings are sentences: at 64px the calculator's ran to four
     lines and 266px tall. 52px holds the template's voice and fits the copy we
     actually have, and `balance` stops the last line being one orphaned word. */
  text-wrap: balance;
}

@media (max-width: 1199px) {
  [data-v2-skin] .v2-live-content h1 { font-size: 44px; }
}

@media (max-width: 767px) {
  [data-v2-skin] .v2-live-content h1 { font-size: 34px; line-height: 1.14; }
}

/* --- The eyebrow. ----------------------------------------------------------
   Live sets these as loose letterspaced caps; the template puts them in a pill
   with a dot. Same values as .heading-section .heading-sub, including the dot,
   which is a ::before there and has to be one here too. */
[data-v2-skin] .v2-live-content p.font-mono.uppercase {
  position: relative;
  display: inline-flex;
  align-items: center;
  /* NOT `width: max-content`. The template's chip uses it because it sits in a
     centred heading block with room to spare; on a live page the same declaration
     sets the element's preferred width to the whole unwrapped string, which grows
     the auto-sized grid track around it. Measured at 390px: /blog went 150px past
     the viewport, /compare 29px, four more besides. `inline-flex` already
     shrink-wraps, and letting the label wrap costs nothing. */
  max-width: 100%;
  border-radius: 8px;
  padding: 6px 16px 6px 30px;
  background: var(--neutral-100);
  box-shadow:
    0px 2px 6px 0px rgba(0, 0, 0, 0.05),
    0px 3px 0px 0px rgba(255, 255, 255, 0.5) inset;
  letter-spacing: 0.12em;
}

[data-v2-skin] .v2-live-content p.font-mono.uppercase::before {
  position: absolute;
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 99px;
  left: 16px;
  /* Aligned to the FIRST line, not the box. The template centres it because its
     own labels are always one line; ours wrap -- "VS HIRING ANOTHER FRONT-DESK
     PERSON" on /compare takes two -- and a centred dot then floats between them. */
  top: 0.95em;
  background: var(--accent);
}

/* A centred section wants a centred chip, and `inline-flex` alone will not do it
   inside a text-align:center parent once it has width:max-content. */
[data-v2-skin] .v2-live-content .text-center > p.font-mono.uppercase,
[data-v2-skin] .v2-live-content .mx-auto > p.font-mono.uppercase {
  margin-left: auto;
  margin-right: auto;
}

/* --- Form fields, to the same radius as everything else. ------------------ */
[data-v2-skin] .v2-live-content input[type="text"],
[data-v2-skin] .v2-live-content input[type="email"],
[data-v2-skin] .v2-live-content input[type="tel"],
[data-v2-skin] .v2-live-content input[type="number"],
[data-v2-skin] .v2-live-content select,
[data-v2-skin] .v2-live-content textarea {
  border-radius: 14px;
}

/* ============ the benefits blocks: art on top of the heading ============ */
/* Qasim, with a screenshot: a plate sitting across "Everything logged" and
   another across "Owned, not rented".

   The template composes these two cards by pulling the copy up underneath the
   art -- `.benefits-secure-inner { margin-bottom: -26px }` and
   `.benefits-design .content { margin-top: -32px }` -- which works because the
   art it ships has transparent space along its bottom edge to absorb the tuck.
   Ours is drawn to its own bounding box and has none, so the copy lands on the
   picture. Measured: the two overlaps are 26px and 32px, the negative margins
   exactly.

   Zeroing the tuck is the smaller change. Re-cropping every plate to carry dead
   space at the foot would mean baking a layout assumption into the art, and the
   same files are used elsewhere at other sizes. */
[data-v2] .benefits-secure-inner {
  margin-bottom: 0;
}

[data-v2] .benefits-design .content {
  margin-top: 0;
}

/* The footer descriptor, held to a readable measure.
   Unconstrained it ran the full 1296px of the container as a single line, which
   is about 160 characters and reads as a strip rather than a sentence. The live
   footer keeps this text in a narrow column; the port's block is centred, so a
   max-width does the same job. 44rem puts it on two lines from 1024px up and
   leaves the existing mobile wrapping alone. */
[data-v2] footer .footer-content > p.text {
  max-width: 44rem;
  margin-left: auto;
  margin-right: auto;
}

/* ---------------------------------------------------------------------------
   Countries served — the floating flag row in the "Healthcare is all we do" band.

   Sits in the dark card between the "Book a call" button and the ribbon image,
   which was ~210px of empty space at 1440. National flags are not copyrightable,
   so these are drawn here rather than lifted from a purchased template's asset
   pack: public/v2/flags/*.svg.

   No `effectFade` on the markup — the scroll reveal was killed on 10-Sep and
   `.effectFade { opacity: 0 }` in styles.css is what made it necessary.
--------------------------------------------------------------------------- */
.cg-flags {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 20px;
  margin: 40px auto 0;
  padding: 0;
  list-style: none;
  max-width: 460px;
}

.cg-flags li { display: flex; }

/* The selector is `.cg-flags li img.cg-flag`, and every part of it is load-bearing.

   styles.css carries `.section-about-us .col-left img { width:100%; border-radius:38px;
   margin-top:-25px }` -- specificity (0,2,1). Being later in the cascade does not help a
   LOWER specificity, which is the trap this hit twice: `.cg-flags img` is (0,1,1), and
   `.cg-flags li img` is (0,1,2) -- `li` is an element, not a class, so it still loses on
   class count. Measured both times: the flags rendered 302px wide and stacked vertically.

   (0,2,2) beats (0,2,1) on element count with classes equal, so this wins on specificity
   alone and does not depend on file order at all. The radius and top margin are reset
   explicitly because they come from that same vendor rule. */
.cg-flags li img.cg-flag {
  display: block;
  width: 56px;
  height: auto;
  margin-top: 0;
  border-radius: 7px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.42);
  animation: cgFlagFloat 5.5s ease-in-out infinite;
}

/* Staggered, so they drift rather than bob in unison. */
.cg-flags li:nth-child(2) img.cg-flag { animation-delay: -1.1s; }
.cg-flags li:nth-child(3) img.cg-flag { animation-delay: -2.2s; }
.cg-flags li:nth-child(4) img.cg-flag { animation-delay: -3.3s; }
.cg-flags li:nth-child(5) img.cg-flag { animation-delay: -4.4s; }

@keyframes cgFlagFloat {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-9px); }
}

/* Five 56px flags plus gaps do not fit 390px once the card's padding is taken. */
@media (max-width: 575px) {
  .cg-flags { gap: 14px; max-width: 320px; margin-top: 32px; }
  .cg-flags li img.cg-flag { width: 44px; }
}

/* The whole point of it is movement, so it is the first thing to go. */
@media (prefers-reduced-motion: reduce) {
  .cg-flags li img.cg-flag { animation: none; }
}

/* ---------------------------------------------------------------------------
   Nav submenu — readable against a near-white hero.

   The vendor sheet gives it `background-color: var(--neutral-100)` (an off-white
   with a green cast), `min-width: 150px` and a 5%-opacity shadow. On the light
   hero the panel barely separated from the page behind it, and 150px forced
   every two-word item onto two lines — "Phones & front / desk", "Back office & /
   infrastructure".

   So: true white with a real shadow and a hairline border so the edge is legible,
   wide enough that no item wraps, and a near-black hover/active state with white
   text, which is the only strong affordance the template's menu had none of.

   `.sub-menu li.sub-menu-item > a` is (0,2,2) against the vendor's
   `.sub-menu li > a` at (0,1,2), so this wins on specificity and not on the
   accident of load order.
--------------------------------------------------------------------------- */
.sub-menu {
  background-color: #fff;
  min-width: 236px;
  padding: 8px;
  border: 1px solid rgba(15, 36, 25, 0.09);
  border-radius: 14px;
  box-shadow: 0 18px 40px rgba(12, 32, 22, 0.16), 0 2px 6px rgba(12, 32, 22, 0.06);
}

.sub-menu li.sub-menu-item > a {
  display: block;
  padding: 10px 14px;
  border-radius: 9px;
  white-space: nowrap;
  transition: background-color 0.18s ease, color 0.18s ease;
}

.sub-menu li.sub-menu-item > a:hover,
.sub-menu li.sub-menu-item > a:focus-visible,
.sub-menu li.sub-menu-item.active > a {
  background-color: #10231A;
  /* `!important` only because the vendor sheet uses it first: styles.css has
     `.link1:hover { color: var(--brand) !important }`, and an !important
     declaration beats ANY specificity. The symptom was the giveaway and worth
     remembering -- the background from this very rule applied while the colour
     from the same rule did not, because only the colour had a competitor. Chrome
     DevTools Protocol named the winning declaration in one call; grepping for
     "important" near `item-link` and `sub-menu` had missed it, since the class
     doing it is `.link1`. */
  color: #fff !important;
}

/* Full width on mobile, where the menu is a stacked drawer rather than a popover
   and a 236px popover would sit off the edge. */
@media (max-width: 991px) {
  .sub-menu {
    min-width: 0;
    box-shadow: none;
    border: 0;
    border-radius: 0;
    background-color: transparent;
  }
  .sub-menu li.sub-menu-item > a { white-space: normal; }
}

/* ---------------------------------------------------------------------------
   Country names in the About band, one colour each.

   The whole string was `.text-brand`, and on this card that is the problem
   rather than a preference: measured against the lightest pixel of the card's
   own gradient, rgb(29,42,36), the brand green scores **2.87** — under even the
   3.0 that WCAG allows for large text. It was not "a bit dark", it failed.

   Each colour below is taken from that country's flag and lightened until it
   clears the same background, and each was measured rather than eyeballed:

     USA       #8FC2FF   blue      8.04
     Canada    #FF7A85   red       6.2
     Australia #FFD75E   gold     10.74
     Ireland   #FFA86B   orange    7.85   (the tricolour's orange: its green
                                           would repeat the failure being fixed)
     UK        #FFFFFF   white    14.91   (Union Jack white; also the only fifth
                                           hue that stays distinct from the other
                                           four at a glance)

   The commas and "Practices" inherit the heading's white, so the colour marks
   the countries and nothing else.
--------------------------------------------------------------------------- */
.cg-geo { color: #fff; }
.cg-geo.cg-geo-us { color: #8FC2FF; }
.cg-geo.cg-geo-ca { color: #FF7A85; }
.cg-geo.cg-geo-au { color: #FFD75E; }
.cg-geo.cg-geo-ie { color: #FFA86B; }
.cg-geo.cg-geo-gb { color: #FFFFFF; }

/* ---------------------------------------------------------------------------
   Section heading rhythm.

   Three things measured on the Proof block, all of them real:

   1. The paragraph under a section heading OVERLAPPED it by 7px. Its markup
      carries `mt-20`, and no rule in the build provides that class -- the only
      declarations matching the element are the vendor resets, so it computed
      `margin-top: 0`. With `.heading-title` at 72px on a 1.0 line-height, the
      descenders of "Quietly / in production" hang into the paragraph's first
      line. It needs real space, not a class that resolves to nothing.

   2. The whitespace above the block is the PREVIOUS section's
      `padding-bottom: 120px` -- `#proof` itself is `pt-0` and the gap between
      them measured 0. Trimming 48px leaves a ~72px section gap, which reads as
      deliberate rather than as a hole.

   3. Every `.heading-sub` on the page is 16px, so "Proof" is not smaller than
      its neighbours -- they are all small standing under a 72px title. Raised
      across the board so the pattern stays consistent.
--------------------------------------------------------------------------- */
[data-v2] .heading-section .heading-title + .text,
[data-v2] .heading-section .heading-title + p {
  margin-top: 28px;
  /* Measured at 1,260px wide, which is roughly 190 characters a line. The
     comfortable range is 60-75, and a centred section intro is exactly where a
     reader gives up. Centred with a ch cap so it tracks the type size. */
  max-width: 68ch;
  margin-left: auto;
  margin-right: auto;
}

[data-v2] #proof {
  margin-top: -48px;
}

[data-v2] .heading-sub {
  font-size: 20px;
  line-height: 1.35;
  letter-spacing: 0.01em;
}

/* ---------------------------------------------------------------------------
   The founder's two titles, in the How We Build quote.

   They were one string -- "Founder, Cart Gaze &middot; Founder, Velaire Health"
   -- and in the card's narrow cite column it broke after the second "Founder,",
   leaving "Velaire Health" stranded on its own line. A middot separator only
   works while the whole thing fits on one line, and this never does.

   One row per company instead, each with its own mark. Both marks are Cart
   Gaze's own brands, so there is no third-party logo question here -- the
   vendor-logo grid's nominative-use reasoning does not apply.
--------------------------------------------------------------------------- */
.cg-founder-of {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.cg-founder-of li {
  display: flex;
  align-items: center;
  gap: 9px;
  /* The whole point: each title stays on its own line and never breaks. */
  white-space: nowrap;
}

.cg-founder-of img {
  width: 20px;
  height: 20px;
  flex: none;
  border-radius: 5px;
}

/* Below ~480px the cite column is too narrow even for one title, so let it wrap
   rather than push the card sideways -- overflow is the worse failure. */
@media (max-width: 479px) {
  .cg-founder-of li { white-space: normal; align-items: flex-start; }
  .cg-founder-of img { margin-top: 2px; }
}

/* ---------------------------------------------------------------------------
   The "What We Do" art panel, sized to the column it lives in.

   The vendor pins it `position: absolute; bottom: 0` at a fixed 416x240. That
   suited a shorter accordion; ours runs four lanes and measures 896px tall, so
   the left column held a 416x240 plate with roughly 650px of nothing above it
   -- 30% of the available width and 27% of the height. It read as an empty
   column with a small picture fallen to the bottom.

   Grown to fill the space it was always sitting in. Still bottom-anchored, so
   it stays clear of the heading and paragraph above it, and still `cover`, so
   the plate crops rather than distorts at the new ratio.
--------------------------------------------------------------------------- */
@media (min-width: 992px) {
  [data-v2] .services-image {
    max-width: 100%;
    width: min(560px, 100%);
    height: clamp(300px, 46vh, 440px);
  }
}

/* ---------------------------------------------------------------------------
   Specialty pages, rebuilt on the template 11-Sep-2026.
   --------------------------------------------------------------------------- */

/* The template puts the PILL SHAPE on `.list-tags a` and only the colour on
   `.tags-item`. That is fine while every chip is a link, which it was until the
   specialty pages started using the component for proof claims — those are
   statements, not destinations, so they are spans, and a span was picking up the
   green and none of the shape: no padding, no radius, no border. Anything
   carrying `.tags-item` should look like a pill whatever element it is. */
.list-tags .tags-item {
  display: flex;
  align-items: center;
  padding: 7px 15px;
  border-radius: 99px;
  border: 1px solid transparent;
}

/* Story-strip frames. The renders are 4:5 with the headline burnt into the art,
   so they are constrained by width and left to find their own height. Without
   this the img falls back to its width/height attributes — 1080x1350 — and a
   single slide fills the viewport. */
.section-services-single .illus-stage {
  border-radius: 14px;
  overflow: hidden;
}
.section-services-single .illus-stage img {
  display: block;
  width: 100%;
  height: auto;
}

/* Practice photography. Every frame in the library is 3:2 except the hero, which
   is 16:9; cropping with object-fit means a future re-lay-out needs no
   regeneration. The rounding matches `.features-item`. */
.section-services-single .top-image img,
.section-services-single .image img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 16px;
}
.section-services-single .top-image img {
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.section-services-single .image img {
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* Swiper ships `--swiper-theme-color: #007aff`, so every pagination bullet and
   nav arrow on the site was iOS blue — the one colour the palette study says to
   avoid, arriving through a vendor default rather than a decision. */
:root {
  --swiper-theme-color: var(--cg-primary);
  --swiper-pagination-bullet-inactive-color: var(--cg-deep);
}

/* `.section-services-single` carries `margin-top: -100px`, and -371px at
   >=1200px, so the template's ONE instance rides up over the tall hero. That is
   a fact about where it sits, not about what it is — and the specialty pages
   reuse the section four times. Every later instance yanked itself up over
   whatever it followed: the FAQ accordion ended up sitting underneath the "More
   verticals" heading, which read as a z-index bug and was a margin.
   Two classes beats one, so this wins inside the media query without
   !important. */
.section-services-single.no-lift {
  margin-top: 0;
}

/* `.heading-section .text` is centred in the template because every stock use of
   it is a centred section. The specialty pages use the left-aligned variant, and
   a centred paragraph under a left-aligned heading reads as a mistake. */
.section-services-single .heading-section:not(.center) .text,
.section-process .heading-section:not(.center) .text {
  /* text-align alone was not enough, and it looked like the fix had simply
     failed. The paragraph also carries a max-width with auto side margins, so
     the BLOCK stays centred however its lines are aligned: measured at
     left:396 in a 1440 viewport with text-align already computing to left.
     Both have to go. */
  text-align: left;
  margin-inline: 0;
}

/* `.heading-sub` is the small pill above a section title, and every template use
   of it is one or two words ("Services", "Benefits", "FAQs"). It is laid out as
   a flex row, so a long line has nothing to wrap on: the specialty pages' CTA
   eyebrow — "Free · no commitment · personal reply in ~2 days" — measured 485px
   inside a 390px viewport and pushed the whole document sideways by 107px.
   Capping the width and letting it wrap costs the short pills nothing. */
.heading-section .heading-sub {
  max-width: 100%;
  flex-wrap: wrap;
}
.heading-section .heading-sub > * {
  min-width: 0;
}

/* `.featured-works-list` is `display: grid`, and a grid item defaults to
   `min-width: auto` — it cannot shrink below its own content. Putting a Swiper
   inside one is therefore a trap: Swiper lays its slides out in a row, so the
   item's intrinsic width is the sum of every slide, and with `loop` (which
   duplicates them) the homepage's works track measured 33,554,432px. The
   document inherited that width, and the whole page could be scrolled sideways
   into empty space. Production, which has no slider here, measures 1440.
   `min-width: 0` lets the item shrink back to its grid track — which every other
   slider on the site gets for free by not being a grid child. */
.featured-works-list > .swiper {
  min-width: 0;
  width: 100%;
}

/* ---------------------------------------------------------------------------
   #clinicfront: heading spacing, and the AI-answering phone.
   --------------------------------------------------------------------------- */

/* `.section-about-us .heading-section { margin-bottom: -17px }` is specificity
   (0,2,0) and beats a `.mb-48` utility, so the 48px written in the markup never
   applied and the card row sat 17px ON TOP of the paragraph — measured, not
   guessed. The template's own about section wants that tuck; a centred heading
   above a row of cards does not, and `#clinicfront` is the only place in the
   file that combines the two. */
.section-about-us .heading-section.center {
  margin-bottom: 48px;
}

/* The transcript used to be a flat card, which read as a quote block rather
   than as a conversation happening on a phone at 11:42pm. The old cartgaze.com
   showed it in a tilted handset and that framing did the work, so it is back —
   rebuilt on the theme's tokens rather than ported from the old Tailwind. */
.cg-phone {
  position: relative;
  width: min(340px, 100%);
  margin-inline: auto;
  border-radius: 40px;
  padding: 12px;
  background: var(--cg-surface);
  border: 1px solid var(--cg-edge);
  box-shadow: 0 30px 60px -28px rgba(6, 78, 59, .45), 0 2px 0 rgba(255, 255, 255, .5) inset;
  transform: rotate(2deg);
}
/* The tilt is charm on a wide screen and a horizontal-overflow risk on a narrow
   one, where the phone is already nearly the full column width. */
@media (max-width: 767px) {
  .cg-phone { transform: none; }
}
/* A soft bloom behind the handset, so it sits in the section rather than on it. */
.cg-phone::before {
  content: "";
  position: absolute;
  inset: -28px;
  border-radius: 999px;
  background: radial-gradient(60% 60% at 50% 40%, rgba(52, 211, 153, .30), transparent 70%);
  filter: blur(28px);
  z-index: -1;
}
.cg-phone-screen {
  border-radius: 30px;
  overflow: hidden;
  background: var(--cg-background);
  border: 1px solid var(--cg-edge);
}
.cg-phone-top {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--cg-edge);
}
.cg-phone-avatar {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  color: #fff;
  background: linear-gradient(135deg, var(--cg-primary), var(--cg-deep));
}
.cg-phone-who { display: flex; flex-direction: column; min-width: 0; }
.cg-phone-clinic { font-size: 13px; line-height: 1.3; color: var(--cg-foreground); }
.cg-phone-status { font-size: 11px; color: var(--cg-muted); }

/* Five bars, breathing. Decorative and aria-hidden, so it carries no meaning
   that is lost when the animation is off. */
.cg-phone-wave { display: flex; align-items: center; gap: 3px; margin-left: auto; }
.cg-phone-wave i {
  width: 3px;
  height: 14px;
  border-radius: 2px;
  background: var(--cg-primary);
  opacity: .55;
  animation: cg-wave 1.1s ease-in-out infinite;
}
.cg-phone-wave i:nth-child(2) { animation-delay: .12s }
.cg-phone-wave i:nth-child(3) { animation-delay: .24s }
.cg-phone-wave i:nth-child(4) { animation-delay: .36s }
.cg-phone-wave i:nth-child(5) { animation-delay: .48s }
@keyframes cg-wave {
  0%, 100% { transform: scaleY(.45); opacity: .4 }
  50%      { transform: scaleY(1);   opacity: .9 }
}
@media (prefers-reduced-motion: reduce) {
  .cg-phone-wave i { animation: none; transform: scaleY(.7) }
}

.cg-phone-thread { display: flex; flex-direction: column; gap: 10px; padding: 16px }
.cg-bubble {
  max-width: 85%;
  margin: 0;
  padding: 10px 14px;
  font-size: 13px;
  line-height: 1.45;
  border-radius: 18px;
  border: 1px solid var(--cg-edge);
  background: var(--cg-surface);
  color: var(--cg-foreground);
}
.cg-bubble.in  { border-bottom-left-radius: 6px }
.cg-bubble.out {
  margin-left: auto;
  border-bottom-right-radius: 6px;
  border-color: color-mix(in srgb, var(--cg-primary) 35%, transparent);
  background: color-mix(in srgb, var(--cg-primary) 12%, var(--cg-surface));
}
.cg-phone-note {
  margin: 6px 0 0;
  text-align: center;
  font-size: 11px;
  color: var(--cg-muted);
}

/* The section behind the handset is dark in dark mode, so the frame has to
   stop being a white slab. */
.dark [data-v2] .cg-phone {
  background: color-mix(in srgb, var(--cg-surface) 88%, #000);
  box-shadow: 0 30px 60px -28px rgba(0, 0, 0, .8), 0 1px 0 rgba(255, 255, 255, .06) inset;
}

/* The phone's bloom is inset -28px on every side. At 390px that reaches past the
   viewport and took the document's scrollWidth to 392 — two pixels of sideways
   scroll on every page carrying the homepage stylesheet.
   THIS BLOCK MUST STAY AFTER `.cg-phone::before`. It was first written directly
   under `.cg-phone`, above that rule, and did nothing at all: a media query adds
   no specificity, so with the two selectors equal the later one still wins.
   Measured, not assumed — the computed inset was still -28px at 390px wide. */
@media (max-width: 767px) {
  .cg-phone::before { inset: -16px -4px; }
}

/* Slider controls for the "Who We Serve" carousel.

   The pagination markup originally sat INSIDE `.swiper`, which is
   `overflow: hidden`, so the four bullets rendered flush at its bottom edge and
   were clipped out of existence — present in the DOM, never on screen. That left
   the two arrows floating alone in the gap under the slide with nothing to sit
   beside. carousel.js already supports finding the pagination on a
   `.tf-pag-swiper` ancestor, so both now live in one row below the slide. */
.cg-slider-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  /* 36px above (the grid gap) and 0 below: the next section carries `pt-0`, so
     the controls sat flush against it and read as its header rather than as the
     slider's footer. Measured, then given room on the side they belong to. */
  margin-top: 4px;
  margin-bottom: 56px;
}
.cg-slider-controls .group-btn-slider {
  display: flex;
  gap: 8px;
}
.cg-slider-controls .tf-sw-pagination {
  display: flex;
  align-items: center;
  gap: 7px;
  width: auto;
  /* Outside the swiper it is a normal flow element; Swiper's own rules only
     absolutely position a pagination that is a child of `.swiper-horizontal`. */
  position: static;
}

/* The service pills inside the accordion sit on a near-black card, and a deep
   forest pill on it separates by 1.82 — they read as one dark mass with text
   floating in it. Everywhere ELSE the same component sits on the off-white
   ground, where deep forest is exactly right (9.18), so this cannot be a global
   swap: `--cg-lift` is designated "mint, highlights on dark only" and this is
   the only place on the site that qualifies.

   Measured from PAINTED PIXELS, not from the cascade. The card is drawn by a
   `::before`, so walking `backgroundColor` up the tree finds the light section
   behind it and reports a comfortable 8.58 — the same blind spot that put two
   false positives in contrast.mjs. Cropping the pill and sampling the bitmap
   gives the real number.

       deep  #064E3B on rgb(24,24,27) -> 1.82
       mint  #34D399 on rgb(24,24,27) -> 9.11, with #0C1A14 ink at 9.5 */
.accordion-content .list-tags .tags-item {
  background-color: var(--cg-lift) !important;
  border-color: var(--cg-lift) !important;
  /* A LITERAL, not `var(--cg-ink)`. The pill is always mint, in both themes, but
     --cg-ink flips to near-white in dark mode — so the token gave light type on
     a light pill and contrast.mjs measured 1.67. A fixed background needs a
     fixed foreground. */
  color: #0C1A14 !important;
}
.accordion-content .list-tags .tags-item:hover {
  background-color: #5FE3B4 !important;
  border-color: #5FE3B4 !important;
  color: #0C1A14 !important;
}

/* `.benefits-design` floats three images at FIXED offsets (item-img-2 at
   top:24px/right:226px, item-img-3 at bottom:-24px/right:-65px) and then pulls
   the copy back up under them with `margin-top: -112px`. Those numbers are
   tuned for three images of one size. Our art is landscape and of three
   different heights, so the in-flow first image came up 48px short, the stack
   collapsed, and the heading rode up into the pictures — which is what "the
   image positioning looks weird" was.
   Squaring them off is the smaller fix: the layout keeps the theme's offsets
   and the art is abstract, so a centre crop costs it nothing. */
.benefits-design .benefits-design-inner img {
  width: 190px;
  height: 190px;
  object-fit: cover;
  border-radius: 18px;
}

/* The comparison table on /compare/<slug>.
   The template has no table component, and this one is genuinely tabular: three
   columns of prose that only make sense read across. It scrolls inside its own
   container so a narrow screen never takes the whole page sideways. */
.cg-compare-scroll {
  overflow-x: auto;
  border-radius: 18px;
  border: 1px solid var(--cg-edge);
  background: var(--cg-surface);
}
.cg-compare {
  width: 100%;
  min-width: 680px;
  border-collapse: collapse;
  font-size: 15px;
  line-height: 1.5;
}
.cg-compare th,
.cg-compare td {
  padding: 16px 18px;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--cg-edge);
}
.cg-compare thead th {
  font-weight: 600;
  color: var(--cg-foreground);
  background: color-mix(in srgb, var(--cg-primary) 7%, transparent);
  white-space: nowrap;
}
.cg-compare tbody th {
  font-weight: 600;
  color: var(--cg-foreground);
  width: 22%;
}
.cg-compare td { color: var(--cg-muted) }
.cg-compare tbody tr:last-child th,
.cg-compare tbody tr:last-child td { border-bottom: 0 }
/* The rows where the alternative genuinely wins are marked, because a
   comparison that never concedes one is an advert. */
.cg-compare td.wins {
  color: var(--cg-foreground);
  background: color-mix(in srgb, var(--cg-signal) 10%, transparent);
}

/* Legal and policy prose: /privacy, /terms, /sms.
   Those pages are carried over verbatim — they are legal text — so the elements
   inside lost their utility classes and inherit from here instead. Measured
   against the theme's own body scale rather than the old site's. */
.cg-prose { color: var(--cg-muted); font-size: 16px; line-height: 1.7 }
.cg-prose > div > section + section { margin-top: 40px }
.cg-prose h2 {
  color: var(--cg-foreground);
  font-size: 24px;
  line-height: 1.3;
  font-weight: 600;
  margin: 0 0 12px;
}
.cg-prose h3 {
  color: var(--cg-foreground);
  font-size: 18px;
  font-weight: 600;
  margin: 24px 0 8px;
}
.cg-prose p { margin: 0 0 14px }
.cg-prose ul, .cg-prose ol { margin: 0 0 14px; padding-left: 22px }
.cg-prose li { margin-bottom: 8px }
/* A policy link needs a colour per theme, and neither brand green works for
   both: --cg-primary measured 3.58 on the light ground, and forest --cg-deep
   measured 1.91 on the dark one. Forest on light, mint on dark — the same
   split the palette already makes for every other accent.
   This block must stay AFTER the base .cg-prose rules. */
.cg-prose a { color: var(--cg-deep); font-weight: 600 }
.dark [data-v2] .cg-prose a { color: var(--cg-lift) }
.cg-prose a:hover { text-decoration: underline }
.cg-prose strong { color: var(--cg-foreground) }

/* Body copy on the rebuilt pages: 17px at 1.65, up from 16/1.5.
   Measured against the competitor set on 11-Sep. Their product pages run 14px
   (agentzap) to 20px (assorthealth); we sat at 16/24 with the tightest leading
   of the group. Qasim's note was "less text but visible text", and the text got
   shorter in industries.ts — this is the other half. Deliberately modest: the
   theme's scale is the theme's, and a jump to 20px would be a different design
   rather than a more readable one. */
.section-services-single .features-item p,
.section-services-single p.text-secondary,
.section-process .process-card .text,
.cg-prose {
  font-size: 17px;
  line-height: 1.65;
}

/* ---------------------------------------------------------------------------
   Lighthouse accessibility, 11-Sep-2026. Mobile audit scored 89.
   --------------------------------------------------------------------------- */

/* Swiper's pagination bullets are 8px, well under the 24px minimum tap target.
   The dot stays 8px; the touch area around it grows, which is the fix WCAG
   actually asks for. */
.tf-sw-pagination .swiper-pagination-bullet {
  width: 24px;
  height: 24px;
  margin: 0 !important;
  position: relative;
  background: transparent !important;
  opacity: 1 !important;
}
.tf-sw-pagination .swiper-pagination-bullet::before {
  content: "";
  position: absolute;
  inset: 8px;
  border-radius: 50%;
  background: var(--cg-deep);
  opacity: 0.3;
}
.tf-sw-pagination .swiper-pagination-bullet-active::before {
  background: var(--cg-primary);
  opacity: 1;
}

/* The accordion card is painted by a ::before, so the element itself computes
   as transparent and every contrast checker walks past it to the light section
   behind — axe reports white-on-white at 1.13, and contrast.mjs has reported
   the same false positive for weeks. Stating the colour the card already is
   makes the checkers agree with the screen and costs nothing visually. */
.accordion-content {
  background-color: #18181b;
}

/* #98a79a on the card measured 2.22. These are the step numbers in a process
   card, small and greyed by design, but 2.22 is not "greyed", it is invisible. */
.number .text-neutral-400 {
  /* !important, because styles.css sets this class with !important and no
     amount of specificity beats that. Measured twice before spotting it: the
     selector was right and the colour never moved. */
  color: #4a5a4f !important;
}
