/* ============================================================
   CATALYXRISE — "EDITORIAL SOFT"
   ------------------------------------------------------------
   Two directions married on purpose:

     the VOICE comes from the Editorial concept — a serif display
     face, an italic word carrying the turn of each headline, a
     drop cap opening the argument, ruled lists rather than boxes,
     and copy that is written rather than bulleted.

     the GROUND comes from the Soft Light concept — white, with
     large very soft fields of the mark's cyan and lime bleeding
     in behind the hero; rounded cards, gentle shadows, and dark
     rounded panels closing the important sections.

   Serif gives the authority an Oracle consultancy needs. The soft
   ground keeps it from reading as a law firm. Neither alone did
   both jobs.

   THE PALETTE RULE, carried across from the previous system and
   re-measured on white:

     the mark's cyan (#03BAF2) and lime (#85F903) are brilliant
     but far too light to be type on white — 2.26:1 and 1.35:1.
     So on this site they are FILLS ONLY: the soft background
     fields, the icon washes, a rule, a hover edge. Wherever the
     brand has to be *read*, it uses the stepped-down ramp
     #0072A3 → #0E8C86 → #3E7F1B, every stop of which clears its
     bar against white.

   Measured, not eyeballed:
     ink   #0D1B2A on white ..... 16.8:1
     ink2  #51627A on white ...... 6.21:1   body copy
     ink3  #5E6E82 on white ...... 5.21:1   meta, captions
     cyani #0072A3 on white ...... 5.34:1   links, kickers
     teal  #0E8C86 on white ...... 4.10:1   display type only (24px+)
     green #3E7F1B on white ...... 4.93:1   ticks, accents
   ============================================================ */

/* Self-hosted, as the project has always done — no Google Fonts
   request on any page. Inter sets the text. The display face is a
   system old-style serif: Iowan on Apple, Palatino on Windows,
   Georgia everywhere else. All three are genuinely good book faces,
   they are already on the machine, and they cost nothing to load. */
@font-face {
  font-family: "Inter";
  font-style: normal; font-weight: 300 900; font-display: swap;
  src: url("../fonts/inter-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal; font-weight: 400 800; font-display: swap;
  src: url("../fonts/plus-jakarta-sans-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- Design tokens ---------- */
:root {
  --bg:      #FFFFFF;
  --bg2:     #F4F9FC;   /* the cool panel tint */
  --bg3:     #FAFCFE;
  --ink:     #0D1B2A;
  --ink2:    #51627A;
  --ink3:    #5E6E82;
  --rule:    #E4EDF4;
  --rule2:   #D3E2EE;

  --dark:    #0D1B2A;
  /* The footer is the only dark surface left, so it is lifted off the
     near-black the rest of the dark tokens use. Still an anchor closing
     the page, but a slate rather than a hole in it. Measured on it:
     --on-dark 12.9:1, --on-dark-2 7.3:1. */
  --footer:  #05070A;
  --on-dark:  #F2F7FA;
  --on-dark-2:#A9BECE;

  /* the mark — fills only */
  --cyan:       #03BAF2;
  --cyan-light: #50D2FD;
  --lime:       #85F903;

  /* the mark, stepped down until every stop can be read on white */
  --cyan-ink:  #0072A3;
  --teal-ink:  #0E8C86;
  --green-ink: #3E7F1B;

  --grad-ink:  linear-gradient(100deg, #0072A3 0%, #0E9C86 50%, #3E7F1B 100%);
  --grad-fill: linear-gradient(100deg, #03BAF2 0%, #2ADCC4 52%, #85F903 100%);
  /* lifted for dark panels, where every stop must clear 4.5:1 on the navy */
  --grad-bright: linear-gradient(100deg, #50D2FD 0%, #4BEBC8 50%, #9DFF2E 100%);

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, "Times New Roman", serif;
  --sans:  "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --head:  "Plus Jakarta Sans", "Inter", sans-serif;

  --header-h: 80px;
  --container: 1200px;
  --gutter: 32px;

  --r:    20px;   /* cards */
  --r-sm: 12px;   /* buttons, fields */
  --r-lg: 28px;   /* the big dark panels */

  --sh:    0 2px 12px rgba(13,27,42,.045);
  --sh-up: 0 18px 40px rgba(13,27,42,.10);

  --ease: cubic-bezier(.22,.61,.36,1);
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: smooth; }
/* Safety net: anything that overruns the viewport widens the document and
   slides the right-aligned .nav-toggle off screen on phones. `clip` not
   `hidden` — clip creates no scroll container, so sticky and the Lenis
   scroller keep working. */
html { overflow-x: clip; }
/* Keep anchor targets visible below the fixed header. */
html { scroll-padding-top: calc(var(--header-h) + 4px); }

/* The glide sets the scroll position every frame, and it must land exactly
   where it is put. scroll-behavior: smooth would ease each of those steps
   as well, easing an ease, so motion.js stands it down for the duration and
   restores it the moment the travel ends — anchor links stay smooth. */
html.is-gliding { scroll-behavior: auto; }

body {
  font-family: var(--sans);
  font-size: 17px; line-height: 1.7;
  color: var(--ink); background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
ul { list-style: none; }
:focus-visible { outline: 2px solid var(--cyan-ink); outline-offset: 3px; border-radius: 4px; }
::selection { background: rgba(3,186,242,.22); color: var(--ink); }

.skip-link {
  position: fixed; top: -100px; left: 16px; z-index: 300;
  background: var(--ink); color: #fff;
  padding: 12px 20px; border-radius: var(--r-sm);
  font-size: 14px; font-weight: 600;
  transition: top .2s var(--ease);
}
.skip-link:focus { top: 16px; }

.container {
  width: 100%; max-width: var(--container);
  margin-inline: auto; padding-inline: var(--gutter);
  position: relative; z-index: 1;
}

/* ============================================================
   THE SOFT GROUND
   Three very large, very blurred fields of the mark's own colours
   sitting behind the top of the page. They are light, not shapes —
   at this blur radius no edge is legible, which is the point: the
   brand tints the paper instead of decorating it.
   ============================================================ */
.blobs {
  position: absolute; inset: 0 0 auto; height: 1180px;
  z-index: 0; pointer-events: none; overflow: hidden;
}
.blobs i { position: absolute; border-radius: 50%; filter: blur(100px); display: block; }
.blobs i:nth-child(1) { width: 780px; height: 780px; left: -210px; top: -300px; background: rgba(3,186,242,.24); }
.blobs i:nth-child(2) { width: 700px; height: 700px; right: -190px; top: -170px; background: rgba(133,249,3,.15); }
.blobs i:nth-child(3) { width: 620px; height: 620px; left: 44%;   top:  330px; background: rgba(80,210,253,.14); }
/* On a 390px viewport the third field is wider than the screen, so the
   lime end swamps the mix and the whole page reads green. Scaled and
   repositioned so the cyan stays the dominant light, as it is on desktop. */
@media (max-width: 720px) {
  .blobs { height: 760px; }
  .blobs i { filter: blur(64px); }
  .blobs i:nth-child(1) { width: 420px; height: 420px; left: -150px; top: -150px; }
  .blobs i:nth-child(2) { width: 340px; height: 340px; right: -130px; top: -60px; background: rgba(133,249,3,.12); }
  .blobs i:nth-child(3) { width: 360px; height: 360px; left: 30%;  top: 300px; background: rgba(80,210,253,.12); }
}

/* ---------- Section rhythm ---------- */
/* A section holds the screen. Taller content exceeds it rather than being
   cut — min-height, not height. */
.psection {
  position: relative; padding-block: clamp(62px, 7.6vw, 108px);
  min-height: 100svh; display: flex; align-items: center;
}
.psection > .container { width: 100%; }

/* The entrance is the reveal system, retimed. It used to fire when 5% of an
   element had peeked past the bottom edge, which on full-height sections
   meant a heading finished animating while the reader was still a section
   above it. It now waits until the element is properly on screen, so the
   heading moves as its section takes the screen, the lead follows, and the
   body follows that — see the rootMargin in main.js. */
.psection--alt { background: var(--bg2); }
.psection--tight { padding-block: clamp(50px, 6vw, 84px); }
.psection--close { padding-bottom: clamp(26px, 3vw, 44px); }

/* ============================================================
   TYPE
   Serif display and italic emphasis carry the editorial voice.
   ============================================================ */
h1, h2, h3, h4 { font-family: var(--serif); font-weight: 600; letter-spacing: -.015em; line-height: 1.08; color: var(--ink); }

.shead { max-width: 64ch; margin-bottom: clamp(38px, 4.4vw, 60px); }
.shead--wide { max-width: 78ch; }
.shead.is-centred { margin-inline: auto; text-align: center; }
.shead.is-centred .slead { margin-inline: auto; }

.stitle { font-size: clamp(28px, 3.9vw, 50px); text-wrap: balance; }
/* The italic carries the turn of the sentence and the only colour in
   the headline. Gradient text needs a fallback colour first, or a
   browser that cannot clip the background paints nothing at all. */
.stitle em, .hero-title em, h1 em {
  font-style: italic;
  color: var(--cyan-ink);
  background: var(--grad-ink);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .stitle em, .hero-title em, h1 em { -webkit-text-fill-color: currentColor; background: none; }
}

.slead { margin-top: 20px; font-size: 17.5px; line-height: 1.72; color: var(--ink2); max-width: 62ch; text-wrap: pretty; }
.slead + .slead { margin-top: 16px; }

/* The drop cap opens the argument. `float` rather than `initial-letter`
   — the latter is still not on enough browsers to be the only mechanism,
   and a float degrades to an ordinary large letter rather than to nothing. */
.slead--drop::first-letter {
  float: left; font-family: var(--serif); font-weight: 600;
  font-size: 3.35em; line-height: .84;
  padding-right: .085em; margin-top: .06em;
  color: var(--cyan-ink);
}

a.tlink { color: var(--cyan-ink); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
a.tlink:hover { text-decoration-thickness: 2px; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 14px 26px;
  /* Fully round. --r-sm is still the radius for fields and small cards, so
     the pill is set here rather than by changing that token — a 999px
     text input is not what was being asked for. */
  border-radius: 999px;
  font-family: var(--sans); font-size: 14.5px; font-weight: 600;
  border: 1px solid transparent; cursor: pointer;
  transition: transform .25s var(--ease), box-shadow .25s var(--ease),
              background-color .25s var(--ease), color .25s var(--ease), border-color .25s var(--ease);
}
/* The arrow rests tucked to the left and sweeps right on hover — a needle
   coming off its stop rather than a nudge. The overshoot in the curve is
   what makes it read as a sweep; a plain ease only slides. */
/* The arrow starts on the LEFT of the label and crosses to the right on
   hover. It cannot do that as a flex child — a flex item can be nudged but
   it cannot change which side of its sibling it is on — so it comes out of
   the flow and is positioned against the button instead. The label keeps
   the middle to itself and never moves.

   The travel is the button its own width, hence calc(100% - ...) rather
   than a fixed distance: a wide button sweeps further than a narrow one,
   which is what makes it read as crossing the button rather than shifting
   by some arbitrary number of pixels. */
/* The arrow sweeps left to right on an ARC, the way a speedometer needle
   comes off its stop — not along a straight line.

   The whole thing is one rotation. The pivot is placed 146px BELOW the
   icon with transform-origin, so swinging it from -20deg to +20deg walks
   the icon around the rim of a circle far larger than the button: it
   travels 146*sin(20) = 50px to each side of centre and rises 8.8px as it
   passes the middle. That rise is the curve.

   Rotating about a distant pivot is also why the icon stays tangent to its
   own path — it tips up leaving the left stop and tips down arriving at
   the right one, which is the part that actually reads as a needle.

   Deliberately one transition on one property, so it sweeps back along the
   same arc when the pointer leaves. A keyframe animation would snap home
   the instant hover ended, which is the one thing a needle does not do.

   The margin is -7.5px to centre the icon, less a further 4.4px so the arc
   sits astride the centre line of the button rather than below it. */
.btn { position: relative; }
.btn svg {
  width: 15px; height: 15px; flex: none;
  position: absolute; top: 50%; left: 50%;
  margin: -11.9px 0 0 -7.5px;
  transform-origin: 50% 146px;
  transform: rotate(-20deg);
  transition: transform .58s cubic-bezier(.34, 1.42, .55, 1);
}
.btn:hover svg, .btn:focus-visible svg { transform: rotate(20deg); }

/* The primary was the near-black ink, which made the loudest element on a
   white page also the darkest. It now carries the mark’s own ramp with the
   ink as the label — the treatment this file already used on dark panels,
   and already measured: every stop on the ramp clears 7:1 against --ink,
   so the lightest one (the lime) is the safe end rather than the risky one.
   Both "Contact us" buttons and the form submit move together; a primary
   that changed colour by which section it sat in would read as two
   different buttons. */
/* Cool slate. A neutral rather than a brand tint, which is the point of it:
   it sits with the page instead of competing with the headline, and it is
   the only family here that does not pull the eye toward one end of the
   mark. Label measures 14.7:1.

   Shadow and border are both neutral for the same reason — a brand-tinted
   shadow under a neutral fill reads as a colour cast, not as depth. */
.btn-primary, .btn-rise {
  background: #E4EDF5;
  color: var(--ink);
  border-color: #BBC2C9;
  box-shadow: 0 5px 14px rgba(13,27,42,.10);
}
.btn-primary:hover, .btn-rise:hover,
.btn-primary:focus-visible, .btn-rise:focus-visible {
  transform: translateY(-2px);
  background: #D4DCE4;
  border-color: #A8AFB5;
  box-shadow: 0 12px 24px rgba(13,27,42,.14);
}
.btn-ghost, .btn-line {
  background: #fff; color: var(--ink); border-color: var(--rule2);
}
.btn-ghost:hover, .btn-line:hover,
.btn-ghost:focus-visible, .btn-line:focus-visible {
  border-color: var(--cyan); color: var(--cyan-ink); transform: translateY(-2px);
}
/* On a dark panel the primary flips to the mark's own ramp carrying ink —
   7:1+ against every stop, and the one place the real brand colour gets to
   be the loudest thing on the screen. */
.on-dark .btn-primary, .on-dark .btn-rise,
.site-footer .btn-primary, .site-footer .btn-rise { background: #E4EDF5; color: var(--ink); border-color: #BBC2C9; box-shadow: none; }
.on-dark .btn-primary:hover, .on-dark .btn-rise:hover,
.site-footer .btn-primary:hover, .site-footer .btn-rise:hover { background: #D4DCE4; box-shadow: 0 10px 28px rgba(13,27,42,.26); }
.on-dark .btn-ghost { background: transparent; color: var(--on-dark); border-color: rgba(242,247,250,.28); }
.on-dark .btn-ghost:hover { border-color: var(--cyan); color: var(--cyan-light); }

/* ---------- Reveal ---------- */
.reveal { opacity: 0; transform: translateY(26px); transition: opacity .8s var(--ease), transform .8s var(--ease); }
.reveal.is-in { opacity: 1; transform: none; }
/* Dense repeated items get a shorter rise and a quicker settle — at the
   full 20px a stack of six reads as six animations rather than one list
   arriving. */
.reveal-soft { transform: translateY(11px); transition-duration: .55s; }
.reveal-left  { transform: translateX(-26px); }
.reveal-right { transform: translateX(26px); }
.reveal-eager { animation: rise .85s var(--ease) both; }

/* The heading block itself no longer moves. Its heading now arrives a word at
   a time and its lead follows a beat later, and a third movement wrapped
   around both of those read as the whole panel lurching. It keeps the .reveal
   class because that is what carries .is-in down to the two of them. */
.reveal.shead { opacity: 1; transform: none; filter: none; }
@keyframes rise { from { opacity: 0; transform: translateY(22px); filter: blur(8px); } to { opacity: 1; transform: none; filter: blur(0); } }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ============================================================
   HEADER
   ============================================================ */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: rgba(255,255,255,.82);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  /* An inset shadow, not a border: a 1px border would put the header's box
     at 81px while --header-h says 80, and every calc below inherits the error. */
  box-shadow: inset 0 -1px 0 transparent;
  transition: box-shadow .3s var(--ease), background-color .3s var(--ease);
}
.site-header.is-scrolled { box-shadow: inset 0 -1px 0 var(--rule); background: rgba(255,255,255,.92); }
.header-inner { height: var(--header-h); display: flex; align-items: center; gap: 26px; }

/* THE LOCKUP — the supplied artwork, used as-is.
   Two files came from the brand: the mark on its own and the full
   wordmark. This is the wordmark, unmodified apart from its viewBox: it
   was drawn on a 1920x1080 artboard with the logo occupying about a
   fifth of it, so as shipped it would have arrived with four times more
   padding than image. The box is now the artwork's own bounds.

   Used as <img> rather than inlined, deliberately. Both files carry
   Illustrator's generated .st0/.st1 classes and linear-gradient1/2 ids;
   inline them into one page and those collide, and the second logo picks
   up the first one's gradients. As images each keeps its own document.

   No colour handling here either. The supplied gradients read on white
   and on the near-black footer alike, so the same file serves both and
   nothing needs a light and a dark variant. */
.brand-link { display: inline-flex; align-items: center; text-decoration: none; }
.brand-logo { height: 34px; width: auto; display: block; }
.site-header .brand-link { transform: translateY(5px); }
.site-footer .brand-logo { height: 36px; }

.nav { margin-left: auto; }
.nav-list { display: flex; align-items: center; gap: 30px; }
.nav-link {
  font-size: 14.5px; font-weight: 500; color: var(--ink2);
  padding-block: 4px; border-bottom: 1.5px solid transparent;
  transition: color .22s var(--ease), border-color .22s var(--ease);
}
.nav-link:hover, .nav-link.is-active { color: var(--ink); border-bottom-color: var(--cyan); }
.nav-cta-mobile { display: none; }
.header-cta { margin-left: 24px; padding: 12px 34px; }
.header-cta .hcta-ico { display: none; }

/* The blue edge sweeps across the green fill with the arrow, then reverses
   on mouse leave. Both transitions share the same duration and curve. */
.header-cta.btn-primary,
.nav-cta-mobile .btn-primary {
  background: linear-gradient(90deg, #03BAF2 0%, #03BAF2 40%, #85F903 60%, #85F903 100%);
  background-size: 300% 100%;
  background-position: 100% 50%;
  border-color: transparent;
  transition: background-position .58s cubic-bezier(.34, 1.42, .55, 1),
              transform .25s var(--ease), box-shadow .25s var(--ease);
}
.header-cta.btn-primary:hover,
.header-cta.btn-primary:focus-visible,
.nav-cta-mobile .btn-primary:hover,
.nav-cta-mobile .btn-primary:focus-visible {
  background-position: 0% 50%;
}

.nav-toggle {
  display: none; width: 44px; height: 44px; margin-left: auto;
  border: 1px solid var(--rule2); border-radius: var(--r-sm); background: #fff;
  flex-direction: column; align-items: center; justify-content: center; gap: 5px;
}
.nav-toggle span { display: block; width: 18px; height: 1.6px; background: var(--ink); border-radius: 2px; transition: transform .3s var(--ease), opacity .2s var(--ease); }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.6px) rotate(-45deg); }

/* ---------- the edges of the window go soft ----------
   Content does not stop at the header or at the bottom of the screen, it
   dissolves into them.

   Three stacked layers per edge, each blurrier than the last and each
   masked over a shorter distance. One strip of uniform blur has a visible
   seam where it stops; three graded ones read as a gradient of focus.

   z-index 90 puts them over the page and under the header, which is 100. */
.edge-blur {
  position: fixed; left: 0; right: 0; height: 92px;
  z-index: 90; pointer-events: none;
}
.edge-blur i { position: absolute; inset: 0; display: block; }
.edge-blur--top    { top: var(--header-h); --dir: to bottom; }
.edge-blur--bottom { bottom: 0;            --dir: to top;
  transition: opacity .45s var(--ease); }
/* the footer is the end of the page; blurring it only makes it look
   out of focus, so main.js stands this down when the two meet */
.edge-blur--bottom.is-off { opacity: 0; }

.edge-blur i:nth-child(1) {
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  -webkit-mask-image: linear-gradient(var(--dir), #000 0%, #000 44%, transparent 100%);
          mask-image: linear-gradient(var(--dir), #000 0%, #000 44%, transparent 100%);
}
.edge-blur i:nth-child(2) {
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  -webkit-mask-image: linear-gradient(var(--dir), #000 0%, transparent 62%);
          mask-image: linear-gradient(var(--dir), #000 0%, transparent 62%);
}
.edge-blur i:nth-child(3) {
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  -webkit-mask-image: linear-gradient(var(--dir), #000 0%, transparent 32%);
          mask-image: linear-gradient(var(--dir), #000 0%, transparent 32%);
}
/* Without backdrop-filter the strips would be three invisible boxes over
   the content. Nothing is lost by not drawing them. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .edge-blur { display: none; }
}

/* ============================================================
   HERO — centred, type-led
   No photograph. On a ground this soft a full-bleed image would have
   to be scrimmed to carry type, and scrimming it would throw away the
   light that makes the ground work. The words do the work instead.
   ============================================================ */
/* The hero is not a .psection, so it never got the full-screen rule the
   rest of the page has — it sized to its contents, which came up about
   100px short of the window. That gap is where the next section's
   background was showing through underneath it, so the first screen held
   two sections at once.

   The header is sticky and sits in the flow above this, so the hero owes
   the window one viewport minus the header, not a whole one. */
.hero {
  position: relative; text-align: center;
  min-height: calc(100svh - var(--header-h));
  display: flex; align-items: center;
  padding-block: clamp(40px, 5vw, 72px);
}
.hero > .container { max-width: 1120px; }

.hero-title {
  font-family: var(--serif); font-weight: 600;
  font-size: clamp(38px, 6.2vw, 78px); line-height: 1.04; letter-spacing: -.018em;
  margin: 0 auto; max-width: 15ch; text-wrap: balance;
}
.hero-title .hl { font-style: italic; color: var(--cyan-ink); background: var(--grad-ink); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .hero-title .hl { -webkit-text-fill-color: currentColor; background: none; }
}

.hero-sub { margin: 24px auto 0; max-width: 58ch; font-size: 18px; line-height: 1.68; color: var(--ink2); text-wrap: pretty; }

.hero-chips { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 30px; }
.hero-chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 16px; border-radius: 999px;
  background: rgba(255,255,255,.9); border: 1px solid var(--rule);
  font-size: 13px; color: var(--ink2);
}
.hero-chip svg { width: 13px; height: 13px; flex: none; color: var(--green-ink); }

.hero-ctas { display: flex; flex-wrap: wrap; gap: 13px; justify-content: center; margin-top: 32px; }

/* ============================================================
   ABOUT
   Headline on the left, the argument on the right, and the
   capabilities on one ruled line beneath. No photograph and no
   cards: the 4.9/5 and the 99.9% have their own section further
   down the page, so carrying them here as well only said the same
   thing twice, and the picture was decorating an argument that
   reads faster without it.
   ============================================================ */
.about-split {
  display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1.05fr);
  gap: clamp(30px, 4.4vw, 72px); align-items: start;
}
.about-split .slead:first-child { margin-top: 0; }

/* The capability line. Six ruled cells rather than six more boxes —
   next to this much prose another grid of cards would shout. */
.cap-row {
  display: grid; grid-template-columns: repeat(6, minmax(0,1fr));
  column-gap: 20px; margin-top: clamp(38px, 4.4vw, 58px);
  border-top: 1px solid var(--rule);
}
.cap-row span {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 15px 0; border-bottom: 1px solid var(--rule);
  font-size: 13px; line-height: 1.45; color: var(--ink2);
}
.cap-row svg { width: 13px; height: 13px; flex: none; margin-top: 4px; color: var(--green-ink); }

/* ============================================================
   SERVICES — the lifecycle rail
   Four columns divided by hairlines, grouped by when a capability
   is needed rather than by how the practice is organised. No cards
   and no shadows: the rules do the dividing, so the twelve names
   are the only things on the band carrying any weight.
   ============================================================ */
.lc-rail {
  display: grid; grid-template-columns: repeat(4, minmax(0,1fr));
  border-top: 2px solid var(--ink);
}
.lc-phase { padding: 28px 26px 0; border-left: 1px solid var(--rule); }
.lc-phase:first-child { border-left: 0; padding-left: 0; }
.lc-phase:last-child { padding-right: 0; }
.lc-marker {
  display: block; font-family: var(--serif); font-weight: 600;
  font-size: 15px; color: var(--cyan-ink);
}
.lc-head h3 { font-size: 23px; margin: 10px 0 8px; }
.lc-head p { font-size: 13.5px; line-height: 1.55; color: var(--ink3); padding-bottom: 18px; }

.lc-items { display: flex; flex-direction: column; }
.lc-item {
  display: block; padding: 14px 0; border-top: 1px solid var(--rule);
  font-family: var(--serif); font-weight: 600; font-size: 15.5px; line-height: 1.32;
  color: var(--ink);
  transition: color .2s var(--ease), padding-left .25s var(--ease);
}
.lc-item:hover { color: var(--cyan-ink); padding-left: 8px; }

/* ============================================================
   PROOF — four figures
   ============================================================ */
.proof-figs { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 18px; }
.proof-fig {
  background: #fff; border: 1px solid var(--rule); border-radius: var(--r);
  padding: 30px 26px; text-align: center; box-shadow: var(--sh);
}
.proof-fig .n {
  font-family: var(--serif); font-weight: 600;
  font-size: clamp(36px, 4.4vw, 56px); line-height: 1; letter-spacing: -.02em;
  color: var(--cyan-ink);
  background: var(--grad-ink); -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  font-variant-numeric: tabular-nums;
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .proof-fig .n { -webkit-text-fill-color: currentColor; background: none; }
}
.proof-fig .n span { font-size: .56em; }
.proof-fig .n i { font-style: normal; opacity: .6; }
.proof-fig .l { margin-top: 13px; font-size: 13.5px; line-height: 1.5; color: var(--ink3); }

/* ============================================================
   WHY — six reasons
   A ruled grid, not six cards: the same three-by-two arrangement
   with hairline cells, matching the services rail. Numbered in the
   serif — the figure is a piece of typography here, not a badge.

   The reveal sits on the grid rather than on each cell. Staggering
   the cells would bring the horizontal rules in piecemeal, and a
   rule that arrives in three parts reads as a rendering fault.
   ============================================================ */
.why-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0,1fr));
  border-top: 2px solid var(--ink);
}
.why-card {
  padding: 28px 28px 32px;
  border-left: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
}
.why-card:nth-child(3n+1) { border-left: 0; padding-left: 0; }
.why-card:nth-child(3n) { padding-right: 0; }
.why-num { font-family: var(--serif); font-weight: 600; font-size: 15px; color: var(--cyan-ink); }
.why-card h3 { font-size: 21px; margin: 11px 0 10px; }
.why-card p { font-size: 14.5px; line-height: 1.65; color: var(--ink2); }

/* ============================================================
   INDUSTRIES — the carousel
   Three cards to a screen, arrows outside the track. Each card's
   photograph rises from below on hover under a scrim and takes the
   text light. main.js owns the widths and the loop.
   ============================================================ */
.ind-carousel {
  position: relative;
  display: grid; grid-template-columns: auto minmax(0,1fr) auto;
  align-items: center; column-gap: 16px; row-gap: 26px;
}
.ind-viewport { grid-column: 2; }
.ind-foot { grid-column: 1 / -1; }
.ind-viewport { overflow: hidden; padding: 6px; margin: -6px; }
.ind-track { display: flex; gap: 18px; will-change: transform; }
.ind-track--live, .ind-track--snap { transition: none; }

.ind-carousel-btn {
  width: 46px; height: 46px; flex: none; align-self: center;
  border: 1px solid var(--rule2); border-radius: 50%; background: #fff; color: var(--ink);
  display: grid; place-items: center; box-shadow: var(--sh);
  transition: background-color .25s var(--ease), color .25s var(--ease), opacity .25s var(--ease);
}
.ind-carousel-btn svg { width: 18px; height: 18px; }
.ind-carousel-btn:hover:not(:disabled) { background: var(--ink); color: #fff; }
.ind-carousel-btn:disabled { opacity: .3; cursor: not-allowed; }

/* The flex basis is the contract main.js measures against: exactly 3, then
   2, then 1 card per view. Changing these numbers changes the paging. */
.ind-strip-item {
  position: relative; flex: 0 0 calc((100% - 36px) / 3); min-width: 0; min-height: 330px;
  display: flex; flex-direction: column;
  background: #fff; border: 1px solid var(--rule); border-radius: var(--r);
  padding: 26px 24px 24px; overflow: hidden; isolation: isolate;
  box-shadow: var(--sh); color: var(--ink);
  transition: transform .3s var(--ease), box-shadow .3s var(--ease);
}
.ind-strip-item:hover { transform: translateY(-4px); box-shadow: var(--sh-up); }
/* Was a photograph under a scrim. It is a flat panel now, one colour per
   sector, set on the card as --tint. The rise is unchanged — it is the
   interaction that made this section, and it works the same whether what
   arrives is a picture or a colour.

   Every tint is chosen light enough to keep the card on dark ink, so no
   scrim is needed at all: the six values ARE the contrast guarantee, and
   the measured worst case across them is 5.3:1 on the body copy. Swap one
   for something darker and that stops being true. */
.ind-photo {
  position: absolute; inset: 0; z-index: -1; border-radius: var(--r);
  background: var(--tint, var(--bg2));
  transform: translateY(101%); transition: transform .55s var(--ease);
}
/* Kept as a hairline only — a flat panel needs no scrim, but it still
   wants an edge where it meets the card. */
.ind-photo::after {
  content: ""; position: absolute; inset: 0; border-radius: var(--r);
  background: none;
  box-shadow: inset 0 0 0 1px rgba(13,27,42,.06);
}
.ind-strip-item:hover .ind-photo, .ind-strip-item:focus-visible .ind-photo, .ind-strip-item.is-lit .ind-photo { transform: translateY(0); }
.ind-strip-item:hover, .ind-strip-item:focus-visible, .ind-strip-item.is-lit { color: var(--ink); }

.ind-icon {
  width: 46px; height: 46px; border-radius: 14px; flex: none; margin-bottom: 20px;
  display: grid; place-items: center;
  background: linear-gradient(140deg, rgba(3,186,242,.16), rgba(133,249,3,.16));
  color: var(--cyan-ink);
  transition: background .35s var(--ease), color .35s var(--ease);
}
.ind-icon svg { width: 23px; height: 23px; }
.ind-strip-item:hover .ind-icon, .ind-strip-item.is-lit .ind-icon { background: rgba(255,255,255,.82); color: var(--cyan-ink); }

.ind-strip-item h3 { font-size: 21px; margin-bottom: 12px; color: inherit; }
.ind-strip-item p { font-size: 14.5px; line-height: 1.6; color: var(--ink3); margin-bottom: 8px; transition: color .35s var(--ease); }
/* one step darker than at rest, not lighter: the ground it sits on has
   gone from white to a veiled photograph. */
.ind-strip-item:hover p, .ind-strip-item.is-lit p { color: var(--ink2); }

/* Hidden, not deleted. main.js reads .ind-progress as the signal that this
   rail indexes itself with a bar rather than dots — take the node away and it
   builds a dot row of its own and appends it into the carousel grid. */
.ind-foot { display: none; }
.ind-progress { flex: 1; height: 3px; border-radius: 2px; background: var(--rule2); position: relative; overflow: hidden; }
.ind-progress > span { position: absolute; inset: 0 auto 0 0; width: 0; border-radius: 2px; background: var(--grad-fill); transition: width .4s var(--ease); }
.ind-dots { display: flex; gap: 8px; }
.ind-dot { width: 30px; height: 4px; border-radius: 2px; background: var(--rule2); position: relative; overflow: hidden; }
.ind-dot-fill { position: absolute; inset: 0 auto 0 0; width: 0; border-radius: 2px; background: var(--grad-fill); transition: width .3s linear; }
.ind-dot.is-on .ind-dot-fill { width: 100%; }

/* ============================================================
   THE CATALYST METHOD — the first dark panel
   Soft Light closes its sections on a dark rounded panel; this is
   that device carrying the method. The photograph sits inside the
   panel at low opacity as texture, not as subject.
   ============================================================ */
/* The two dark panels and the footer are now one surface.

   They were a gradient running to #123449 — a mid teal that is in neither the
   mark nor the text palette, so the panels read as a different site pasted
   into this one. The footer was already flat --dark and looked right, so both
   panels now match it: the same ink the body copy is set in, lit by the same
   soft cyan and lime the .blobs cast over the light sections. The page is
   simply the same page with the lights down.

   The method panel also carried a photograph at .14 under a half-grayscale,
   which landed as a grey-green murk in the bottom corner. It is gone; the
   layers that drew it are no longer in the markup. */
/* The method and the contact band used to be dark rounded panels. They now
   sit on the page like every other section — the only dark surface left on
   the site is the footer. The layers that drew the photograph and the scrim
   are no longer in the markup. */
.method-bg, .method-scrim { display: none; }

.steps { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 24px; }
.step { padding-top: 22px; border-top: 2px solid var(--rule); transition: border-color .4s var(--ease); }
.step.is-lit, .step:hover { border-top-color: var(--cyan); }
.step .snum {
  font-family: var(--serif); font-weight: 600; font-size: 15px;
  color: var(--cyan-ink); margin-bottom: 12px; transition: color .4s var(--ease);
}
.step h3 { font-size: 23px; margin-bottom: 10px; }
.step p { font-size: 14.5px; line-height: 1.66; color: var(--ink2); }

/* ============================================================
   TESTIMONIALS
   The quotation is the design. Set large in the serif, opened by a
   rule rather than a quotation mark.
   ============================================================ */
.testi-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 20px; }
.testi {
  display: flex; flex-direction: column;
  background: #fff; border: 1px solid var(--rule); border-radius: var(--r);
  padding: 34px 32px 30px; box-shadow: var(--sh);
}
.testi blockquote::before { content: ""; display: block; width: 52px; height: 3px; border-radius: 2px; background: var(--grad-fill); margin-bottom: 24px; }
.testi blockquote p {
  font-family: var(--serif); font-weight: 400; font-style: italic;
  font-size: clamp(18px, 1.8vw, 22px); line-height: 1.5; color: var(--ink); text-wrap: pretty;
}
.testi-who { display: flex; align-items: center; gap: 13px; margin-top: auto; padding-top: 26px; }
.testi-av {
  width: 42px; height: 42px; flex: none; border-radius: 13px; display: grid; place-items: center;
  background: linear-gradient(140deg, rgba(3,186,242,.16), rgba(133,249,3,.16)); color: var(--cyan-ink);
}
.testi-av svg { width: 20px; height: 20px; }
.testi-who b { display: block; font-family: var(--serif); font-weight: 600; font-size: 16px; color: var(--ink); }
.testi-who small { display: block; margin-top: 3px; font-size: 12.5px; color: var(--ink3); }

/* ============================================================
   FAQ
   The panel animates on grid-template-rows, which is what main.js
   expects — it only toggles .is-open and never measures a height.
   ============================================================ */
.faq-wrap { max-width: 900px; margin-inline: auto; display: flex; flex-direction: column; gap: 12px; }
.faq-item {
  background: #fff; border: 1px solid var(--rule); border-radius: var(--r-sm);
  overflow: hidden; transition: border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.faq-item.is-open { border-color: rgba(3,186,242,.45); box-shadow: var(--sh); }
.faq-q {
  width: 100%; display: flex; align-items: center; gap: 20px; text-align: left;
  padding: 21px 24px;
  font-family: var(--serif); font-weight: 600; font-size: 18px; line-height: 1.35; color: var(--ink);
  transition: color .22s var(--ease);
}
.faq-q:hover { color: var(--cyan-ink); }
.faq-pm { width: 24px; height: 24px; flex: none; margin-left: auto; position: relative; border-radius: 50%; background: var(--bg2); transition: background-color .3s var(--ease); }
.faq-pm::before, .faq-pm::after {
  content: ""; position: absolute; top: 50%; left: 50%; translate: -50% -50%;
  background: var(--ink); border-radius: 2px; transition: transform .3s var(--ease), background-color .3s var(--ease);
}
.faq-pm::before { width: 11px; height: 1.8px; }
.faq-pm::after  { width: 1.8px; height: 11px; }
.faq-item.is-open .faq-pm { background: var(--ink); }
.faq-item.is-open .faq-pm::before, .faq-item.is-open .faq-pm::after { background: #fff; }
.faq-item.is-open .faq-pm::after { transform: scaleY(0); }

.faq-a { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .34s var(--ease); }
.faq-item.is-open .faq-a { grid-template-rows: 1fr; }
.faq-a > div { overflow: hidden; }
.faq-a p { padding: 0 60px 24px 24px; font-size: 15.5px; line-height: 1.72; color: var(--ink2); }
@media (prefers-reduced-motion: reduce) { .faq-a, .faq-pm::before, .faq-pm::after { transition: none; } }

/* ============================================================
   CONTACT FORMS
   ============================================================ */
.contact-grid { display: grid; grid-template-columns: minmax(0,.92fr) minmax(0,1.08fr); gap: clamp(32px, 4.4vw, 62px); align-items: start; position: relative; z-index: 2; }
#contact .contact-grid { align-items: center; }
#contact .contact-intro { max-width: 540px; }

/* The shared form card has a light edge and a small lift on both pages. */
.contact-card {
  background: #fff; color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--r);
  padding: clamp(26px, 3vw, 36px); box-shadow: var(--sh-up);
}
.contact-card h3 { font-size: 23px; }
.ccard-sub { margin-top: 9px; padding-bottom: 20px; margin-bottom: 22px; border-bottom: 1px solid var(--rule); font-size: 14.5px; line-height: 1.6; color: var(--ink3); }

.cform-row { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 16px; }
.cfield { margin-bottom: 16px; display: flex; flex-direction: column; }
.cfield label { font-size: 13px; font-weight: 600; color: var(--ink2); margin-bottom: 8px; }
.cfield .req { color: var(--green-ink); }
.cfield .opt { color: var(--ink3); font-weight: 400; }
.cfield input, .cfield textarea, .cfield select {
  font-family: var(--sans); font-size: 15px; color: var(--ink);
  background: var(--bg3); border: 0; border-radius: var(--r-sm);
  padding: 13px 15px; width: 100%;
  transition: box-shadow .2s var(--ease), background-color .2s var(--ease);
}
.cfield textarea { resize: vertical; min-height: 104px; }
.cfield input::placeholder, .cfield textarea::placeholder { color: var(--ink3); opacity: .75; }
.cfield input:focus, .cfield textarea:focus, .cfield select:focus {
  outline: none; background: #fff; box-shadow: 0 0 0 4px rgba(3,186,242,.16);
}
.cfield.has-error input, .cfield.has-error textarea, .cfield.has-error select { box-shadow: 0 0 0 2px #B3261E; }
.cf-error { margin-top: 7px; font-size: 12.5px; line-height: 1.4; color: #B3261E; }

.cf-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.cf-agree { display: flex; gap: 11px; align-items: flex-start; margin: 6px 0 22px; font-size: 13px; line-height: 1.55; color: var(--ink2); }
.cf-agree input { width: 17px; height: 17px; flex: none; margin-top: 2px; accent-color: var(--cyan-ink); }
.cf-agree a { color: var(--cyan-ink); text-decoration: underline; text-underline-offset: 2px; }
.cf-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }
.cf-status { font-size: 13.5px; font-weight: 500; }
.cf-status.is-ok { color: var(--green-ink); }
.cf-status.is-err { color: #B3261E; }

/* contact.html only — the attachment control */
.btn-attach { display: inline-flex; align-items: center; gap: 9px; padding: 12px 18px; border-radius: var(--r-sm); border: 1px dashed var(--rule2); background: var(--bg3); font-size: 13.5px; font-weight: 500; color: var(--ink2); transition: border-color .2s var(--ease), color .2s var(--ease); }
.btn-attach:hover { border-color: var(--cyan); color: var(--cyan-ink); }
.btn-attach svg { width: 15px; height: 15px; }
.cf-filename { font-size: 13.5px; color: var(--ink2); }
.cf-file-clear { font-size: 12.5px; font-weight: 600; color: #B3261E; }
.cf-file { position: absolute; left: -9999px; }
.cform-attach { display: flex; flex-wrap: wrap; align-items: center; gap: 13px; margin-bottom: 8px; }
.cf-attach-hint, .cform-note { font-size: 12.5px; line-height: 1.5; color: var(--ink3); }
.cform-note { margin-top: 14px; }

/* Home-page consultation: a soft brand-tinted card and generous controls. */
.consultation-card {
  position: relative;
  padding: clamp(24px, 3.2vw, 44px);
  border: 1px solid #dcebea;
  border-radius: 28px;
  background: radial-gradient(ellipse at 100% 0%, rgba(133,249,3,.10), transparent 48%),
              linear-gradient(145deg, #f3fbff, #fff 52%);
  box-shadow: 0 20px 65px rgba(13,55,65,.07);
}
.consultation-card h3 {
  font-family: var(--sans); font-size: clamp(24px, 2.3vw, 32px);
  font-weight: 600; letter-spacing: -.045em; line-height: 1.2;
}
.consultation-card .ccard-sub {
  margin: 12px 0 28px; padding: 0; border: 0; max-width: 43ch;
}
.consultation-card .cform-row { gap: 18px; }
.consultation-card .cfield { min-width: 0; margin-bottom: 20px; }
.consultation-card .cfield label { color: var(--ink); font-size: 12px; }
.consultation-card .opt { margin-left: 4px; font-size: 10px; }
.consultation-card .cfield input,
.consultation-card .cfield textarea {
  background: rgba(255,255,255,.9);
  border-radius: 13px; padding: 15px 16px; font-size: 14px;
  box-shadow: 0 2px 3px rgba(13,27,42,.02);
}
.consultation-card .cfield textarea { min-height: 122px; }
.consultation-card .cfield input:focus,
.consultation-card .cfield textarea:focus {
  background: #fff;
  box-shadow: 0 0 0 4px rgba(3,186,242,.1);
}
.consultation-card .cfield.has-error input,
.consultation-card .cfield.has-error textarea { box-shadow: 0 0 0 2px #B3261E; }
.consultation-card .cf-agree { font-size: 12px; margin: 0 0 24px; }
.consultation-card .cf-foot { gap: 12px; }
.consultation-card .consultation-submit {
  isolation: isolate; overflow: hidden;
  width: min(100%, 238px); height: 56px; justify-content: flex-start;
  padding: 0 17px; border-color: transparent; border-radius: 999px;
  background: #85F903; color: var(--ink);
  font-size: 13.5px; font-weight: 700;
  box-shadow: 0 7px 16px rgba(62,127,27,.13);
  transition: box-shadow .35s var(--ease), transform .35s var(--ease);
}
.consultation-submit::before {
  content: ""; position: absolute; z-index: 0; inset: 0;
  background: linear-gradient(100deg, #03BAF2, #53D9CF);
  clip-path: inset(0 100% 0 0 round 999px);
  transition: clip-path .65s var(--ease);
}
.consultation-submit-label {
  position: relative; z-index: 2; padding-left: 41px;
  transition: padding-left .54s var(--ease);
}
.consultation-submit-arrow {
  position: absolute; z-index: 3; left: 11px; top: 12px;
  display: grid; place-items: center; width: 31px; height: 31px;
  border-radius: 50%; background: var(--ink); color: #fff;
  font-size: 22px; font-weight: 400; line-height: 1;
  transition: left .65s var(--ease), transform .65s var(--ease), opacity .25s var(--ease);
}
.consultation-card .consultation-submit:not(:disabled):is(:hover, :focus-visible) {
  background: #85F903; border-color: transparent;
  box-shadow: 0 13px 22px rgba(3,186,242,.18);
}
.consultation-submit:not(:disabled):is(:hover, :focus-visible)::before { clip-path: inset(0 0 0 0 round 999px); }
.consultation-submit:not(:disabled):is(:hover, :focus-visible) .consultation-submit-label { padding-left: 0; }
.consultation-submit:not(:disabled):is(:hover, :focus-visible) .consultation-submit-arrow {
  left: calc(100% - 42px); transform: rotate(38deg);
}
.consultation-card .consultation-submit:disabled { opacity: .65; cursor: wait; transform: none; }
.consultation-submit:disabled .consultation-submit-label { padding-left: 0; }
.consultation-submit:disabled .consultation-submit-arrow { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .consultation-submit::before, .consultation-submit-label, .consultation-submit-arrow { transition: none; }
}
@media (max-width: 600px) {
  .consultation-card { border-radius: 22px; padding: 24px 20px; }
  .consultation-card .cform-row { grid-template-columns: 1fr; gap: 0; }
}

/* ============================================================
   FOOTER
   ============================================================ */
/* Shortened. The height came from four places, not one, so all four are
   trimmed together — cutting only the top padding would have left the
   column spacing looking loose against a tighter frame. */
.site-footer { background: var(--footer); color: var(--on-dark); padding-block: clamp(38px, 4vw, 54px) 0; position: relative; z-index: 1; }
.foot-grid { display: grid; grid-template-columns: 1.7fr 1fr 1fr 1.15fr; gap: clamp(24px, 2.8vw, 42px); padding-bottom: 30px; }
.foot-about p { margin-top: 15px; font-size: 13.5px; line-height: 1.62; color: var(--on-dark-2); max-width: 44ch; }

.foot-col h4 { font-family: var(--sans); font-weight: 600; font-size: 11.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--on-dark-2); padding-bottom: 10px; margin-bottom: 12px; border-bottom: 1px solid rgba(242,247,250,.16); }
.foot-col ul { display: flex; flex-direction: column; gap: 8px; }
.foot-col a { font-size: 14px; line-height: 1.45; color: var(--on-dark); display: inline-flex; align-items: center; gap: 9px; transition: color .2s var(--ease); }
.foot-col a:hover { color: var(--cyan-light); }
.foot-col a svg { width: 15px; height: 15px; flex: none; color: var(--on-dark-2); }

.foot-bottom { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; padding-block: 16px; border-top: 1px solid rgba(242,247,250,.14); font-size: 12.5px; color: var(--on-dark-2); }
.foot-policies { margin-left: auto; display: flex; flex-wrap: wrap; gap: 20px; }
.foot-policies a, .foot-policies button { font: inherit; color: var(--on-dark-2); transition: color .2s var(--ease); }
.foot-policies a:hover, .foot-policies button:hover { color: var(--cyan-light); }

/* ============================================================
   COOKIE BANNER — injected by js/cookie-consent.js
   ============================================================ */
.cc-banner {
  position: fixed; left: 50%; bottom: 22px; translate: -50% 0; z-index: 250;
  width: min(640px, calc(100vw - 32px));
  background: #fff; border: 1px solid var(--rule); border-radius: var(--r);
  padding: 26px 28px; box-shadow: 0 24px 60px rgba(13,27,42,.22);
  animation: rise .5s var(--ease) both;
}
.cc-title { font-family: var(--serif); font-weight: 600; font-size: 20px; color: var(--ink); margin-bottom: 11px; }
.cc-banner p { font-size: 14.5px; line-height: 1.65; color: var(--ink2); }
.cc-banner a { color: var(--cyan-ink); text-decoration: underline; text-underline-offset: 2px; }
.cc-actions { display: flex; flex-wrap: wrap; gap: 11px; margin-top: 20px; }

/* ============================================================
   INNER-PAGE HERO — contact, legal, 404/500, thank-you
   Same soft ground, shorter band. No photograph: these pages open
   on the argument, exactly as the home page does.
   ============================================================ */
/* The banner. A photograph under a flat dark scrim with the page title
   centred on it — the contact page and the three legal pages all use it.

   The scrim is a flat wash rather than a gradient on purpose: a gradient
   leaves one end of a wide banner too light to carry white type once the
   photograph is cropped by a short viewport, and this one has to hold a
   heavy headline at every size. */
.phero {
  position: relative; z-index: 1; isolation: isolate; overflow: hidden;
  background: var(--dark); color: #fff; text-align: center;
  padding-block: clamp(68px, 8.4vw, 124px);
}
.phero-bg {
  position: absolute; inset: 0; z-index: -2;
  background-image: var(--img); background-size: cover; background-position: center;
}
.phero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: rgba(10, 21, 33, .74);
}
.phero h1 {
  font-family: var(--serif); font-weight: 600; color: #fff;
  font-size: clamp(34px, 5.4vw, 64px); line-height: 1.08; letter-spacing: -.02em;
}
.phero .slead { margin-top: 16px; margin-inline: auto; color: var(--on-dark-2); }
.phero--center { text-align: center; }
.phero--slim { padding-block: clamp(54px, 6.4vw, 92px); }

.crumb-strip { border-bottom: 1px solid var(--rule); background: #fff; position: relative; z-index: 1; }
.breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: 9px; padding-block: 14px; font-size: 12.5px; color: var(--ink3); }
.breadcrumb a { color: var(--ink3); transition: color .2s var(--ease); }
.breadcrumb a:hover { color: var(--cyan-ink); }
.breadcrumb .cur { color: var(--ink); }

/* ============================================================
   LEGAL PROSE
   These are long documents, so the type relaxes: a measured column,
   a serif for the headings, and rules instead of boxes.
   ============================================================ */
.legal { max-width: 74ch; }
.legal .updated { font-size: 12.5px; color: var(--ink3); padding-bottom: 16px; margin-bottom: 30px; border-bottom: 1px solid var(--rule); }
.legal h2 { font-size: clamp(22px, 2.4vw, 30px); margin-top: 46px; margin-bottom: 15px; padding-top: 22px; border-top: 1px solid var(--rule); }
.legal h2:first-of-type { margin-top: 0; border-top: 0; padding-top: 0; }
.legal h3 { font-size: 19px; margin-top: 28px; margin-bottom: 10px; }
.legal p, .legal li { font-size: 16px; line-height: 1.78; color: var(--ink2); }
.legal p { margin-bottom: 16px; }
.legal ul, .legal ol { margin: 0 0 18px; padding-left: 0; }
.legal li { position: relative; padding-left: 24px; margin-bottom: 9px; }
.legal ul li::before { content: ""; position: absolute; left: 0; top: .72em; width: 8px; height: 2px; border-radius: 2px; background: var(--grad-fill); }
.legal ol { counter-reset: lg; }
.legal ol li { counter-increment: lg; }
.legal ol li::before { content: counter(lg) "."; position: absolute; left: 0; top: 0; font-family: var(--serif); font-weight: 600; font-size: 15px; line-height: 1.78; color: var(--cyan-ink); }
.legal a { color: var(--cyan-ink); text-decoration: underline; text-underline-offset: 2px; }
.legal table { width: 100%; border-collapse: collapse; margin-bottom: 20px; font-size: 15px; }
.legal th, .legal td { text-align: left; padding: 13px 16px 13px 0; border-bottom: 1px solid var(--rule); color: var(--ink2); vertical-align: top; }
.legal th { font-family: var(--sans); font-size: 12px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--ink); }
.legal-scroll { overflow-x: auto; }

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 1180px) {
  .foot-grid { grid-template-columns: 1.6fr 1fr 1fr; }
  .foot-contact { grid-column: 2 / -1; }
}

@media (max-width: 1080px) {
  .lc-rail { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .lc-phase:nth-child(odd) { border-left: 0; padding-left: 0; }
  .lc-phase:nth-child(n+3) { margin-top: 32px; }
  .cap-row { grid-template-columns: repeat(3, minmax(0,1fr)); }
  .steps { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .proof-figs { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .why-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .why-card:nth-child(3n+1) { border-left: 1px solid var(--rule); padding-left: 28px; }
  .why-card:nth-child(3n) { padding-right: 28px; }
  .why-card:nth-child(odd) { border-left: 0; padding-left: 0; }
  .why-card:nth-child(even) { padding-right: 0; }
  /* two per view — the flex basis is what main.js pages against */
  .ind-strip-item { flex: 0 0 calc((100% - 18px) / 2); }
}

@media (max-width: 960px) {
  :root { --gutter: 24px; }
  .about-split, .contact-grid { grid-template-columns: 1fr; }
}

@media (max-width: 940px) {
  /* Drawer. The header CTA is hidden at this width, so the drawer carries
     its own copy of the same action rather than losing it to the breakpoint. */
  .nav-toggle { display: flex; }
  .header-cta { display: none; }
  .nav {
    position: fixed; inset: var(--header-h) 0 auto; margin-left: 0;
    background: #fff; border-bottom: 1px solid var(--rule);
    padding: 22px var(--gutter) 28px; display: grid; gap: 0;
    box-shadow: 0 20px 40px rgba(13,27,42,.10);
    clip-path: inset(0 0 100% 0); pointer-events: none;
    transition: clip-path .4s var(--ease);
    max-height: calc(100svh - var(--header-h)); overflow-y: auto;
  }
  .nav.is-open { clip-path: inset(0 0 0 0); pointer-events: auto; }
  .nav-list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav-list li { border-bottom: 1px solid var(--rule); }
  .nav-link { display: block; padding: 16px 0; font-size: 16px; border-bottom: 0; }
  .nav-cta-mobile { display: block; margin-top: 22px; }
  .nav-cta-mobile .btn { width: 100%; }
}

@media (max-width: 860px) {
  .ind-carousel-btn { display: none; }
  .testi-grid { grid-template-columns: 1fr; }
  .foot-grid { grid-template-columns: 1fr 1fr; }
  .foot-about, .foot-contact { grid-column: 1 / -1; }
}

@media (max-width: 720px) {
  :root { --gutter: 20px; --header-h: 68px; --r-lg: 22px; }
  body { font-size: 16px; }
  .lc-rail, .steps, .proof-figs, .why-grid { grid-template-columns: 1fr; }
  .ind-strip-item { flex: 0 0 100%; min-height: 300px; }
  .lc-phase { border-left: 0; padding-inline: 0; }
  .why-card, .why-card:nth-child(3n+1), .why-card:nth-child(3n) { border-left: 0; padding-inline: 0; }
  .lc-phase + .lc-phase { margin-top: 26px; }
  .cap-row { grid-template-columns: 1fr; }
  .cform-row { grid-template-columns: 1fr; gap: 0; }
  .foot-grid { grid-template-columns: 1fr; }
  .foot-bottom { flex-direction: column; align-items: flex-start; }
  .foot-policies { margin-left: 0; gap: 14px; }
  .faq-a p { padding-right: 24px; }
  .faq-q { font-size: 16.5px; gap: 14px; padding-inline: 20px; }
  .cc-banner { bottom: 0; width: 100%; border-radius: var(--r) var(--r) 0 0; }
  .cc-actions .btn { flex: 1; }
  .hero-ctas .btn { width: 100%; }
  .slead--drop::first-letter { font-size: 3em; }
}

/* ---------- Print ---------- */
@media print {
  .site-header, .nav, .blobs, .cc-banner, .ind-carousel-btn, .edge-blur { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .site-footer { background: #fff !important; color: #000 !important; border: 1px solid #ccc; }
  .reveal { opacity: 1 !important; transform: none !important; }
  .method-bg, .method-scrim { display: none !important; }
}

/* ============================================================
   MOTION
   ------------------------------------------------------------
   The page had become very quiet: after the chips, buttons and
   rotator came out, the hero was two lines of type on a still
   wash and every section below it was prose. This adds movement
   that reads as depth rather than as decoration.

   Everything here animates transform, opacity or filter only —
   nothing that forces layout — and the whole file switches off
   under prefers-reduced-motion at the bottom.
   ============================================================ */

/* ---------- 1. the ground now drifts ----------
   The three soft fields were static. Moving them slowly on
   different periods means the light behind the page is never
   quite the same twice, which the eye reads as depth without
   ever resolving into a shape it can name. */
@keyframes drift-a {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(70px, 50px) scale(1.14); }
  66%      { transform: translate(-40px, 30px) scale(.94); }
}
@keyframes drift-b {
  0%, 100% { transform: translate(0, 0) scale(1); }
  40%      { transform: translate(-80px, 40px) scale(1.18); }
  75%      { transform: translate(30px, -30px) scale(.9); }
}
@keyframes drift-c {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50%      { transform: translate(60px, -60px) scale(1.2); }
}
.blobs i { will-change: transform; }
.blobs i:nth-child(1) { animation: drift-a 34s ease-in-out infinite; }
.blobs i:nth-child(2) { animation: drift-b 41s ease-in-out infinite; }
.blobs i:nth-child(3) { animation: drift-c 29s ease-in-out infinite; }

/* ---------- 2. the hero object ----------
   Three rings in real 3D space, tipped back on X and turning at
   different speeds around a lit core. It is the mark's own
   geometry — a crossing of arcs — rather than a stock shape, and
   it is built from five empty elements: no library, no canvas,
   no image, and nothing to download. */
.hero { position: relative; }
.hero-orbit {
  position: absolute; inset: 0; z-index: 0;
  display: grid; place-items: center;
  perspective: 1100px; pointer-events: none;
}
.hero-orbit i {
  position: absolute; border-radius: 50%;
  border: 1.5px solid rgba(3, 186, 242, .48);
  transform-style: preserve-3d; will-change: transform;
}
/* Each ring is tipped differently so the three never line up into
   a flat target; the odd periods keep the pattern from repeating
   on any comfortable interval. */
.hero-orbit i:nth-child(1) {
  width: min(78vw, 660px); aspect-ratio: 1;
  animation: ring-a 26s linear infinite;
}
.hero-orbit i:nth-child(2) {
  width: min(60vw, 500px); aspect-ratio: 1;
  border-color: rgba(103, 193, 60, .42);
  animation: ring-b 34s linear infinite;
}
.hero-orbit i:nth-child(3) {
  width: min(44vw, 360px); aspect-ratio: 1;
  border-color: rgba(3, 186, 242, .38);
  border-style: dashed;
  animation: ring-c 21s linear infinite;
}
@keyframes ring-a { from { transform: rotateX(72deg) rotateZ(0deg); }   to { transform: rotateX(72deg) rotateZ(360deg); } }
@keyframes ring-b { from { transform: rotateX(64deg) rotateY(18deg) rotateZ(360deg); } to { transform: rotateX(64deg) rotateY(18deg) rotateZ(0deg); } }
@keyframes ring-c { from { transform: rotateX(78deg) rotateZ(0deg); }   to { transform: rotateX(78deg) rotateZ(360deg); } }

/* the lit core, breathing */
.hero-orbit .core {
  position: absolute; width: min(38vw, 300px); aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle at 38% 34%,
              rgba(80, 210, 253, .48), rgba(3, 186, 242, .26) 42%, transparent 68%);
  filter: blur(26px);
  animation: breathe 9s ease-in-out infinite;
  will-change: transform, opacity;
}
@keyframes breathe {
  0%, 100% { transform: scale(1);    opacity: .85; }
  50%      { transform: scale(1.14); opacity: 1; }
}
/* a single bead riding the outer ring, so there is always one
   thing on screen that is unambiguously moving */
.hero-orbit .bead {
  position: absolute; width: min(78vw, 660px); aspect-ratio: 1;
  animation: ring-a 26s linear infinite;
  transform-style: preserve-3d;
}
.hero-orbit .bead::before {
  content: ""; position: absolute; top: -5px; left: 50%;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--lime);
  box-shadow: 0 0 20px 5px rgba(133, 249, 3, .6);
}
/* the headline has to sit in front of all of it */
.hero > .container { position: relative; z-index: 2; }

/* ---------- 3. headings arrive a word at a time ----------
   Fading a whole line in is what every template does, and wiping a whole
   line out from under a clipped edge — which is what this page used to do —
   is the same gesture with better manners. Neither reads as someone
   speaking.

   motion.js splits these headings into words. Each one rises, sharpens out
   of a blur and lands a beat after the one before it, so the sentence
   assembles left to right at roughly the speed it gets read. The lead
   underneath waits for the last word rather than racing it.

   The gradient italic travels as a single piece: splitting it would restart
   the gradient inside every word and lose the ramp across the phrase. */
.w { display: inline-block; }

/* Whatever entrance the element had is the words job now, and running both
   plays the move twice. */
.is-split { animation: none !important; opacity: 1 !important; filter: none !important; }
.reveal .stitle.is-split, .hero-title.is-split { clip-path: none; transform: none; }

/* Scoped to headings that actually animate. A bare .w rule would have hidden
   the headings on thank-you, 404 and 500 for good, because nothing on those
   pages ever adds .is-in — which is why motion.js does not split them. */
.reveal .stitle .w {
  opacity: 0; transform: translateY(.42em); filter: blur(7px);
  transition: opacity .6s var(--ease), transform .6s var(--ease), filter .6s var(--ease);
  transition-delay: calc(var(--i, 0) * 48ms);
}
.reveal.is-in .stitle .w { opacity: 1; transform: none; filter: blur(0); }

/* Above the fold, so it plays on load rather than waiting to be scrolled to. */
.reveal-eager.is-split .w {
  animation: rise-w .72s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 52ms);
}
@keyframes rise-w {
  from { opacity: 0; transform: translateY(.42em); filter: blur(7px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

.reveal .slead {
  opacity: 0; transform: translateY(13px);
  transition: opacity .66s var(--ease) .24s, transform .66s var(--ease) .24s;
}
.reveal.is-in .slead { opacity: 1; transform: none; }

/* ---------- the section settles as you arrive ----------
   Gated behind a class motion.js only adds when it is actually going to
   stage sections. Transform only, deliberately: nothing here may hide
   content, so the worst case if the script ever died mid-page is a block
   sitting 22px low rather than an invisible one. */
html.sec-anim main > section > .container {
  transform: translateY(34px) scale(.988);
  transition: transform 1.05s var(--ease);
}
html.sec-anim main > section.sec-in > .container { transform: none; }
/* the hero headline is above the fold, so it plays on load */
.hero-title { animation: wipe-in 1s var(--ease) both; }
@keyframes wipe-in {
  from { clip-path: inset(0 0 110% 0); transform: translateY(16px); }
  to   { clip-path: inset(-25% -5% -25% -5%); transform: none; }
}

/* ---------- 4. the lifecycle rail wakes column by column ----------
   Each phase already reveals on a stagger; this gives the rule
   above it something to do as it arrives. */
.lc-phase { position: relative; }
.lc-phase::before {
  content: ""; position: absolute; top: -2px; left: 0; right: 0; height: 2px;
  background: var(--grad-fill); transform: scaleX(0); transform-origin: left;
  transition: transform .8s var(--ease);
}
.lc-phase.is-in::before { transform: scaleX(1); }
.lc-phase:nth-child(2)::before { transition-delay: .12s; }
.lc-phase:nth-child(3)::before { transition-delay: .24s; }
.lc-phase:nth-child(4)::before { transition-delay: .36s; }

/* ---------- 5. the figures shimmer once as they land ----------
   The count-up already runs; this sweeps a highlight across the
   ramp as it finishes so the number reads as a result rather
   than as a label. */
.proof-fig .n {
  background-size: 220% 100%;
  animation: sweep 2.6s var(--ease) 1 both;
  animation-play-state: paused;
}
.proof-figs.is-in .proof-fig .n { animation-play-state: running; }
.proof-figs.is-in .proof-fig:nth-child(2) .n { animation-delay: .1s; }
.proof-figs.is-in .proof-fig:nth-child(3) .n { animation-delay: .2s; }
.proof-figs.is-in .proof-fig:nth-child(4) .n { animation-delay: .3s; }
@keyframes sweep {
  from { background-position: 100% 0; }
  to   { background-position: 0 0; }
}

/* ---------- 6. the method ----------
   This carried a pulse travelling the rule the four steps hang from. The
   section now announces itself as it takes the screen, and a second loop
   running forever underneath was competing with that rather than adding
   to it. What is left is the numeral lighting as each step passes the
   centre line — motion tied to where the reader is, not to a timer. */
.steps { position: relative; }

.step { position: relative; }
.step .snum { transition: color .5s var(--ease), transform .5s var(--ease); }
.step.is-lit .snum { transform: translateX(3px); }

/* ---------- 7. cards lift toward the pointer ----------
   A flat translate on hover is fine; tilting the card very
   slightly toward where the cursor entered is what makes it feel
   like an object rather than a rectangle. Two degrees is enough —
   past about four it starts to look like a toy. */
.why-card, .ind-strip-item, .testi, .proof-fig {
  transition: transform .45s var(--ease), box-shadow .45s var(--ease), border-color .3s var(--ease);
}
.ind-strip-item:hover { transform: translateY(-5px); }
.testi:hover { transform: translateY(-3px); }

/* ---------- 8. and none of it happens if the visitor said no ----------
   prefers-reduced-motion is a real request from people who get
   motion sickness or seizures from movement. Everything above is
   ornament; all of it stops, and the page still reads. */
@media (prefers-reduced-motion: reduce) {
  .blobs i,
  .hero-orbit i,
  .hero-orbit .core,
  .hero-orbit .bead,
  .proof-fig .n,
  .hero-title { animation: none !important; }
  .hero-orbit { display: none; }
  .reveal .stitle, .hero-title { clip-path: none; transform: none; transition: none; }
  .lc-phase::before { transform: scaleX(1); transition: none; }
  .ind-strip-item:hover, .testi:hover { transform: none; }
}

/* ============================================================
   MOTION, ROUND TWO
   Interaction rather than ambience: things that answer the reader
   instead of moving on their own. Ambient motion is what starts to
   feel like too much on a second visit; motion that responds does
   not, because the reader caused it.
   ============================================================ */

/* ---------- read progress ----------
   Two pixels, the mark's own ramp, pinned above the header. It is the
   only element on the page that reports something the page cannot
   otherwise say: how much is left. */

/* ---------- lists assemble instead of appearing ----------
   The phase column already arrives as one block. Letting its three
   capabilities follow a beat behind turns a card that appears into a
   column that fills. */
.lc-item {
  opacity: 0; transform: translateY(9px);
  transition: opacity .55s var(--ease), transform .55s var(--ease),
              padding-left .28s var(--ease), color .2s var(--ease);
}
.lc-phase.is-in .lc-item { opacity: 1; transform: none; }
.lc-phase.is-in .lc-item:nth-child(2) { transition-delay: .09s; }
.lc-phase.is-in .lc-item:nth-child(3) { transition-delay: .18s; }

/* the six reasons do the same, reading left to right then down */
.why-card { opacity: 0; transform: translateY(11px); transition: opacity .6s var(--ease), transform .6s var(--ease); }
.why-grid.is-in .why-card { opacity: 1; transform: none; }
.why-grid.is-in .why-card:nth-child(2) { transition-delay: .07s; }
.why-grid.is-in .why-card:nth-child(3) { transition-delay: .14s; }
.why-grid.is-in .why-card:nth-child(4) { transition-delay: .21s; }
.why-grid.is-in .why-card:nth-child(5) { transition-delay: .28s; }
.why-grid.is-in .why-card:nth-child(6) { transition-delay: .35s; }

/* and the four figures */
.proof-fig { opacity: 0; transform: translateY(11px); }
.proof-figs.is-in .proof-fig { opacity: 1; transform: none; transition: opacity .6s var(--ease), transform .6s var(--ease); }
.proof-figs.is-in .proof-fig:nth-child(2) { transition-delay: .08s; }
.proof-figs.is-in .proof-fig:nth-child(3) { transition-delay: .16s; }
.proof-figs.is-in .proof-fig:nth-child(4) { transition-delay: .24s; }

/* ---------- links draw their own underline ----------
   A colour change on hover is the cheapest possible signal. A rule that
   grows from the left costs nothing more and says the same thing with
   some manners. */
.foot-col a, a.tlink, .legal a, .breadcrumb a {
  position: relative; text-decoration: none;
}
.foot-col a::after, a.tlink::after, .legal a::after, .breadcrumb a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 1px;
  background: currentColor; transform: scaleX(0); transform-origin: right;
  transition: transform .3s var(--ease);
}
.foot-col a:hover::after, a.tlink:hover::after,
.legal a:hover::after, .breadcrumb a:hover::after {
  transform: scaleX(1); transform-origin: left;
}
/* .tlink and the legal links are underlined at rest, so they keep that and
   skip the drawn rule — two underlines on one word is just a mistake */
a.tlink, .legal a { text-decoration: underline; text-underline-offset: 3px; }
a.tlink::after, .legal a::after { display: none; }

/* ---------- the enquiry card lifts as you fill it ----------
   A focused field is already ringed; raising the card a hair while any
   field inside it has focus says the whole form is the active thing. */
.contact-card { transition: box-shadow .45s var(--ease), transform .45s var(--ease); }
.contact-card:focus-within { transform: translateY(-3px); box-shadow: 0 26px 56px rgba(13,27,42,.14); }

/* ---------- the inner-page banner settles ----------
   The photograph arrives very slightly over-scaled and comes to rest.
   It is half a second and eight per cent; any more and it reads as a
   slideshow rather than as the page arriving. */
.phero-bg { animation: settle 1.4s var(--ease) both; }
@keyframes settle {
  from { transform: scale(1.08); }
  to   { transform: scale(1); }
}

/* ---------- none of it under reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .psection { min-height: 0; display: block; }
  .w { opacity: 1 !important; transform: none !important; filter: none !important; animation: none !important; }
  .reveal .slead { opacity: 1 !important; transform: none !important; }
  html.sec-anim main > section > .container { transform: none !important; }
  .edge-blur { display: none; }
  .lc-item, .why-card, .proof-fig { opacity: 1 !important; transform: none !important; transition: none !important; }
  .foot-col a::after, .breadcrumb a::after { display: none; }
  .contact-card:focus-within { transform: none; }
  .phero-bg { animation: none; }
}

/* ============================================================
   SECTION BACKGROUNDS
   ------------------------------------------------------------
   A photograph was tried behind the method panel once before and
   taken out again: it was drawn at .14 under a half-grayscale and
   landed as a grey-green murk in one corner (see the note above
   .method-bg). The failure was the treatment, not the idea — a
   part-covered, desaturated image reads as a stain, because there
   is no edge at which it is meant to stop.

   So this borrows the treatment that already works on this site:
   the inner-page banner. Full bleed, cover, and a scrim across the
   whole section rather than a wash across part of it. The image
   either owns the section or only tints it; there is no in-between.

   Two modes:
     .psection--photo    dark band  — image + navy scrim, text goes light
     .psection--texture  light band — image veiled back to a tint

   Nothing here is fetched at first paint. The image sits in --img,
   which the [data-bg] loader in main.js sets once the band is within
   a screen and a half of the viewport.
   ============================================================ */

.sec-bg {
  position: absolute; inset: 0; z-index: -2;
  background-image: var(--img);
  background-size: cover;
  background-position: var(--img-pos, center);
  background-repeat: no-repeat;
}

/* ---------- the dark band ----------
   The token block is the whole mechanism. Every rule inside a
   section already paints from --ink / --ink2 / --rule / --cyan-ink,
   so redefining those six on the section itself turns the entire
   band light-on-dark without one per-element override. --grad-ink
   swaps to the lifted --grad-bright for the same reason: the
   headline italic and the proof figures clip a gradient to text,
   and the ink version of it disappears on navy. */
.psection--photo {
  isolation: isolate; overflow: hidden;
  background: #05070A;
  color: var(--ink);

  --ink:      #F2F7FA;
  --ink2:     #BDD0DE;
  --ink3:     #A9BECE;
  --rule:     rgba(255,255,255,.15);
  --rule2:    rgba(255,255,255,.26);
  --cyan-ink: #9DFF2E;
  --grad-ink: linear-gradient(100deg,#9DFF2E 0%,#CBFF7A 100%);
  --sh:       0 2px 12px rgba(0,0,0,.30);
  --sh-up:    0 18px 40px rgba(0,0,0,.42);
}

/* The scrim is a pool under the content rather than a flat sheet over
   the whole band. The corners run at about .31, which is open enough to
   read the photograph properly; the middle, where every line of type
   sits, closes to about .94. That shape is the whole trick — a flat
   scrim thin enough to show this much picture measures 2.5:1 on the
   body copy, and this measures 9.4:1, because the light never reaches
   the type in the first place. */
.psection--photo::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(118% 76% at 50% 50%,
      rgba(3,5,8,.93) 0%, rgba(3,5,8,.88) 36%, rgba(3,5,8,.34) 74%, rgba(3,5,8,.10) 100%),
    linear-gradient(180deg, rgba(3,5,8,.32) 0%, rgba(3,5,8,.22) 50%, rgba(3,5,8,.36) 100%);
}

/* No filter on these layers, deliberately. A CSS filter on an element
   the size of the viewport forces Chrome to re-rasterise it as the
   section moves under the scroll animation, and it locked the renderer
   up hard in testing. The scrim below does the colour work instead, at
   compositor cost of nothing. */

/* The proof figures are the one card on the page with a hardcoded
   white fill, so they are also the one thing the token swap cannot
   reach. On the dark band they become glass instead. */
.psection--photo .proof-fig {
  background: rgba(157,255,46,.05);
  border-color: rgba(157,255,46,.22);
  box-shadow: none;
}
/* No backdrop-filter here. A blur behind four cards, each sampling a
   full-viewport photograph, froze the compositor outright in testing.
   The translucent fill alone reads as glass and costs nothing. */
.psection--photo .proof-fig:hover { box-shadow: var(--sh-up); }

/* ---------- the light band ----------
   The veil is a horizontal gradient in the section's own background
   colour, opaque where the heading starts and thinning across. The
   photograph is therefore strongest in the margin the copy does not
   use, and the column that holds text sits on flat page white. */
.psection--texture {
  isolation: isolate; overflow: hidden;
  --veil-a: rgba(255,255,255,1);
  --veil-b: rgba(255,255,255,.72);
  --veil-c: rgba(255,255,255,.34);
}
.psection--alt.psection--texture {
  --veil-a: rgba(244,249,252,1);
  --veil-b: rgba(244,249,252,.74);
  --veil-c: rgba(244,249,252,.38);
}
.psection--texture > .sec-bg { opacity: .16; }
.psection--texture::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(104deg,
      var(--veil-a) 0%, var(--veil-a) 20%,
      var(--veil-b) 54%, var(--veil-c) 100%);
}
/* Mirrored, for a band whose text runs down the right. */
.psection--texture.is-flipped::after {
  background: linear-gradient(256deg,
      var(--veil-a) 0%, var(--veil-a) 20%,
      var(--veil-b) 54%, var(--veil-c) 100%);
}

/* One screen wide, the veil has no margin left to hide in: the copy
   runs the full width, so the photograph goes back to being a tint
   under all of it rather than a panel beside it. */
@media (max-width: 860px) {
  .psection--texture > .sec-bg { opacity: .12; }
  .psection--texture::after,
  .psection--texture.is-flipped::after {
    background: linear-gradient(180deg, var(--veil-a) 0%, var(--veil-b) 58%, var(--veil-b) 100%);
  }
  .psection--photo::after {
    background: linear-gradient(180deg, rgba(13,27,42,.94) 0%, rgba(13,27,42,.87) 50%, rgba(13,27,42,.95) 100%);
  }
}

@media print {
  .sec-bg { display: none !important; }
  .psection--photo { background: #fff !important; color: #000 !important; }
  .psection--photo::after, .psection--texture::after { display: none !important; }
}

/* ---------- the bands breathe ----------
   The brief asked for something "live" behind these sections. The two
   honest ways to get it are a video loop or this. The only loop to hand
   is stock boardroom footage at ~4 MB per format — off-subject for an
   Oracle practice, and the single heaviest thing that would ever ship on
   this page — so the movement comes from the photograph instead.

   A 44-second drift, transform only. The compositor moves a layer it has
   already rasterised, so this costs no repaint and no extra bytes, which
   is the opposite of the filters that locked the renderer up earlier.
   Always scaled past 1 so no edge can swing into view.

   Slow on purpose: you are meant to notice it between glances, not
   during one. A background that visibly moves while you read is a
   background that is competing with the reading. */

/* ============================================================
   THE LIGHT BAND
   ------------------------------------------------------------
   The third treatment, and the one these two sections now use.

     --photo    dark ground, light type, photograph under a scrim
     --texture  photograph held right back to a tint
     --light    photograph fully present, page keeps its own ink

   It only works on a genuinely bright picture. The veil is white, so it
   lifts the photograph rather than sinking it — run it over a dark image
   and you get grey, which is precisely how the method panel failed the
   first time anyone tried a photograph here. Both pictures used with it
   are sky-dominated, and that is a condition of the treatment, not a
   coincidence.

   Open at the top and bottom, closed through the middle. These bands are
   a full viewport tall with the content centred, so the margins above and
   below the type are where the photograph gets to be a photograph, while
   the type itself never leaves near-white. Measured on the darkest glass
   in either picture, --ink2 reads 5.0:1 through the closed middle.

   No token overrides: on a light ground the page's own ink is correct,
   which is the whole point of the treatment.
   ============================================================ */
.psection--light {
  isolation: isolate; overflow: hidden;
  background: var(--bg);
}
.psection--light::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg,
      rgba(250,253,254,.50) 0%,
      rgba(250,253,254,.86) 17%,
      rgba(250,253,254,.91) 50%,
      rgba(250,253,254,.86) 83%,
      rgba(250,253,254,.52) 100%);
}

/* The proof figures are white cards, which is right here — they read as
   paper laid on the photograph rather than as glass cut into it. Only
   the shadow needs lifting, to hold them off a busier ground. */
.psection--light .proof-fig { box-shadow: var(--sh-up); }

/* One screen wide there is no generous top and bottom margin left to
   open into, so the veil closes up and the photograph becomes a tint. */
@media (max-width: 860px) {
  .psection--light::after {
    background: linear-gradient(180deg,
        rgba(250,253,254,.80) 0%, rgba(250,253,254,.93) 22%,
        rgba(250,253,254,.93) 78%, rgba(250,253,254,.82) 100%);
  }
}


/* ---------- the supplied vector landscape ----------
   It is a portrait SVG (1134 x 1604) carrying its own cream ground. At
   `cover` in a band twice as wide as it is tall it scales to roughly
   2170px, and the visible window lands on the sun — which arrives as a
   large ochre arc cutting across the top of the section.

   Dropping the focal point puts the horizon across the band instead:
   water, then hills, which is the part of the composition that reads as
   a landscape at this crop. The sun stays above the frame. */
.sec-bg--art { --img-pos: center 74%; }

/* ---------- arriving in a band ----------
   The drift used to start on page load and run whether or not anyone was
   looking at the section. It now starts when the section does, and a
   one-shot settle runs in front of it: the photograph comes in a little
   over-scaled and eases down as the copy rises into place. Together they
   are what makes crossing into a band feel like arriving somewhere
   rather than like more page going past.

   Two animations on one element, both driving `transform`. The second is
   delayed by exactly the length of the first, and an animation that has
   not reached its delay does not apply — so the settle owns the transform
   for 1.2s and the drift takes over from precisely where it left off.
   That is why band-drift's `from` and band-settle's `to` are the same
   value; change one without the other and the handover jumps. */
@media (prefers-reduced-motion: no-preference) {
  .psection--photo.sec-in > .sec-bg,
  .psection--light.sec-in > .sec-bg,
  .psection--texture.sec-in > .sec-bg {
    animation:
      band-settle 1.2s var(--ease) both,
      band-drift 44s ease-in-out 1.2s infinite alternate;
  }
  @keyframes band-settle {
    from { transform: scale(1.16) translate3d(-.8%, -.6%, 0); }
    to   { transform: scale(1.06) translate3d(-.8%, -.6%, 0); }
  }
  @keyframes band-drift {
    from { transform: scale(1.06) translate3d(-.8%, -.6%, 0); }
    to   { transform: scale(1.12) translate3d(.8%,  .6%, 0); }
  }
}

/* Services: four tonal stages with the depth of layered paper sheets. */
#services.services-tonal-sheets { background: #f7fafb; }
#services .lc-rail { gap: 15px; padding: 8px 12px 18px 0; border-top: 0; align-items: stretch; }
#services .lc-phase {
  position: relative; isolation: isolate; min-width: 0;
  margin: 0; padding: 28px 22px 18px;
  border: 1px solid #d8e6e8; border-radius: 16px;
  box-shadow: 6px 7px 0 #fff, 11px 12px 0 #dae7eb,
              0 16px 30px rgba(13,27,42,.055);
  transition: opacity .55s var(--ease), transform .55s var(--ease),
              box-shadow .3s var(--ease), border-color .3s var(--ease);
}
#services .lc-phase:first-child { padding-left: 22px; }
#services .lc-phase:nth-child(1) { background: #f0fafc; }
#services .lc-phase:nth-child(2) { background: #f3f7fb; }
#services .lc-phase:nth-child(3) { background: #f3fbf6; }
#services .lc-phase:nth-child(4) { background: #f8fbea; }
#services .lc-phase.is-in { transform: none; }
#services .lc-phase.is-in:hover,
#services .lc-phase.is-in:focus-within {
  z-index: 5; transform: translateY(-7px) rotate(-.5deg);
  border-color: #afd2d9;
  box-shadow: 7px 9px 0 #fff, 12px 14px 0 #d4e4e8,
              0 22px 38px rgba(13,27,42,.10);
}
#services .lc-phase::before { display: none; }
#services .lc-head p { color: var(--ink2); }

/* Home consultation: H10's offset editorial frame, built from straight rules. */
#contact.contact-asymmetric {
  display: block; min-height: 0; overflow: hidden;
  background: #f7fafc;
  padding-block: clamp(96px, 10vw, 142px) clamp(110px, 11vw, 160px);
}
#contact.contact-asymmetric::before,
#contact.contact-asymmetric::after {
  content: ""; position: absolute; height: 1px; pointer-events: none;
  background: #bbd3da;
}
#contact.contact-asymmetric::before { top: 82px; left: 5%; width: 49%; }
#contact.contact-asymmetric::after { bottom: 86px; right: 5%; width: 54%; }
#contact.contact-asymmetric .contact-grid {
  grid-template-columns: minmax(0,.93fr) minmax(0,1.07fr);
  align-items: start; gap: clamp(48px, 6vw, 90px);
}
#contact.contact-asymmetric .contact-intro {
  max-width: 500px; padding-top: 28px;
}
#contact.contact-asymmetric .shead { margin-bottom: 0; }
#contact.contact-asymmetric .stitle {
  max-width: 500px; font-size: clamp(52px, 5.9vw, 82px);
  line-height: 1.06; letter-spacing: -.045em; text-wrap: balance;
}
#contact.contact-asymmetric .slead {
  max-width: 470px; margin-top: 26px;
  font-size: 17px; line-height: 1.78;
}
#contact.contact-asymmetric .consultation-card {
  margin-top: clamp(105px, 11vw, 155px);
  border-color: #d9e6ec; border-radius: 0 22px 22px 22px;
  background: #fff;
  box-shadow: -12px 12px 0 #e2eef0, 0 24px 40px rgba(31,83,96,.10);
}
#contact.contact-asymmetric .consultation-card h3 { font-size: clamp(26px, 2.5vw, 34px); }
#contact.contact-asymmetric .consultation-card .cfield input,
#contact.contact-asymmetric .consultation-card .cfield textarea {
  border-radius: 9px; background: #fbfdfe;
}
@media (max-width: 940px) {
  #contact.contact-asymmetric .contact-grid { grid-template-columns: 1fr; gap: 40px; }
  #contact.contact-asymmetric .contact-intro { max-width: 700px; padding-top: 0; }
  #contact.contact-asymmetric .consultation-card { max-width: 700px; margin-top: 0; }
}
@media (max-width: 600px) {
  #contact.contact-asymmetric { padding-block: 74px 100px; }
  #contact.contact-asymmetric::before { top: 46px; width: 56%; }
  #contact.contact-asymmetric::after { bottom: 56px; width: 48%; }
  #contact.contact-asymmetric .contact-grid { gap: 33px; }
  #contact.contact-asymmetric .stitle { font-size: clamp(43px, 11vw, 58px); }
  #contact.contact-asymmetric .slead { font-size: 15.5px; }
  #contact.contact-asymmetric .consultation-card {
    border-radius: 0 19px 19px 19px;
    box-shadow: -8px 8px 0 #e2eef0, 0 16px 32px rgba(31,83,96,.08);
  }
}

/* Why: warm paper cards that return from alternating sides on each visit. */
#why.why-paper-cards { background: #f5f3ed; }
#why .why-grid { gap: clamp(12px, 1.4vw, 20px); border-top: 0; }
#why .why-card {
  --enter-x: -42px; --enter-y: 15px;
  padding: 26px 25px 29px; border: 1px solid #e1e5de;
  border-radius: 16px; background: #fff;
  box-shadow: 0 10px 27px rgba(34,48,45,.055);
}
#why .why-card:nth-child(even) { --enter-x: 42px; }
#why .why-num { color: var(--green-ink); }
#why .why-grid.why-motion-ready .why-card {
  opacity: 0; transform: translate3d(var(--enter-x), var(--enter-y), 0);
  transition: opacity .7s var(--ease), transform .7s var(--ease), box-shadow .3s var(--ease);
  transition-delay: 0ms;
}
#why .why-grid.why-motion-ready.why-motion-in .why-card {
  opacity: 1; transform: translate3d(0, 0, 0);
  transition-delay: calc((var(--card-order, 0)) * 70ms);
}
#why .why-card:nth-child(1) { --card-order: 0; }
#why .why-card:nth-child(2) { --card-order: 1; }
#why .why-card:nth-child(3) { --card-order: 2; }
#why .why-card:nth-child(4) { --card-order: 3; }
#why .why-card:nth-child(5) { --card-order: 4; }
#why .why-card:nth-child(6) { --card-order: 5; }
#why .why-grid.why-motion-ready.why-motion-in .why-card:hover {
  transform: translateY(-5px); transition-delay: 0ms;
  box-shadow: 0 19px 35px rgba(34,48,45,.12);
}

@media (max-width: 1080px) {
  #services .lc-rail { gap: 17px; padding-right: 12px; }
  #services .lc-phase, #services .lc-phase:first-child,
  #services .lc-phase:nth-child(n+3) { margin: 0; padding: 25px 21px 18px; }
  #services .lc-phase.is-in { transform: none; }
  #services .lc-phase.is-in:hover, #services .lc-phase.is-in:focus-within { transform: translateY(-5px); }
}
@media (max-width: 720px) {
  #services .lc-rail { gap: 16px; }
  #services .lc-phase + .lc-phase { margin-top: 0; }
  #why .why-grid { gap: 13px; }
  #why .why-card { --enter-x: 0px; --enter-y: 30px; padding: 23px 21px 25px; }
}
@media (prefers-reduced-motion: reduce) {
  #services .lc-phase, #services .lc-phase.is-in,
  #services .lc-phase.is-in:hover, #services .lc-phase.is-in:focus-within,
  #why .why-grid .why-card, #why .why-grid .why-card:hover {
    opacity: 1 !important; transform: none !important; transition: none !important;
  }
}
