/* ==========================================================================
   Section styles, keyed by section. Desktop (>1024) first; the responsive
   pass adds the <=1024 and <=767 breakpoints.
   ========================================================================== */

/* ==========================================================================
   1. Site header
   Overlays the hero. Nav is styled against the real output classes of
   wp_nav_menu (.menu / .menu-item / .sub-menu / .current-menu-item) so the
   markup can be swapped for a WordPress menu with no CSS changes.
   ========================================================================== */

.site-header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  padding: 0 var(--gutter);
}

.site-header__inner {
  display: flex;
  align-items: center;
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding: var(--header-pad-y) 0;
}

/* --- Brand -------------------------------------------------------------- */
.site-header__brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: var(--header-brand-w);
  padding: 10px;
}

/* The 177x50 box is FIXED and `object-fit: contain` letterboxes inside it,
   so the mark's own ratio never moves the brand column. That is what made
   the 2026-09-09 logo swap cheap: the new wordmark is 2.193:1 where the old
   was 2.657:1, so it paints 109.6x50 here instead of 132.9x50, and the box
   does not care.

   THE BRIGHTNESS LIFT IS 1.2, DOWN FROM 1.4 ON 2026-09-09, AND THE NUMBER IS
   LOAD-BEARING. It is the CONTRAST treatment approved 2026-09-02: the
   wordmark is a dark teal measuring about 2.7:1 on this build's black header
   and worse on the purple footer, so the filter raises it without touching
   the artwork and the supplied file stays the file that ships.

   1.4 was safe on the OLD mark, whose brightest channel was 167, and clipped
   nothing. The NEW mark sets "MEDIA" in bright cyan peaking at
   rgb(98,187,207), so 1.4 pushes a channel past 255 on 26.7% of its pixels
   and burns the word out to white. The no-clip ceiling is x1.232. At 1.2 the
   mark clips NOTHING and still reads 3.5:1 median on black, which is better
   than the old artwork managed at 1.4. Re-measure this if the logo changes
   again; do not restore 1.4 by habit. Alpha is preserved either way, so only
   the ink lifts. */
.site-logo img {
  width: var(--header-logo-w);
  height: var(--header-logo-h);
  object-fit: contain;
  filter: brightness(1.2);
}

/* --- Primary navigation ------------------------------------------------- */
.site-nav {
  width: var(--header-nav-w);
  padding: 10px;
}

.site-nav .menu {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  height: 55px;
}

.site-nav .menu-item { position: relative; }

.site-nav .menu-item > a {
  display: flex;
  align-items: center;
  gap: var(--nav-caret-gap);
  padding: var(--nav-link-pad);
  color: var(--c-white);
  font-size: var(--nav-link-fs);
  font-weight: var(--nav-link-fw);
  line-height: var(--nav-link-lh);
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease);
}

.site-nav .menu-item > a:hover,
.site-nav .current-menu-item > a { color: var(--c-accent); }

.menu-caret { display: inline-flex; }

/* Stays pointing down at all times, matching the reference, which applies no
   transform to its indicator in any state. */
.menu-caret svg {
  width: var(--nav-caret-size);
  height: var(--nav-caret-size);
  stroke-width: var(--nav-caret-stroke);
}

/* --- Offcanvas parts, desktop state -------------------------------------- */

/* All three belong to the sub-1024 header and are switched on in
   responsive.css. They are hidden here rather than there so that the default,
   with no media query matched at all, is the desktop header. */
.site-header__burger,
.site-nav__bar,
.site-nav .menu-toggle { display: none; }

/* --- Dropdown ----------------------------------------------------------- */
.site-nav .sub-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 20;
  min-width: 230px;
  padding: 5px 0;
  background: var(--c-dropdown-bg);
  border-radius: 20px;
  box-shadow: 0 0 0 0 rgba(0, 0, 0, .5);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  transition: opacity var(--dur) var(--ease),
              transform var(--dur) var(--ease),
              visibility var(--dur) var(--ease);
}

.site-nav .menu-item-has-children:hover > .sub-menu,
.site-nav .menu-item-has-children:focus-within > .sub-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.site-nav .sub-menu a {
  display: block;
  padding: 6px 20px;
  color: var(--c-white);
  font-size: 16px;
  font-weight: 600;
  line-height: 1.6;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}

.site-nav .sub-menu a:hover {
  background: rgba(255, 255, 255, .1);
}

/* --- Header socials ----------------------------------------------------- */
.site-header__social {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 20px;
  width: var(--header-social-w);
  padding: 10px;
}

/* Fixed width + space-between reproduces the reference spacing, where the
   icons are glyphs of differing widths rather than equal-sized boxes. */
.social-list {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 85px;
  height: 29px;
}

.social-list a {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--c-accent);
  transition: color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}

/* Height-locked, width derived from each tightly-cropped viewBox, so the
   narrow marks stay narrow the way icon-font glyphs do. */
.social-list svg { height: 20px; width: auto; }
.social-list svg.social-glyph--inset { height: calc(20px * var(--social-inset)); }

.social-list a:hover { color: var(--c-white); transform: translateY(-2px); }

/* WordPress injects a 32px admin bar for logged-in editors; without this the
   header sits underneath it. Ships now so the theme is correct on day one. */
body.admin-bar .site-header { top: 32px; }

@media screen and (max-width: 782px) {
  body.admin-bar .site-header { top: 46px; }
}

/* ==========================================================================
   3. Hero
   Fixed-height showcase with a full-bleed background video. Child positions
   are absolute to reproduce the reference layout exactly at desktop; the
   responsive pass replaces this with a flow layout below 1024.
   ========================================================================== */

.hero {
  position: relative;
  height: var(--hero-h);
  overflow: hidden;
  background: var(--c-black);
}

.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}

.hero__content {
  position: relative;
  z-index: 1;
  height: 100%;
}

.hero__el { position: absolute; }

/* Display headline, Puritan */
.hero__h1 {
  top: 136px;
  left: 210px;
  width: 846px;
  text-align: center;
}

.hero__h1-inner {
  margin-left: 250px;
  margin-right: -6px;
  font-family: var(--ff-display);
  font-size: var(--hero-h1-fs);
  font-weight: 300;
  line-height: var(--hero-h1-lh);
  letter-spacing: var(--hero-h1-ls);
  color: var(--c-white);
  text-shadow: var(--hero-h1-shadow);
}

/* Main headline, Radio Canada Big */
.hero__h2 {
  top: 278px;
  left: 210px;
  width: 1091px;
  text-align: center;
}

.hero__h2-inner {
  margin-left: 50px;
  font-family: var(--ff-display-alt);
  font-size: var(--hero-h2-fs);
  font-weight: 700;
  line-height: var(--hero-h2-lh);
  white-space: nowrap;
  color: var(--c-white);
  text-shadow: var(--hero-h2-shadow);
}

/* Rotating phrase line */
.hero__fancy {
  top: 426px;
  left: 370px;
  font-size: var(--hero-fancy-fs);
  font-weight: 700;
  line-height: var(--hero-fancy-lh);
  color: var(--c-white);
  white-space: nowrap;
}

.fancy-text { display: inline-flex; align-items: baseline; gap: 10px; }

.fancy-text__rotator {
  display: inline-flex;
  align-items: center;
  color: var(--c-accent);
  white-space: nowrap;
}

/* Source words. Visible as a plain first word when JS has not run, hidden
   once the typewriter takes over. Keeping them in the markup means the list
   stays editable in a template or an ACF repeater later. */
.fancy-text__item { display: none; }
.fancy-text__item:first-child { display: inline; }
.fancy-text__rotator.is-typing .fancy-text__item { display: none; }

.fancy-text__output { white-space: pre; }

/* Supporting paragraph */
.hero__para {
  top: 548px;
  left: 532px;
  width: 622px;
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--c-text);
}

/* Call to action.

   MOVED 615 -> 644 ON 2026-09-09, when the user supplied a 219-character
   hero paragraph in place of the 137 that fitted on two lines. At the box's
   622px it sets to THREE lines, so its bottom went 605.6 -> 634.4 and would
   have run under this button.

   THE OVERLAP WOULD NOT HAVE LOOKED LIKE AN OVERLAP. `.hero` is
   `height: var(--hero-h)` with `overflow: hidden`, so nothing here pushes
   anything down and nothing spills visibly: text just gets silently clipped
   at 703. 644 keeps the same 9.4px gap under the paragraph that 615 kept
   under the two-line version, and 652.6 is the last top that still fits this
   50.4px button inside the hero. */
.hero__cta { top: 644px; left: 526px; }

/* The play disc that used to sit at top 545 / left 334 was removed on
   2026-09-09: a dead `#` control with no showreel behind it. The ring below
   kept its own `left`, so nothing else on this row moved. */

/* Rotating circular caption ring. Self-contained: the caption spins on this
   element and the arrow is its own background-image, so the arrow turns with
   it rather than pointing at anything outside. */
.hero__ring {
  top: 546px;
  left: 404px;
  width: var(--hero-circle-size);
  height: var(--hero-circle-size);
  padding: 10px;
  background: url("../img/vector-arrow.svg") no-repeat center center;
  background-size: auto;
  animation: spin var(--spin-dur) linear infinite;
}

.hero__ring-spin {
  width: 100%;
  height: 100%;
}

.hero__ring svg { width: 100%; height: 100%; overflow: visible; }

.hero__ring text {
  font-family: var(--ff-body);
  font-size: 29px;
  font-weight: 500;
  letter-spacing: 3.2px;
  fill: var(--c-text);
}

.hero__ring path { fill: none; }

@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ==========================================================================
   Global: magic cursor
   A dot that trails the pointer and swells over interactive elements.
   Injected by main.js so every page gets it with no markup of its own.
   Reference values: 10px accent dot, z-index 1000000, growing to 34px at
   0.5 opacity on hover.
   ========================================================================== */

.magic-cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000000;
  width: var(--cursor-size);
  height: var(--cursor-size);
  border-radius: 50%;
  background: var(--c-accent);
  pointer-events: none;
  opacity: 0;
  will-change: transform;
  transform: translate(-50%, -50%);
  transition: width var(--dur) var(--ease),
              height var(--dur) var(--ease),
              opacity var(--dur) var(--ease);
}

/* Revealed only once the pointer has actually moved, so it never flashes
   in the top-left corner on load. */
.magic-cursor.is-active { opacity: 1; }

.magic-cursor.is-hover {
  width: var(--cursor-size-hover);
  height: var(--cursor-size-hover);
  opacity: .5;
}

/* Pointerless devices have no cursor to decorate, and a reduced-motion
   preference rules out a trailing follower entirely. */
@media (hover: none), (pointer: coarse) {
  .magic-cursor { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .magic-cursor { display: none; }
}

/* ==========================================================================
   4. Services ticker
   Purple band of service labels scrolling left, separated by asterisks.
   main.js duplicates the group so the loop wraps with no seam; the keyframe
   then only ever has to travel exactly half the track.
   ========================================================================== */

.ticker-band {
  padding: 10px 0;
  overflow: hidden;
}

.ticker {
  background: var(--c-accent);
  overflow: hidden;
}

.ticker__track {
  display: flex;
  width: max-content;
  animation: ticker-scroll var(--ticker-dur) linear infinite;
}

.ticker__group {
  display: flex;
  align-items: center;
  flex: none;
}

/* 12px, not 13. The band is 70px on the reference: a 50px row inside the
   section's own 10px. Ours is line-height 26 (20px x 1.3) plus this padding,
   so 12 a side lands on 50 and the section on 70. At 13 it was 52 and 72,
   which is the 2px the fidelity map used to report against section 4.
   Vertical only -- the horizontal rhythm is carried by .ticker__sep. */
.ticker__item {
  padding: 12px 0;
  color: var(--c-white);
  font-size: var(--ticker-fs);
  font-weight: 700;
  line-height: 1.3;
  white-space: nowrap;
}

/* Asterisk mark between labels, carrying the spacing on either side.
   15 x 24 and squashed to fill, matching the reference and the eyebrow's
   mark. This one is a FLEX ITEM, so its width is part of the group width,
   which is what the marquee's --ticker-dur is tuned against: change one and
   the other has to move with it or the scroll speed drifts. */
.ticker__sep {
  flex: none;
  width: 15px;
  height: 24px;
  margin: 0 30px;
  background: url("../img/asterisk-icon.svg") no-repeat center center;
  background-size: 100% 100%;
}

@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .ticker__track { animation: none; }
}

/* ==========================================================================
   5 + 6. Showreel
   One full-bleed video where the `Our Story` heading band and the two-column
   intro copy used to be. Modelled on humaan.com's showreel, measured off
   their live page.

   Desktop is the base state here, per this file's convention. A 200vh runway
   holds a `position: sticky` 100vh pin, and `--progress` runs 0 to 1 over the
   LAST 50vh of the approach, driving both the side insets and the corner
   radius. The video therefore grows from gutter width to full bleed and its
   corners square off exactly as it lands, then it holds pinned for a further
   viewport. `src/scripts/showreel.ts` writes `--progress` and nothing else.

   The insets are `left`/`right` on the inner box rather than a width, so
   "full bleed" resolves against the section's own content box. That is what
   keeps the scrollbar out of it: `100vw` would have overshot by the scrollbar
   width and reintroduced horizontal overflow on a page whose body is
   `overflow-x: clip`.
   ========================================================================== */

.section-showreel { position: relative; }

.showreel__wrapper { height: 200vh; }

/* `position: sticky` rather than ScrollTrigger's own `pin: true`. GSAP
   pinning wraps the element in a generated div and writes inline geometry
   onto it, which would add a node the `_reference/` baseline does not have
   and break `portsec` for a purely visual reason. */
.showreel__pin {
  --progress: 0;
  position: sticky;
  top: 0;
  height: 100vh;
  z-index: 1;
}

/* `--progress: 0` MUST stay declared on the rule above. Without it these two
   `calc()`s have an empty substitution, the whole declaration is invalid at
   computed-value time, and both the inset and the radius silently fall to 0
   on first paint before the script has run. */
.showreel__inner {
  position: absolute;
  height: 100%;
  left:  calc(var(--showreel-gutter) * (1 - var(--progress)));
  right: calc(var(--showreel-gutter) * (1 - var(--progress)));
  display: grid;
  overflow: hidden;
  border-radius: calc(var(--showreel-radius) * (1 - var(--progress)));

  /* `minmax(0, 1fr)`, NOT the implicit `auto` track. An auto row is sized by
     the poster's intrinsic height, so at 1423.5 wide the 16:9 still made the
     row 800.7 inside a 681.6 box: the items' own `height: 100%` then resolved
     against the inflated row, `overflow: hidden` clipped the surplus off the
     BOTTOM, and the cover crop silently anchored to the top instead of the
     centre. The `0` minimum is the load-bearing half; `1fr` alone still takes
     an automatic minimum from the content. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
}

/* Poster and video occupy the SAME grid cell, the video painting over the
   poster once it has frames. The poster is a `<picture>` rather than the
   `<video poster>` attribute because `poster` takes one URL and cannot be
   made responsive, and the wide and square crops are genuinely different
   images rather than one at two sizes. */
.showreel__poster,
.showreel__video {
  /* NOT `grid-area: 1 / -1`, which is what humaan write. That is shorthand
     for row-start 1 and COLUMN-START -1, so both items begin on the explicit
     grid's last line and Chrome opens an implicit second column to hold them:
     the tracks resolved to `211.75px 1211.72px` and the video sat 212px in,
     at its intrinsic width rather than the box's. Naming the single cell
     outright is what actually stacks them. */
  grid-row: 1;
  grid-column: 1;
  width: 100%;
  height: 100%;
}

/* Both dimensions are set here so the `<img>`'s own width/height attributes
   can never reach layout. They describe the WIDE crop and would be wrong for
   the square one, and a presentational `height` attribute beats
   `aspect-ratio` outright, which cost 147.5px a card on /blog/. */
.showreel__poster img,
.showreel__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* THE COVER ABOVE MUST NOT SURVIVE INTO FULLSCREEN, AND IT DOES WITHOUT THIS.
   The rule above is author-level and beats the UA stylesheet's fullscreen
   rule, so the mobile play button — whose whole job is to show the film
   properly — was cropping it hardest of anywhere on the site. Measured
   2026-09-10 by pressing the real button: the fullscreen box is PORTRAIT
   (501.6x732 at that viewport) against a 16:9 source, leaving 38.5% of the
   frame's width, and about 26% on a real 390x844 phone. The hero video's
   worst crop is 30.46%.

   `:-webkit-full-screen` is NOT a droppable legacy alias: iPhone Safari
   implements fullscreen on the video element via `webkitEnterFullscreen`
   and never matches `:fullscreen`, and iOS is the one browser the mobile
   path exists for. See the matching comment in `src/scripts/showreel.ts`.

   THEY ARE TWO SEPARATE RULES ON PURPOSE. One invalid selector anywhere in a
   comma-separated list invalidates the WHOLE list, so pairing them would mean
   a browser that does not know `:fullscreen` silently drops the `-webkit-`
   half as well — which is exactly the iOS case this rule exists for.

   No gate here can see this — nothing measures fullscreen — and it moves no
   box in normal flow, so it needs no `_reference` round. */
.showreel__video:fullscreen { object-fit: contain; }
.showreel__video:-webkit-full-screen { object-fit: contain; }

/* Mobile only. Shown below 992 in responsive.css, where the video is never
   autoplayed and this is the only way to start it. */
.showreel__play {
  display: none;
  position: absolute;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 2;
  align-items: center;
  gap: 10px;
  padding: 12px 22px;
  border: 0;
  border-radius: 100px;
  cursor: pointer;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  color: var(--c-white);
  background: rgba(0, 0, 0, .55);
  backdrop-filter: blur(8px);
  transition: background-color var(--dur-fast) var(--ease);
}

.showreel__play:hover { background: rgba(0, 0, 0, .75); }

/* A borders triangle rather than another inline SVG. Landmine 2 warns off
   touching the 131 hand-cropped viewBoxes in this build, and a play glyph
   does not need to become the 132nd. */
.showreel__play-icon {
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 6px 0 6px 10px;
  border-color: transparent transparent transparent currentColor;
}

/* The pause and mute pill, reproducing humaan's measured geometry. At
   `font-size: 26px` the two buttons come to 18+26+12 and 12+26+18 with a 1px
   divider between them, which is exactly the 113 x 46 box measured on their
   live page.

   BOTTOM LEFT, WHERE THEIRS IS BOTTOM RIGHT, AND THAT IS FORCED RATHER THAN
   PREFERRED. The WhatsApp launcher is `position: fixed` at the bottom right
   of EVERY page on this site with `z-index: 999999999`, measured at
   x 1339-1395 against this pill's 1292-1405. It covered the mute button
   outright and no z-index here can win, nor should it: the widget is a real
   feature. humaan have no such widget, so their corner is free and ours is
   not. **Do not "restore" this to the right.**

   THE PILL LOOKS ABSENT IN A SCREENSHOT AND IS NOT. `rgba(0, 0, 0, .66)`
   over a near-black frame is invisible, so only the white icons read.

   `box-sizing: content-box` is load-bearing rather than tidiness: `width:
   1em` sizes the ICON, and under this site's global border-box the padding
   would eat into that instead of adding to it, collapsing the pill to 26px
   wide and overlapping the two glyphs. */
.showreel__controls {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 2;
  margin: 20px;
  display: flex;
  align-items: center;
  font-size: 26px;
  color: var(--c-white);
  background: rgba(0, 0, 0, .66);
  backdrop-filter: blur(8px);
  border-radius: 100px;
  transition: opacity .2s var(--ease);
}

/* RUNTIME STATE IS CARRIED ON DATA ATTRIBUTES, NOT CLASSES, AND THAT IS A
   GATE REQUIREMENT RATHER THAN A STYLE PREFERENCE. `secmap.js` records
   `el.getAttribute('class')` and nothing else, so a state CLASS here shows up
   as a `portsec` difference against `_reference`, which runs no script and
   can never have one. `is-dimmable` did exactly that and cost a 289th
   difference. Attributes are invisible to the gate, which is correct: this is
   transient state, not structure.

   Dims to a third three seconds after playback starts. `forwards` holds the
   end state, and the hover rule below cancels the animation outright rather
   than raising the opacity, so leaving the pill restarts the countdown. */
.showreel__controls[data-dim] {
  animation: showreel-controls-fade 1s ease-in-out 3s forwards;
}

.showreel__controls:hover,
.showreel__controls:focus-within { animation: none; }

@keyframes showreel-controls-fade {
  from { opacity: 1; }
  to   { opacity: .33; }
}

/* The hairline between the buttons. It lands BETWEEN them because the first
   button is pulled ahead of it with `order: -1`, which is the whole reason
   this can be a pseudo-element instead of a third node. */
.showreel__controls::before {
  content: "";
  display: block;
  width: 1px;
  height: 1em;
  background: rgba(255, 255, 255, .2);
  transition: opacity .1s var(--ease);
}

.showreel__controls:hover::before,
.showreel__controls:focus-within::before { opacity: 0; }

.showreel__control {
  box-sizing: content-box;
  width: 1em;
  height: 1em;
  margin: 0;
  padding: 10px 12px;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  transition: background-color .1s var(--ease);
}

.showreel__control:first-child {
  order: -1;
  padding-left: 18px;
  border-radius: 100px 0 0 100px;
}

.showreel__control:last-child {
  padding-right: 18px;
  border-radius: 0 100px 100px 0;
}

.showreel__control:hover,
.showreel__control:focus-visible { background-color: rgba(255, 255, 255, .2); }

.showreel__ctl-icon {
  display: block;
  width: 100%;
  height: 100%;
}

/* Both icons ship in each button and CSS shows one, so a state change costs
   no request and no `innerHTML`. */
.showreel__ctl-icon--play,
.showreel__ctl-icon--sound { display: none; }

.showreel__controls[data-paused] .showreel__ctl-icon--pause { display: none; }
.showreel__controls[data-paused] .showreel__ctl-icon--play  { display: block; }

.showreel__controls[data-unmuted] .showreel__ctl-icon--muted { display: none; }
.showreel__controls[data-unmuted] .showreel__ctl-icon--sound { display: block; }

.showreel__inner.is-playing .showreel__play   { display: none; }
.showreel__inner.is-playing .showreel__poster { opacity: 0; }
.showreel__inner.is-playing .showreel__video  { opacity: 1; }

/* `base.css` line 235 is a global `*` reduced-motion reset on
   `animation-duration`, which stops CSS animation and has no reach at all
   over `position: sticky` or a JS scrub. So without this block the pin still
   fires for someone who has asked for less movement. The script bails out
   separately; both halves are needed, because either alone leaves the other
   running. */
@media (prefers-reduced-motion: reduce) {
  .showreel__wrapper { height: auto; }

  .showreel__pin {
    position: relative;
    height: auto;
  }

  .showreel__inner {
    position: relative;
    aspect-ratio: 16 / 9;
    left: auto;
    right: auto;
    border-radius: var(--showreel-radius);
    margin-inline: var(--showreel-gutter);
  }
}

/* ==========================================================================
   7. About the agency
   Left column sticks in place while the right column of feature blocks
   scrolls past it. Blocks fade up as they enter the viewport.
   ========================================================================== */

.section-about { padding: 120px var(--gutter); }

.section-about__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;   /* 653px each at desktop */
  align-items: stretch;             /* aside must span the row for sticky to travel */
}

.section-about__aside { padding: 10px 55px 10px 10px; }
.section-about__main  { padding: 10px 10px 10px var(--about-main-pad); }

.section-about__sticky {
  position: sticky;
  top: 140px;
}

.section-about__title {
  margin: 18px 0 40px;
  font-size: var(--fs-about-title);
  font-weight: 300;
  line-height: 1.2;
  color: var(--c-primary);
}

.section-about__title strong { font-weight: 700; }

.section-about__main {
  display: flex;
  flex-direction: column;
  gap: 50px;
}

.about-block { position: relative; }

/* Small ringed dot on the divider line to the left of each block. */
/* Centred on the timeline rule, which sits on the column's left edge. The
   block starts one column-padding in from that edge, so the dot is pulled
   back by the padding plus half its own width. DERIVED, not a constant: the
   padding is 50 at desktop, 46 at tablet and 20 at mobile, and a hard -57px
   put the dot 4px off the rule at tablet and 27px off the screen at mobile. */
.about-block__marker {
  position: absolute;
  top: 62px;
  left: calc(var(--about-main-pad) * -1 - 7px);
  width: 14px;
  height: 14px;
  border: 2px solid var(--c-accent);
  border-radius: 50%;
  background: var(--c-black);
}

.about-block__marker::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--c-accent);
}

/* Each block carries its own icon, 40px in the accent colour. */
.about-block__glyph {
  display: block;
  width: 40px;
  height: 40px;
  margin-bottom: 26px;
  color: var(--c-accent);
}

.about-block__glyph svg {
  width: 100%;
  height: 100%;
  display: block;
}

.about-block__title {
  margin-bottom: 16px;
  font-size: var(--fs-block-title);
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-primary);
}

.about-block__body {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

/* A vertical rule running the height of the scrolling column. */
/* Timeline rule. Sits on the column's left border edge, which is exactly the
   centre line the dots are positioned against, so the dots sit ON the rule
   rather than beside it. */
.section-about__main::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -0.5px;
  width: 1px;
  background: var(--c-divider);
}

.section-about__main { position: relative; }

/* ==========================================================================
   8. Stats counters
   Black card with a purple gradient ring that fades out toward the foot.
   The ring is a pseudo-element behind the card rather than a border, so the
   gradient can run across the whole outline.
   ========================================================================== */

.section-stats { padding: 0 var(--gutter); }

/* 90 vertical, not 80. The reference nests an extra box with 10px of padding
   between its section and the card, worth 20px of section height. Its 10px
   horizontal is already accounted for here (the card is 1280 in a 1300
   container either way); only the vertical was missing. */
.section-stats .container { padding: 90px 10px; }

.stats-card {
  display: flex;
  flex-direction: column;
  gap: 20px;

  /* 78 vertical against 80 horizontal, which looks like a typo and is not.
     The gradient ring below is painted into a 2px border that the reference
     does not have. Horizontally that is absorbed, because the card's width is
     a percentage and it measures 1280 either way; vertically the height is
     auto, so the border adds 4px on top. The padding gives those 4px back and
     the card's OUTER box lands on the reference's. */
  padding: 78px 80px;
  border: 2px solid transparent;
  border-radius: 30px;

  /* Two-layer background: solid black clipped to the padding box, gradient
     clipped to the border box. That paints the gradient in the border ring
     only, with no pseudo-element and no z-index juggling. */
  background:
    linear-gradient(var(--c-black), var(--c-black)) padding-box,
    linear-gradient(var(--c-accent) 0%, rgba(0, 0, 0, 0) 90.04%) border-box;
}

.section-stats__title {
  /* 60 here plus the card's own 20px gap is the reference's 80. It splits the
     distance across two boxes the same way, so the breakpoints can move
     either one without the other going stale. */
  margin-bottom: 60px;
  font-size: var(--fs-display);
  font-weight: 300;
  line-height: 1.2;
  color: var(--c-primary);
}

.section-stats__title strong { font-weight: 700; }

.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

/* Hairline rules between cells, not around them. */
.stat {
  padding: 0 30px;
  border-left: 1px solid var(--c-divider);
}

.stat:first-child { padding-left: 0; border-left: 0; }

/* Number sits ABOVE its label. */
.stat__value {
  margin-bottom: 14px;
  font-size: var(--fs-display);
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-accent);
}

.stat__suffix { font-weight: 700; }

/* The reference's divider widget. Black on a black card, so it paints
   nothing at any width; it is a 31px spacer wearing a rule's clothes, and the
   honest border is kept so it reads as what it is and so phase 2 can make it
   visible with one token if Cinqo wants a real rule there.
   Hidden at desktop, where the reference sits it BESIDE the content in a flex
   row and it adds no height. Turned on at 1024 and below in responsive.css.
   15 + 1 + 15 = the reference's 31. */
.stats-card__rule {
  display: none;
  padding: 15px 0;
  border-top: 1px solid var(--c-black);
}

.stat__label {
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--c-text);
}

/* ==========================================================================
   9. Team intro
   Two fixed-percentage columns, 35% and 50%, that deliberately leave slack on
   the right. The text column is pushed in 90px from the container edge, which
   is what lines its heading up under the section above.
   ========================================================================== */

.section-team-intro { padding: 0 var(--gutter); }

.section-team-intro__grid {
  display: flex;
  flex-direction: row;
  gap: 0;
  padding: 10px 0;
}

.section-team-intro__col {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px;
}

.section-team-intro__col--text {
  width: 35%;
  margin-left: 90px;
}

.section-team-intro__col--media {
  width: 50%;
  margin-left: 20px;
}

/* The head and CTA boxes are inset a further 10px; the body copy is not, so
   the paragraph runs the full width of the column. */
.section-team-intro__head,
.section-team-intro__cta { padding: 10px; }

.section-team-intro__title {
  font-size: 44px;
  font-weight: 600;
  line-height: 1.2em;
  color: var(--c-primary);
}

.section-team-intro__body p {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

.section-team-intro__body strong { font-weight: 700; }

.section-team-intro__figure {
  border-radius: 30px;
  /* .image-anime already sets position and overflow; the radius has to clip
     both the photo and the hover sweep, which is why it lives on the frame
     rather than on the image. */
}

/* The photo renders at its OWN ratio. The reference does nothing to this
   image: measured live at 1440 it is 630 x 502.5, `aspect-ratio: auto
   761 / 607`, `object-fit: fill` -- the browser's defaults -- and its file is
   761x607, the same as ours.

   This rule used to force `aspect-ratio: 1; object-fit: cover` on a comment
   claiming the reference's photo was "SQUARE at every width, 630x630 at
   desktop and 482x482 at tablet". It is not, and those two numbers were never
   the photograph. The reference lazy-loads with LiteSpeed, so 111 of its
   images ship a 1x1 transparent GIF in `src` and the real file in `data-src`;
   a 1x1 GIF at `width: 100%; height: auto` renders A PERFECT SQUARE at
   whatever the column width happens to be. 630 and 482 are the two column
   widths. Anything probing the reference MUST swap `data-src` into `src`
   first; see HANDOFF.md.

   The rule stayed because it was thought to be robust to the phase 4 photo.
   It is not robust, it is wrong: crop the replacement to the ratio you want
   rather than asking CSS to crop it, or this section changes height again. */
.section-team-intro__figure img {
  width: 100%;
  max-width: 100%;
  height: auto;
}

/* ==========================================================================
   10. Services grid
   Head row of two columns over a wrapping card grid. The cards are sized by
   percentage rather than by a CSS grid because the closing line rides along
   as a full-width item in the same flex container.
   ========================================================================== */

.section-services { padding: 80px var(--gutter); }

.section-services .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.section-services__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0;
  padding: 10px;
  margin-bottom: 40px;
}

.section-services__head-text {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 58.33%;
  padding-right: 30px;
}

.section-services__head-aside {
  display: flex;
  flex-direction: column;
  gap: var(--card-gap);
  width: 41.66%;
}

/* Light and small for a section lede: the reference deliberately runs this
   below the card titles in both size and weight. */
.section-services__lede {
  font-size: 23px;
  font-weight: 300;
  line-height: 1.2;
  color: var(--c-primary);
}

.section-services__lede strong { font-weight: 700; }

.section-services__intro {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

.service-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* CENTRES THE SHORT LAST ROW ONLY, added 2026-09-08 when Meta Ads and TikTok
     Ads merged and the grid went nine cards to eight. A full row of three at
     `calc(33.33% - 20px)` plus two 30px gaps fills exactly 100%, so there is no
     free space to distribute and full rows do not move; only a part row does.
     `.service-footer` is 100% wide and is likewise unaffected. Serves the
     homepage grid and the hub's, which share this class. */
  justify-content: center;
  gap: var(--card-gap);
  padding: 10px;
}

.service-item {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: calc(33.33% - 20px);
  padding: var(--card-pad);
  border: 1px solid var(--c-divider);
  border-radius: var(--card-radius);
  /* A single soft white blob, bled off the top left corner. One SVG serves
     all nine cards, so they read as a set rather than as nine treatments. */
  background-image: url("../img/service-card-bg.svg");
  background-position: left top;
  background-repeat: no-repeat;
  /* Frosts the page's purple gradient showing through from behind. */
  backdrop-filter: blur(100px);
  -webkit-backdrop-filter: blur(100px);
  transition: transform var(--dur) var(--ease-in-out);
}

/* Flat tint between the blob and the content, which is what stops the blob
   reading as a highlight and keeps the card body legible. */
.service-item::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: var(--c-secondary);
  opacity: .4;
  pointer-events: none;
}

.service-item > * { position: relative; z-index: 1; }

.service-item:hover { transform: translateY(-5px); }

.service-item__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* The whole gap between the icon row and the text block is 100px: this
     80px plus the card's own 20px flex gap. */
  margin-bottom: 80px;
}

.service-item__icon,
.service-item__link { color: var(--c-accent); }

.service-item__icon,
.service-item__icon svg {
  width: var(--card-icon);
  height: var(--card-icon);
}

.service-item__link,
.service-item__link svg {
  display: block;
  width: var(--card-link);
  height: var(--card-link);
}

.service-item__link {
  transition: transform var(--dur-fast) var(--ease-in-out),
              color var(--dur-fast) var(--ease-in-out);
}

/* Hovering anywhere on the card, not just the arrow, swings it round and
   turns it white. The card itself lifts 5px at the same time. */
.service-item:hover .service-item__link {
  transform: rotate(45deg);
  color: var(--c-primary);
}

.service-item__title {
  margin-bottom: 20px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-primary);
}

.service-item__text {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-primary);
}

/* Rides in the same flex container as the cards, taking a full row of its
   own after the third rank. */
.service-footer {
  width: 100%;
  margin-top: 20px;
  text-align: center;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.service-footer a {
  font-weight: 700;
  color: var(--c-accent);
  text-decoration: underline;
  transition: color var(--dur-fast) var(--ease-in-out);
}

.service-footer a:hover { color: var(--c-white); }

/* ==========================================================================
   11. Feature block
   A single 64% column. The eyebrow and title obey that width; everything
   below is pushed back out to 134.762% of it so the body runs wider than the
   heading it sits under. flex-grow/shrink are pinned off so the wide blocks
   are not pulled back to the column.
   ========================================================================== */

.section-feature { padding: 0 var(--gutter); }

.section-feature .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

.section-feature__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0;
  padding: 10px;
  margin-bottom: 40px;
}

.section-feature__col {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 64%;
  padding-right: 30px;
}

.section-feature__wide {
  display: flex;
  flex-direction: column;
  gap: 30px;
  width: 134.762%;
  flex-grow: 0;
  flex-shrink: 0;
}

/* Both headings are set light; the first is turned bold by an inline element
   rather than by its own weight, which is how the reference gets a fully
   bold heading out of the same 300 base as the light one below it. */
.section-feature__title,
.section-feature__subtitle {
  font-size: 30px;
  font-weight: 300;
  line-height: 1.2;
  color: var(--c-primary);
}

.section-feature__title strong { font-weight: 700; }

.section-feature__body p,
.section-feature__closing {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

.section-feature__body p:not(:last-child) { margin-bottom: 1.6em; }
.section-feature__body strong { font-weight: 700; color: var(--c-primary); }
.section-feature__body em { font-style: italic; }

/* Five single-line rows. The 5px the reference sets between them is split
   into 2.5px of padding under each row and 2.5px of margin over the next,
   which is why the last row is 2.5px shorter than the rest. */
.feature-list__item {
  display: flex;
  align-items: center;
  padding-bottom: 2.5px;
}

.feature-list__item:last-child { padding-bottom: 0; }
.feature-list__item + .feature-list__item { margin-top: 2.5px; }

/* 14px glyph in a 17.5px box: the 3.5px of slack is the whole gap before the
   label, so there is no separate margin to keep in step with it. */
.feature-list__icon {
  flex: 0 0 17.5px;
  display: flex;
  align-items: center;
  color: var(--c-text);
}

.feature-list__icon svg { width: 14px; height: 14px; }

.feature-list__text {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  /* A one-off in the reference's kit, very slightly warmer than --c-text. */
  color: #fcf6f6;
}

.feature-list__text b { font-weight: 700; }
.feature-list__text i { font-style: italic; }

/* ==========================================================================
   12. Clients across industries
   The only full-bleed section on the page: no container, so the gradient runs
   the whole viewport width. Three logo marquees, the middle one reversed.
   ========================================================================== */

.section-clients {
  padding: 60px 20px 20px;
  text-align: center;
  color: var(--c-white);
  /* A purple of its own, close to but not the same as --c-accent. Left as a
     literal so the rebrand does not silently drag it onto the accent. */
  background: linear-gradient(to right, var(--c-black), #753fa4);
}

.clients__heading {
  max-width: 900px;
  margin: 0 auto 40px;
}

.clients__pre {
  margin-bottom: 10px;
  font-size: 14.4px;
  font-weight: 600;
  line-height: var(--lh-body);
  letter-spacing: 1px;
  text-transform: uppercase;
  opacity: .8;
}

.clients__title {
  font-size: 35px;
  font-weight: 700;
  line-height: 1.2;
}

/* The bottom margin here collapses with the heading block's own 40px, which
   is what sets the gap down to the marquees. */
.clients__sub {
  margin-bottom: 1.6em;
  font-size: 17.6px;
  font-weight: 400;
  line-height: 1.6;
}

.clients__sub strong { font-weight: 700; }

.clients__carousel-title {
  margin-bottom: 30px;
  font-size: 28.8px;
  font-weight: 600;
  line-height: 1.2;
}

/* --- Logo marquees ------------------------------------------------------ */
/* Same construction as the services ticker: the markup holds one group,
   main.js appends a clone, and the keyframe travels exactly -50% so the
   wrap is invisible. The reference animates an odd number of cards by -50%
   and visibly jumps; this does not. */
.logo-marquee {
  margin: 20px 0;
  overflow: hidden;
}

.logo-marquee__track {
  display: flex;
  gap: var(--logo-gap);
  width: max-content;
  animation: logo-scroll var(--logo-dur) linear infinite;
}

.logo-marquee--rtl .logo-marquee__track {
  animation-name: logo-scroll-rev;
}

/* Negative delays start each row part-way through its cycle, so the three
   never sit in step with one another. */
.section-clients > .logo-marquee .logo-marquee__track { animation-delay: -47s; }
.logo-marquee--rtl .logo-marquee__track { animation-delay: -23s; }

.logo-marquee__group {
  display: flex;
  gap: var(--logo-gap);
  flex: none;
}

/* The reference only reaches 240px because its cards overflow their track
   and flex-shrink squeezes them down onto their min-width floor. Nothing
   overflows here, so the width is stated outright and the min-width kept as
   the floor it was meant to be. */
.logo-card {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--logo-card-w);
  min-width: var(--logo-card-w);
  height: var(--logo-card-h);
  padding: 20px;
  border: 1px solid var(--c-divider);
  border-radius: 12px;
  background: rgba(255, 255, 255, .05);
  box-shadow: 0 4px 10px rgba(0, 0, 0, .2);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

/* height:auto is what lets max-width shrink the mark without squashing it.
   Without it the width/height attributes pin the box and the logo distorts. */
.logo-card img {
  max-width: 100%;
  max-height: 80px;
  height: auto;
  object-fit: contain;
}

@keyframes logo-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* Reversed track: same travel, run the other way, so both directions share
   one duration and therefore one speed. */
@keyframes logo-scroll-rev {
  from { transform: translateX(-50%); }
  to   { transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
  .logo-marquee__track { animation: none; }
}

/* ==========================================================================
   13. Longform copy block
   Not a container section: the reference sizes this one at 97.572% and lets
   auto margins centre it, so it sits a few pixels inside the viewport rather
   than lining up with the 1300px container used everywhere else.
   ========================================================================== */

.section-seo {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 97.572%;
  margin-inline: auto;
  padding: 10px;
}

.section-seo__spacer { height: 36px; }

/* Authored at 800 but wrapped in an inline element that pulls it back to
   700, which is what actually renders. Same trick as section 11, in the
   opposite direction. */
.section-seo__title {
  font-size: 27px;
  font-weight: 800;
  line-height: 1.2;
  color: var(--c-primary);
}

.section-seo__title span { font-weight: 700; }

/* Narrower than the heading above it, on purpose. */
.section-seo__body {
  width: 83.024%;
  max-width: 83.024%;
}

.section-seo__body p,
.section-seo__note {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

.section-seo__body p + p { margin-top: 1.6em; }

/* Bold inside a paragraph goes white; bold in the closing block keeps the
   body colour. The reference really does treat the two differently. */
.section-seo__body p strong { font-weight: 700; color: var(--c-primary); }

.section-seo__note {
  /* Stands in for the empty line the reference leaves between the two. */
  margin-top: 27.2px;
}

.section-seo__note strong { font-weight: 700; }

/* ==========================================================================
   14. Social cards
   Head row reuses section 10's split. Below it, three pills, each with an
   80px circular icon hung off its top left corner and a 36px arrow ring at
   the bottom right of the text block.
   ========================================================================== */

.section-social { padding: 80px var(--gutter); }

.section-social .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.section-social__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0;
  padding: 10px;
  margin-bottom: 40px;
}

.section-social__head-text {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 58.33%;
  padding-right: 30px;
}

.section-social__head-aside { width: 41.66%; }

/* Light throughout, with a single word turned bold by an inline span. Unlike
   sections 11 and 13, where the span wraps the whole heading, here it wraps
   only one word, so the two weights sit side by side on the same line. */
.section-social__title {
  font-size: var(--fs-h-primary);
  font-weight: var(--fw-h-primary);
  line-height: var(--lh-h-primary);
  color: var(--c-primary);
}

.section-social__title span { font-weight: 700; }

.section-social__intro {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

.social-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--card-gap);
  padding: 10px;
}

.social-card { width: calc(33.33% - 20px); }

/* The pill is inset from its card so the icon has somewhere to hang. */
.social-card__pill {
  position: relative;
  margin: 30px 0 0 30px;
  padding: 30px 40px 30px 70px;
  border: 1px solid var(--c-divider);
  border-radius: 100px;
}

/* Pinned 30px outside the pill's top left corner, which is exactly what the
   pill's own top and left margins make room for. Clips its own hover disc. */
.social-card__icon {
  position: absolute;
  top: -30px;
  left: -30px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--social-icon);
  height: var(--social-icon);
  border-radius: 100px;
  background: var(--c-accent);
  color: var(--c-black);
  transition: background var(--dur) var(--ease-in-out),
              color var(--dur) var(--ease-in-out);
}

/* A dark disc parked off the icon's bottom right corner. On hover it slides
   home while the purple underneath fades out, so the fill reads as a wipe
   rather than a colour swap. */
.social-card__icon::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: 50%;
  background: var(--c-secondary);
  transform: translate(100%, 100%);
  transition: transform var(--dur) var(--ease-in-out);
}

/* Lifted above the disc. The reference sets z-index on a static element,
   where it does nothing; this positions the glyph so it actually works. */
/* Lifted above the disc. The reference sets z-index on a static element,
   where it does nothing; this positions the glyph so it actually works.

   Each glyph's viewBox is cropped tight to its own ink, so fixing the height
   at 40px and letting the width follow reproduces the reference's sizes on
   all three (Facebook 25x40, LinkedIn 35x40, Instagram 40x40) from one rule. */
.social-card__icon svg {
  position: relative;
  z-index: 1;
  width: auto;
  height: 40px;
}

.social-card__body { position: relative; }

/* Held short of the body's full width so a two-line title cannot run under
   the arrow ring sitting at its bottom right. */
.social-card__title,
.social-card__handle { max-width: 220px; }

.social-card__title {
  margin-bottom: 5px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-primary);
}

.social-card__handle {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

/* Anchored to the text block, not the pill, so it stays put whether the
   title runs to one line or two. */
.social-card__link {
  position: absolute;
  right: -4px;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--social-link);
  height: var(--social-link);
  border: 1px solid var(--c-white);
  border-radius: 100px;
  color: var(--c-white);
  transform: translateY(-25%);
  transition: border-color var(--dur) var(--ease-in-out);
}

.social-card__link svg {
  width: 24px;
  height: 24px;
  transition: transform var(--dur-fast) var(--ease-in-out);
}

/* Hovering anywhere on the pill swings the arrow round. The ring itself only
   changes colour when the pointer is actually on it. */
.social-card__pill:hover .social-card__link svg,
.social-card__link:focus-visible svg { transform: rotate(45deg); }

.social-card__link:hover,
.social-card__link:focus-visible { border-color: var(--c-accent); }

/* --- Pill hover ---------------------------------------------------------- */
.social-card__pill:hover .social-card__icon {
  background-color: transparent;
  color: var(--c-white);
}

.social-card__pill:hover .social-card__icon::before { transform: translate(0); }

/* ==========================================================================
   15. Spacer
   20px of nothing: the container's own 10px top and bottom padding.
   ========================================================================== */

.section-spacer { padding: 0 var(--gutter); }
.section-spacer .container { padding: 10px 0; }

/* ==========================================================================
   16. Founder message
   A single bordered card frosting the page gradient behind it, with a tinted
   overlay between that frost and the content. The portrait column drives the
   card height; the message column is shorter and simply stretches.
   ========================================================================== */

.section-founder { padding: 0 var(--gutter); }

.section-founder .container {
  display: flex;
  padding: 10px 0;
}

.founder-card {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  border: 1px solid var(--c-divider);
  border-radius: 30px;
  backdrop-filter: blur(100px);
  -webkit-backdrop-filter: blur(100px);
}

/* Sits over the border as well as the fill, which is why it is inset by -1px
   rather than 0. Same overlay pattern as the service cards. */
.founder-card::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 0;
  border-radius: inherit;
  background: var(--c-secondary);
  opacity: .4;
  pointer-events: none;
}

.founder-card__inner {
  position: relative;
  z-index: 1;
  display: flex;
  width: 100%;
  padding: 10px;
}

.founder__col {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px;
}

.founder__col--media { width: 51%; }
.founder__col--text  { width: 45.788%; }

/* --- Portrait column ----------------------------------------------------- */
/* Desaturates the page gradient showing through the card, but only inside the
   portrait's own box, which is what gives that panel its flat grey cast
   against the purple-tinted card around it. */
.founder__portrait {
  backdrop-filter: grayscale(1);
  -webkit-backdrop-filter: grayscale(1);
}

.founder__portrait img {
  width: 58%;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
  border-radius: 20px;
  /* Offset hard to the right, with no blur softening beyond the spread. */
  box-shadow: 25px 0 10px -1px rgba(0, 0, 0, .5);
}

.founder__name {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px;
}

.founder__name-title {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-primary);
  text-align: center;
}

/* The reference pulls this line 18px up out of its own box and leaves the box
   at 9.2px, so the block below it sits closer than the line looks. Odd, but
   it is what sets the card height, so it is reproduced rather than tidied. */
.founder__meta {
  align-self: center;
  height: 9.2px;
}

.founder__meta span {
  display: block;
  margin-top: -18px;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  white-space: nowrap;
}

/* Reserves a 50px row in the flow, then renders 147px higher, which lands the
   badge on the bottom of the portrait. */
.founder__social { text-align: center; }

.founder__social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: var(--c-divider-dark);
  color: var(--c-accent);
  transform: translateY(-147px);
  transition: background var(--dur-fast) var(--ease-in-out);
}

.founder__social-link svg { width: 25px; height: 25px; }
.founder__social-link:hover { background: rgba(247, 251, 250, .2); }

/* --- Message column ------------------------------------------------------ */
.founder__title {
  margin: 0 20px;
  font-size: 45px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--c-primary);
}

.founder__title span { font-weight: 700; }

.founder__body {
  margin: 0 20px 20px;
  font-size: 15px;
  line-height: 2;
  color: var(--c-text);
  text-shadow: 0 0 10px rgba(0, 0, 0, .3);
}

.founder__body p { margin: 0; }
.founder__body strong { font-weight: 700; color: var(--c-primary); }

.founder__cta-title {
  margin: 0 20px;
  font-size: 20px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--c-primary);
}

/* ==========================================================================
   18. Numbers band
   One centred display headline, the page's largest type outside the hero.
   The container carries the 80px band padding; the inner box adds the
   theme's usual 10px, which is what leaves the heading exactly 1280px to
   wrap in. The heading is pure white here, not the --c-text off-white the
   rest of the page uses.

   The span on the second line is an animation hook on the reference and
   carries no styling of its own, so it gets none here either. It must stay
   inline: as a block it would take the full 1280px instead of its own text
   width, and the geometry check reads that width.
   ========================================================================== */

.section-numbers { padding: 0 var(--gutter); }

.section-numbers .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 80px 0;
}

.section-numbers__box {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px;
}

.section-numbers__head { text-align: center; }

.section-numbers__title {
  margin: 0;
  font-size: 116px;
  font-weight: 700;
  line-height: 1.7em;
  /* The theme opens the word gaps on this heading only. It is worth 8px per
     space, which is enough to move a line's width by a whole word's stem, so
     it is not cosmetic: without it the two lines measure ~8px narrow. */
  word-spacing: 8px;
  color: var(--c-white);
}

/* ==========================================================================
   19. How it works
   Head row over three process cards. The band carries the 80px padding, the
   head row a 40px margin under it, and the card row the usual 10px, which is
   what leaves the cards 1260px to share.

   The cards are the section 10 treatment again (blob SVG, backdrop blur, a
   tint between the two, -1px inset so the overlay covers the border), with
   two additions: an arrow hung off the top right of the body, and a coloured
   block pinned into the bottom right corner.
   ========================================================================== */

.section-process { padding: 0 var(--gutter); }

.section-process .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

.section-process__band {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 80px 10px;
}

/* --- Head row ------------------------------------------------------------ */

/* The aside is centred against the heading column rather than top-aligned,
   which is what drops it level with the second line of the heading. */
.section-process__head {
  display: flex;
  align-items: center;
  padding: 10px;
  margin-bottom: 40px;
}

.section-process__head-main {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 58.333%;
  padding-right: 30px;
}

.section-process__head-aside { width: 41.667%; }

.section-process__title {
  margin: 0;
  font-size: var(--fs-h-primary);
  font-weight: var(--fw-h-primary);
  line-height: var(--lh-h-primary);
  color: var(--c-primary);
}

.section-process__intro { margin: 0; }

/* --- Card row ------------------------------------------------------------ */

/* No align-items here on purpose. The cards stretch to the tallest of them,
   and each one lays its own content out from the top, so the step rows sit
   at different heights across the three. That is the reference's behaviour,
   not a defect: do not bottom-align the step row to "fix" it. */
.process-grid {
  display: flex;
  gap: 30px;
  padding: 10px;
}

.process-item {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: calc(33.333% - 20px);
  padding: var(--card-pad);
  border: 1px solid var(--c-divider);
  border-radius: var(--card-radius);
  background-image: url("../img/service-card-bg.svg");
  background-position: left top;
  background-repeat: no-repeat;
  backdrop-filter: blur(100px);
  -webkit-backdrop-filter: blur(100px);
  /* Clips the corner block to the rounded corner and hides the parked wipe. */
  overflow: hidden;
  transition: transform var(--dur) var(--ease-in-out);
}

/* Inset by -1px so the tint covers the border too, as in sections 10 and 16. */
.process-item::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 0;
  border-radius: inherit;
  background: var(--c-secondary);
  opacity: .4;
}

.process-item > * { position: relative; z-index: 1; }

.process-item:hover { transform: translateY(-5px); }

/* The 60px of padding is what holds the step row clear of the copy; the card
   gap adds the last 20px of the 80px total. */
.process-item__body {
  position: relative;
  padding-bottom: 60px;
}

.process-item__title {
  max-width: 275px;
  margin: 0 0 40px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-primary);
}

.process-item__text { margin: 0; }

/* Hangs off the body's top right, 3px above the title's own box. */
.process-item__link {
  position: absolute;
  top: -3px;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--c-primary);
  border-radius: 100px;
  color: var(--c-primary);
  transition: border-color var(--dur-fast) var(--ease-in-out);
}

.process-item__link svg {
  width: 24px;
  height: 36px;
  transition: transform var(--dur-fast) var(--ease-in-out);
}

.process-item:hover .process-item__link svg { transform: rotate(45deg); }
.process-item__link:hover { border-color: var(--c-accent); }

/* --- Step row and corner block ------------------------------------------- */

.process-item__foot {
  position: relative;
  display: flex;
}

.process-item__step {
  width: 50%;
  padding-right: 10px;
}

.process-item__step-label {
  margin: 0 0 5px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-primary);
}

.process-item__step-num {
  margin: 0;
  font-size: 40px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-accent);
}

/* Pinned into the card's bottom right corner. The negative offsets are the
   card's own padding, so the block sits flush inside the border rather than
   inside the content box. */
.process-item__icon-box {
  position: absolute;
  right: calc(var(--card-pad) * -1);
  bottom: calc(var(--card-pad) * -1);
  width: 62%;
  height: 140px;
  padding: 40px;
  overflow: hidden;
  border-radius: var(--card-radius) 0;
  background: var(--c-accent);
  text-align: center;
  line-height: 0;
}

/* The hover wipe: parked off the bottom right corner and clipped, it slides
   home over the accent block. Same mechanism as the social pills in 14. */
.process-item__icon-box::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--card-radius) 0;
  background: var(--c-primary);
  transform: translate(100%, 100%);
  transition: transform var(--dur) var(--ease-in-out);
}

.process-item:hover .process-item__icon-box::before { transform: translate(0); }

/* Positioned so the glyph clears the wipe. A z-index alone would not: the
   wipe is a positioned pseudo, so a static glyph paints underneath it. */
.process-item__icon {
  position: relative;
  z-index: 1;
  display: inline-block;
  width: 60px;
  height: 60px;
  color: var(--c-black);
}

.process-item__icon svg { display: block; width: 60px; height: 60px; }

/* ==========================================================================
   20. Testimonials
   Head row over a two column body: a bordered stat card beside a looping
   quote slider, centred against one another so the shorter card sits level
   with the taller slider.
   ========================================================================== */

.section-testimonials { padding: 0 var(--gutter); }

.section-testimonials .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 80px 0;
}

/* --- Head ---------------------------------------------------------------- */

.section-testimonials__head {
  display: flex;
  align-items: center;
  padding: 10px;
  margin-bottom: 40px;
}

.section-testimonials__head-main {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 86%;
  padding-right: 30px;
}

.section-testimonials__title {
  margin: 0;
  font-size: var(--fs-h-primary);
  font-weight: var(--fw-h-primary);
  line-height: var(--lh-h-primary);
  color: var(--c-primary);
}

/* The section 11 overflow trick again: this block is pushed back out past
   the heading above it, so the line and the button run wider than the
   heading rather than aligning to it. The flex-grow/shrink lock is what
   stops the row reclaiming the overflow. */
.section-testimonials__lede {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 138.618%;
  flex-grow: 0;
  flex-shrink: 0;
}

.section-testimonials__intro { margin: 0; }

/* --- Body ---------------------------------------------------------------- */

.section-testimonials__body {
  display: flex;
  align-items: center;
  gap: 30px;
  padding: 10px;
}

/* --- Review card --------------------------------------------------------- */

.review-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: calc(32% - 15px);
  padding: 50px;
  border: 1px solid var(--c-divider);
  border-radius: var(--card-radius);
  backdrop-filter: blur(100px);
  -webkit-backdrop-filter: blur(100px);
}

.review-card::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 0;
  border-radius: inherit;
  background: var(--c-secondary);
  opacity: .4;
}

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

.review-card__score {
  margin: 0;
  font-size: 80px;
  font-weight: 600;
  line-height: 1;
  text-align: center;
  color: var(--c-primary);
}

.review-card__stars {
  display: flex;
  justify-content: center;
  gap: 7px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.review-card__stars li {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  color: var(--c-accent);
}

.review-card__stars svg { width: 18px; height: 16px; }

/* The -5px pulls the line up into the star row's slack, which is what keeps
   the card as tight as the reference's. */
.review-card__count {
  margin: -5px 0 10px;
  text-align: center;
}

.review-card__title {
  margin: 0 0 15px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  text-align: center;
  color: var(--c-primary);
}

/* Seven 40px avatars on a 26px stride, so each one laps the last by 14px. */
/* The review card's avatar row was removed on 2026-09-06 and its rules with
   it. The reference still ships the row, but every image in it carries a
   never-opening reveal wipe, so it paints nothing on their live site. The
   lapping rule that used to live here was `li + li { margin-left: -14px }`
   over 40px circles, a 26px stride, if it is ever wanted back. */

/* The review card's client-logo row. Five 40px marks on a 26px stride, so
   each laps the one before it by 14px -- the same figures `/about/`'s
   `.about-grow__avatars` uses, and the same reason: the chips are composited
   onto an OPAQUE disc, without which the overlap would show one mark through
   another and the row would read as a smear.

   RESTORED 2026-09-07 with real client logos. The row that stood here until
   2026-09-06 held seven generated placeholder FACES and was removed for it;
   the lapping rule below is the one that comment said to bring back if it was
   ever wanted, and it is. */
.review-card__clients {
  display: flex;
  justify-content: center;
  margin: 0;
  padding: 0;
  list-style: none;
}

.review-card__clients li + li { margin-left: -14px; }

.review-card__clients img {
  display: block;
  width: 40px;
  height: 40px;
  border: 1px solid var(--c-secondary);
  border-radius: 50%;
}

/* --- Slider -------------------------------------------------------------- */

.testimonial-slider {
  position: relative;
  width: calc(68% - 15px);
}

/* `pan-y` hands the browser the vertical scroll and leaves the horizontal
   axis to the drag handler in main.js, which is what the reference's Swiper
   sets too. Without it a vertical swipe that starts on a slide fights the
   page scroll on touch. */
.testimonial-slider__viewport {
  overflow: hidden;
  touch-action: pan-y;
}

/* Only while a horizontal drag is actually claimed, so the quote stays
   selectable the rest of the time. The reference blocks selection outright
   through Swiper's `touchStartPreventDefault`. */
.testimonial-slider.is-dragging,
.testimonial-slider.is-dragging * {
  user-select: none;
  -webkit-user-select: none;
}

/* flex-start, not stretch: each slide keeps its own height and the track
   takes the tallest, which is what sets the section height. */
.testimonial-slider__track {
  display: flex;
  align-items: flex-start;
  gap: 15px;
}

.testimonial {
  flex: 0 0 100%;
  padding-left: 30px;
}

.testimonial__head {
  display: flex;
  flex-direction: column;
}

.testimonial__mark {
  display: block;
  margin: 0 0 30px;
  line-height: 0;
}

/* The reference watermarks each slide with its own logo in a 120x120 box, and
   this now carries Cinqo's wordmark in that same box. The square is kept and
   object-fit does the fitting, so the 2.66:1 mark paints 120x45 inside it and
   the element's own box never changes. Sizing to the mark's real ratio instead
   would move every slide and take the homepage height map off zero.

   IT PAINTS AT FULL STRENGTH, which looks wrong for a thing called a
   watermark and is what the reference actually does. Their parent sets
   color: rgba(37,117,252,.2), but the logo SVG's own paths carry an explicit
   fill, so that 20% never reaches them and their mark renders fully
   saturated. Matching the number rather than the result would have left ours
   about a quarter as visible as theirs. */
.testimonial__mark img {
  display: block;
  width: 120px;
  height: 120px;
  object-fit: contain;
  object-position: left center;
  filter: brightness(1.2);
}

/* The row is 22.4px tall because the reference's stars are inline-block on a
   text baseline, which leaves 0.8px under the glyph and the rest above it.
   Reproduced explicitly here so the quote below starts at the same y. */
.testimonial__stars {
  display: flex;
  align-items: flex-end;
  gap: 5.7px;
  height: 22.4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.testimonial__stars li { display: flex; color: var(--c-accent); }

.testimonial__stars svg { width: 18px; height: 16px; margin-bottom: .8px; }

.testimonial__quote { margin: 20px 0 40px; }

.testimonial__quote p {
  margin: 0;
  font-size: 22px;
  font-weight: 600;
  line-height: 1.7;
  color: var(--c-primary);
}

/* THE CLIENTS' OWN TESTIMONIALS ARRIVE AS TWO TO FOUR PARAGRAPHS, 2026-09-09,
   so the slide needs a gap between them where every slide until then was one
   block. 32px is `.testimonial-card__quote`'s own paragraph gap on
   `/testimonial/`, reused rather than invented so the two surfaces share one
   rhythm.

   `p + p` rather than a bottom margin with a `:last-child` reset: a
   single-paragraph slide then matches `margin: 0` above EXACTLY and moves no
   box, which is what keeps the slides that are still one paragraph out of the
   diff. */
.testimonial__quote p + p { margin-top: 32px; }

.testimonial__bio {
  display: flex;
  align-items: center;
}

.testimonial__avatar {
  flex: none;
  margin: 0 15px 0 0;
  line-height: 0;
}

.testimonial__avatar img {
  display: block;
  width: 60px;
  height: 60px;
  border-radius: 50%;
}

.testimonial__name {
  display: block;
  margin-bottom: 5px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-primary);
}

.testimonial__role { color: var(--c-primary); }

/* Both arrows sit on the slider's bottom edge, which is also the bottom of
   the author row, so they read as part of that line. */
.testimonial-slider__nav {
  position: absolute;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 60px;
  height: 60px;
  padding: 0;
  border: 0;
  border-radius: 14px;
  background: var(--c-secondary);
  color: var(--c-primary);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-in-out);
}

/* The glyph is sized by its BOX, not by its ink, and the two are not the
   same number: the path's ink fills 16 of the 18-unit viewBox, so a box of
   side S renders an arrow 16 * S / 18 wide. The reference's svg is stretched
   by its button to 24 wide over the same viewBox, so its arrow ink is 21.3px
   here, 16px at tablet and 15.1px at mobile. Stating the box that produces
   each of those is what keeps the three bands honest; a flat 18px everywhere
   drew the desktop arrow a third too small. */
.testimonial-slider__nav svg { width: 24px; height: 24px; }

.testimonial-slider__nav[data-slider-prev] { right: 90px; }
.testimonial-slider__nav[data-slider-next] { right: 0; }

.testimonial-slider__nav:hover { background: var(--c-accent); }

/* ==========================================================================
   21. FAQ heading
   A tight band: no 80px section padding at all, just the container's own
   10px above and below a single 39px line.
   ========================================================================== */

.section-faq-head { padding: 0 var(--gutter); }

.section-faq-head .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

/* 300 here and 700 on the inline element, so the rule still serves a light
   heading if only part of the line is ever wrapped. */
.section-faq-head__title {
  margin: 0;
  font-size: 39px;
  font-weight: 300;
  line-height: 1.2;
  color: var(--c-primary);
}

.section-faq-head__title strong { font-weight: 700; }

/* ==========================================================================
   22. FAQ accordion
   Six near-white bars on the dark ground. One card open at a time; main.js
   animates the panel height and toggles .is-open.
   ========================================================================== */

.section-faq { padding: 0 var(--gutter); }

.section-faq .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

.faq__item { margin-bottom: 10px; }

.faq__header { margin: 0; }

/* The 0.8px border is transparent and is there purely to reserve the space.
   It is not cosmetic: at 1px each bar grows 0.4px and the six of them push
   the section past the 2px gate. */
.faq__toggle {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  padding: 14px 30px;
  border: .8px solid transparent;
  border-radius: 0;
  background: #f7f9fb;
  color: var(--c-black);
  font-family: inherit;
  font-size: 16px;
  font-weight: 700;
  line-height: 24px;
  text-align: left;
  cursor: pointer;
}

.faq__question { padding-right: 10px; }

/* Absolute, so the arrow stays pinned to the right edge however long the
   question runs. The 30px margin sits inside the toggler's own padding. */
.faq__icon {
  position: absolute;
  right: 0;
  margin-right: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 24px;
}

.faq__chev { width: 16px; height: 20px; }

.faq__chev--up { display: none; }
.faq__item.is-open .faq__chev--up { display: block; }
.faq__item.is-open .faq__chev--down { display: none; }

/* Collapsed panels keep a measurable box rather than display:none, so the
   height animation has a scrollHeight to travel to. */
.faq__panel {
  overflow: hidden;
  height: 0;
}

.faq__item.is-open .faq__panel { height: auto; }

.faq__body { padding: 30px 85px 30px 65px; }

.faq__body p { margin: 0; }

/* ==========================================================================
   23. Closing CTA heading
   ========================================================================== */

.section-cta-head { padding: 0 var(--gutter); }

.section-cta-head .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

/* An Elementor spacer widget, reproduced as an empty box rather than as
   margin, so the container's 20px gap still sits either side of it. */
.section-cta-head__spacer { height: 25px; }

.section-cta-head__title {
  margin: 0;
  font-size: 33px;
  font-weight: 300;
  line-height: 1.2;
  color: var(--c-primary);
}

.section-cta-head__title strong { font-weight: 700; }

/* ==========================================================================
   24. Closing CTA body
   ========================================================================== */

.section-cta { padding: 0 var(--gutter); }

.section-cta .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

/* Locked against flex growing or shrinking so the measure holds at 85.63%
   rather than stretching to the container. */
.section-cta__text {
  width: 85.63%;
  flex-grow: 0;
  flex-shrink: 0;
  margin: 0;
}

/* ==========================================================================
   25. Location line
   ========================================================================== */

.section-location { padding: 0 var(--gutter); }

.section-location .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

.section-location__text {
  width: 88.676%;
  flex-grow: 0;
  flex-shrink: 0;
  margin: 0;
}

/* Renders nothing and is meant to. It holds the container's second flex row
   open so the 20px gap above it still counts toward the section height, and
   it is where phase 2's organisation schema goes. */
.section-location__schema { height: 0; }

/* ==========================================================================
   26. Work together
   The footer's opening block: a 180px display heading with a 150px accent
   disc centred over it. The disc is out of flow, so the block's height is
   the eyebrow plus the heading plus the gaps and nothing else.
   ========================================================================== */

.section-work-together {
  padding: 0 var(--gutter);
  /* A grid-and-glow tile pinned to the section's centre, which is also where
     the disc sits, so the glow reads as coming from behind the button. */
  background-image: url("../img/work-together-bg.webp");
  background-position: center center;
  background-repeat: no-repeat;
}

.section-work-together .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 80px 0;
}

.work-together {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px;
}

/* Not the global .eyebrow: this one is a 22px heading with no asterisk and
   no tracking, centred rather than left aligned. */
.work-together__eyebrow {
  margin: 0;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  text-align: center;
  color: var(--c-primary);
}

.work-together__title {
  margin: 0;
  font-size: 180px;
  font-weight: 800;
  line-height: 1.2;
  text-align: center;
  color: var(--c-primary);
}

/* Centred on the block rather than on the heading, and lifted above it. */
.work-together__disc-wrap {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
}

/* A second button variant, not the pill: the arrow stacks above the label
   and the whole thing is a disc. Only the 20% wash is shared. */
.work-together__disc {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 150px;
  height: 150px;
  /* Uniform 14px, not the reference's 14px/25px. The disc is a CIRCLE, so the
     real limit on the label is the chord at its baseline -- 142.7px measured,
     not the padding box. At 25px the box capped it at 100px and "Get In Touch"
     cleared it by 4.3px, so any phase 4 type change wrapped it. 14px gives a
     122px box: still ~20px inside the chord, and 26px of headroom. Nothing
     moves today -- the content is centred in a fixed 150x150 box. */
  padding: 14px;
  border-radius: 50%;
  background: var(--c-accent);
  color: var(--c-black);
  font-size: 16px;
  font-weight: 700;
  line-height: 1.4;
  text-align: center;
  text-decoration: none;
  transition: background-color .3s var(--ease-in-out),
              color .3s var(--ease-in-out);
}

/* This variant deliberately has NO 20% wash. The reference defines one and
   then switches it off with display:none, so the disc is solid accent. Leave
   the wash in and the purple reads visibly lighter than the reference's. */

/* Hover fills the disc with the dark secondary and flips the label and arrow
   to white; the arrow also rotates, on its own slower curve. */
.work-together__disc:hover {
  background: var(--c-secondary);
  color: var(--c-primary);
}

.work-together__disc-icon {
  transition: transform var(--dur) var(--ease-in-out);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
}

.work-together__disc:hover .work-together__disc-icon { transform: rotate(45deg); }

/* The path is filled with currentColor, so it follows the hover colour
   change without a rule of its own. */
.work-together__disc-icon svg { width: 24px; height: 24px; }

/* ==========================================================================
   27. Footer columns
   A divider rule, four columns, and a copyright bar. Elementor pays for the
   30px column gaps out of the percentages, hence the "- 22.5px" on every
   column: three gaps of 30 shared across four columns.
   ========================================================================== */

.section-footer {
  padding: 0 var(--gutter);
  border-top: 1px solid var(--c-divider);
}

.section-footer .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 60px 0 0;
}

.footer-cols {
  display: flex;
  flex-wrap: wrap;
  gap: 30px;
  padding: 10px;
  margin-bottom: 30px;
}

.footer-col {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: calc(16.67% - 22.5px);
}

/* The brand column is wider AND spaces its rows further apart than the rest. */
.footer-col--brand {
  width: calc(33.33% - 22.5px);
  gap: 30px;
}

.footer-col--wide { width: calc(33.33% - 22.5px); }

/* --- Brand column -------------------------------------------------------- */

/* The logo widget is a fixed 264px box that CENTRES a 250px mark, which is
   what indents the logo 7px from the column edge while the contact rows
   below it sit flush. It looks like a mistake and it is the reference's. */
.footer-logo {
  display: block;
  width: 264px;
  /* Kills the baseline gap under the inline-block mark, which would
     otherwise add ~7px to the column and to the whole section. */
  line-height: 0;
  text-align: center;
}

/* HEIGHT-pinned, not width-pinned, since the swap to Cinqo's wordmark.
   The reference's mark was 3.5:1 and 250px wide came out 71.4 tall; pinning
   the width again at 2.657:1 would have come out 94.1 and grown this section
   by 53.7px on every page of the site. Pinning the height instead holds the
   section at 496 and lets the width fall where the ratio puts it -- 189.7,
   still centred in the fixed 264px box, so nothing moves horizontally
   either. Same brightness lift as the header, 1.2 since the 2026-09-09
   logo swap; see that rule for why 1.4 now clips. */
.footer-logo img {
  display: inline-block;
  width: auto;
  height: 71.4px;
  filter: brightness(1.2);
}

.footer-contact {
  display: flex;
  align-items: center;
  text-decoration: none;
}

.footer-contact__icon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 9px;
  margin-right: 20px;
  color: var(--c-primary);
}

.footer-contact__icon svg { width: 22px; height: 22px; }

.footer-contact__text {
  font-size: var(--fs-h-secondary);
  font-weight: 600;
  line-height: var(--lh-h-secondary);
  color: var(--c-primary);
}

/* The address is deliberately set smaller and tighter than the phone and
   email above it, which is what runs it to three lines. */
.footer-contact--address .footer-contact__text {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.5;
}

/* The ABN, under the address. 2026-09-06: the user's placement, and their
   call the same day to give it an icon matching the three rows above it.

   It takes `.footer-contact` wholesale, so the icon box and the 20px gap are
   inherited and the text lines up with the phone, email and address by
   construction. An earlier pass set it as a bare paragraph with a hand-copied
   `padding-left: 60px` to fake that alignment; carrying the real row is both
   simpler and safe against the icon box ever changing width.

   Only the type is overridden. One weight lighter than the address at the
   same size and leading: legal metadata that stays legible without competing
   with the address for the eye. */
.footer-contact--abn .footer-contact__text {
  font-size: 17px;
  font-weight: 600;
  line-height: 1.5;
}

/* --- Link columns -------------------------------------------------------- */

/* The 50px of padding is what drops the column headings level with the
   contact rows rather than with the logo. */
.footer-col__title {
  margin: 0 0 20px;
  padding-top: 50px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-primary);
}

.footer-menu {
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-menu li { margin-bottom: 15px; }
.footer-menu li:last-child { margin-bottom: 0; }

.footer-menu a {
  font-size: 16px;
  color: var(--c-primary);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-in-out);
}

.footer-menu a:hover { color: var(--c-accent); }

/* --- Signup form --------------------------------------------------------- */

/* flex-start, not stretch: the field is 50px and the button 50.4px, and the
   reference lets them differ rather than levelling them. */
.footer-form {
  display: flex;
  /* Inert with only the input and button in the row -- the input is `flex: 1`
     and shrinks rather than overflowing. It is here so `.footer-form__status`
     can take its own line when the newsletter is wired. */
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 10px;
}

.footer-form__input {
  flex: 1;
  min-width: 0;
  height: 50px;
  padding: 12px 20px;
  border: 1px solid var(--c-divider);
  border-radius: 10px;
  background: transparent;
  color: var(--c-primary);
  font-family: inherit;
  font-size: 16px;
}

.footer-form__submit {
  flex: none;
  padding: 14px 30px;
  border: 0;
  border-radius: 10px;
  background: var(--c-accent);
  color: var(--c-black);
  font-family: inherit;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.4;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-in-out);
}

.footer-form__submit:hover { background: var(--c-accent-alt); }

/* The newsletter's inline status line. EMPTY AND `hidden` AT REST, so it has
   no box and the footer's resting geometry is exactly what the port gate
   measured before the newsletter was wired -- proved by re-running portsec
   after adding it.

   `.footer-form` is a flex ROW, so this needs `flex-basis: 100%` to take its
   own line under the input and button. That is also why the parent gained
   `flex-wrap: wrap`: with only the input and button present nothing wraps,
   because the input is `flex: 1` and shrinks rather than overflowing, so the
   rule is inert until this element becomes visible.

   It only exists once an endpoint is configured -- see Footer.astro. */
.footer-form__status {
  flex-basis: 100%;
  margin: 10px 0 0;
  font-size: 15px;
  line-height: 1.4;
  color: var(--c-primary);
}

.footer-form__status[data-state="error"] { color: var(--c-accent-alt); }

/* --- Social row ---------------------------------------------------------- */

/* inline-block rather than flex, so the 2.7px of whitespace between the
   items survives; it is part of the reference's 48.7px stride. */
.footer-social {
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
}

.footer-social li {
  display: inline-block;
  margin: 5px;
  /* An inline-block takes its baseline from its last line box, and the ring is
     a flex container whose baseline comes from the glyph inside it. Without
     this, Instagram's shorter glyph drops its ring 1.1px below the other three
     and the row grows to 47.1px. Aligning the boxes instead of their contents
     makes the row independent of glyph height. */
  vertical-align: middle;
}

.footer-social a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--c-primary);
  border-radius: 50%;
  color: var(--c-primary);
  transition: color var(--dur) var(--ease-in-out),
              border-color var(--dur) var(--ease-in-out);
}

.footer-social a:hover {
  color: var(--c-accent);
  border-color: var(--c-accent);
}

/* Each glyph's viewBox is cropped tight to its own ink, so this one height
   rule renders every mark at the size the reference's Font Awesome glyphs
   render at (18px of ink inside the 36px ring) and a redrawn glyph corrects
   itself. Do NOT put a fixed width back on this rule: the marks have
   different aspect ratios and a square box shrinks the tall ones to fit. */
.footer-social svg {
  height: 18px;
  width: auto;
}

/* Instagram is the one exception, in both social rows: its rounded square is
   inset inside Font Awesome's em box and renders shorter than the letterform
   marks beside it. Matching the reference means shrinking it rather than
   levelling it up. */
.footer-social svg.social-glyph--inset { height: calc(18px * var(--social-inset)); }

/* --- Copyright bar ------------------------------------------------------- */

.footer-bar { padding: 10px; }

/* Centred on purpose. The reference left-aligns this line; this is a wanted
   change, not a match, so do not "correct" it back during a review. */
.footer-bar p {
  margin: 0;
  text-align: center;
}

/* ==========================================================================
   Global: scroll reveals
   Movement lives here; main.js only toggles .is-revealed.
   ========================================================================== */

[data-reveal] {
  opacity: 0;
  transform: translateY(34px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* ==========================================================================
   Global: floating background shapes
   The theme decorates several sections with a single rotating shape bled off
   the left edge, drawn as a ::before on the section itself. Four keyframes
   drive them; sections 9 to 26 reuse the same set, so they are all defined
   here once.
   ========================================================================== */

@keyframes circlerotate {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

@keyframes circlezoomrotate {
  from { transform: rotate(0deg) scale(.5); }
  to   { transform: rotate(360deg) scale(1); }
}

@keyframes roundrotate {
  from { transform: translateY(0) rotate(0deg); }
  to   { transform: translateY(100px) rotate(360deg); }
}

@keyframes circlemoverotate {
  from { transform: translateY(0) rotate(0deg); }
  to   { transform: translateY(160px) rotate(360deg); }
}

/* Shared plumbing. Each section then sets its own art, size and offsets. */
.has-shape { position: relative; }

.has-shape::before {
  content: "";
  display: block;
  position: absolute;
  pointer-events: none;
  background-repeat: no-repeat;
  background-position: left center;
  background-size: contain;
}

/* 7. About the agency */
.section-about::before {
  left: -120px;
  top: 50%;
  width: 386px;
  height: 400px;
  opacity: .5;
  background-image: url("../img/shape-about-agency-bg.webp");
  animation: circlerotate 20s linear infinite;
}

/* 18. Numbers band
   Sits behind the content rather than over it: the pseudo-element is a
   positioned descendant, so without the negative z-index it would paint on
   top of the in-flow heading. */
.section-numbers::before {
  left: -90px;
  top: -25%;
  width: 310px;
  height: 325px;
  opacity: .5;
  z-index: -1;
  background-image: url("../img/shape-why-choose-bg-shape.webp");
  animation: circlezoomrotate 10s linear infinite alternate;
}

/* 19. How it works. The shape hangs off the band, not the section, because
   the band is what carries the 80px padding the offsets are measured from. */
.section-process__band::before {
  left: -90px;
  top: 40%;
  width: 297px;
  height: 340px;
  opacity: .5;
  z-index: -1;
  background-image: url("../img/shape-how-work-bg-shape.webp");
  animation: roundrotate 8s linear infinite alternate;
}

/* 20. Testimonials. THE ONE SHAPE THE PORT MISSED. `shape-testimonial-bg-shape
   .png` was mirrored with the other four and then never referenced by any
   rule, so section 20 shipped without its decoration while sections 7, 18, 19
   and 26 all had theirs. Added 2026-09-07 on the user's direction, measured
   off the reference's own `::before`: 306x304 at `left: -70px; top: 50%`,
   opacity .7, and `circlerotate` over 20s linear.

   Its opacity is .7 where the other four sit at .4 or .5, which is the
   reference's own value and is why this one reads more strongly than the
   rest.

   `z-index: -1` for the same reason as the numbers band: the pseudo-element
   is a positioned descendant, so without it the shape paints OVER the card
   and the slider instead of behind them. */
.section-testimonials::before {
  left: -70px;
  top: 50%;
  width: 306px;
  height: 304px;
  opacity: .7;
  z-index: -1;
  background-image: url("../img/shape-testimonial-bg-shape.webp");
  animation: circlerotate 20s linear infinite;
}

/* 26. Work together. Fainter than the others at .4, and the only shape in
   the footer template. */
.section-work-together::before {
  left: -120px;
  top: 30%;
  width: 305px;
  height: 315px;
  opacity: .4;
  z-index: -1;
  background-image: url("../img/shape-work-together-bg-shape.webp");
  animation: circlemoverotate 12s linear infinite alternate;
}

@media (prefers-reduced-motion: reduce) {
  .has-shape::before { animation: none; }
}

/* ==========================================================================
   29. Floating WhatsApp widget

   A real feature rather than scaffolding, so it is hand-built to replace the
   reference's NinjaTeam plugin and survives every later phase unchanged.

   It lives OUTSIDE every page template, as a direct child of <body>, which is
   where the reference puts it too. `assets/js/whatsapp.js` renders it from a
   single config object and toggles classes only; nothing here is set from
   script, so the page still carries zero inline styles.

   Two colour families on purpose: the launcher and the panel heading take the
   site accent (Cinqo cyan in phase 2), while the agent row keeps WhatsApp's
   own green, because green is what identifies the service the row opens.
   ========================================================================== */

.wa {
  /* The widget's own type context. Arial rather than the page face, matching
     the reference, and an em line-height so 27.2px lands as a LENGTH that the
     11px and 12px children inherit unchanged. A unitless 1.7 would recompute
     per element and every small row would come out short. */
  font-family: Arial, Helvetica, sans-serif;
  font-size: 16px;
  line-height: 1.7em;
}

/* --- Launcher ------------------------------------------------------------ */

.wa__launcher {
  position: fixed;
  right: var(--wa-offset);
  bottom: var(--wa-offset);
  z-index: 999999999;
  width: var(--wa-launcher);
  height: var(--wa-launcher);
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
  color: inherit;
  /* No background of its own, but the radius is what makes the focus ring
     below trace the visible circle instead of boxing it. */
  border-radius: 50%;
}

/* The reference's launcher is a div and cannot be reached by keyboard at all,
   so it never needed this. Ours is a real button, and the ring has to read
   against both the dark page and the accent disc. */
.wa__launcher:focus-visible {
  outline: 2px solid var(--c-white);
  outline-offset: 3px;
}

/* The pill sits entirely outside the launcher, to its left, and is centred on
   it. `right: 100%` plus the 7px margin is what sets the 7px gap. */
.wa__label {
  position: absolute;
  right: 100%;
  top: 50%;
  transform: translateY(-50%);
  width: 156px;
  margin-right: 7px;
  padding: 8px 12px;
  border-radius: 4px;
  background-color: var(--wa-label-bg);
  color: var(--wa-label-fg);
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: -.03em;
  text-align: left;
  transition: all var(--dur) ease;
}

.wa__label b { font-weight: 700; }

/* Opening the panel drops the pill out rather than fading it in place. */
.wa.is-open .wa__label {
  opacity: 0;
  visibility: hidden;
  transform: translateY(15px);
}

.wa__icon {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: var(--wa-accent);
  box-shadow: 0 6px 8px 2px rgba(0, 0, 0, .14);
}

/* Two glyphs stacked in the same 56px box, cross-faded by a scale-and-spin:
   the mark unwinds a full turn out as the cross winds a full turn in. */
.wa__icon::before,
.wa__icon::after {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-repeat: no-repeat;
  background-position: 50%;
  transition: all var(--dur) ease;
}

.wa__icon::before {
  z-index: 1;
  background-image: url("../img/wa-glyph-light.svg");
  background-size: 30px auto;
}

.wa__icon::after {
  z-index: 2;
  opacity: 0;
  transform: scale(0) rotate(-1turn);
  background-image: url("../img/wa-close.svg");
  background-size: 14px auto;
}

.wa.is-open .wa__icon::before {
  opacity: 0;
  transform: scale(0) rotate(1turn);
}

.wa.is-open .wa__icon::after {
  opacity: 1;
  transform: scale(1) rotate(0deg);
}

/* --- Panel --------------------------------------------------------------- */

.wa__panel {
  position: fixed;
  right: var(--wa-offset);
  bottom: var(--wa-panel-lift);
  z-index: 999999998;
  width: var(--wa-panel-w);
  max-width: calc(100% - 50px);
  overflow: hidden;
  border-radius: 5px 5px 8px 8px;
  box-shadow: 0 10px 10px 4px rgba(0, 0, 0, .04);
  opacity: 0;
  visibility: hidden;
  transform: translateY(var(--wa-panel-slide));
  transition: all var(--dur) ease;
}

/* `visibility: hidden` is doing real work here, not just hiding paint: it also
   takes the panel and everything in it out of the tab order and the
   accessibility tree while closed, so no aria-hidden is needed. */
.wa.is-open .wa__panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.wa__panel:hover,
.wa__panel:focus-within {
  box-shadow: 0 10px 10px 4px rgba(32, 32, 37, .23);
}

/* The heading's 74px left padding is reserved for the glyph, which is
   absolutely positioned into it rather than sitting in the flow. */
.wa__head {
  position: relative;
  padding: 15px 43px 17px 74px;
  background: var(--wa-accent);
  color: var(--wa-head-fg);
}

.wa__head::before {
  content: "";
  display: block;
  position: absolute;
  left: 12px;
  top: 20px;
  width: 55px;
  height: 33px;
  background: url("../img/wa-glyph-light.svg") top no-repeat;
  background-size: 33px;
}

.wa__title {
  min-height: 24px;
  padding-top: 2px;
  color: var(--c-white);
  font-size: 18px;
  font-weight: 400;
  line-height: 24px;
}

.wa__intro {
  min-height: 20px;
  padding-top: 4px;
  font-size: 12px;
  line-height: 20px;
}

.wa__intro b { font-weight: 700; }

.wa__body {
  padding: 13px 20px 21px 19px;
  background: var(--wa-panel-bg);
  text-align: left;
}

.wa__notice {
  padding: 0 3px;
  color: var(--wa-notice-fg);
  font-size: 11px;
  font-weight: 500;
}

/* --- Agent row ----------------------------------------------------------- */

/* The row starts dropped and transparent and animates home a beat after the
   panel lands, so the panel arrives first and the contact settles into it.
   The delay is per-row, so a second agent would follow 200ms later. */
.wa__item {
  margin: 14px 0 0;
  opacity: 0;
  transform: translateY(20px);
}

.wa.is-open .wa__item {
  opacity: 1;
  transform: translateY(0);
  transition: all var(--dur) ease .3s;
}

.wa.is-open .wa__item + .wa__item { transition-delay: .5s; }

/* A table, not flex: the avatar is absolutely positioned into the row's own
   left padding, and the text is a cell so it centres against the 48px avatar
   however many lines it runs to. */
.wa__agent {
  display: table;
  position: relative;
  width: 100%;
  padding: 13px 40px 12px 74px;
  border-left: 2px solid var(--wa-brand);
  border-radius: 2px 4px 2px 4px;
  background: var(--wa-row-bg);
  text-decoration: none;
  transition: all .2s ease;
}

.wa__agent:hover,
.wa__agent:focus,
.wa__agent:active {
  background: var(--wa-row-bg-hov);
  box-shadow: 0 7px 15px 1px rgba(55, 62, 70, .07);
}

.wa__agent::after {
  content: "";
  position: absolute;
  right: 14px;
  top: 26px;
  width: 20px;
  height: 20px;
  background: url("../img/wa-glyph-brand.svg") 0 0 no-repeat;
  background-size: 100% 100%;
}

.wa__avatar {
  position: absolute;
  left: 12px;
  top: 12px;
  width: 48px;
  height: 48px;
  overflow: hidden;
  border-radius: 50%;
}

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

.wa__text {
  display: table-cell;
  height: 48px;
  min-height: 48px;
  vertical-align: middle;
}

.wa__name {
  color: var(--wa-name-fg);
  font-size: 14px;
  line-height: 1.188em;
}

.wa__role {
  padding: 2px 0 0;
  color: var(--wa-role-fg);
  font-size: 11px;
  line-height: 1.125em;
}

/* Only rendered outside working hours. It fits inside the 48px cell, so the
   row stays 73px tall in both states. */
.wa__status {
  padding: 5px 0 0;
  color: var(--wa-status-fg);
  font-size: 10px;
  line-height: 1.125em;
}

/* Outside working hours the row greys out but stays a live link. The
   reference sets `pointer-events: none` here; WhatsApp queues messages, so
   closing the only contact channel overnight would lose real enquiries.
   See "Deliberate divergences" in HANDOFF.md. */
.wa__agent.is-offline {
  border-left-color: var(--wa-row-off-rule);
  background: var(--wa-row-off-bg);
  color: var(--wa-row-off-fg);
  box-shadow: none;
}

.wa__agent.is-offline::after {
  background-image: url("../img/wa-glyph-muted.svg");
}

@media (prefers-reduced-motion: reduce) {
  .wa__label,
  .wa__icon::before,
  .wa__icon::after,
  .wa__panel,
  .wa.is-open .wa__item,
  .wa__agent {
    transition: none;
  }
}

/* ==========================================================================
   PHASE 3. Inner pages.

   Everything below this line belongs to pages other than the homepage and
   matches nothing on it, so it cannot move the port gate. `_reference/` is
   the vanilla HOMEPAGE build and has no counterpart for any of it, which is
   why these rules go in `public/` only -- unlike the shared geometry fixes
   (the ticker's mobile speed, the FAQ band's 5px), which had to land in both.
   ========================================================================== */

/* --------------------------------------------------------------------------
   The inner-page banner. Shared by every page except the homepage.

   Measured on /careers/ and /testimonial/, which render it correctly. NOT on
   /contact-us/, whose own banner sits inside the reference's one broken
   container and comes out 50px and left-aligned. The vertical rhythm below
   resolves to their 395.2 exactly:

     150 + 10 + 88 (h1) + 10 + 27.2 (crumbs) + 10 + 100 = 395.2

   THE NEGATIVE MARGIN IS LOAD-BEARING. `.site-header` is `position: absolute`
   at desktop, so it overlays the first thing in `<main>`. The reference pulls
   this band up under it, landing its top at y -44, exactly as the homepage
   hero is pulled up by -91. Remove it and every inner page sits 44px low
   under a transparent bar.
   -------------------------------------------------------------------------- */
/* The dark fabric texture behind every inner-page banner. `cover` at 50% 50%
   with no repeat, the reference's own values, and no background COLOUR behind
   it -- theirs is transparent, so the body gradient shows through anywhere
   the image does not reach. The band is pulled up under the absolute header,
   so the image runs behind the header too, which is what the reference does
   and why its header appears to sit on artwork rather than on the gradient. */
/* NO NEGATIVE MARGIN. The band's top sits flush with the top of the page and
   the absolute header overlays its first 106px, which is what the reference's
   /faqs/ banner does: measured at 1440 its box starts at y 0 and its 235px of
   top padding is what clears the heading past the header, landing it at 245.

   THIS REPLACED A -44px PULL AND AN 80px HEADING, on the user's ruling of
   2026-09-02. The earlier build followed /careers/ and /testimonial/ -- 395.2
   tall on 150/100 with an 80/300/88 heading -- because six of the reference's
   seven inner pages measure that way and /faqs/ was the lone outlier. The
   user compared the two by eye and chose the outlier, for every inner page.
   So this is now the /faqs/ treatment and contact-us shares it. */
.page-banner {
  background: url("../img/page-header-bg.webp") no-repeat center center;
  background-size: cover;
}

/* The rhythm resolves to the reference's 488.3 exactly:

     235 + 10 + 56.1 (h1) + 10 + 27.2 (crumbs) + 10 + 140 = 488.3            */
.page-banner__inner {
  padding: 235px 0 140px;
}

/* The 10px lives on the wrappers so the heading and the nav keep their own
   boxes: the reference's h1 is 1280 x 88 at (80, 116) and its breadcrumb
   1280 x 27.2 at (80, 214). Padding on the h1 itself measures 1300 x 98 at
   (70, 106) -- the right rhythm on the wrong element. */
.page-banner__title-wrap {
  padding: 10px var(--gutter) 0;
}

.page-banner__crumbs-wrap {
  padding: 10px var(--gutter);
}

/* 51/300/56.1, the reference's own /faqs/ value, on its 1.1 leading ratio.
   Deliberately NOT the 80/300/88 the other six inner pages carry. */
.page-banner__title {
  margin: 0;
  font-size: 51px;
  font-weight: 300;
  line-height: 56.1px;
  text-align: center;
  color: var(--c-text);
}

/* The bold run is an inline element inside a 300 heading, never a weight on
   the heading itself. Same mechanism as the homepage's nine split headings. */
.page-banner__title strong {
  font-weight: 700;
}

/* NO GAP. The spacing between the three items is the separator's own
   horizontal padding, which is how the reference builds it; a gap here would
   add to that and push the row past its measured 141.6. */
.page-banner__crumb-list {
  display: flex;
  justify-content: center;
  /* `center`, not the flex default `stretch`. Stretched, the separator item
     grows from its natural 13px to the row's 27.2 and its glyph then sits at
     the top of that box under the 3px padding rather than beside the text.
     The reference's separator measures 13.0 tall; this is what reproduces it. */
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
}

/* THE CURRENT PAGE IS THE ACCENT ONE AND THE LINK IS WHITE -- not the other
   way round, which is the intuitive guess and what this file had first.
   Measured on /about/: both <li> carry #7d3ea3 and the <a> inside "Home"
   overrides to #ffffff, so the trail reads white, white asterisk, purple. */
.page-banner__crumb--current {
  color: var(--c-accent);
}

.page-banner__crumb--home a {
  color: var(--c-white);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}

.page-banner__crumb--home a:hover,
.page-banner__crumb--home a:focus-visible {
  color: var(--c-accent);
}

/* The asterisk separator. The reference draws Font Awesome's star-of-life at
   10px; this uses the site's own asterisk mark instead -- the same file the
   eyebrow and the ticker separator use -- so there is one asterisk on this
   site rather than two drawings of one.

   MASK, NOT BACKGROUND. `.ticker__sep` paints the SVG as a background and so
   takes whatever colour is baked into the file; `.eyebrow::before` masks it
   and so recolours from a token. This one has to be white, so it masks.

   The li measures 26 x 13 with `padding: 3px 8px 0` around a 10 x 10 glyph,
   which is what produces the reference's 46.6 / 26 / 69 run. */
.page-banner__crumb-sep {
  padding: 3px 8px 0;
  line-height: 0;
}

.page-banner__crumb-sep span {
  display: block;
  width: 10px;
  height: 10px;
  background-color: var(--c-white);
  -webkit-mask-image: url("../img/asterisk-icon.svg");
          mask-image: url("../img/asterisk-icon.svg");
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
}

/* --------------------------------------------------------------------------
   Contact page body.

   AUTHORED, NOT CLONED -- the user's ruling on 2026-09-01. The reference's
   contact page is the only one on the site whose containers have `--display`
   unset, so `display` falls back to `inline`, `max-width: min(100%, 1300px)`
   stops applying to a non-replaced inline box, and the page renders edge to
   edge with its three icon boxes at x695, x695 and x0. There is no layout
   there to copy, so this follows the house patterns instead: the 1300px
   container, an 80px band, and a three-across row on the shared --card-gap.

   The TYPE is the reference's exactly, because type comes from the global
   Elementor kit rather than from the missing page CSS.
   -------------------------------------------------------------------------- */
.section-contact__inner {
  padding: 80px var(--gutter);
}

/* On the shared heading tokens, not pinned values: --fs-h-primary is 50 at
   desktop, 40 at tablet and 30 at mobile, and --lh-h-primary is the 1.2 ratio
   that turns each of those into 60 / 48 / 36. Hard-coding 50/60 here would
   leave a 40px face on 60px leading at tablet. */
.section-contact__title {
  margin: 0 0 20px;
  font-size: var(--fs-h-primary);
  font-weight: var(--fw-h-primary);
  line-height: var(--lh-h-primary);
  color: var(--c-text);
}

.section-contact__title strong {
  font-weight: 700;
}

.section-contact__lede {
  max-width: 860px;
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  color: var(--c-text);
}

.section-contact__lede-link {
  color: var(--c-link);
  text-decoration: none;
}

.section-contact__lede-link:hover,
.section-contact__lede-link:focus-visible {
  text-decoration: underline;
}

/* Three across on the same gap as the services and process grids, so the
   page reads as part of the same system. */
.section-contact__cards {
  display: flex;
  flex-wrap: wrap;
  gap: var(--card-gap);
  margin: 50px 0 0;
  padding: 0;
  list-style: none;
}

.contact-card {
  flex: 0 0 calc(33.333% - (var(--card-gap) * 2 / 3));
  box-sizing: border-box;
  padding: var(--card-pad);
  border: 1px solid var(--c-divider);
  border-radius: var(--card-radius);
  background: rgba(27, 27, 27, .4);
  backdrop-filter: blur(100px);
}

/* `height` with `width: auto`, never a square box -- the icon system on this
   site sizes every mark off its own cropped viewBox. See CLAUDE.md. */
.contact-card__icon {
  display: block;
  margin-bottom: 20px;
  color: var(--c-accent);
}

.contact-card__icon svg {
  height: 50px;
  width: auto;
}

/* 22/700/26.4 -- the site's own card-title scale, which --fs-h-secondary
   carries down to 20 at tablet and 18 at mobile.

   NOT the 28/700/33.6 the reference's contact page renders. That page's CSS
   is missing, so its titles fall back to the theme's base sizing -- which is
   also what the odd 28 -> 26.944 step between 1440 and 1024 is, a fallback
   scaling, not an authored value. Measured on /about/, whose CSS works, the
   same widget is 22/700/26.4 at 1440 and 20/700/24 at 1024: the documented
   "every card title goes 22 -> 20" tablet rule. */
.contact-card__title {
  margin: 0 0 10px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-text);
}

.contact-card__text {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  color: var(--c-text);
  overflow-wrap: anywhere;
}

.contact-card__text a {
  color: inherit;
  text-decoration: none;
}

.contact-card__text a:hover,
.contact-card__text a:focus-visible {
  color: var(--c-accent);
}

/* --------------------------------------------------------------------------
   The form. Row heights, the two-column first row and the 176px textarea are
   the reference's; the column WIDTH is not, because theirs is measured at its
   broken 1440. Here the pair measures 625 each on the same 30px gap -- the
   container's CONTENT width after its 10px gutters is 1280, not 1300.
   -------------------------------------------------------------------------- */
.contact-form {
  margin-top: 50px;
}

/* The contact form's inline result line. EMPTY AND `hidden` AT REST, so it
   has no box and the page's measured geometry is unchanged -- the same
   arrangement as `.footer-form__status`. `.contact-form` is a plain block, so
   unlike the footer's flex row this needs no wrap rule.

   It only exists once an endpoint is configured; see ContactBody.astro. */
.contact-form__status {
  margin: 20px 0 0;
  font-size: 16px;
  line-height: 27.2px;
  color: var(--c-primary);
}

.contact-form__status[data-state="error"] { color: var(--c-accent-alt); }

.contact-form__grid {
  display: flex;
  flex-wrap: wrap;
  gap: 24px var(--card-gap);
}

.contact-form__row {
  flex: 0 0 100%;
}

.contact-form__row--half {
  flex: 0 0 calc(50% - (var(--card-gap) / 2));
}

.contact-form__input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  height: 56px;
  padding: 6px 20px;
  border: 1px solid var(--c-divider);
  border-radius: 0;
  background: rgba(255, 255, 255, .04);
  font-family: var(--ff-body);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  color: var(--c-text);
  transition: border-color var(--dur-fast) var(--ease);
}

.contact-form__textarea {
  height: 176px;
  padding-top: 16px;
  resize: vertical;
}

.contact-form__input::placeholder {
  color: var(--c-text);
  opacity: .55;
}

.contact-form__input:focus-visible {
  outline: none;
  border-color: var(--c-accent);
}

.contact-form__submit {
  margin-top: 30px;
  padding: 12px 40px;
  border: 0;
  border-radius: var(--btn-radius);
  background: var(--c-accent);
  font-family: var(--ff-body);
  font-size: 15px;
  font-weight: 700;
  line-height: 21px;
  color: var(--c-white);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}

.contact-form__submit:hover,
.contact-form__submit:focus-visible {
  background: var(--c-accent-alt);
}

/* --------------------------------------------------------------------------
   The map band. 450px tall in a 12px-radius box that clips the iframe -- the
   reference's numbers, carried over from its inline styles, which this build
   does not allow in markup.
   -------------------------------------------------------------------------- */
.section-map__frame {
  width: 100%;
  overflow: hidden;
  border-radius: 12px;
}

.section-map__embed {
  display: block;
  width: 100%;
  height: 450px;
  border: 0;
}

/* --------------------------------------------------------------------------
   FAQs page body. Five accordion groups, then the closing CTA card.

   CLONED, NOT AUTHORED -- unlike the contact page. All three of this page's
   top-level containers carry `--display` on the reference, so its layout
   renders correctly and its numbers can be trusted. Two things measured off
   it are deliberately NOT reproduced, both recorded in faqs.astro: the
   banner's own odd sizing, and a -127px pull that overlaps the CTA card at
   tablet.

   The container flip IS reproduced -- see the responsive sheet. On the
   reference this container is `flex-direction: row` at desktop (wrapping, so
   the two children stack) and `column-reverse` at 1024 and below, which lifts
   the CTA card above the accordions on tablet and mobile.
   -------------------------------------------------------------------------- */
/* No padding on the section itself, matching `.section-contact`: the 1300px
   `.container` centres the band and the 10px gutters live on `__inner`, which
   is what puts the content at x80 in a 1280 measure exactly as the banner
   above it does. */
.faqs-body__inner {
  display: flex;
  flex-direction: column;
  gap: 80px;
  padding: 80px var(--gutter);
}

.faqs-body__groups {
  display: flex;
  flex-direction: column;
  /* The reference's own group rhythm: 80px between groups at desktop,
     halving to 40 at tablet. Measured 1052.1 -> 1132.1 at 1440. */
  gap: 80px;
}

.faq-group {
  display: flex;
  flex-direction: column;
  /* 40px between a group's heading and its cards, measured 627.3 -> 667.3. */
  gap: 40px;
}

/* CENTRED, and on the shared heading tokens rather than pinned values.

   The reference renders these at 33/300/39.6 at desktop, 40/300/48 at tablet
   and 30/300/36 at mobile. The last two ARE --fs-h-primary exactly; only the
   desktop value is odd, and it is odd in the same direction as this page's
   banner -- smaller at 1440 than at 1024, which no responsive scale does on
   purpose. So the token is used and this heading steps 50 -> 40 -> 30 with
   the rest of the site, matching the reference exactly at two of its three
   widths and diverging only where the reference contradicts itself. */
.faq-group__title {
  margin: 0;
  font-size: var(--fs-h-primary);
  font-weight: var(--fw-h-primary);
  line-height: var(--lh-h-primary);
  text-align: center;
  color: var(--c-white);
}

/* --------------------------------------------------------------------------
   The outline accordion. A SECOND SKIN over the homepage's `.faq`, driven by
   the same untouched `accordion.ts`.

   Everything below overrides section 22's near-white bar; nothing here leaks
   back to it, because every rule is scoped under `.faq--outline`. The two
   files' numbers are set side by side in FaqsBody.astro.
   -------------------------------------------------------------------------- */
/* The border and radius sit on the ITEM, the way the reference's
   `.elementskit-card` carries them, so the panel opens inside the outline
   rather than beside it. Card 62.4 = toggler 60.4 + the 2px of border. */
.faq--outline .faq__item {
  margin-bottom: 30px;
  border: 1px solid var(--c-divider);
  border-radius: 10px;
}

.faq--outline .faq__item:last-child { margin-bottom: 0; }

/* `border: 0`, NOT the .8px transparent one section 22 needs. That reserve is
   load-bearing on the homepage and would add 1.6px to every bar here, where
   the outline is already a real 1px on the item. Measured toggler height is
   60.4 = 17 + 26.4 + 17. */
.faq--outline .faq__toggle {
  padding: 17px 55px 17px 20px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--c-white);
  font-size: var(--fs-h-secondary);
  line-height: var(--lh-h-secondary);
}

/* 24 wide against the homepage's 16, and inset 20 against its 30. The height
   follows the type: 1.2em resolves to the measured 26.4 / 24 / 21.6 as
   --fs-h-secondary steps 22 -> 20 -> 18. */
.faq--outline .faq__icon {
  margin-right: 20px;
  width: 24px;
  height: 1.2em;
}

.faq--outline .faq__body {
  padding: 0 55px 18px 20px;
}

/* The circled plus and minus, 24 x 24, and the two states are DIFFERENT
   COLOURS on the reference: accent while closed, white while open. The
   show/hide swap itself needs no rule here -- these carry the same
   `faq__chev--down` / `--up` hooks the homepage's chevrons do, so the
   `.faq__item.is-open` rules above already drive them. */
.faq--outline .faq__chev {
  width: 24px;
  height: 24px;
}

.faq--outline .faq__chev--down { color: var(--c-accent); }
.faq--outline .faq__chev--up   { color: var(--c-white); }

/* --------------------------------------------------------------------------
   The closing CTA card. The contact page's card treatment, which is the same
   kit part on the reference: the --c-divider hairline, the 30px radius and
   the 100px backdrop blur.
   -------------------------------------------------------------------------- */
.faq-cta {
  box-sizing: border-box;
  padding: 50px;
  border: 1px solid var(--c-divider);
  border-radius: var(--card-radius);
  background: rgba(27, 27, 27, .4);
  backdrop-filter: blur(100px);
  text-align: center;
}

/* `height` with `width: auto`. The viewBox is square, so this lands the
   measured 60 x 60. See CLAUDE.md, landmine 2 -- the viewBox is not to be
   normalised. */
.faq-cta__icon {
  display: block;
  margin-bottom: 40px;
  color: var(--c-white);
}

/* `margin-inline: auto`, NOT the card's `text-align: center`. This build
   renders SVGs as blocks, and a block box with `width: auto` ignores
   text-align and sits hard left -- which is exactly how it first shipped.
   The reference centres it: icon 60 wide at x690 inside a card spanning
   52.5 to 1387.5, whose centre is 720. */
.faq-cta__icon svg {
  height: 60px;
  width: auto;
  margin-inline: auto;
}

.faq-cta__title {
  margin: 0 0 20px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-white);
}

.faq-cta__text {
  margin: 0 0 20px;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  color: var(--c-white);
}

/* `.btn-wrap` reserves the width the detached icon circle overhangs by, so
   the pill stays optically centred rather than sitting 50px left of centre. */
.faq-cta__action {
  display: flex;
  justify-content: center;
}

/* --------------------------------------------------------------------------
   Privacy policy page.

   Twenty-four sections against the reference's twenty-four visible top-level
   containers, one for one, because `secheights.py` pairs them POSITIONALLY.
   Every number below is measured off tanzilite.info at 1440 with
   `tools/js/privacy-blocks.js`, and the tablet and mobile values come from
   `delta.py 1440 1024` and `delta.py 1024 767` on the same page.

   THE PAGE'S FOUR CONTAINER WIDTHS ARE ALL DIFFERENT AND ALL AUTHORED:

     prose, who, cards      1300   the site's standard --container
     intro card             1072   `margin: 0 174px` on the reference
     collect head            851   `margin: 0 284.5px`, and it KEEPS an inset
                                   at 1024 where the others drop theirs
     talk                   1407   the only full-bleed block on the page

   The first three fall out of `max-width` plus the shared `.container`
   centring, because at 1024 the reference's margins go to 0 and a
   max-width-constrained box lands on the same number from the other
   direction. Only the collect head needed checking at both widths; it is 851
   at 1440 AND at 1024, which is what says its inset is authored per
   breakpoint rather than inherited.

   ONE DELIBERATE DIVERGENCE IN THE PROSE, and it is spacing rather than
   layout. The reference's paragraphs have `margin: 0` and the air between
   them comes from EMPTY PARAGRAPHS typed into the rich-text field by hand.
   They are not consistent: block 17 has three of them in nine slots, block 15
   has none at all, and block 11 has one between its first and second
   paragraphs and none between its second and third. There is no rule to
   clone, so this uses a uniform 25.5px — one line of body leading, which is
   exactly what one of their empty paragraphs measures — and heights land
   within a line of theirs wherever their spacing was regular. Reproducing
   hand-placed blank lines would be reproducing an accident, and the copy
   lengths differ anyway.
   -------------------------------------------------------------------------- */

/* --- Spacers -------------------------------------------------------------
   `.section-spacer` is 20px on the homepage: an empty `.container` with
   `padding: 10px 0`. This page needs a 76 and four 70s, both INCLUSIVE of
   that padding, so the modifiers set the box rather than the gap.
   -------------------------------------------------------------------------- */
.section-spacer--56 .container { min-height: 76px; }
.section-spacer--50 .container { min-height: 70px; }

/* --- The intro card ------------------------------------------------------ */
.section-privacy-intro { padding: 0 var(--gutter); }

.privacy-intro {
  display: flex;
  max-width: 1072px;
  padding: 10px 0;
}

.privacy-intro__col {
  display: flex;
  flex: 0 0 50%;
  flex-direction: column;
  gap: 20px;
  padding: 10px;
}

/* NOT `.section-title`. The site's eyebrow is 14px/600 on 2.8px of tracking
   with an asterisk hung off a ::before; measured, this is 15px/500/18, white,
   untracked and unmarked. The 20px of padding under it is what makes the
   reference's widget box 38 tall for 18px of ink, and the heading's -30px
   pull below is measured against that 38. */
.privacy-intro__label {
  margin: 0;
  padding-bottom: 20px;
  font-size: 15px;
  font-weight: 500;
  line-height: 18px;
  color: var(--c-primary);
}

/* THE -30px IS LOAD-BEARING and it is not an overlap. The label's box is 38
   tall for 18px of ink, so this takes back that slack: the h2's own box lands
   at y525.2 while the flex item it sits in occupies 22.8 of the column, which
   is the reference's arrangement exactly. Remove it and the card is 30px
   taller with the heading floating clear of its label. */
.privacy-intro__title {
  margin: -30px 0 0;
  font-size: 44px;
  font-weight: 700;
  line-height: 52.8px;
  color: var(--c-primary);
}

.privacy-intro__lede {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

/* `display: block`, OVERRIDING THE COLUMN'S FLEX, and that is the whole
   point rather than a tidy-up. The mark is 153 square and the reference's
   wrapper measures 160.2; the extra 7.2 is the descent under an inline-block
   baseline, not an authored pad. A flex container has no line box, so an
   inline-block child inside one is blockified and the 7.2 never appears —
   which is exactly what happened on the first pass and read as a 5px section
   error. Reproducing the box model reproduces the number. */
.privacy-intro__col--mark {
  display: block;
  line-height: 27.2px;
  text-align: center;
}

.privacy-intro__mark {
  display: inline-block;
  height: 153px;
  width: auto;
  color: var(--c-primary);
}

/* --- "Who We Are" -------------------------------------------------------- */
.section-privacy-who { padding: 0 var(--gutter); }

.privacy-who {
  display: flex;
  padding: 10px 0;
}

.privacy-who__col {
  display: flex;
  flex: 0 0 50%;
  flex-direction: column;
  gap: 20px;
  padding: 10px;
}

/* CENTRED OVER LEFT-ALIGNED PROSE, which is the reference's own arrangement
   and reads as a mistake. Its box is the full 630 column with
   `text-align: center` while the paragraphs under it are left-aligned inside
   a 510 box. Every other heading on the page shrink-wraps to its text, so
   centring does nothing to them and does to this one. */
.privacy-who__title {
  margin: 0;
  font-size: 42px;
  font-weight: 700;
  line-height: 50.4px;
  text-align: center;
  color: var(--c-primary);
}

/* Two nested insets, 10 then 50, which is what puts 510 of text inside a 630
   column. Both are the reference's and both are on wrappers rather than on
   the paragraphs, so a long word cannot escape them. */
.privacy-who__body { padding: 10px; }

.privacy-who__body p {
  margin: 0 50px 25.5px;
  font-size: 15px;
  font-weight: 400;
  line-height: 25.5px;
  color: var(--c-text);
}

.privacy-who__body p:last-child { margin-bottom: 0; }

/* The 60px sits on the COLUMN, not the image, so the picture's top clears the
   heading's baseline rather than sitting level with it. */
.privacy-who__col--media { margin-top: 60px; }

/* The radius is on the frame and the frame clips, so it survives a
   replacement file of a different size — which phase 4 will bring. */
.privacy-who__frame {
  overflow: hidden;
  border-radius: 30px;
}

/* `cover`, measured, not assumed: the source file is square and the box is
   630 x 570, so squashing and cropping are both possible and only one is
   right. The reference crops. */
.privacy-who__img {
  display: block;
  width: 100%;
  height: 570px;
  object-fit: cover;
}

/* --- The collect head ---------------------------------------------------- */
.section-privacy-collect-head { padding: 0 var(--gutter); }

.privacy-collect {
  display: flex;
  gap: 20px;
  max-width: 851px;
  padding: 10px 0;
}

/* 42% OF THE CONTAINER PLUS 41px, WITH THE 41 TAKEN BACK AS MARGIN. That is
   the reference's own shape rather than a trick: its heading widget is 42% at
   every width (357.4 of 851, 313.7 of 747) and the widget-container inside
   carries a flat `margin-right: -41px`, so the heading RENDERS 398.4 wide and
   OCCUPIES 357.4 of the row. The two columns therefore add up to 102% plus a
   gap and overflow their container by 37px, which sounds like a defect and is
   not visible: the heading is centred in its 398.4 and its ink stops well
   short of the text column beside it. Checked against a screenshot, because
   the numbers alone read as a collision. */
.privacy-collect__title {
  flex: 0 0 calc(42% + 41px);
  margin: 0 -41px 0 0;
  font-size: 42px;
  font-weight: 700;
  line-height: 50.4px;
  text-align: center;
  color: var(--c-primary);
}

.privacy-collect__body {
  flex: 0 0 60%;
  padding: 10px;
}

.privacy-collect__body p {
  margin: 0 50px;
  font-size: 15px;
  font-weight: 400;
  line-height: 25.5px;
  color: var(--c-text);
}

/* --- The prose stack ----------------------------------------------------- */
.section-privacy-prose { padding: 0 var(--gutter); }

.privacy-prose {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

/* LEFT, NOT CENTRED, and that renders identically to the reference. Theirs
   carries `text-align: center` on a box that shrink-wraps to its text, where
   centring moves nothing; copying the centre onto a full-width block would
   centre every heading on the page.

   THE REFERENCE CONSTRAINS EACH OF THESE HEADINGS TO ITS OWN ARBITRARY
   WIDTH, AND THAT IS NOT REPRODUCED. Every one of the twelve carries a
   percentage width on the widget AND a flat pixel margin-right on the
   container inside it, and no two agree: measured, 67.3% / 319px, 82.4% /
   347px, 82.4% / 422px, and so on down the page. Between them they decide
   where each heading is allowed to wrap.

   At 1440 they change nothing — 1300px of container leaves every heading on
   one line whatever the margin is. At 1024 three of them wrap to a SECOND
   line inside 356.8, 480.2 and 405.2 boxes while a full 1004 sits unused
   beside them, which is a heading broken in half for no reason a reader can
   see. Twelve hand-dragged numbers from a page builder are not a system, so
   this lets the headings wrap at the container instead.

   THE COST IS 48px ON THREE SECTIONS AT 1024, and it is in the height map
   rather than on the page. Recorded here so it is not read later as a
   regression: `privacy-told-head`, `privacy-auto-head` and `privacy-campaign`
   are one line where theirs are two. */
.privacy-prose__title {
  margin: 0;
  font-size: 32px;
  font-weight: 700;
  line-height: 38.4px;
  color: var(--c-primary);
}

/* The 50px inset that gives the measured 1200px text column inside 1300. */
.privacy-prose__body {
  margin: 0 50px;
  font-size: 15px;
  font-weight: 400;
  line-height: 25.5px;
  color: var(--c-text);
}

.privacy-prose__body p { margin: 0 0 25.5px; }
.privacy-prose__body > :last-child { margin-bottom: 0; }

.privacy-prose__body strong { font-weight: 700; }

.privacy-prose__body h3 {
  margin: 0 0 25.5px;
  font-size: 28px;
  font-weight: 700;
  line-height: 33.6px;
  color: var(--c-primary);
}

/* 32px of indent and 16px under, both the reference's. The list marker is the
   browser's own disc, which is what theirs resolves to. */
.privacy-prose__body ul {
  margin: 0 0 16px;
  padding: 0 0 0 32px;
  list-style: disc;
}

.privacy-prose__body li { margin: 0; }

/* --- "Talk to Us" -------------------------------------------------------- */

/* THE ONLY FULL-BLEED BLOCK ON THE PAGE. The 1.148% is measured, not chosen:
   the reference's margin is 16.53px at 1440 and 11.75px at 1024, which is the
   same percentage to four figures at both widths. It goes on the section as
   padding so the row itself can stay a plain flex container. */
.section-privacy-talk { padding: 0 1.148%; }

.privacy-talk {
  display: flex;
  padding: 10px;
}

/* NOT a 50/50 split and not on --card-gap either. 38.586% and 55.407% of the
   row's content box, measured at 1440 and confirmed at 1024; they leave 6% of
   the row unused at the right, which is the reference's own layout. */
.privacy-talk__col {
  display: flex;
  flex: 0 0 38.586%;
  flex-direction: column;
  gap: 20px;
  margin-bottom: 26px;
  padding: 10px;
}

.privacy-talk__col--text {
  flex-basis: 55.407%;
  margin-bottom: 0;
}

/* The 7px top offset is the reference's `margin-top` on this widget, and it
   is what makes the column 94.2 tall rather than 87.2. */
.privacy-talk__title {
  margin: 7px 0 0;
  font-size: 56px;
  font-weight: 700;
  line-height: 67.2px;
  text-align: center;
  color: var(--c-primary);
}

/* CENTRED, unlike every other paragraph on the page, and inset 50px each side
   inside its own column: 648.5 of text in a 748.5 box. */
.privacy-talk__text {
  margin: 0 50px;
  font-size: 15px;
  font-weight: 400;
  line-height: 25.5px;
  text-align: center;
  color: var(--c-text);
}

/* --- The four contact cards ---------------------------------------------- */
.section-privacy-cards { padding: 0 var(--gutter); }

/* Four 25% columns with the card inset 10px inside each, which is what puts
   305px cards at x80 / 405 / 730 / 1055 in a 1300 container. Expressed as
   padding on the card rather than as a gap on the row, because that is where
   the reference carries it and because it is what makes the cards stack flush
   at mobile. */
.privacy-cards {
  display: flex;
  flex-wrap: wrap;
  padding: 10px 0;
}

/* THE ONLY WHITE SURFACE ON A BLACK PAGE. The 5px radius is deliberate and
   deliberately not `--card-radius`: every other card on this site is 30px and
   this ElementsKit part is 5. The border resolves to `currentColor` on the
   reference, which at that point in the cascade is --c-text — near-invisible
   on white, and reproduced rather than "fixed" into something visible.

   The basis comes off the 25% once the 10px margins are added, or four cards
   no longer fit on one row. `calc` rather than a rounded 24%: the column is
   exactly a quarter of the container and the margin is exactly 20px of it. */
.privacy-card {
  flex: 0 0 calc(25% - 20px);
  box-sizing: border-box;
  margin: 10px;
  padding: 50px 40px;
  border: 1px solid var(--c-text);
  border-radius: 5px;
  background: var(--c-white);
  text-align: center;
  text-decoration: none;
}

.privacy-card__icon {
  display: block;
  margin-bottom: 13px;
  color: #656565;
}

/* `height` with `width: auto`, so the 24-unit viewBox lands the measured 40px
   box. See CLAUDE.md, landmine 2 — do not normalise these viewBoxes. */
.privacy-card__icon svg {
  height: 40px;
  width: auto;
}

/* BLACK, hard-coded, and not `--c-primary`. This is a light-surface widget:
   --c-primary is white and would put the title on white. Same reason the
   body line below is #656565 rather than --c-text. */
.privacy-card__title {
  margin: 0;
  font-size: 23px;
  font-weight: 700;
  line-height: 27.6px;
  color: var(--c-black);
}

.privacy-card__text {
  margin: 0 0 10px;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: #656565;
}

/* THE ADDRESS CARD GETS 18px MORE ROOM EACH SIDE, and that much is authored:
   measured, its inner box is 259 wide against the other three's 223 inside
   the same 305 card, so its horizontal padding is 22 rather than 40. It is
   the one card whose content is a paragraph rather than a single line, and
   somebody widened it on purpose.

   WHAT IS NOT REPRODUCED IS WHAT THEY DID NEXT. ElementsKit's equal-height
   script then pins all four cards to one height and the infobox carries
   `overflow: hidden`, so on the reference the address card's content is
   simply cut off: at 1440 its icon is sliced in half across the middle and
   only the bottom of the glyph renders. Confirmed in a screenshot, not
   inferred — the numbers alone (218.2 against the others' 219.8) look like a
   rounding difference rather than a clipped card.

   Here the row is a flex container, so the four cards stretch to the tallest
   of them and nothing is clipped. That makes this section taller than the
   reference's, by however much the address runs to — a difference that is
   entirely `site.address`, which is placeholder and changes at phase 4. */
.privacy-card:last-child {
  padding-left: 22px;
  padding-right: 22px;
}

/* --------------------------------------------------------------------------
   About page.

   Twelve sections against the reference's thirteen visible top-level
   containers. The one we do not render is their TEAM GRID (their container
   11, 2586.2px), dropped on the user's direction because Cinqo is founder-led
   -- the same reason `/our-team/` is deferred out of phase 3. See
   `src/pages/about.astro` for the gate consequence, which is real:
   `secheights.py` pairs positionally and needs `--refskip 11` here.

   Every number below is measured off the reference at 1440. The responsive
   values come from `delta.py 1440 1024` and `delta.py 1024 767` on the same
   page, and the two breakpoints were CHECKED rather than assumed -- captures
   at 1023, 991 and 768 put none of this page's own changes anywhere but
   Elementor's 1024 and 767.

   THE SYSTEM IS UNIFORM ACROSS THE WHOLE PAGE, which is what keeps the
   responsive block in `responsive.css` short:

     1024   every section heading -> 40/48, card titles 22 -> 20,
            section padding 80 -> 40, the container's 60px side margin -> 0,
            the gap ladder 20/30/60/80 -> 15/20/30/40
      767   section headings -> 30/36, card titles 20 -> 18, columns stack
   -------------------------------------------------------------------------- */

/* --- c4, the opening block ------------------------------------------------
   A sticky column. `position: sticky; top: 30px` is the reference's own and
   changes nothing at this height, but it is authored, so it is here. It is
   also why `overflow-x: clip` on <body> is load-bearing on this page -- see
   CLAUDE.md landmine 1.

   The 55px right inset on the block is theirs and is NOT symmetrical with the
   10px on the other three sides. */
.section-about-intro { padding: 0 var(--gutter); }

.about-intro__block,
.about-why__block {
  padding: 10px 55px 10px 10px;
}

.about-intro__col,
.about-why__col {
  position: sticky;
  top: 30px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.section-about-intro .container,
.section-about-why .container {
  padding: 10px 0;
}

/* 43px here against c5's 28px. Both converge on 40 at 1024, so the desktop
   difference is authored per section rather than a scale. */
.about-intro__title {
  margin: 0 0 20px;
  font-size: 43px;
  font-weight: 600;
  line-height: 51.6px;
  color: var(--c-primary);
}

.about-intro__body p,
.about-why__body p {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

/* 25.6px, not 27.2: the reference's paragraph margin here is 1.6em on a 16px
   body, which is not the same number as its line-height. */
.about-intro__body p:first-child,
.about-why__body p:first-child {
  margin-bottom: 25.6px;
}

.about-intro__body strong,
.about-why__body strong {
  font-weight: 700;
  color: var(--c-primary);
}

/* The first bold run is a link: body-copy cyan, and UNDERLINED. The site's
   reset strips underlines from every <a>, so this has to put one back --
   measured on the reference as a solid underline in the link's own colour.
   No gate here can see it; it was caught by eye against their page, the same
   way the FAQ accordion's plus/minus glyph was. */
.about-intro__body a {
  color: var(--c-link);
  text-decoration: underline;
  text-decoration-color: currentColor;
}

.about-intro__body a strong { color: inherit; }

/* --- c5, heading and two paragraphs --------------------------------------- */
.section-about-why { padding: 0 var(--gutter); }

.about-why__title {
  margin: 0 0 20px;
  font-size: 28px;
  font-weight: 600;
  line-height: 33.6px;
  color: var(--c-primary);
}

/* --- c6, mission / vision / value ----------------------------------------- */
.section-about-pillars { padding: 0 var(--gutter); }

.section-about-pillars .container { padding: 10px 0; }

.pillars {
  display: flex;
  gap: 30px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The three stretch to the tallest of them. The reference pins all three to
   one height with ElementsKit's equal-height script and then clips the
   overflow -- the same mechanism that slices the privacy page's address-card
   icon in half. Stretching costs nothing and clips nothing. */
.pillar {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  gap: 30px;
  padding: 40px;
  border: 1px solid var(--c-divider);
  border-radius: 30px;
}

/* `relative` for the icon, which is absolutely placed over the photograph. */
.pillar__media {
  position: relative;
  border-radius: 20px;
  overflow: hidden;
}

/* `height: auto` IS NOT DECORATION AND `aspect-ratio` DOES NOTHING WITHOUT
   IT. Landmine 3 requires the width/height attributes stay on the <img>, and
   `height="2560"` is a presentational hint that computes to a real used
   height — so the ratio never binds and every image on this page rendered at
   its full 2560px, taking `about-pillars` to 2956 and `about-approach` to
   3259. Same shape as the preloader mark's `height: auto`. Set both.

   A RATIO, NOT A HEIGHT, and the reference proves it holds at every width:
   331.3/258.4 at 1440, 425/331.5 at 1024 and 328/255.8 at 390 are all
   1.2821. A fixed pixel height looks identical at 1440 and is wrong at both
   the others. The value is written as the reference's own authored
   `1 / 0.78` rather than as our derived decimal. */
.pillar__img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 0.78;
  object-fit: cover;
}

/* 20px in from the left and 20px up from the bottom of the PHOTOGRAPH, not of
   the card. Measured: icon at (131, 1359.4) against a media box at
   (111, 1161.0) 331.3 x 258.4. In the content flow instead it costs 60px per
   card and pushes every body down by the same amount. */
/* PURPLE, the user's call on review 2026-09-02. The reference renders these
   three white; the accent reads better against the photographs and matches
   the counters opposite. `currentColor` is what every path in the lifted
   artwork strokes with, so the token drives all three marks. */
.pillar__icon {
  position: absolute;
  left: 20px;
  bottom: 20px;
  display: block;
  color: var(--c-accent);
}

/* The third mark's viewBox is `0 0 60 60` where the other two are
   `0 0 40 40`, and all three render 40 x 40. Their artwork, lifted by parser
   and left alone -- normalising a viewBox silently resizes it. Landmine 2. */
.pillar__icon svg {
  display: block;
  width: 40px;
  height: 40px;
}

.pillar__title {
  margin: 0 0 20px;
  font-size: var(--fs-block-title);
  font-weight: 700;
  line-height: 26.4px;
  color: var(--c-primary);
}

.pillar__body {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

/* --- c7, the growth block -------------------------------------------------
   Two columns, 676 and 624 at 1440, centred against each other. */
.section-about-grow { padding: 0 var(--gutter); }

.about-grow {
  display: flex;
  align-items: center;
  padding: 80px 0;
}

.about-grow__main {
  display: flex;
  flex: 0 0 676px;
  flex-direction: column;
  gap: 40px;
  padding: 10px 110px 10px 10px;
}

.about-grow__head {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.about-grow__title {
  margin: 0;
  font-size: 35px;
  font-weight: 600;
  line-height: 42px;
  color: var(--c-primary);
}

.about-grow__lede {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

.about-grow__media {
  display: flex;
  align-items: center;
}

.about-grow__video {
  position: relative;
  flex: 0 0 278px;
  border-radius: 20px;
  overflow: hidden;
}

.about-grow__poster {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 0.71;
  object-fit: cover;
}

/* The 50px play disc that was absolutely centred over this still (both
   centred on 219, 2276.8) was removed on 2026-09-09 with the homepage hero's.
   It was a dead `#` control and this component is shared, so it had been
   shipping on `/about/` and the services hub. The still is unchanged: the
   disc was absolutely positioned, so removing it moved no box. */

.about-grow__review {
  display: flex;
  flex: 0 0 278px;
  flex-direction: column;
  gap: 25px;
  padding-left: 50px;
}

/* THE STAR ROW HAS TO BE SIZED HERE, and forgetting it is not a small
   miss: `Stars` emits five bare <li> holding an unsized <svg>, so without a
   rule the row rendered 1210.9px tall and took the whole review column with
   it. Measured on the reference: 15px marks on 4px gaps, the row 15 tall.
   The homepage's two uses of the same component (7px and 5.7px gaps) are why
   the component carries a class prop rather than styling itself. */
.about-grow__stars {
  display: flex;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.about-grow__stars li {
  display: flex;
  width: 15px;
  height: 15px;
}

.about-grow__stars svg {
  width: 15px;
  height: 15px;
}

.about-grow__count {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-primary);
}

.about-grow__avatars {
  display: flex;
  margin: 0 0 5px;
  padding: 0;
  list-style: none;
}

.about-grow__avatars img {
  width: 40px;
  height: 40px;
  border-radius: 50%;
}

/* The five overlap. Measured from their x positions -- 408, 448, 474, 500,
   526 -- which is one 40 followed by four 26s, so each after the first pulls
   back 14. */
.about-grow__avatars li + li { margin-left: -14px; }

/* A TEXT LINK, NOT THE SITE'S PILL. Measured at 228 x 22.4 on 16/700/22.4
   against `.btn`'s 50.4 — the reference's button widget with its padding and
   its icon disc styled off. `Btn` here would be 28px taller and would put a
   second pill in a column that has none. */
.about-grow__cta {
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: 22.4px;
  color: var(--c-primary);
  transition: color var(--dur) var(--ease);
}

.about-grow__cta:hover { color: var(--c-accent); }

/* The four counters: two columns of two, offset against each other by 60px.
   That offset is a PAIR OF MARGINS on the reference, not a gap, and cannot be
   expressed as one. */
.about-grow__facts {
  display: flex;
  flex: 0 0 624px;
  gap: 80px;
  padding: 10px;
}

.funfacts {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  gap: 80px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.funfacts--lead  { margin-bottom: 60px; }
.funfacts--trail { margin-top: 60px; }

.funfact {
  padding: 30px;
  border: 1px solid var(--c-divider);
  border-radius: 20px;
}

/* Cards 1 and 4 carry the background; 2 and 3 are transparent with the
   border. That alternation is measured off the reference, not invented. */
.funfact--framed {
  background: url("../img/about/funfact-bg.webp") center / cover no-repeat;
}

/* TOP-RIGHT, the user's call on review 2026-09-02 — the reference puts these
   four marks at the top LEFT. Flex rather than `text-align`, because the svg
   is a block and text-align would not move it. */
.funfact__icon {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 20px;
  color: var(--c-primary);
}

.funfact__icon svg { display: block; }

/* THE 30px UNDER THE ICON IS SPLIT 20 + 10 ON THE REFERENCE, and getting
   that wrong is what put `about-grow` 57.2 short at 1024 while it measured
   EXACT at 1440.

   Theirs is `margin-bottom: 20px` under the icon and `margin: 0 0 10px` under
   the number, at 1440, 1024 and 767 alike. This build had 30 under the icon
   and nothing under the number. The TOTAL matches at desktop -- which is
   precisely why the section was exact there and nobody looked further -- but
   the two are overridden at different breakpoints, so they come apart below
   1024: our icon margin steps to 20 at tablet and the missing 10 never
   arrives, leaving each card 10px short and the stacked column 20.

   The first attempt at this added the 10 and left the icon at 30, which fixed
   1024 and broke the exact 1440 by the same amount. Matching the split rather
   than the total is what holds at both. */
.funfact__value {
  margin: 0 0 10px;
  font-size: 50px;
  font-weight: 800;
  line-height: 60px;
  color: var(--c-accent);
}

.funfact__label {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-primary);
}

/* --- c13, the reviews block ------------------------------------------------
   THE REFERENCE CONFIGURES THIS SECTION DIFFERENTLY ON ITS TWO PAGES, and
   that is measured on their side rather than inferred from ours. Their own
   two instances, at 1440:

                     head row              body row
     homepage        234.4, ONE column     462.6, card + slider
     /about/         164.8, TWO columns    482.6

   On their homepage the intro and its button sit UNDER the heading; on their
   about page they sit BESIDE it, in a 533.2 column against the heading's
   746.6. That is worth 129.6px, and with the body's extra 20.6 of padding it
   accounts for the whole 108.9 our reused section was out by. It is also
   visible by eye, not just in the map, which is why it is reproduced rather
   than written off the way the ticker's +20 was.

   IT IS A MODIFIER AND THE DOM IS UNCHANGED. `--split` re-lays the SAME
   markup with grid, so `Testimonials.astro` emits byte for byte what it
   emitted before on the homepage, and the homepage can never receive this
   class. That is what keeps the 0.0px port gate intact through a layout
   change to a signed-off shared component. */
.section-testimonials--split .section-testimonials__head-main {
  display: grid;
  grid-template-columns: 746.6px 533.2px;
  align-items: start;
  width: 100%;
  padding-right: 0;
}

.section-testimonials--split .eyebrow {
  grid-column: 1;
  grid-row: 1;
}

.section-testimonials--split .section-testimonials__title {
  grid-column: 1;
  grid-row: 2;
  padding-right: 30px;
}

.section-testimonials--split .section-testimonials__lede {
  grid-column: 2;
  grid-row: 1 / span 2;
  width: 100%;
}

.section-testimonials--split .section-testimonials__body { padding: 20px; }

/* --- c9, the approach block ----------------------------------------------- */
.section-about-approach { padding: 0 var(--gutter); }

/* 90 AND 10, WHICH IS TWO OF THE REFERENCE'S BOXES COLLAPSED INTO ONE. It
   nests a container on `10px 0` inside a block on `80px 10px`; both land on
   the same element here, so the values are summed rather than written twice —
   writing both as separate rules on `.about-approach` silently drops the
   first, which cost exactly 160px of section height when it happened. */
.about-approach {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 90px 10px;
}

.about-approach__title {
  margin: 0;
  font-size: 35px;
  font-weight: 600;
  line-height: 42px;
  color: var(--c-primary);
}

.about-approach__head {
  display: flex;
  align-items: center;
  margin-bottom: 40px;
  padding: 10px;
}

.about-approach__lede {
  flex: 0 0 630px;
  margin: 0;
  padding-right: 30px;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

.about-approach__aside {
  display: flex;
  flex: 0 0 630px;
  flex-direction: column;
  gap: 30px;
}

.about-approach__note {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

/* 726 and 474, DELIBERATELY UNEQUAL, on a 60px gap. Not a 50/50 pair and not
   a fraction of one; the images inside differ in ratio to match. */
.about-approach__features {
  display: flex;
  gap: 60px;
  margin: 0;
  padding: 10px;
  list-style: none;
}

.feature-card {
  padding: 60px;
  border: 1px solid var(--c-divider);
  border-radius: 30px;
}

/* PROPORTIONS, NOT PIXELS: 726 and 474 of the 1200 left after the 60px gap,
   so 60.5% and 39.5%. Pinning the pixel widths looks right at the width they
   were measured at and OVERFLOWS below about 1000, where 515.2 + 30 + 418.8
   stops fitting a 708px row — which is what the overflow gate caught at 768.

   IT HAS TO BE A BASIS, NOT A GROW WEIGHT. `flex: 1.5316 1 0` is the obvious
   way to write a ratio and it is wrong here: with `flex-basis: 0` these cards
   still floor at their own 122px of padding and border, so the free space is
   956 rather than 1200 and the split comes out 700.4 / 499.6 instead of
   726 / 474. The 18.2px that put in the height map is the whole reason this
   is written as a calc. */
.feature-card--wide   { flex: 0 0 calc((100% - 60px) * 0.605); }
.feature-card--narrow { flex: 0 0 calc((100% - 60px) * 0.395); }

/* THE FRAME EXISTS FOR THE HOVER SWEEP, and it costs a number that used to
   come for free. `.image-anime` needs `overflow: hidden` to clip the band, and
   an inline-block with `overflow` other than visible takes its BOTTOM MARGIN
   EDGE as its baseline — which removes the 7.2px descent the bare
   inline-block image used to contribute.

   That 7.2 is real and measured: the reference's image-box wrapper reads
   375.6 for 368.4 of content and 379 for 371.8, the same 7.2 both times,
   which is what identifies it as a line box rather than a margin. So it is
   added back explicitly here, 30 authored + 7.2 recovered. Drop it and every
   feature card is 7.2 short. */
.feature-card__frame {
  display: block;
  margin-bottom: 37.2px;
  border-radius: 30px;
}

.feature-card__img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
}

/* THESE TWO RESTEP AT 991 AND AGAIN AT 767; the pillars' and the poster's
   do not. See responsive.css. */
.feature-card--wide .feature-card__img   { aspect-ratio: 1 / 0.455; }
.feature-card--narrow .feature-card__img { aspect-ratio: 1 / 0.713; }

.feature-card__title {
  margin: 0 0 10px;
  font-size: var(--fs-block-title);
  font-weight: 700;
  line-height: 26.4px;
  color: var(--c-primary);
}

.feature-card__body {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

/* --- c12, the closing argument -------------------------------------------- */
.section-about-best { padding: 0 var(--gutter); }

.about-best {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 80px 0;
}

.about-best__head {
  display: flex;
  align-items: center;
  margin-bottom: 40px;
  padding: 10px;
}

.about-best__title {
  flex: 0 0 637.4px;
  margin: 0;
  padding-right: 30px;
  font-size: var(--fs-display);
  font-weight: 300;
  line-height: 60px;
  color: var(--c-primary);
}

.about-best__title strong { font-weight: 700; }

.about-best__lede {
  flex: 1 1 auto;
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

.about-best__lede b {
  font-weight: 700;
  color: var(--c-primary);
}

.about-best__body {
  display: flex;
  align-items: center;
  padding: 10px;
}

.about-best__points {
  display: flex;
  flex: 0 0 640px;
  flex-direction: column;
  gap: 30px;
  margin: 0;
  padding-right: 60px;
  list-style: none;
}

/* A 20px radius and 25/30 of padding -- a TIGHTER card than the 30px-radius
   ones elsewhere on this page, and authored rather than a rounding. */
.about-best__point {
  padding: 25px 30px;
  border: 1px solid var(--c-divider);
  border-radius: 20px;
}

/* THEIR ICON-BOX CARRIES NO ICON. `.elementor-icon-box-icon` is null in all
   three, so the widget renders a title and a description and nothing else.
   Adding a mark here puts something on the page the reference does not have. */
.about-best__point-title {
  margin: 0 0 10px;
  font-size: var(--fs-block-title);
  font-weight: 700;
  line-height: 26.4px;
  color: var(--c-primary);
}

.about-best__point-body {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

/* `image-anime` in the markup; this is only the box it clips to. */
.about-best__media { flex: 0 0 640px; }

/* 10/9, and this one is load-bearing rather than tidy: the source file is
   1920 x 2560 PORTRAIT, so letting it run `height: auto` at a full-width
   column renders it 1333px tall and takes the section to 3144. The reference
   holds the ratio instead — 640 x 576 at 1440 and 984 x 885.6 at 1024. */
.about-best__img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 0.9;
  object-fit: cover;
}

/* ==========================================================================
   /digital-marketing-services/ -- the services hub
   Phase 3's fifth page. EIGHT sections against the reference's nine top-level
   containers; the one we do not render is their container 4, a second content
   ticker carrying `elementor-hidden-desktop`, `-tablet` AND `-mobile`, so it
   is hidden at every width rather than being a responsive twin. Measured 0.0
   at 1440, 1024, 991 and 767.

   MOST OF THIS PAGE IS NOT HERE, because the reference builds it out of its
   own blocks: `.service-item` is its homepage's card (section 10 above), and
   `.about-grow` and `.section-testimonials` are its /about/'s. Only the three
   blocks below are this page's own.

   Every number is measured off the reference at 1440. The percentages are
   AUTHORED, not derived from one pixel measurement: each was read back as a
   computed `width` at 1440 AND at 1024 and holds at both, which is what says
   it is a percentage rather than a coincidence.

     1024   headings -> 40/48, card titles 22 -> 20, card padding 40 -> 30,
            icons 60 -> 50, section bands 80 -> 40, every column stacks
      767   headings -> 30/36, card titles -> 18, card padding -> 20, and the
            grid's closing line 16 -> 12, one of the few places on this site
            where body copy gets SMALLER at mobile
      991   why-choose's two images restep their ASPECT RATIO, and nothing
            else on the page moves there. `delta.py` is blind to it.
   ========================================================================== */

/* --- c6, the services grid ------------------------------------------------
   `.service-grid` and `.service-item` are section 10's, reused whole. Only
   the head row and the card's two local differences are here.

   The container's 20px gap plus its 10px vertical padding, the head's 40px
   bottom margin and the grid itself are the section's 1975.7. */
.section-services-grid { padding: 0 var(--gutter); }

.section-services-grid .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

.services-grid__head {
  display: flex;
  flex-wrap: wrap;
  padding: 10px;
  margin-bottom: 40px;
}

/* ONE column, not the homepage head's 58.33/41.66 split: there is no aside
   copy and no CTA up here. 57.43% of the row's 1300, giving 746.6. */
.services-grid__head-text {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 57.43%;
  padding-right: 30px;
}

.services-grid__title {
  font-size: 29px;
  font-weight: 600;
  line-height: 34.8px;
  color: var(--c-primary);
}

/* The card is section 10's with its head emptied -- the 60px icon sits alone
   where the homepage also hangs a corner arrow -- and a small pill added
   underneath. The head's 80px bottom margin is unchanged, so the gap from
   icon to title is still 100px once the card's own 20px flex gap counts. */
.service-item--stacked .service-item__head { justify-content: flex-start; }

.service-item--stacked .service-item__body {
  display: flex;
  flex-direction: column;
}

/* 133.2 x 50.4 measured, which `--btn-pad` (14px 25px) on `--fs-accent`
   (16/700 at 1.4) already gives. `Btn` renders it with `icon={false}`; this
   rule is only the space above it. */
.service-item__cta {
  margin-top: 20px;
  align-self: flex-start;
}

/* --- c8, the fun-facts, REUSED from /about/ -------------------------------
   `AboutGrow` renders it. This instance's heading is 27/600/32.4 against
   about's larger one, and it carries an eyebrow that about's does not. Both
   are props; only the type needs a rule. */
.section-about-grow--compact .about-grow__title {
  font-size: 27px;
  line-height: 32.4px;
}


/* ==========================================================================
   The SERVICES HUB, REBUILT 2026-09-02.

   The reference replaced this page's middle with nine pasted Elementor `html`
   widgets, each carrying its own stylesheet, its own Google Fonts import and
   a cyan accent in one of five foreign class namespaces. The user's ruling
   was to clone the STRUCTURE and build it here, so every rule below is this
   build's own: `--c-accent`, `--c-text`, the local font stack and the four
   linked stylesheets' cascade. Nothing is copied from theirs.

   TWO BLOCKS ABOVE THIS ONE SURVIVED THE REBUILD and are deliberately left
   where they are rather than folded in here: `.section-services-grid` (their
   container 8, still the homepage's card skin) and
   `.section-about-grow--compact` (their 17). The rules below run in their
   containers' order, 5 to 16, starting after the grid.

   THE BAND IS 70px, HALVING TO 40 AT 1024 AND 30 AT 767. Twelve sections at
   the site's usual 80 would put 160px between every pair and make the page
   read as a stack of unrelated slabs. It is stated once here and overridden
   once per breakpoint rather than per section.
   ========================================================================== */

.section-services-intro,
.section-services-what,
.section-services-list,
.section-services-local,
.section-services-process,
.section-services-why,
.section-services-results,
.section-services-industries,
.section-services-cost,
.section-services-faq-head,
.section-services-faq {
  padding: 70px var(--gutter);
}

/* The heading scale shared by every rebuilt section. 35/600/42 is the figure
   the deleted lede block carried, which is this page's own authored size for
   a content heading below the banner -- reused so the rebuild does not
   introduce a second scale alongside it. */
.services-intro__title,
.services-what__title,
.services-list__title,
.services-local__title,
.services-process__title,
.services-why__title,
.services-results__title,
.services-industries__title,
.services-cost__title,
.services-faq-head__title {
  font-size: 35px;
  font-weight: 600;
  line-height: 42px;
  color: var(--c-primary);
}

/* Body copy, likewise once rather than eleven times. */
.section-services-intro p,
.section-services-what p,
.section-services-list p,
.section-services-local p,
.section-services-process p,
.section-services-why p,
.section-services-results p,
.section-services-industries p,
.section-services-cost p {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

/* THE CARD SKIN, stated once and used by five sections. It is the figure the
   deleted `.services-why__card` carried and the same one /about/'s closing
   points use -- 25px 30px on a 1px divider at a 20px radius. Kept as one
   selector list so the five cannot drift apart. */
.services-intro__panel,
.services-what__card,
.services-local__card,
.services-cost__factor {
  padding: 25px 30px;
  border: 1px solid var(--c-divider);
  border-radius: 20px;
}

/* --- c5, the intro --------------------------------------------------------
   Heading, lede, then two columns: the body copy against a bordered panel.
   `flex-wrap: wrap` with a ZERO flex-basis, not percentages -- the
   service-detail page's lesson, where percentages that summed at one gap
   stopped summing at another and cost 28.6px on the column. */
/* NO EYEBROW ANYWHERE ON THIS PAGE. The user asked on review 2026-09-04 for
   no section sub-headings on the hub, and seven came out: this one, the
   grid's, why-choose's, the FAQ head's and the three the rebuilt sections
   would otherwise have gained. The `.eyebrow` rule itself is untouched --
   five other pages still use it.

   THE HEADING IS THE PAGE'S LARGEST piece of content type, which is what the
   reference does here and what this build had not reproduced: it was set at
   the same 35px as every other section title. 46/600 on a 1.12 leading,
   capped at 20ch so it breaks into the two lines the screenshot shows rather
   than running out to the container. */
/* NO `!important` HERE, and that is deliberate. The shared heading list
   above sets 35px at the same specificity, so this later rule wins on order
   alone -- while `responsive.css`, which loads after this file, is still free
   to take it to 40/48 at 1024 and 30/36 at 767. An `!important` would have
   pinned 46px at every width and silently defeated both breakpoints. */
.services-intro__title {
  max-width: 20ch;
  font-size: 46px;
  line-height: 1.12;
}

/* WEIGHT, NOT COLOUR. The reference accents the second half of this heading
   in its cyan, and the first attempt here reproduced that with `--c-accent`
   -- which reads at 1440 against the dark top of the gradient and very nearly
   disappears at 1024, where the same band is bright purple. This site's own
   convention for a heading span is a weight step and nothing else, in all
   three places it already does it: `.section-seo__title span`,
   `.section-social__title span` and `.founder__title span` are each
   `font-weight: 700` with no colour. Followed here rather than invented.

   The span is in the data rather than a separate field, so the split point is
   editorial and moves with the copy at the rebrand. */
.services-intro__title span {
  font-weight: 800;
  /* `--c-link`, THIS SITE'S OWN CYAN, not the reference's. It is already in
     `tokens.css` as the colour for inline links in body copy, so it is not a
     new value -- and it is the one accent on this palette with real contrast
     against BOTH ends of the page gradient, which `--c-accent` purple does
     not have on the bright purple band at 1024. Raised with the user. */
  color: var(--c-link);
}

/* The CTA row that closes the block. A full-width divider, the solid pill and
   a short line beside it. */
.services-intro__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 26px;
  margin-top: 46px;
  padding-top: 34px;
  border-top: 1px solid var(--c-divider);
}

.services-intro__cta-note {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .06em;
  color: rgba(245, 245, 244, .55);
}

.services-intro__lede {
  max-width: 62ch;
  margin-top: 22px;
  font-size: 18px !important;
  line-height: 1.75 !important;
}

.services-intro__cols {
  display: flex;
  flex-wrap: wrap;
  gap: 40px;
  align-items: flex-start;
  margin-top: 40px;
}

.services-intro__body { flex: 1.35 1 0; }
.services-intro__body p + p { margin-top: 25.6px; }
.services-intro__panel { flex: 1 1 0; }

/* The struck-through chips. `text-decoration` rather than the reference's
   absolutely positioned rule, because a real strike follows the text when it
   wraps and their pseudo-element does not. */
.services-intro__tried {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 22px;
  padding: 0;
}

.services-intro__tried li {
  padding: 8px 16px;
  border: 1px solid var(--c-divider);
  border-radius: 999px;
  font-size: 13px;
  line-height: 1.4;
  color: rgba(245, 245, 244, .6);
  text-decoration: line-through;
}

.services-intro__panel-title {
  margin-bottom: 18px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-primary);
}

.services-intro__steps {
  display: grid;
  gap: 16px;
  margin: 0;
  padding: 0;
}

.services-intro__steps li {
  display: grid;
  grid-template-columns: 30px 1fr;
  gap: 12px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--c-text);
}

.services-intro__steps b {
  font-weight: 700;
  color: var(--c-accent);
}

.services-intro__stat {
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px solid var(--c-divider);
  font-size: 13.5px !important;
  color: rgba(245, 245, 244, .6) !important;
}

.services-intro__stat b {
  display: block;
  margin-bottom: 4px;
  font-size: 22px;
  color: var(--c-primary);
}

/* The reserved state, while no verified figure exists. Smaller than the real
   22px so the marker cannot overflow the panel, and muted so it never reads as
   the statistic itself. Hub only; nothing on the homepage uses this class, so
   it is outside the port gate. */
.services-intro__stat--reserved b {
  font-size: 15px;
  color: rgba(245, 245, 244, .45);
}

/* --- c6, what the services are --------------------------------------------
   Text column against one image. ONE image, where the block this page used to
   carry had two -- see the note in ServicesWhy.astro about the 991 that went
   with them. */
.services-what {
  display: flex;
  flex-wrap: wrap;
  gap: 40px;
  align-items: flex-start;
}

.services-what__text { flex: 1.4 1 0; }
.services-what__media { flex: 1 1 0; }

.services-what__lede { margin-top: 22px; }

.services-what__cards {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  margin: 30px 0;
}

.services-what__card { flex: 1 1 0; }

.services-what__card b {
  display: block;
  margin-bottom: 8px;
  font-weight: 700;
  color: var(--c-primary);
}

.services-what__media img {
  width: 100%;
  height: auto;
  border-radius: 20px;
  object-fit: cover;
}

/* --- c7, the editorial list -----------------------------------------------
   Seven numbered rows. The divider is a bottom border on each row with the
   LAST one suppressed, rather than a top border on all but the first: the
   section ends on the list, and a trailing rule under it reads as a mistake.
   That is the same call the service-detail sidebar's item rule makes. */
/* THE ONLY SECTION ON THE PAGE THAT TAKES ITSELF OFF THE GRADIENT. Filled
   black at container width, with the page's purple showing either side, which
   is what makes the list read as a table rather than as more prose. */
.services-list__panel {
  padding: 60px 50px;
  border-radius: 20px;
  background: var(--c-black);
}

.services-list__lede {
  max-width: 72ch;
  margin-top: 22px;
}

.services-list__rows {
  margin: 40px 0 0;
  padding: 0;
}

.services-list__row {
  display: grid;
  /* The name column is CAPPED rather than fractional. At `1fr` against the
     detail's `1.4fr` a 26-character name left 230px of empty row before the
     detail began, which reads as a broken column rather than as spacing. */
  grid-template-columns: 56px minmax(0, 300px) minmax(0, 1fr);
  gap: 20px 30px;
  align-items: baseline;
  position: relative;
  padding: 22px 0 22px 24px;
  border-bottom: 1px solid var(--c-divider);
}

.services-list__row:last-child { border-bottom: 0; }

/* THE HOVER BAR IS AN OPACITY FADE ON A BAR THAT IS ALWAYS THERE, not a
   border that appears: a border arriving on hover changes the row's box and
   shifts every cell in it by its width. The row's 24px left padding is what
   the bar sits in, so nothing moves at all. */
.services-list__row::before {
  content: "";
  position: absolute;
  top: 14px;
  bottom: 14px;
  left: 0;
  width: 3px;
  border-radius: 3px;
  background: var(--c-accent);
  opacity: 0;
  transition: opacity var(--dur-fast, .3s) ease-in-out;
}

.services-list__row:hover::before,
.services-list__row:focus-within::before { opacity: 1; }

.services-list__row:hover .services-list__name,
.services-list__row:focus-within .services-list__name { color: var(--c-accent); }

.services-list__idx {
  font-size: 14px;
  font-weight: 700;
  color: var(--c-accent);
}

.services-list__name {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.4;
  color: var(--c-primary);
  transition: color var(--dur-fast, .3s) ease-in-out;
}

.services-list__tag {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(245, 245, 244, .5);
}

.services-list__detail {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

/* --- c9, the local block --------------------------------------------------
   Three paragraphs and a contact card against two stacked images. The card
   reads from `site.ts`, so the rebrand changes it in one place. */
.services-local {
  display: flex;
  flex-wrap: wrap;
  gap: 40px;
  align-items: flex-start;
}

.services-local__text { flex: 1.3 1 0; }
.services-local__media { flex: 1 1 0; }

.services-local__title { margin-bottom: 22px; }
.services-local__text p + p { margin-top: 25.6px; }

.services-local__card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 30px;
  font-style: normal;
}

.services-local__card b {
  margin-bottom: 6px;
  font-size: 17px;
  color: var(--c-primary);
}

.services-local__card span,
.services-local__card a {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

.services-local__card a { transition: color .3s ease-in-out; }

.services-local__card a:hover,
.services-local__card a:focus-visible { color: var(--c-accent); }

.services-local__media {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.services-local__figure img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 0.72;
  border-radius: 20px;
  object-fit: cover;
}

/* --- c10, the five-step process -------------------------------------------
   ONE RAIL, drawn as a `::before` on the list rather than a border on each
   step: a per-item border shows a seam at every gap, and the rail has to stop
   at the last marker instead of running to the bottom of the last item.

   The marker is a `::before` on the step, positioned on the rail. It carries
   the page background as its own so the rail does not show through it. */
/* CENTRED HEAD, and the rail block centred under it. */
.services-process__head {
  max-width: 720px;
  margin-inline: auto;
  text-align: center;
}

.services-process__lede {
  max-width: 62ch;
  margin: 22px auto 0;
}

.services-process__steps {
  position: relative;
  max-width: 900px;
  margin: 46px auto 0;
  padding: 0;
}

.services-process__steps::before {
  content: "";
  position: absolute;
  top: 8px;
  bottom: 0;
  left: 139px;
  width: 1px;
  background: var(--c-divider);
}

.services-process__step {
  position: relative;
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr);
  gap: 40px;
  padding-bottom: 34px;
}

.services-process__step:last-child { padding-bottom: 0; }

/* A HOLLOW RING THAT FILLS ON HOVER, and the hover is on the STEP rather
   than on the marker: an 11px dot is too small a target to ask anyone to
   find. The fill is `--c-accent`, not the reference's cyan.

   The ring is OPAQUE so the rail does not show through it -- `--c-secondary`
   reads as a dark centre against both ends of the page gradient. The centre
   is `left + 5.5` under `box-sizing: border-box`, so it and the rail are
   positioned from the same number and cannot drift apart. */
.services-process__step::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 134px;
  z-index: 1;
  width: 11px;
  height: 11px;
  border: 1.5px solid rgba(255, 255, 255, .35);
  border-radius: 50%;
  background: var(--c-secondary);
  transition: background var(--dur-fast, .3s) ease-in-out,
              border-color var(--dur-fast, .3s) ease-in-out;
}

.services-process__step:hover::before,
.services-process__step:focus-within::before {
  border-color: var(--c-accent);
  background: var(--c-accent);
}

.services-process__when {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: rgba(245, 245, 244, .5);
}

/* THE MEASURE IS CAPPED, and it has to be capped HERE rather than on the
   section: the step's body sits in a 1060px grid cell, so an uncapped
   paragraph runs to about 150 characters a line. Every other prose block on
   this page carries its own `max-width` in ch for the same reason. */
.services-process__body p { max-width: 78ch; }

.services-process__step-title {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 10px;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--c-primary);
}

.services-process__idx {
  font-size: 14px;
  font-weight: 700;
  color: var(--c-accent);
}

/* --- c11, why choose us ---------------------------------------------------
   REPLACED, not edited: their rebuild deleted the two-column block this
   page used to clone. Six bolded points against one image. */
.services-why {
  display: flex;
  flex-wrap: wrap;
  gap: 40px;
  align-items: flex-start;
}

.services-why__text { flex: 1.4 1 0; }
.services-why__media { flex: 1 1 0; }

.services-why__title { margin-top: 14px; }

.services-why__points {
  margin: 30px 0;
  padding: 0;
  display: grid;
  gap: 18px;
}

.services-why__points li {
  padding-left: 20px;
  position: relative;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

/* The reset sets `list-style: none` on every list, so a marker has to be put
   BACK per use. Six defects in phase 3 came from missing that; this is a
   deliberate replacement rather than an inherited bullet. */
.services-why__points li::before {
  content: "";
  position: absolute;
  top: 11px;
  left: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--c-accent);
}

.services-why__points b {
  color: var(--c-primary);
  font-weight: 700;
}

.services-why__media img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1.1;
  border-radius: 20px;
  object-fit: cover;
}

/* --- c12, the case study --------------------------------------------------
   The tallest container the reference added. A bordered summary card over
   five prose blocks.

   THE NOTE IS NOT DECORATION. The figures are REAL as of 2026-09-08 (Soul
   Imaging's two dashboards) and the note now carries the caveat that matters
   instead: the two screens cover one month against four. See the component. */
/* CENTRED HEAD. */
.services-results__head {
  max-width: 820px;
  margin: 0 auto 44px;
  text-align: center;
}

.services-results__lede {
  max-width: 78ch;
  margin: 22px auto 0;
}

.services-results__case-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--c-divider);
}

.services-results__case-title {
  font-size: 22px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--c-primary);
}

.services-results__period {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(245, 245, 244, .5);
}

/* THE NOTE IS NOT DECORATION. The figures below are placeholder and this says
   so on the page, because an invented client result reads as a claim rather
   than as unfinished copy. It goes when a real account replaces them. */
.services-results__note {
  display: inline-block;
  margin-top: 18px;
  padding: 5px 12px;
  border: 1px solid var(--c-accent);
  border-radius: 999px;
  font-size: 12px !important;
  line-height: 1.4 !important;
  color: var(--c-accent) !important;
}

/* --- the 1080 centred column ---------------------------------------------
   THE REFERENCE DOES NOT RUN THIS SECTION AT THE FULL CONTAINER WIDTH, and
   that is what made ours read as off-centre. Measured on their live page:
   `.tzr` is 1300 like our container, but `.tzr__wrap` inside it is
   `max-width: 1080px` centred, its two panels are 523 each with a 34px arrow
   column between them, and its prose blocks are inset AGAIN to 740 and
   centred within the 1080.

   Ours had the case head, note, frames and stats at the full 1300 with the
   prose left-aligned inside `max-width: 82ch`, so the right-hand side of the
   prose column was empty and the whole block sat left. This reproduces their
   two insets.

   It binds only above about 1100px. At 1024 the container is already 1004, so
   this max-width never applies and no responsive rule is needed for it. */
.services-results__wrap {
  max-width: 1080px;
  margin-inline: auto;
}

/* --- the two image frames -------------------------------------------------
   THE IMAGES ARE SUPPLIED AS OF 2026-09-08, Soul Imaging's own two
   dashboards. The empty-slot path still exists and still holds a 16:9 box, so
   a frame whose `src` is pulled degrades to a label rather than a gap.

   THE BEFORE FILE IS PADDED TO 16:9 RATHER THAN CROPPED TO IT. It arrived
   3:2, and `object-fit: cover` below would otherwise have eaten 66 rows top
   and bottom, which is exactly where its date pill and bottom panels live.
   The after file is natively 16:9 and is padded not at all. Do not "fix"
   either by changing the aspect-ratio here.

   THE SLOT AND THE IMAGE ARE THE SAME BOX, which is what makes that true
   rather than merely intended: both carry `aspect-ratio: 16 / 9` and a 1px
   border under the global `box-sizing: border-box`, so only the border style
   and the fill differ. Verified by measuring the section with a stand-in
   image at a real dashboard ratio: 0.0px at four widths. */
.services-results__frames {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-top: 30px;
}

.services-results__frame {
  flex: 1 1 0;
  margin: 0;
  padding: 20px;
  border: 1px solid var(--c-divider);
  border-radius: 20px;
}

.services-results__frame-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 14px;
}

.services-results__frame-tag,
.services-results__frame-when {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}

/* BEFORE IS MUTED, AFTER IS THE ACCENT, AND BOTH HALVES OF THE LABEL AGREE.
   The reference does exactly this and the pairing is the point: on their
   `tzr__panel` the tag AND the date both read `rgb(138,143,163)`, and on
   `tzr__panel--after` the tag AND the date both read their cyan
   `rgb(34,191,221)`, with the panel border going accent to match. Ours had it
   split down the middle instead: BOTH tags accent and BOTH dates muted, so
   "Before" was highlighted and the "After" date was not.

   The colours here are ours, not theirs. Only the structure is copied. */
.services-results__frame-tag,
.services-results__frame-when { color: rgba(245, 245, 244, .5); }

.services-results__frame.is-after .services-results__frame-tag,
.services-results__frame.is-after .services-results__frame-when {
  color: var(--c-accent);
}

/* The after panel is ringed in the accent, as theirs is. `--c-accent` is a
   hex so the alpha is written out; keep the two in step if the token moves. */
.services-results__frame.is-after { border-color: rgba(108, 68, 176, .45); }

/* ONE BOX, RENDERED WHETHER OR NOT AN IMAGE EXISTS. The image fills it from
   out of flow, so the slot's own size is all that ever sets the geometry. */
.services-results__frame-slot {
  position: relative;
  overflow: hidden;          /* clips the image to the rounded corners */
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  border: 1px dashed var(--c-divider);
  border-radius: 12px;
  background: rgba(255, 255, 255, .015);
  font-size: 13px;
  color: rgba(245, 245, 244, .35);
}

/* Filled: the dashes and the tint were saying "nothing here yet". */
.services-results__frame-slot.is-filled {
  border-style: solid;
  background: none;
}

/* `position: absolute` IS LOAD-BEARING AND IS NOT A STYLING CHOICE.

   An in-flow `<img>` contributes its INTRINSIC width to content-based
   sizing. At 390 `.services-results__frames` is a COLUMN that still carries
   `align-items: center`, so each frame is sized to its content rather than
   stretched -- and a 1200px-wide screenshot therefore widened both frames to
   the container and grew the section 16.1px. Out of flow, the image
   contributes nothing and the box is identical with or without it. Measured
   at four widths, before and after, with stand-ins at 3:4 and 16:10.

   It also settles the `/blog/` trap for free: an `<img>`'s `height` ATTRIBUTE
   maps to a presentational height that beats `aspect-ratio` outright, worth
   +147.5px at 1440 there. `height: 100%` here outranks it.

   `cover` CROPS, deliberately: a dashboard screenshot is rarely 16:9, and
   letterboxing would float the image inside a box that no longer matches the
   frame it replaced. Frame the screenshots knowing the top and bottom go. */
.services-results__frame-img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.services-results__frame figcaption {
  margin-top: 14px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--c-text);
}

.services-results__arrow {
  flex: 0 0 auto;
  font-size: 22px;
  color: var(--c-accent);
}

/* --- the stat strip -------------------------------------------------------
   One bordered rail of four cells. The divider is a right border with the
   LAST cell's suppressed, rather than a left border on all but the first:
   the strip is read left to right and the trailing rule is the one that
   looks like a mistake. */
.services-results__stats {
  display: flex;
  flex-wrap: wrap;
  margin: 24px 0 0;
  border: 1px solid var(--c-divider);
  border-radius: 20px;
  overflow: hidden;
}

.services-results__stat {
  flex: 1 1 0;
  padding: 22px 26px;
  border-right: 1px solid var(--c-divider);
}

.services-results__stat:last-child { border-right: 0; }

.services-results__stat dt {
  margin-bottom: 8px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(245, 245, 244, .5);
}

.services-results__stat dd { margin: 0; }

.services-results__stat dd b {
  display: block;
  font-size: 28px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-primary);
}

.services-results__stat dd span {
  display: block;
  margin-top: 4px;
  font-size: 13px;
  color: rgba(245, 245, 244, .45);
}

.services-results__blocks {
  display: grid;
  gap: 34px;
  /* 740 CENTRED, WHICH IS THE REFERENCE'S SECOND INSET (their 740 inside the
     same 1080). This replaced `max-width: 82ch`, which measured 960.4px here
     -- about 110 characters a line, far past a readable measure, and nothing
     like their column. `ch` is the width of a "0" and in Plus Jakarta Sans it
     is 11.7px, so 82ch was never the ~730 it looks like. Measure in px.

     IT COSTS +163.2px OF SECTION HEIGHT, measured at 1440 (2078.8 -> 2242.0),
     because a narrower column wraps to more lines. That is a deliberate trade
     for matching their layout; the screenshots themselves got 61.9px SHORTER
     in the same pass. The nine paragraphs were budgeted against the
     reference's own character counts, and those counts were authored for a
     740 column, so this also brings our wrapping closer to theirs. */
  max-width: 740px;
  margin: 44px auto 0;
}

.services-results__block-title {
  margin-bottom: 10px;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--c-primary);
}

.services-results__block p + p { margin-top: 20px; }

/* --- c13, industries ------------------------------------------------------
   Eight chips. THE MARKER IS SUPPRESSED ON PURPOSE here, unlike
   `.services-why__points` above: the reset's `list-style: none` is what is
   wanted, so nothing is put back. Stated so the next reader knows which of
   the two cases this is. */
.services-industries {
  display: flex;
  flex-wrap: wrap;
  gap: 60px;
  align-items: flex-start;
}

.services-industries__text { flex: 1 1 0; }

.services-industries__body { margin-top: 22px; }

.services-industries__body p + p { margin-top: 20px; }

.services-industries__body b { color: var(--c-primary); }

/* TWO COLUMNS OF FOUR, each entry on a hairline, hovering purple like the
   editorial list above. It was eight pills under the copy before the review
   of 2026-09-04; the reference sets them beside it. */
.services-industries__list {
  flex: 1.1 1 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 40px;
  margin: 0;
  padding: 0;
}

.services-industries__list li {
  padding: 16px 0;
  border-bottom: 1px solid var(--c-divider);
  font-size: 15px;
  line-height: 1.5;
  color: var(--c-text);
  transition: color var(--dur-fast, .3s) ease-in-out;
}

.services-industries__list li:hover { color: var(--c-accent); }

/* --- c14, the cost explainer ----------------------------------------------
   Six factors, two across. IT PUBLISHES NO PRICES: see the component. */
.services-cost__body {
  max-width: 78ch;
  margin-top: 22px;
}

.services-cost__body p + p { margin-top: 20px; }

.services-cost__factors {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
  margin-top: 40px;
}

.services-cost__factor-title {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 10px;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--c-primary);
}

.services-cost__idx {
  font-size: 14px;
  font-weight: 700;
  color: var(--c-accent);
}

/* --- c15 and c16, the FAQ -------------------------------------------------
   The heading is its own top-level container on the reference, so it is its
   own section here. The accordion reuses /faqs/'s `.faq--outline` skin whole
   and adds no rule of its own. */
.section-services-faq-head { padding-bottom: 0; }

.services-faq-head__title { margin-top: 14px; }

.services-faq-head__title span { font-weight: 700; }

/* ==========================================================================
   The SERVICE-DETAIL template. Phase 3's sixth page, cloned against the
   reference's /digital-marketing-services/website-development/ and rendered
   by this build at the flat /website-development/.

   ONE SECTION FOR THEIR CONTAINER 4. Their other three are the banner, the
   ticker and an all-widths-hidden second ticker that measures 0.0 everywhere,
   so secheights.py pairs three against three with no --refskip.

   EVERY NUMBER HERE IS MEASURED off tanzilite at 1440 with tools/js/sd-*.js;
   the tablet and mobile steps come from `delta.py 1440 1024` and
   `delta.py 768 767` on the same page, plus a DIRECT aspect-ratio sweep,
   because that property is invisible to delta.py and this page has three
   distinct ratios, two of which restep at 991.
   ========================================================================== */

.section-service-detail { padding: 0 var(--gutter); }

/* THE FIRST TWO-COLUMN INNER PAGE IN THIS BUILD. 871 against 429 in a 1300
   row whose left and right padding are both 0 -- so its border box and
   content box coincide and the split is a clean 67 / 33. Divided from the
   PIXEL widths rather than copied from their reported percentage: Elementor
   resolves a column percentage against the row's BORDER box, and the hub paid
   ~9px a column for transcribing one verbatim.

   `flex-wrap: wrap` at every width, which is why the restack at 1024 is a
   change of WIDTH and not of direction -- and delta.py excludes `width`
   outright, so the sweep cannot see that one either. */
.service-detail {
  display: flex;
  flex-wrap: wrap;
  padding: 160px 0 80px;
}

.service-detail__main {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 67%;
  padding: 10px 50px 10px 10px;
}

.service-detail__side {
  width: 33%;
  padding: 10px;
}

/* --- The blocks ----------------------------------------------------------
   Four gaps, not one: 20 between a heading and its paragraph, 40 between the
   feature image and the lede, 40 across the list row, and 40 between the FAQ
   heading and its accordion. The two blocks that carry a 20px bottom margin
   are the ones whose measured spacing to the next block is 40 where every
   other pair is the column's own 20. */
.service-block {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.service-block--feature { gap: 40px; }
.service-block--faq { gap: 40px; }

.service-block--entry {
  flex-direction: row;
  align-items: center;
  gap: 40px;
  margin-bottom: 20px;
}

.service-detail__title {
  margin: 0;
  font-size: 50px;
  font-weight: 300;
  line-height: 60px;
  color: var(--c-white);
}

.service-detail__intro,
.service-lede {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  color: var(--c-text);
}

/* --- The two standalone images -------------------------------------------
   THE RADIUS IS ON THE WRAPPER, NOT THE IMAGE. Measured: every img here
   reports `border-radius: 0` and its `.elementor-widget-container` reports
   30px with `overflow: hidden`.

   `height: auto` BESIDE `aspect-ratio` IS MANDATORY on all three. Landmine 3
   keeps the width/height attributes on every img, and a `height` attribute
   computes to a real used height, so the ratio never binds and the image
   renders at full intrinsic size. Fourth page to write this down. */
.service-feature,
.service-entry {
  border-radius: var(--card-radius);
  overflow: hidden;
}

.service-feature img,
.service-entry img,
.service-process__figure img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
}

.service-feature img { aspect-ratio: 1 / 0.67; }
.service-entry img { aspect-ratio: 1 / 0.89; }

/* --- The included list ---------------------------------------------------
   A LIST, NOT A PARAGRAPH. The hub paid 70.4px for that assumption on a block
   of this exact shape, and a paragraph of about the right length looks
   entirely plausible beside their screenshot.

   THE SPLIT IS PROPORTIONAL, NOT A PAIR OF PERCENTAGES, and that distinction
   is worth 14.3px at 1024. Percentages of the row only add up while the gap
   happens to be the width it is at 1440: 52.53 + 42.53 leaves exactly the
   4.93% that 40px is of 811, and at 1024 the same gap is 20px -- 2.03% -- so
   the pair falls 28.6px short of filling the row. Growing them from a zero
   basis makes the ratio hold at every width instead.

   55.2 : 44.8 is their measured ratio, read in PIXELS at three widths and
   averaged: 426 / 344.9 at 1440, 531.2 / 432.8 at 1024, 390.4 / 317.6 at 768.
   It drifts by about a tenth of a percent across those, so one ratio lands
   every width within a pixel. */
.service-included {
  flex: 55.2 1 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.service-included li {
  display: flex;
  align-items: center;
  padding-bottom: 12px;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  color: var(--c-text);
}

.service-included li + li { margin-top: 12px; }
.service-included li:last-child { padding-bottom: 0; }

/* The marker is an ICON, so base.css stripping `list-style` costs nothing
   here -- but a marker still has to be put back per use, which is the third
   defect of that shape this phase has produced. 22.5 is the glyph's own
   advance at 18px and it is what puts the label at x102.5. */
.service-included__mark {
  flex: 0 0 22.5px;
  display: flex;
  align-items: center;
  color: var(--c-accent);
}

.service-included__mark svg {
  height: 18px;
  width: auto;
}

.service-entry { flex: 44.8 1 0; }

/* --- The process grid ----------------------------------------------------
   Six steps, each with its own image, in a two-across zigzag: the step card
   leads rows 1, 3 and 5 and the image leads rows 2, 4 and 6.

   THE REFERENCE SHIPS THIS TWICE -- block 5 `elementor-hidden-mobile` and
   block 6 `elementor-hidden-desktop` -- the duplicate-widget trap in its
   canonical form. We build ONE grid, authored in the MOBILE reading order so
   the stacked view needs no rules at all, with `order` producing the desktop
   zigzag above 767. Authoring it the other way round leaves the stacked view
   reading step, image, image, step, which is precisely what their second
   block exists to prevent.

   The row height is set by the IMAGE cells: 390.5 on `1 / 0.9` is 351.4,
   against the step cards' own 312.8 of content, so each card stretches and
   carries 38.6 of slack at the bottom. That is `align-items: stretch` doing
   the work, not a height. */
.service-process {
  display: flex;
  flex-wrap: wrap;
  gap: 30px;
  margin-bottom: 20px;
}

.service-process__cell { flex: 0 0 calc(50% - 15px); }

/* THE STEP CARD IS THE HOMEPAGE'S `.service-item` SKIN, and the reference
   makes that call rather than this build: same blob SVG bled off the top
   left, same blur(100px) frost, same rgb(27,27,27) tint at .4 -- which is
   `--c-secondary`, measured on their `::before` -- and the same 30px radius.
   Reproduced here rather than shared by selector because the two differ in
   their border and in the tint's inset, and `.service-item` is signed-off
   homepage CSS that a shared selector would put at risk.

   THE BORDER IS .8px, NOT 1px. Third fractional border in this build after
   `.faq__toggle` and the sidebar's two dividers; at 1px, twelve cards grow
   the grid. Do not let a formatter round it. */
.service-process__step {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 70px;
  box-sizing: border-box;
  padding: var(--card-pad);
  border: .8px solid var(--c-divider);
  border-radius: var(--card-radius);
  background-image: url("../img/service-card-bg.svg");
  background-position: left top;
  background-repeat: no-repeat;
  backdrop-filter: blur(100px);
  -webkit-backdrop-filter: blur(100px);
}

/* The flat tint between the blob and the content, exactly as `.service-item`
   does it. `inset: -1px` on the reference, so the tint covers the border
   rather than stopping inside it. */
.service-process__step::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  background: var(--c-secondary);
  opacity: .4;
  pointer-events: none;
}

.service-process__step > * { position: relative; z-index: 1; }

.service-process__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

/* THE ACCENT, NOT WHITE. Measured rgb(125, 62, 163) on their `<path>`'s
   stroke, which is `--c-accent` exactly, so it follows the palette at the
   rebrand. White was an assumption here and it read as a different card. */
.service-process__icon {
  display: block;
  color: var(--c-accent);
}

.service-process__icon svg {
  height: var(--card-icon);
  width: auto;
}

/* STEP over its number, at the end of the head row. The label is white and
   the NUMBER is the accent purple -- measured rgb(125, 62, 163), which is
   --c-accent exactly, so it follows the palette at the rebrand rather than
   needing a second edit. */
.service-process__n {
  margin: 0;
  text-align: center;
}

.service-process__n-label,
.service-process__n-value {
  display: block;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
}

.service-process__n-label {
  color: var(--c-white);
  text-transform: uppercase;
}

.service-process__n-value { color: var(--c-accent); }

.service-process__title {
  margin: 0 0 20px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-white);
}

.service-process__text {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  color: var(--c-text);
}

.service-process__figure {
  border-radius: var(--card-radius);
  overflow: hidden;
}

.service-process__figure img { aspect-ratio: 1 / 0.9; }

/* THE ZIGZAG. Twelve explicit orders rather than a clever selector, because
   `order` is resolved across the WHOLE container: giving only the swapped
   cells a non-zero value would send all three of them past every cell still
   sitting at 0. Pairs 3/4, 7/8 and 11/12 are the ones that swap. */
@media (min-width: 768px) {
  .service-process__cell:nth-child(1)  { order: 1; }
  .service-process__cell:nth-child(2)  { order: 2; }
  .service-process__cell:nth-child(3)  { order: 4; }
  .service-process__cell:nth-child(4)  { order: 3; }
  .service-process__cell:nth-child(5)  { order: 5; }
  .service-process__cell:nth-child(6)  { order: 6; }
  .service-process__cell:nth-child(7)  { order: 8; }
  .service-process__cell:nth-child(8)  { order: 7; }
  .service-process__cell:nth-child(9)  { order: 9; }
  .service-process__cell:nth-child(10) { order: 10; }
  .service-process__cell:nth-child(11) { order: 12; }
  .service-process__cell:nth-child(12) { order: 11; }
}

/* --- The sidebar ---------------------------------------------------------
   `position: sticky` ON THE INNER COLUMN, not the section: the outer column
   is 4354.9 tall and the sticky child 1268.3, which is what lets the cards
   ride down beside the main column. Landmine 1 is why this works at all --
   body is `overflow-x: clip`; had it been `hidden`, body would be a scroll
   container and sticky would silently do nothing. */
.service-side {
  position: sticky;
  top: 30px;
  display: flex;
  flex-direction: column;
  gap: 60px;
}

/* NO BORDER ON THIS CARD. Measured `0px none`, where the CTA card beneath it
   carries the divider hairline -- so the frost alone separates it from the
   page, and its background is genuinely transparent rather than the CTA's
   rgba(27,27,27,.4). */
.service-cats {
  position: relative;
  border-radius: var(--card-radius);
  backdrop-filter: blur(100px);
  -webkit-backdrop-filter: blur(100px);
}

/* The same tint the step cards and the CTA carry, at `inset: 0` here rather
   than -1px because this card has no border for it to cover. Without it the
   card has no fill at all and reads as text floating on the page gradient. */
.service-cats::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--c-secondary);
  opacity: .4;
  pointer-events: none;
}

.service-cats > * { position: relative; z-index: 1; }

/* THE TWO DIVIDERS, CAUGHT BY EYE AND NOT BY ANY NUMBER. One under the
   heading and one between each pair of items, both `.8px solid` on
   `--c-divider` -- rgba(255,255,255,.1), measured. They cost NOTHING in
   height: the item rule is an absolutely positioned `::after` pinned to the
   bottom of the padding box, so every gate in tools/ reports a clean page
   with the lines missing. That is the fourth defect of this shape in phase 3,
   after /about/'s inline underline, the faqs plus/minus glyph and the hub's
   list bullets.

   THE .8 IS NOT A ROUNDING ARTEFACT. It is the same fractional border the
   `.faq__toggle` landmine documents, and at 1px eight items grow the card by
   1.6px. Do not let a formatter round it. */
.service-cats__head {
  padding: 40px 40px 30px;
  border-bottom: .8px solid var(--c-divider);
}
.service-cats__body { padding: 30px 40px 40px; }

.service-cats__title {
  margin: 0;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-white);
}

.service-cats__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.service-cats__item {
  padding-bottom: 30px;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
}

.service-cats__item + .service-cats__item { margin-top: 30px; }
.service-cats__item:last-child { padding-bottom: 0; }

/* Pinned to the bottom of the padding box, so the rule sits midway between
   two items -- 30px under one and 30px over the next -- and adds no height.
   The LAST item has none: the reference sets `content: none` there rather
   than closing the list with a line. */
.service-cats__item { position: relative; }

.service-cats__item:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  border-top: .8px solid var(--c-divider);
}

/* THE CHECK SITS RIGHT, NOT LEFT. Their icon is at x1287.5 in a column
   running 991 to 1320, so it is pushed to the far end of the row. They get
   there with `flex-direction: row-reverse` on an icon-first list; this build
   puts the LABEL first in the DOM and uses plain `space-between`, which
   renders identically and reads in the right order to a screen reader. */
.service-cats__item a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: var(--c-white);
}

/* THE HOVER IS ON THE LABEL, NOT THE ROW. Measured by hovering their live
   list and reading the computed styles back: the item's text goes
   rgb(255,255,255) -> rgb(125,62,163) and NOTHING else moves -- no background,
   no transform, no padding shift, and the check mark is already the accent at
   rest so it does not change either. The transition is their own
   `.3s ease-in-out`, which is `--dur` at .4s against their .3, so the .3 is
   written out rather than borrowed from the token.

   `--c-accent` rather than the literal, so the rebrand carries it. */
.service-cats__label { transition: color .3s ease-in-out; }

.service-cats__item a:hover .service-cats__label,
.service-cats__item a:focus-visible .service-cats__label {
  color: var(--c-accent);
}

.service-cats__check {
  flex: 0 0 32.5px;
  display: flex;
  justify-content: flex-end;
  color: var(--c-accent);
}

.service-cats__check svg {
  height: 26px;
  width: auto;
}

/* --- /testimonial/ : the eleven cards ------------------------------------
   Phase 3's eighth page, and the simplest body on the site: one column of
   eleven glass panels and nothing else. Their container 4 holds all of it;
   the banner, the ticker and the ticker's hidden mobile twin are the other
   three, and the work-together block below comes from their footer template.

   THE CONTAINER IS 1268, NOT THE SITE'S 1300, and that is the reference's
   own authored width for this one container rather than an accident worth
   authoring around. Every other inner page of theirs -- faqs, about,
   privacy-policy -- measures 1300 like ours, and this page's banner and
   work-together still do, so the cards sit 16px inside the blocks above and
   below them exactly as they do on the reference. 1268 at a 1440 viewport
   puts the panel edge at x86, which is theirs to the pixel.

   Below 1025 nothing needs to override it: the natural width there is
   984 (body padding 10 + the section's own gutter 10, each side), which is
   already narrower than the cap. */
.section-testimonial-list {
  padding: 160px var(--gutter) 80px;
}

.section-testimonial-list .container { max-width: 1268px; }

.testimonial-list__rows {
  display: flex;
  flex-direction: column;
  gap: 60px;
}

/* The panel. No fill at all -- the blur IS the surface, over the body
   gradient, which is why a background-color here would flatten it. */
.testimonial-card {
  display: flex;
  align-items: center;
  gap: 50px;
  padding: 80px;
  border: 1px solid var(--c-divider);
  border-radius: var(--card-radius);
  backdrop-filter: blur(100px);
  -webkit-backdrop-filter: blur(100px);
}

/* THE PORTRAIT COLUMN IS FLUID, IN TWO BANDED RANGES. It is neither a fixed
   length nor a percentage, and both of those readings LOOK right if you
   sample only 1440 and 1024. Measured at nine widths:

     >= 1309     273.61 flat
     1025-1308   27vw - 79.55px     1300 271.45   1200 244.45   1100 217.45
     768-1024    32vw - 47.65px     1024 280.03    880 233.95    768 198.11
     <= 767      120 flat            700 120       480 120       360 120

   Linear to 0.01px inside each band. It is not a percentage of anything on
   the card: 273.6 of a 1106 content box is 24.7% and 280 of 922 is 30.4%,
   and no single base reconciles the two. It is the same
   `min(30vw - 26px, 250px)` technique their header logo uses, which
   `HANDOFF.md` already records under the header's height below 1024 -- the
   same author reaching for the same tool.

   THE FIRST PASS HERE PINNED 273.6 AND 280, which are exactly the two values
   a 1440-and-1024 check returns, and was 82px wide at 768. Nothing caught
   it: no overflow check can see a column that still fits, `delta.py` cannot
   see `width` at all, and it surfaced only as an unexplained +614.2 in
   `secheights.py` at that one width.

   Slope and cap are both load-bearing: 27vw - 79.55 reaches 273.61 at 1308,
   and above that the flat value takes over so the column stops growing. */
/* THE PORTRAIT IS A CIRCLE, and nothing on this page says so until a square
   photograph lands in it. The reference hangs `border-radius: 50%` and
   `overflow: hidden` on the `.elementor-widget-container` BETWEEN the image
   widget and the `<img>` -- not on the widget root and not on the image,
   both of which read `border-radius: 0px`. A skin probe that samples the
   obvious two elements walks straight past it.

   It is invisible today in BOTH directions: our placeholder avatars are
   circular artwork on transparency, so they look right unclipped, and the
   clip moves no box, so no height gate can see it either. It would have
   surfaced at phase 4, as eleven square photographs on a page that had
   already been signed off.

   The radius goes on the frame rather than on the image, following
   `.section-team-intro__figure`: `.image-anime` already sets `position` and
   `overflow`, and the clip has to take the hover sweep with it. */
.testimonial-card__media {
  --portrait: min(27vw - 79.55px, 273.61px);
  --portrait-scale: 1;
  flex: 0 0 calc(var(--portrait) * var(--portrait-scale));
  width: calc(var(--portrait) * var(--portrait-scale));
  border-radius: 50%;
}

/* TWO OF THE ELEVEN ARE SMALLER, and it is theirs rather than an accident:
   0.81 and 0.66 of the others, holding to three decimals at 1440, 1024, 768
   and 390 alike. A single multiplier over the fluid base reproduces every
   width to 0.05px, which is what confirmed it is one authored number per
   card and not four.

   ON THEIR PAGE THOSE SLOTS HOLD CLIENT LOGOS, not faces, and logos carry
   different amounts of internal whitespace -- so the shrink is optical
   balancing of two specific files. `testimonialPage.ts` carries the value
   and the note that PHASE 4 SHOULD RESET IT unless a real Cinqo logo needs
   the same.

   Driven by a data attribute because this build has zero inline styles. */
.testimonial-card__media[data-scale="81"] { --portrait-scale: .81; }
.testimonial-card__media[data-scale="66"] { --portrait-scale: .66; }

.testimonial-card__media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
}

/* `min-width: 0` so a long unbroken word cannot push the column past its
   share -- the default `min-width: auto` on a flex item would let it. */
.testimonial-card__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* Five stars at 14 wide on a 5px gap comes to the 90px their rating widget
   measures. `--c-accent` is the marked icon's own colour on the reference,
   not an interpretation: `rgb(125, 62, 163)`. */
/* THE BOX IS 13 AND THE MARGIN IS THE BREAKPOINT. Their rating widget is a
   13px row of stars inside a container whose BOTTOM MARGIN is the only thing
   that moves: 5px here, 0 at 1024 and -5px at 767, so the block occupies 18,
   13 and 8 in the column while the stars themselves never change size. It
   reads as noise in a `delta.py` sweep -- an unnamed `div` with a margin and
   an `x11` beside it -- and it is worth 5px a card at 1024 and 10 at 767,
   which is 55 and 110 down a page of eleven. Reproduced as height + margin
   rather than as a single height so the two halves stay separable. */
.testimonial-card__stars {
  display: flex;
  align-items: center;
  gap: 5px;
  height: 13px;
  margin: 0 0 5px;
  padding: 0;
  list-style: none;
}

.testimonial-card__stars li { display: flex; color: var(--c-accent); }

.testimonial-card__stars svg { width: 14px; height: 12.4px; }

.testimonial-card__quote p {
  margin: 0 0 32px;
  font-size: 20px;
  font-weight: 400;
  line-height: 34px;
  color: var(--c-text);
}

/* Only card 1 carries two paragraphs, and its second must not add the 32
   below the block that the 20px column gap is already providing. */
.testimonial-card__quote p:last-child { margin-bottom: 0; }

/* 22/700/1.2 is `--fs-h-secondary` and its pair exactly, which is this
   build's own block-title scale and happens to be theirs here too. */
.testimonial-card__name {
  margin: 0 0 5px;
  font-size: var(--fs-h-secondary);
  font-weight: var(--fw-h-secondary);
  line-height: var(--lh-h-secondary);
  color: var(--c-primary);
}

.testimonial-card__role {
  margin: 0;
  font-size: 16px;
  font-weight: 400;
  line-height: 27.2px;
  color: var(--c-text);
}

/* ==========================================================================
   /book-your-consultation/ : the heading and the scheduler slot
   Phase 3's ninth page, and the thinnest body on the site. Their whole page
   is one heading, four spacers and a third-party scheduler iframe.
   ========================================================================== */

/* --- Their container 4 ---------------------------------------------------
   TWO 50px spacer widgets in one container at its own 20px gap, so the
   authored inner is 50 + 20 + 50 = 120 and the box is 140. The modifier is
   named for the authored inner, following `--50` and `--56` above.
   -------------------------------------------------------------------------- */
.section-spacer--120 .container { min-height: 140px; }

/* --- Their container 5, the one heading ----------------------------------
   Heading and a 50px spacer as SIBLINGS inside the container's 20px gap,
   which is what makes the box 150 rather than 140:

     10 + 60 + 20 + 50 + 10 = 150   at 1440
     10 + 48 + 20 + 50 + 10 = 138   at 1024 and 768
     10 + 36 + 20 + 50 + 10 = 126   at 767
     10 + 72 + 20 + 50 + 10 = 162   at 390, the heading wrapping to two lines

   Folding the spacer into the container's bottom padding gives 140 at 1440
   and the wrong number at every other width. The type is `--fs-h-primary`,
   which already steps 50 -> 40 -> 30 at 1024 and 767; measured on the
   reference at 50/300/60, 40/300/48 and 30/300/36, so nothing is pinned.
   -------------------------------------------------------------------------- */
.section-booking-head { padding: 0 var(--gutter); }

.section-booking-head .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

.section-booking-head__title {
  margin: 0;
  font-size: var(--fs-h-primary);
  font-weight: var(--fw-h-primary);
  line-height: var(--lh-h-primary);
  text-align: center;
  color: var(--c-primary);
}

.section-booking-head__title strong { font-weight: 700; }

.section-booking-head__spacer { height: 50px; }

/* --- Their container 6, the scheduler ------------------------------------
   THE PANEL IS THE TESTIMONIAL CARD'S RECIPE, measured off their page and
   landing on the same three tokens: `--c-divider` is their
   rgba(255,255,255,.1), `--card-radius` their 30px, and the blur is 100px
   over NO FILL. The gradient behind it is the surface; a background-color
   here flattens it.

   `--booking-frame-h` IS RETUNED FOR CALENDLY, 2026-09-04. It reserves the
   space the embed occupies, and the two values are now MEASURED AGAINST THE
   REAL PROVIDER rather than inherited from the reference's.

   They were the reference's until today: their WordPress widget self-sized
   to 253px at 1440 and 821px at 1024 and below, and the reserved figures
   carried a further 7.2px for the baseline gap their INLINE iframe sat on.
   Ours is `display: block`, so there is no baseline gap and the measured
   height is used directly.

   HOW THESE WERE MEASURED, because the method matters more than the numbers
   -- Calendly will redesign eventually and these will need redoing:

     1. Measure the FRAME's inner width at each breakpoint, not the viewport.
        The provider lays out to the frame. Ours are 1218 / 942 / 686 / 705
        / 328 at 1440 / 1024 / 768 / 767 / 390. Note 767 is WIDER than 768,
        because the panel padding drops 30 -> 20 there.
     2. Load the provider's page at those widths WITH A TALL VIEWPORT.
        `scrollHeight` is floored at the viewport, so a short page reports
        the viewport's own height and looks like a real measurement. That
        error read a convincing 768 twice before it was caught.
     3. Measure BOTH STEPS. The booking form is a different page from the
        calendar and below 1025 it is TALLER: 1195.8 against 1139.2. Sizing
        to the calendar alone puts an internal scrollbar on every phone and
        tablet booking at the exact moment of conversion.

   THE VALUE IS THE MAXIMUM ACROSS BOTH STEPS, NOT A PER-WIDTH FIT, and that
   is deliberate. Calendly's own height is NOT MONOTONIC in width: measured
   on the form step it runs 1130.8 at 326, 1091.8 at 400, 1038.8 from 480 to
   620, then back UP to 1195.8 from 684 to 940, and 796 at 1216. Fitting each
   step would hardcode a third party's internal breakpoints, which they can
   change without telling us. So the frame reserves the tallest case: there
   is up to ~157px of slack inside the panel at mid-range widths and ~65px at
   390, and that is the safe direction to be wrong in. Empty space is
   invisible; a scrollbar at the booking form is not.

   Their panel's own `gap: 70px` is INERT -- one child, nothing to space --
   so it is not reproduced. See `BookingSlot.astro`.
   -------------------------------------------------------------------------- */
.section-booking-slot { padding: 0 var(--gutter); }

.section-booking-slot .container { padding: 10px 0; }

.booking-slot__panel {
  --booking-frame-h: 796px;
  padding: 40px;
  border: 1px solid var(--c-divider);
  border-radius: var(--card-radius);
  backdrop-filter: blur(100px);
  -webkit-backdrop-filter: blur(100px);
}

/* The reserved frame. Dashed and faint on purpose, following the hub case
   study's empty image frames: it must read as a slot awaiting content, not
   as a component that failed to load. */
.booking-slot__frame {
  display: grid;
  place-items: center;
  min-height: var(--booking-frame-h);
  border: 1px dashed var(--c-divider);
  border-radius: 12px;
  background: rgba(255, 255, 255, .015);
}

/* The live embed, once `booking.embedUrl` is set. Same reserved height, no
   dashed border and no fill, so swapping the placeholder for the real
   scheduler moves no box. */
.booking-slot__frame--live {
  display: block;
  width: 100%;
  height: var(--booking-frame-h);
  border: 0;
  background: none;
}

.booking-slot__label {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
  text-align: center;
  color: rgba(245, 245, 244, .35);
}

.booking-slot__label span {
  display: block;
  font-size: 12px;
  opacity: .75;
}

/* ==========================================================================
   /careers/ : the whole page body
   Phase 3's tenth page. Their container 5 holds every visible block, so this
   is one section -- `secheights.py` pairs positionally and five sections
   against their one would misgrade everything below it.
   ========================================================================== */

.section-careers { padding: 0 var(--gutter); }

.section-careers .container { padding: 50px 0 80px; }

/* THE COLUMN'S PADDING IS ASYMMETRIC AND IT IS THEIRS: 50 on the right, 10 on
   the left. That is what makes every block 1240 wide inside the 1300
   container, and it flattens to a plain 10px at 1024. */
.careers__column {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 50px 10px 10px;
}

/* Their spacer widget, 50px, used four times down the column. */
.careers__spacer { height: 50px; }

.careers__block {
  display: flex;
  flex-direction: column;
}

/* Block A's gap is 40 where B's and D's are 20. Measured, not assumed. */
.careers__block--lede { gap: 40px; }
.careers__block--head { gap: 20px; }

/* THE 50px SIDE MARGIN IS THE WHOLE BALL GAME AND `delta.py` CANNOT REPORT IT.
   Both text blocks on this page put `margin: 0 50px` on the widget's inner
   container, so the text measures 100px narrower than the box around it -- 1140
   inside 1240, 884 inside 984, 628 inside 728, 270 inside 370. It is the SAME
   at every width, and delta only prints what CHANGES between two widths, so a
   full sweep is silent on it by design.

   Without it the lede reads 2 lines at 1024 where theirs takes 3, and 5 at 390
   where theirs takes 8 -- worth 34px and 102px on this block alone, at
   identical character counts. Necessary-but-not-sufficient, again. */
.careers__lede {
  margin: 0 50px;
  font-size: 20px;
  line-height: 34px;
  text-align: center;
  color: var(--c-text);
}

/* The two headings. Both animate; both run 300 then 700. The first is a step
   ABOVE `--fs-h-primary` at 1440 (60 against 50) and lands back on it at 1024,
   which is why it is pinned here and the second is not. */
.careers__join-title {
  margin: 0;
  font-size: 60px;
  font-weight: var(--fw-h-primary);
  line-height: 1.2;
  text-align: center;
  color: var(--c-primary);
}

.careers__roles-title {
  margin: 0;
  font-size: var(--fs-h-primary);
  font-weight: var(--fw-h-primary);
  line-height: var(--lh-h-primary);
  text-align: center;
  color: var(--c-primary);
}

.careers__join-title strong,
.careers__roles-title strong { font-weight: 700; }

/* --- Block C: copy beside image ------------------------------------------
   A wrapping row, vertically centred. Both children go full width at 767,
   which is the restack -- and `delta.py` is structurally blind to it, because
   it excludes `width` and the row is `flex-wrap: wrap` at every width. The
   20px bottom margin is real and is worth 20px down the page.
   -------------------------------------------------------------------------- */
.careers__intro {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 40px;
  margin-bottom: 20px;
}

/* BOTH COLUMNS ARE FLUID AND NEITHER IS THE SAME KIND OF FLUID, which is the
   third time this reference has done it -- their header logo and the
   testimonial portrait were the first two. Measured at 1440, 1300, 1200, 1100
   and 1025, because sampling only 1440 and 1024 returns two plausible fixed
   values and hides the whole thing:

     copy   43.241% of the row, flat, at every width in every band
     figure calc(45% - half the row's gap)
              1240 row, 40 gap -> 0.45 x 1240 - 20 = 538      exact
              1220 row, 40 gap -> 0.45 x 1220 - 20 = 529      exact
              1120 row, 40 gap -> 0.45 x 1120 - 20 = 484      exact
               984 row, 20 gap -> 0.45 x  984 - 10 = 432.8    exact
               728 row, 20 gap -> 0.45 x  728 - 10 = 317.6    exact

   Linear to 0.01px across the band, and the half-gap term is what makes the
   1024 figure fall out of the same rule rather than needing its own. */
.careers__intro-copy {
  flex: 0 0 auto;
  width: 43.241%;
}

/* The same 100px inset as the lede, one level in, so the 43.241% above stays
   the WIDGET's width exactly as the reference measures it. */
.careers__intro-copy p {
  margin: 0 50px;
  font-size: 26px;
  line-height: 44.2px;
  color: var(--c-text);
}

/* THE FIGURE IS A FIXED BOX WITH A CROPPED IMAGE, not an image at its own
   ratio. Its rendered aspect is 538/478.81 against a source that is 3:4, so
   `object-fit: cover` is doing real work and the height comes from the box.
   `delta.py` CANNOT SEE EITHER HALF OF THIS -- it excludes `width` outright
   and has no `aspect-ratio` in its property list -- so a full sweep is silent
   through it, and below 767 it is silent through a fixed 406px height too. */
.careers__intro-figure {
  flex: 0 1 auto;
  width: calc(45% - 20px);
  aspect-ratio: 538 / 478.81;
}

.careers__intro-figure img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --- Block E: the job grid ----------------------------------------------- */

/* Two columns that wrap to one at 767. Grown from a zero basis rather than a
   percentage, because a percentage that adds up at a 30px gap stops adding up
   at 20 -- the lesson the service page's two columns already paid for. */
.careers__grid {
  display: flex;
  flex-wrap: wrap;
  gap: 30px;
  margin-bottom: 20px;
}

/* THE GLASS PANEL, for the third time on this site and identical every time:
   1px of --c-divider, --card-radius, blur(100px) and NO FILL. Measured on
   their careers cards, their testimonial cards and their booking panel. */
.careers-card {
  display: flex;
  flex-direction: column;
  /* TWO columns, and `flex-grow` must be 0. With `1 1 0` all three share one
     row at 393 each; with a grow of 1 the third would stretch to the full
     1240 on row two. Theirs stays 605 there because the slot beside it is
     taken by one of the dead STEP blocks, so no-grow reproduces it without
     building the dead container. */
  flex: 0 1 calc(50% - 15px);
  min-width: 0;
  gap: 70px;
  padding: 40px;
  border: 1px solid var(--c-divider);
  border-radius: var(--card-radius);
  backdrop-filter: blur(100px);
  -webkit-backdrop-filter: blur(100px);
}

/* The reference's empty fourth cell. Same basis as a card so it takes a
   column, but nothing drawn and no intrinsic height. */
.careers__grid-slot {
  flex: 0 1 calc(50% - 15px);
  min-width: 0;
}

/* Their icon sits in its own 60-tall container, so the card's 70px gap
   measures from that box and not from the glyph. */
.careers-card__icon-row {
  display: flex;
  align-items: center;
  height: 60px;
}

.careers-card__icon {
  display: block;
  color: var(--c-accent);
}

.careers-card__icon svg {
  display: block;
  width: 60px;
  height: 60px;
}

/* THE 30px BOTTOM MARGIN IS THEIRS AND IT IS NOT THE CARD'S GAP. Their title
   widget measures 56.4 where the h3 inside it is 26.4: an `image-box` widget
   whose h3 carries `margin: 0 0 30px`, stacking on top of the card's own 70px
   gap. Without it every card is exactly 30.4 short at 1440 and 1024 -- a
   constant offset on all three, which is what distinguishes it from a copy
   difference and is how it was found. */
.careers-card__title {
  margin: 0 0 30px;
  font-size: var(--fs-h-secondary);
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-primary);
}

.careers-card__body {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

/* THEIR PARAGRAPH MARGIN IS 25.6, NOT 16, and the lists' really is 16 -- two
   different values in the same body, which is why one rule for both comes out
   38.4px short per card. 25.6 is 1.6em at this 16px body, the theme's own
   paragraph rhythm; the `ul` overrides it back down to 16. */
.careers-card__body p { margin: 0 0 25.6px; }
.careers-card__body p:last-child { margin-bottom: 0; }
.careers-card__body strong { font-weight: 700; }

/* THE BULLETS HAVE TO BE PUT BACK. `base.css` sets `list-style: none` on every
   list, so a marker is a per-use decision -- the same reset gap the services
   hub's lists already paid for. Theirs are `disc`, outside, in --c-text, on a
   32px indent with a 16px bottom margin. */
.careers-card__body ul {
  margin: 0 0 16px;
  padding: 0 0 0 32px;
  list-style: disc;
}

.careers-card__body li { margin: 0; }

.careers-card__body a {
  color: inherit;
  text-decoration: underline;
}

/* Pushed to the card's foot so the three cards' buttons align when their
   bodies differ, which is what the reference's equal-height row does. */
.careers-card__actions { margin-top: auto; }

/* The button itself is the shared `.btn --ghost` from base.css; nothing is
   restyled here. `.btn-wrap` sits on the same element as `__actions` to
   reserve the width the detached disc overhangs by. */

/* --- /blog/ : the post archive -------------------------------------------
   Phase 3's last template, and the only page whose body the reference does
   NOT build in Elementor -- it is a WordPress archive rendered by the theme.
   Six cards in one grid; no pagination, meta, date, category or excerpt.

   THE SECTION TAKES NO HORIZONTAL GUTTER, WHICH IS NOT THE HOUSE PATTERN AND
   IS DELIBERATE. Their `.container` here is fluid with a 1300 cap and 15px of
   its own padding, so below 1300 it is FULL BLEED: measured 1300 at 1440 but
   1024 at 1024, 992 at 992 and 390 at 390, always at x0. Adding the usual
   `var(--gutter)` would inset the container by 10px a side at every width
   below the cap and take 6.7px off each of the three cards. The 15px inset
   the cards actually sit on is on the grid below, where theirs is.
   -------------------------------------------------------------------------- */
.section-blog-archive { padding: 160px 0 80px; }

/* Their Bootstrap row, rebuilt. Their `.container` pads 15px, their `.row`
   pulls back -15px and their `.col-lg-4` pads 15px again, which nets out to a
   1270 track inset 15px inside the 1300 container with 30px between cards.
   Three columns of 403.33 + two gaps of 30 = 1270 exactly.

   ROW SPACING IS THE CARD'S MARGIN, NOT `row-gap`, AND THAT IS THE POINT.
   Their `.post-item` carries `margin-bottom: 40px`, so the LAST row
   contributes 40px below it as well. A `row-gap` only goes BETWEEN rows and
   would come out 40 short at 3-up, 30 short at 2-up and 30 short stacked --
   an error that grows with the row count and reads like a padding bug. */
.blog__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  column-gap: 30px;
  row-gap: 0;
  padding: 0 15px;
}

/* `align-items: stretch` is the grid default and is doing real work here: the
   reference equalises the row with `height: calc(100% - 40px)` on the card, so
   a 2-line title in a row with a 3-line one grows to match. At 1024 that is
   what makes all six cards 390.8 while their bodies stay 101 and 127.4. */
.blog-card {
  display: flex;
  flex-direction: column;
  margin-bottom: 40px;
}

/* An `<a>`, so it needs `display: block`, exactly as their `figure a` does.
   `.image-anime` supplies position and overflow; the radius has to be here as
   well as on the image so the hover scale is clipped to the rounded box. */
.blog-card__media {
  display: block;
  border-radius: var(--card-radius);
}

/* THE 4:3 CROP IS ON THE IMAGE, NOT THE FRAME, and it is the largest single
   thing on this page -- about 70% of every card's height. Their source files
   are 800x450 (16:9), so `cover` discards a real band of pixels rather than
   being decorative; the placeholders in `public/assets/img/blog/` are
   generated at the same 16:9 so the crop behaves identically.

   `aspect-ratio` is invisible to `delta.py` -- it is not on its property list
   and `width` is excluded outright -- so nothing in the harness would have
   found this rule. It came off their stylesheet. */
/* `height: auto` IS LOAD-BEARING AND IS NOT TIDY-UP. The `<img>` keeps its
   file's `height="450"` attribute, which the UA maps to a presentational
   `height: 450px`; that beats `aspect-ratio` outright, so without this the
   frame renders 450 tall at EVERY width -- +147.5 at 1440 and +216.5 at 1024,
   measured. The attributes stay because they reserve the right box while the
   file loads; this is what lets the ratio, not the file, decide the height. */
.blog-card__media img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / .75;
  object-fit: cover;
  border-radius: var(--card-radius);
  transition: transform .5s ease-in-out;
}

/* Theirs scales the image on hover of the WHOLE CARD, not of the image. */
.blog-card:hover .blog-card__media img { transform: scale(1.1); }

/* Their 30px lives on `.post-featured-image`, which our markup collapses into
   the media anchor itself. Same box, one node fewer. */
.blog-card__media { margin-bottom: 30px; }

/* 22px at >= 992, and the line-height is 1.2 at all three sizes: 26.4, 24 and
   21.6 against 22, 20 and 18. Their `.post-item-content` carries the 20px and
   their h3 carries none; ours puts it on the h3, which is that wrapper's only
   child, for the same result with one node fewer. */
.blog-card__title {
  margin: 0 0 20px;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-white);
}

.blog-card__title a { color: inherit; }

/* A BARE TEXT LINK PLUS AN ARROW -- NOT the pill `.btn`, and this is the
   exact inverse of the /careers/ CTA finding. Their rule sets no background
   and no padding and their `::after` computes to `content: none`, so a `.btn`
   here would paint a filled capsule the reference does not have. */
.blog-card__btn { margin-top: auto; }

.blog-card__btn a {
  display: inline-block;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.2;
  text-transform: capitalize;
  color: var(--c-accent);
  transition: color .3s ease-in-out;
}

/* Their hover is `--e-global-color-primary`, which resolves to #FFFFFF in
   their kit -- read out of the stylesheet, not sampled, because nothing in
   this harness reads a colour. */
.blog-card__btn a:hover { color: var(--c-white); }

/* BOTH OF THESE UNDO A DEFAULT AND BOTH ARE LOAD-BEARING, and each cost a
   measurable amount before it was found:

   `display: inline` -- `base.css` sets `img, svg, video { display: block }`,
   so left alone the arrow takes its own line inside the link and the button
   row measures 48.2 against their 28.1. A clean +20.0 on every card at every
   width, which reads like a stray margin rather than a display mode.

   `vertical-align: middle` -- theirs is centred on the text, not sitting on
   the baseline. On the baseline the 25px glyph adds the font's descender
   below itself and the link measures 28.19 where theirs is exactly 25.0,
   putting +4.0 into every card. Neither of these moves a colour or a class,
   so no gate but this one would have seen either. */
.blog-card__btn svg {
  display: inline;
  vertical-align: middle;
  width: 25px;
  height: 25px;
  margin-left: 3px;
  transition: transform .3s ease-in-out;
}

.blog-card__btn a:hover svg { transform: rotate(45deg); }

/* ==========================================================================
   /our-team/ -- a HIDDEN page, cloned 2026-09-06

   Nothing links to it. It is out of the nav, out of the footer, out of the
   sitemap and carries its own permanent noindex, and it is revealed when
   Cinqo has a team to put on it.

   THE GRID SHIPS EMPTY AND THAT IS THE DESIGN. Their container 4 is 5951.3
   tall, of which the 40-card grid is 5134.5. `teamMembers` is reserved, so
   the empty state below stands in until there is somebody to show. The rules
   for the populated grid are written anyway, so filling the array is the
   whole change.
   ========================================================================== */

.section-our-team-body,
.section-our-team-award,
.section-our-team-why,
.section-our-team-trust,
.section-our-team-experience {
  padding: 40px 0;
}

.our-team-body,
.our-team-why,
.our-team-trust,
.our-team-experience {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* Their H2s run 34/700 and break to two lines at 68px, so the line box is the
   font size rather than a ratio. Reproduced explicitly. */
.our-team-body__title,
.our-team-body__grid-title,
.our-team-body__award-title,
.our-team-why__title,
.our-team-trust__title,
.our-team-experience__title {
  margin: 0;
  font-size: 34px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-text);
}

.our-team-body__lede,
.our-team-body__grid-intro,
.our-team-why__body,
.our-team-award {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* One rendered line is 27px on the reference, which is 16px on 1.7. Every
   multi-line block on the page is a multiple of it: 54 for two, 82 for three. */
.our-team-body__para,
.our-team-body__grid-intro p,
.our-team-award__para,
.our-team-award__note,
.our-team-why__body p,
.our-team-experience p {
  margin: 0;
  font-size: var(--fs-body);
  line-height: 1.7;
  color: var(--c-text);
}

.our-team-award__note em { font-style: italic; }

.our-team-why__list-title {
  margin: 0;
  font-size: 28px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-text);
}

/* THE MARKER HAS TO BE PUT BACK. `base.css` sets `list-style: none` on every
   list in the build, so a bulleted list restores it per use. Six defects of
   this exact shape have been paid for already. */
.our-team-why__list,
.our-team-trust__list {
  margin: 0;
  padding-left: 22px;
  list-style: disc;
}

.our-team-why__list li,
.our-team-trust__list li {
  font-size: var(--fs-body);
  line-height: 1.7;
  color: var(--c-text);
}

/* --- The team grid ------------------------------------------------------- */

.our-team-body__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 30px 20px;
  padding: 80px 0;
}

/* Empty is the shipped state. One labelled slot, no grid tracks, and a
   smaller band so the page does not carry 80px of padding around nothing. */
.our-team-body__grid[data-empty] {
  display: block;
  padding: 40px 0;
}

.our-team-body__empty {
  margin: 0;
  padding: 40px 20px;
  border: 1px dashed var(--c-divider);
  border-radius: 12px;
  background: rgba(255, 255, 255, .03);
  color: var(--c-text);
  font-size: var(--fs-body);
  line-height: 1.7;
  text-align: center;
  opacity: .75;
}

.team-card { display: flex; flex-direction: column; gap: 12px; }

.team-card__media { margin: 0; line-height: 0; }

/* `height: auto` beats the intrinsic height attribute, which would otherwise
   win outright over aspect-ratio -- the defect that cost /blog/ +147.5 a card
   before it was found. Their portraits are 334x460. */
.team-card__media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 334 / 460;
  object-fit: cover;
  border-radius: 12px;
}

.team-card__name {
  margin: 0;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--c-text);
}

.team-card__role {
  margin: 0;
  font-size: var(--fs-body);
  line-height: 1.7;
  color: var(--c-text);
  opacity: .8;
}

/* --- /our-team/, the measured column widths -------------------------------

   TWO OF THEIR FIVE CONTAINERS ARE WIDER THAN THE 1300 CONTAINER, which is
   easy to miss because the other three match `--container` exactly. Their 4
   is `max-width: 97.625%` and their 6 is `96.912%` -- percentages of the
   viewport, not fixed values, so sampling one width returns a plausible fixed
   number and hides it. Checked at two widths: 1485 at a 1521 viewport and
   1405.8 at 1440 for container 4, which is 97.625% of both.

   AND THE TEXT INSIDE EACH IS INSET AGAIN. Measured against the container
   each sits in: the lede runs 1114 of 1465, the grid intro 1289, the award
   copy 812 of 1300, the trust list 1092, the experience copy 1111. Leaving
   them full width made the award block 303 tall against their 551, because
   the same characters wrapped to half as many lines. */

.section-our-team-body > .container { max-width: 97.625%; }
.section-our-team-why  > .container { max-width: 96.912%; }

.our-team-body__lede       { max-width: 76%; }
.our-team-body__grid-intro { max-width: 88%; }
.our-team-why__body        { max-width: 87%; }
.our-team-award__para,
.our-team-award__note      { max-width: 62.5%; }
.our-team-trust__list      { max-width: 84%; }
.our-team-experience p     { max-width: 85.5%; }

@media (max-width: 1024px) {
  .our-team-body__lede,
  .our-team-body__grid-intro,
  .our-team-why__body,
  .our-team-award__para,
  .our-team-award__note,
  .our-team-trust__list,
  .our-team-experience p { max-width: 100%; }

  .section-our-team-body > .container,
  .section-our-team-why  > .container { max-width: var(--container); }
}

/* ==========================================================================
   30. Page not found (/404.html)
   ==========================================================================

   Added 2026-09-10 with the Cloudflare Workers launch. This is the only block
   in this file that renders on exactly one page, and none of it can reach the
   homepage, so it needs no `_reference` round: `portsec` compares boxes and
   colour on the homepage's DOM, and nothing here matches any node in it.

   Modelled on `.section-cta` above rather than inventing a second rhythm. */

.section-not-found { padding: 0 var(--gutter); }

.section-not-found .container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 10px 0;
}

.not-found__title {
  margin: 0;
  font-size: 33px;
  font-weight: 300;
  line-height: 1.2;
  color: var(--c-primary);
}

/* Held to a measure rather than the full 1300, for the same reason
   `.section-cta__text` is: a single short paragraph set the full width of the
   container reads as a stray line rather than as a body. */
.not-found__lede {
  width: 85.63%;
  flex-grow: 0;
  flex-shrink: 0;
  margin: 0;
}

/* `.btn-wrap` carries only `padding-right` for the icon disc, so the row needs
   its own flex. `wrap` matters: three buttons plus their icon discs do not fit
   one line below about 700, and without it they overflow rather than stack. */
.not-found__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  align-items: center;
}

@media (max-width: 767px) {
  .not-found__lede { width: 100%; }

  /* Stacked at mobile: three pills plus their icon discs need 450px of a
     370px column, so in a row the third would wrap on its own and read as an
     afterthought. Measured at 390: 3 rows, no overflow.

     `align-items: start` rather than `stretch`, and the difference is real but
     invisible in the box numbers: `.btn-wrap` would stretch to the column, but
     `.btn` inside it is an inline-level anchor that does not, so `stretch`
     buys a wider WRAPPER around the same 159.7px pill. Left-aligned is what
     every other button on this site does. */
  .not-found__actions { flex-direction: column; align-items: start; }
}
