/* DAIXR landing. Dark canvas, cyan light, magenta for calls to action only. */

@font-face {
  font-family: "Space Grotesk";
  src: url("assets/fonts/space-grotesk.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --void: #08080d;
  --bg: #13131f;
  --bg-2: #1a1925;
  --bg-3: #22202e;
  --band: #0a2328;
  --band-2: #0e2d33;

  --ink: #ecebef;
  --ink-2: #bcb9c1;
  --muted: #9893a3;
  --border: #3a3547;
  --line: rgba(236, 235, 239, 0.08);
  --glass: rgba(236, 235, 239, 0.04);

  --mag: #ff3ba6;
  --mag-deep: #e02a8e;
  --cy: #5ed8e8;
  --cy-dim: #3aa3b4;
  --on-accent: #13131f;

  --font: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
  --maxw: 1200px;
  --gutter: clamp(16px, 4vw, 48px);
  --section-pad: clamp(80px, 11vw, 150px);
  --radius: 22px;
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
}

*,
*::before,
*::after { box-sizing: border-box; }

* { margin: 0; padding: 0; }

html {
  background: var(--void);
  color-scheme: dark;
  scroll-behavior: smooth;
  scroll-padding-top: 88px;
  -webkit-text-size-adjust: 100%;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img,
svg { display: block; }

ul,
ol { list-style: none; }

a { color: inherit; }

::selection { background: var(--cy); color: var(--on-accent); }

:focus-visible {
  outline: 2px solid var(--cy);
  outline-offset: 3px;
  border-radius: 6px;
}

.nowrap { white-space: nowrap; }

.accent { color: var(--cy); }

.skip {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 100;
  padding: 10px 16px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--on-accent);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform 160ms var(--ease);
}

.skip:focus { transform: none; }

/* ---------- buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  border-radius: 999px;
  font-weight: 600;
  font-size: 15px;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition: background 160ms var(--ease), color 160ms var(--ease), border-color 160ms var(--ease), transform 160ms var(--ease), box-shadow 160ms var(--ease);
}

.btn-mag {
  padding: 13px 18px;
  background: var(--mag);
  color: var(--on-accent);
}

.btn-mag:hover {
  background: #ff5ab3;
  box-shadow: 0 0 24px rgba(255, 59, 166, 0.35);
}

.btn-hero {
  padding: 5px 22px 5px 5px;
  background: var(--mag);
  color: var(--on-accent);
  box-shadow: 0 0 0 1px rgba(255, 59, 166, 0.45), 0 10px 40px rgba(255, 59, 166, 0.25);
}

.btn-hero .btn-icon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: var(--on-accent);
  color: var(--mag);
  transition: transform 160ms var(--ease);
}

.btn-hero:hover { transform: translateY(-1px); background: #ff5ab3; box-shadow: 0 0 0 1px rgba(255, 90, 179, 0.6), 0 12px 48px rgba(255, 59, 166, 0.35); }

.btn-hero:hover .btn-icon { transform: translateX(2px); }

.btn-ghost {
  padding: 13px 20px;
  border: 1px solid rgba(94, 216, 232, 0.35);
  background: rgba(94, 216, 232, 0.08);
  color: var(--ink);
}

.btn-ghost:hover { background: rgba(94, 216, 232, 0.16); border-color: var(--cy); }

/* ---------- nav ---------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  padding: 14px var(--gutter);
  /* backdrop-filter is not transitioned: while it animates, the open menu's dim layer is clipped. */
  transition: background 220ms var(--ease), border-color 220ms var(--ease);
  border-bottom: 1px solid transparent;
}

html:not(.js) .nav,
.nav.is-scrolled {
  background: rgba(8, 8, 13, 0.72);
  border-bottom-color: var(--line);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.nav-logo { justify-self: start; display: inline-flex; padding: 6px 0; }

.nav-logo img { width: 110px; height: auto; }

.nav-links {
  display: flex;
  gap: 2px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(19, 19, 31, 0.6);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.nav-links a {
  padding: 8px 16px;
  border-radius: 999px;
  color: var(--ink-2);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: background 140ms var(--ease), color 140ms var(--ease);
}

.nav-links a:hover { background: var(--glass); color: var(--ink); }

.nav-actions {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 10px;
}

.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(19, 19, 31, 0.6);
  cursor: pointer;
  place-items: center;
  align-content: center;
  gap: 5px;
}

.nav-toggle span {
  display: block;
  width: 16px;
  height: 1.5px;
  background: var(--ink);
  transition: transform 180ms var(--ease);
}

.nav-toggle[aria-expanded="true"] span:first-child { transform: translateY(3.25px) rotate(45deg); }

.nav-toggle[aria-expanded="true"] span:last-child { transform: translateY(-3.25px) rotate(-45deg); }

/* Load: the logo and actions blur in; the desktop link pill opens (see the hero's local block). */
.nav-logo,
.nav-actions { animation: hero-blur-in 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) 0.3s backwards; }

@keyframes hero-blur-in {
  from { opacity: 0; filter: blur(8px); }
}

/* ---------- lens (hero light arcs) ---------- */

.lens {
  position: absolute;
  inset: 0;
  overflow: clip;
  pointer-events: none;
}

.lens-top,
.lens-bottom {
  position: absolute;
  left: 50%;
  width: 2600px;
  height: 1500px;
  margin-left: -1300px;
  border-radius: 50%;
  background: var(--void);
}

.lens-top {
  bottom: calc(100% - var(--waist, 62%) + var(--gap, 34px));
  box-shadow:
    0 1px 0 0 rgba(190, 244, 250, 0.8),
    0 2px 3px 0 rgba(94, 216, 232, 0.6),
    0 1px 0 0 rgba(94, 216, 232, 0.5),
    0 2px 8px 0 rgba(94, 216, 232, 0.55),
    0 8px 34px 0 rgba(94, 216, 232, 0.3),
    0 32px 100px 0 rgba(94, 216, 232, 0.14);
}

.lens-bottom {
  top: calc(var(--waist, 62%) + var(--gap, 34px));
  box-shadow:
    0 -1px 0 0 rgba(190, 244, 250, 0.8),
    0 -2px 3px 0 rgba(94, 216, 232, 0.6),
    0 -1px 0 0 rgba(94, 216, 232, 0.45),
    0 -2px 8px 0 rgba(94, 216, 232, 0.5),
    0 -8px 34px 0 rgba(94, 216, 232, 0.26),
    0 -32px 100px 0 rgba(94, 216, 232, 0.12);
}

/* Extra strands: a wider, flatter ellipse and a narrower, steeper one share each arc's
   centre point, so the strands meet at the pinch and fan apart toward the edges. */
.lens-top::before,
.lens-top::after,
.lens-bottom::before,
.lens-bottom::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}

.lens-top::before { inset: -10% -14% 0 -14%; box-shadow: 0 1px 0 rgba(94, 216, 232, 0.28), 0 6px 26px rgba(94, 216, 232, 0.12); }
.lens-top::after { inset: -10% 9% 0 9%; box-shadow: 0 1px 0 rgba(94, 216, 232, 0.18), 0 3px 14px rgba(94, 216, 232, 0.1); }
.lens-bottom::before { inset: 0 -14% -10% -14%; box-shadow: 0 -1px 0 rgba(94, 216, 232, 0.26), 0 -6px 26px rgba(94, 216, 232, 0.11); }
.lens-bottom::after { inset: 0 9% -10% 9%; box-shadow: 0 -1px 0 rgba(94, 216, 232, 0.16), 0 -3px 14px rgba(94, 216, 232, 0.09); }

/* Concentrate the light around the pinch; arcs fade out toward the screen edges.
   The hero's arcs stay bright further out, like the reference. */
.hero-copy > .lens {
  -webkit-mask-image: radial-gradient(ellipse 80vw 75% at 50% var(--waist), #000 45%, transparent 100%);
  mask-image: radial-gradient(ellipse 80vw 75% at 50% var(--waist), #000 45%, transparent 100%);
  animation: lens-in 1.1s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
}

.closer-copy > .lens {
  -webkit-mask-image: radial-gradient(ellipse 58vw 60% at 50% var(--waist), #000 35%, transparent 100%);
  mask-image: radial-gradient(ellipse 58vw 60% at 50% var(--waist), #000 35%, transparent 100%);
}

@keyframes lens-in {
  from { opacity: 0; transform: scaleX(1.12); }
}

.lens-core {
  position: absolute;
  left: 50%;
  top: var(--waist, 62%);
  width: min(900px, 90vw);
  height: 240px;
  transform: translate(-50%, -50%);
  background:
    radial-gradient(closest-side, rgba(255, 59, 166, 0.10), transparent 70%),
    radial-gradient(closest-side, rgba(94, 216, 232, 0.12), transparent);
  filter: blur(10px);
}

/* ---------- hero ---------- */

.hero {
  position: relative;
  isolation: isolate;
  min-height: max(760px, 100svh);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 88px var(--gutter) 56px;
  background: var(--void);
  text-align: center;
  overflow: clip;
}

/* The lens hangs off the copy block so its waist always frames the CTA:
   bottom inset (420px) + half the CTA height (22px) puts the waist on the button. */
.hero-copy > .lens {
  inset: -900px -100vw -420px;
  z-index: -1;
  --waist: calc(100% - 442px);
  --gap: 56px;
}

/* Three stacked SVGs share one viewBox: the static circuit, the moving pulses, and the chip.
   The two animated layers are composited apart, so a pulse frame never repaints the chip's
   filters and a pad frame never repaints the traces. */
.circuit-wrap {
  position: relative;
  width: min(1200px, 100%);
  margin-top: 8px;
}

.circuit-wrap > svg {
  width: 100%;
  height: auto;
  overflow: visible;
}

.circuit-flow,
.circuit-chip {
  position: absolute;
  inset: 0;
  will-change: transform;
}

/* Circuit and chip. One 2.4 s cycle drives the idle loop: a riser pulse lands on the chip at
   1.2 s (and every 2.4 s after), the pad ring lights in a wave from where it lands, then pulses
   leave along the side traces and flash each node as they arrive. Resting styles are the
   finished, lit-but-still state, so reduced motion and disabled animations show a lit chip. */

.node-key {
  fill: url(#node-fill);
  stroke: rgba(236, 235, 239, 0.12);
  stroke-width: 1;
}

.node-hl {
  fill: none;
  stroke: url(#node-hl);
  stroke-width: 1;
}

.nodes path {
  stroke: var(--ink-2);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.traces path {
  stroke: rgba(236, 235, 239, 0.12);
  stroke-width: 1;
}

.risers path {
  stroke: rgba(94, 216, 232, 0.14);
  stroke-width: 1;
}

.trace-bundle { animation: hero-fade 0.6s ease-out 1.45s backwards; }

@keyframes hero-fade {
  from { opacity: 0; }
}

/* A pulse is one short dash (pathLength="100") running along its trace. The soft look comes
   from the static #pulse-soft filter on the .pulses group (blur only, no sharp core), so each
   frame restyles one path per pulse. */
.pulse {
  stroke: var(--cy);
  stroke-width: 4;
  stroke-dasharray: 10 200;
  stroke-linecap: round;
  opacity: 0;
  animation-duration: 2.4s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.p1, .p3 { animation-name: pulse-short; animation-delay: 1.65s; }
.p2, .p4 { animation-name: pulse-long; animation-delay: 1.65s; }
.g1 { animation-name: pulse-bundle; animation-delay: 2.25s; }
.g2 { animation-name: pulse-bundle; animation-delay: 2.85s; }
.r1 { animation-name: pulse-riser; animation-delay: 0.3s; }
.r2 { animation-name: pulse-riser; animation-delay: 1.5s; }
.r3 { animation-name: pulse-riser; animation-delay: 0.9s; }

@keyframes pulse-short {
  0% { stroke-dashoffset: 10; opacity: 1; }
  60% { stroke-dashoffset: -100; opacity: 1; }
  60.01%, 100% { stroke-dashoffset: -100; opacity: 0; }
}

@keyframes pulse-long {
  0% { stroke-dashoffset: 10; opacity: 1; }
  83% { stroke-dashoffset: -100; opacity: 1; }
  83.01%, 100% { stroke-dashoffset: -100; opacity: 0; }
}

/* The bundle lines fade out, so their pulses fade with them (about 1 s to the end). */
@keyframes pulse-bundle {
  0% { stroke-dashoffset: 10; opacity: 1; }
  40% { stroke-dashoffset: -100; opacity: 0; }
  100% { stroke-dashoffset: -100; opacity: 0; }
}

/* 0.9 s down the riser; it lands on the chip at 1.2 s. */
@keyframes pulse-riser {
  0% { stroke-dashoffset: 10; opacity: 1; }
  37.5% { stroke-dashoffset: -100; opacity: 1; }
  37.51%, 100% { stroke-dashoffset: -100; opacity: 0; }
}

.node-hit {
  stroke: var(--cy);
  stroke-width: 1;
  opacity: 0;
  animation: node-hit 2.4s linear infinite;
}

.node-hit.h-short { animation-delay: 3.1s; }
.node-hit.h-long { animation-delay: 3.65s; }

@keyframes node-hit {
  0% { opacity: 0.8; }
  14%, 100% { opacity: 0; }
}

.chip-spill {
  opacity: 0.6;
  animation: chip-spill 2.4s ease-out 1.2s infinite backwards;
}

@keyframes chip-spill {
  0%, 50%, 100% { opacity: 0.35; }
  6% { opacity: 0.95; }
  35% { opacity: 0.5; }
}

/* The halo flashes once as the chip arrives, then rests. */
.chip-halo {
  fill: rgba(94, 216, 232, 0.22);
  opacity: 0.7;
  animation: halo-flash 1s ease-out 0.1s backwards;
}

@keyframes halo-flash {
  0% { opacity: 0; }
  35% { opacity: 1; }
  100% { opacity: 0.7; }
}

.chip-pins path {
  fill: none;
  stroke: rgba(94, 216, 232, 0.25);
  stroke-width: 2;
  stroke-linecap: round;
}

.chip-pins .pin-hit { animation: pin-hit 2.4s linear 1.2s infinite; }

@keyframes pin-hit {
  0% { stroke: #c9f6fb; }
  8%, 100% { stroke: rgba(94, 216, 232, 0.25); }
}

.chip-body {
  fill: url(#chip-fill);
  stroke: url(#chip-rim);
  stroke-width: 1.5;
  animation: trace-draw 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) 0.2s backwards;
}

.chip-bevel {
  fill: none;
  stroke: url(#bevel);
  stroke-width: 1;
}

.chip-plate {
  fill: #090b11;
  stroke: rgba(94, 216, 232, 0.08);
  stroke-width: 2;
}

.pads-off > * {
  fill: rgba(94, 216, 232, 0.09);
  transform-box: fill-box;
  transform-origin: center;
  animation: pad-pop 0.3s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
  animation-delay: calc(0.85s + var(--i) * 18ms);
}

.pads-off > .hollow {
  fill: none;
  stroke: rgba(94, 216, 232, 0.35);
  stroke-width: 1;
}

/* The lit layer's filter (#pad-bloom) puts a soft halo under each crisp pad. Each pad lights in
   a wave from the riser's landing point (--w). Pads marked .b alternate bright and dim cycles;
   pads marked .c do the opposite, so the lit set changes from one cycle to the next. */
.pads-lit .pad {
  fill: #7ee6f2;
  opacity: 0;
  animation: pad-a 2.4s linear infinite backwards;
  animation-delay: calc(1.2s + var(--w));
}

.pads-lit .pad.b { animation: pad-b 4.8s linear calc(1.2s + var(--w)) infinite backwards; }
.pads-lit .pad.c { animation: pad-c 4.8s linear calc(1.2s + var(--w)) infinite backwards; }

/* At rest (reduced motion, animations off) a fixed set of pads stays lit. */
.pads-lit .pad.rest-on { opacity: 1; }

@keyframes pad-a {
  0% { opacity: 0; }
  5% { opacity: 1; }
  25% { opacity: 0.9; }
  45%, 100% { opacity: 0; }
}

/* Two 2.4 s cycles: bright, then dim. */
@keyframes pad-b {
  0% { opacity: 0; }
  2.5% { opacity: 1; }
  12.5% { opacity: 0.9; }
  22.5%, 50% { opacity: 0; }
  52.5% { opacity: 0.4; }
  60% { opacity: 0.3; }
  67.5%, 100% { opacity: 0; }
}

/* Two 2.4 s cycles: dim, then bright. */
@keyframes pad-c {
  0% { opacity: 0; }
  2.5% { opacity: 0.4; }
  10% { opacity: 0.3; }
  17.5%, 50% { opacity: 0; }
  52.5% { opacity: 1; }
  62.5% { opacity: 0.9; }
  72.5%, 100% { opacity: 0; }
}

.chip-dots {
  opacity: 0.3;
  animation: chip-dots 2.4s ease-out 1.25s infinite backwards;
}

@keyframes chip-dots {
  0%, 55%, 100% { opacity: 0.14; }
  8% { opacity: 0.5; }
}

.chip-mark-glow { opacity: 0.5; }

/* Loops run only while the hero is on screen (app.js adds .is-live to [data-live]). */
.js .hero:not(.is-live) :is(.pulse, .pad, .chip-spill, .chip-dots, .node-hit, .pin-hit) {
  animation-play-state: paused;
}

/* Load sequence: the chip scales in and its rim draws, the pads pop in clockwise,
   the risers draw down behind the first pulse, the nodes pop, then the traces draw outward. */
.chip-in {
  transform-box: fill-box;
  transform-origin: center;
  animation: chip-in 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) 0.1s backwards;
}

@keyframes chip-in {
  from { opacity: 0; transform: scale(0.55); filter: blur(8px); }
}

@keyframes trace-draw {
  from { stroke-dasharray: 100; stroke-dashoffset: 100; }
  to { stroke-dasharray: 100; stroke-dashoffset: 0; }
}

@keyframes pad-pop {
  from { opacity: 0; transform: scale(0.4); }
}

.risers path { animation: trace-draw 0.9s linear 0.3s backwards; }

.traces path { animation: trace-draw 0.6s cubic-bezier(0.2, 0.7, 0.2, 1) 1.45s backwards; }

.node-in {
  transform-box: fill-box;
  transform-origin: center;
  animation: node-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
}

.node-in.n1 { animation-delay: 1.45s; }
.node-in.n2 { animation-delay: 1.5s; }
.node-in.n3 { animation-delay: 1.55s; }
.node-in.n4 { animation-delay: 1.6s; }

@keyframes node-pop {
  from { opacity: 0; transform: scale(0.3); }
}

.hero-copy {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: clamp(-8px, -1vw, 0px);
}

.hero-title {
  max-width: 14ch;
  font-size: clamp(44px, min(6.4vw, 8.6vh), 88px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.035em;
  text-wrap: balance;
  /* Each word paints the fill itself. A text-clipped background on the h1 too would show
     every word's glyphs, unblurred, while the h1 animates. */
  --title-fill: linear-gradient(180deg, #ffffff 30%, #a9a6b4 100%);
  color: transparent;
  /* The line settles like a camera pulling back while its words arrive. */
  transform-origin: 50% 70%;
  animation: title-settle 1.5s cubic-bezier(0.33, 0, 0.2, 1) 0.45s backwards;
}

/* Phones settle less (--settle in the 640px block). A custom property, so rotating does not replay it. */
@keyframes title-settle {
  from { transform: scale(var(--settle, 1.12)) translateY(-0.06em); }
}

.hero-title .w {
  display: inline-block;
  animation: word-in 0.95s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
  animation-delay: calc(var(--i) * 60ms + 80ms);
  background: var(--title-fill);
  -webkit-background-clip: text;
  background-clip: text;
}

@keyframes word-in {
  from { opacity: 0; filter: blur(18px); transform: translateY(0.5em) scale(0.94); }
  60% { opacity: 1; }
}

.hero-sub {
  max-width: 34em;
  margin-top: clamp(18px, 2.4vw, 26px);
  color: var(--ink-2);
  font-size: clamp(16px, 1.5vw, 19px);
  line-height: 1.5;
  text-wrap: pretty;
  animation: hero-rise 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) 0.55s backwards;
}

@keyframes hero-rise {
  from { opacity: 0; filter: blur(8px); transform: translateY(8px); }
}

/* The CTA opens from its icon: the clip and the slide move together, so it grows from its centre. */
.hero-copy .btn-hero {
  margin-top: clamp(36px, 4vw, 56px);
  animation:
    cta-open 1.05s cubic-bezier(0.65, 0, 0.35, 1) 0.8s backwards,
    cta-glow 0.4s ease-out 1.85s backwards;
}

@keyframes cta-open {
  0% { opacity: 0; clip-path: inset(0 calc(100% - 44px) 0 0 round 999px); transform: translateX(calc(50% - 22px)) scale(0.7); }
  18% { opacity: 1; clip-path: inset(0 calc(100% - 44px) 0 0 round 999px); transform: translateX(calc(50% - 22px)); }
  100% { clip-path: inset(0 0 0 0 round 999px); transform: none; }
}

@keyframes cta-glow {
  from { box-shadow: none; }
}

/* The bottom arc sits about 34px under the hero copy, so this keeps the label clear of it on short screens. */
.hero-paths {
  position: relative;
  margin-top: auto;
  padding-top: clamp(72px, 9vh, 96px);
}

/* Soft floor glow under the tiles; it fades out inside the hero so no seam shows at its edge. */
.hero-paths::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -40px;
  width: 720px;
  height: 280px;
  transform: translateX(-50%);
  background: radial-gradient(closest-side, rgba(94, 216, 232, 0.1), transparent);
  z-index: -1;
  pointer-events: none;
  animation: hero-fade 0.8s ease-out 1.3s backwards;
}

.hero-paths-label {
  color: var(--ink);
  font-size: 15px;
  font-weight: 500;
  text-shadow: 0 0 12px var(--void), 0 0 4px var(--void);
  margin-bottom: 14px;
  animation: hero-rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) 1.15s backwards;
}

/* No blur on the tiles: a filter over their backdrop-filter repaints the whole row every frame. */
.tiles li { animation: tile-in 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) backwards; }
.tiles li:nth-child(1) { animation-delay: 1.3s; }
.tiles li:nth-child(2) { animation-delay: 1.42s; }
.tiles li:nth-child(3) { animation-delay: 1.54s; }

@keyframes tile-in {
  from { opacity: 0; transform: translateY(12px) scale(0.97); }
}

.tiles {
  display: grid;
  grid-template-columns: repeat(3, 132px);
  gap: 10px;
  justify-content: center;
}

.tile {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 104px;
  padding: 14px 10px;
  border: 1px solid rgba(236, 235, 239, 0.1);
  border-radius: 16px;
  background: linear-gradient(180deg, rgba(34, 32, 46, 0.75), rgba(19, 19, 31, 0.75));
  box-shadow: inset 0 1px 0 rgba(236, 235, 239, 0.08);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--ink);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.25;
  text-decoration: none;
  transition: border-color 160ms var(--ease), transform 160ms var(--ease), background 160ms var(--ease);
}

.tile:hover {
  border-color: rgba(94, 216, 232, 0.5);
  transform: translateY(-2px);
}

.tile-icon {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  padding: 7px;
  border-radius: 10px;
  background: rgba(236, 235, 239, 0.06);
  border: 1px solid rgba(236, 235, 239, 0.1);
  fill: none;
  stroke: var(--cy);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Hero: local responsive rules. */

/* The links sit in the menu below 821px, so only the desktop pill opens from its centre. */
@media (min-width: 821px) {
  .nav-links { animation: pill-open 0.9s cubic-bezier(0.65, 0, 0.35, 1) 0.35s backwards; }

  .nav-links a { animation: link-in 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) backwards; }
  .nav-links a:nth-child(1) { animation-delay: 0.55s; }
  .nav-links a:nth-child(2) { animation-delay: 0.65s; }
  .nav-links a:nth-child(3) { animation-delay: 0.75s; }
  .nav-links a:nth-child(4) { animation-delay: 0.85s; }
}

@keyframes pill-open {
  from { opacity: 0; clip-path: inset(0 calc(50% - 28px) round 999px); }
  15% { opacity: 1; }
  to { clip-path: inset(0 0 round 999px); }
}

@keyframes link-in {
  from { opacity: 0; filter: blur(6px); transform: translateY(8px); }
}

@media (max-width: 640px) {
  /* A bigger circuit keeps the chip's pad ring legible (about 75px wide at 390). */
  .circuit-wrap { width: 190%; max-width: none; margin-left: -45%; margin-right: -45%; }

  /* Only the chip shows on phones: the risers would sit in the nav bar beside Book a call. */
  .nodes,
  .node-hits,
  .traces,
  .trace-bundle,
  .risers,
  .pulses { display: none; }

  .hero-title { --settle: 1.06; }

  .hero-paths::before { width: 100%; }
}

/* ---------- shared section pieces ---------- */

.section-head {
  max-width: 820px;
  margin: 0 auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.pill {
  display: inline-block;
  padding: 5px 14px;
  border: 1px solid rgba(94, 216, 232, 0.4);
  border-radius: 999px;
  color: var(--ink);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.4;
}

.section-title {
  margin-top: 18px;
  font-size: clamp(32px, 4.6vw, 58px);
  font-weight: 500;
  line-height: 1.06;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.section-lede {
  max-width: 36em;
  margin-top: 18px;
  color: var(--ink-2);
  font-size: clamp(16px, 1.4vw, 18px);
  text-wrap: pretty;
}

.problem .section-title .accent { display: block; }

/* ---------- reveal on scroll (only when JS runs) ---------- */

.js .reveal {
  opacity: 0;
  filter: blur(10px);
  transform: translateY(16px);
  transition: opacity 900ms var(--ease), filter 900ms var(--ease), transform 900ms var(--ease);
  transition-delay: calc(var(--d, 0) * 110ms);
}

.js .reveal.is-in {
  opacity: 1;
  filter: none;
  transform: none;
}

/* Word by word: app.js splits .words headings into .w spans; each word blurs in on its own. */
.words .w { display: inline-block; }

.js .words .w {
  transition: opacity 800ms var(--ease), filter 800ms var(--ease), transform 800ms var(--ease);
  transition-delay: calc(var(--i, 0) * 110ms + var(--words-delay, 0ms));
}

.js .words:not(.is-in) .w {
  opacity: 0;
  filter: blur(12px);
  transform: translateY(12px);
}

/* A .words heading that is also .reveal keeps no block blur of its own. */
.js .reveal.words {
  opacity: 1;
  filter: none;
  transform: none;
}

.section-title .accent { text-shadow: 0 0 24px rgba(94, 216, 232, 0.35); }

/* ---------- how an engagement runs ---------- */

/* One black canvas from the hero into the steps; a pool of cyan light bridges the seam.
   Bottom padding carries the problem section's raised sheet (36px overlap). */
.how {
  position: relative;
  isolation: isolate;
  overflow-x: clip;
  overflow-y: visible;
  padding: clamp(40px, 6vw, 72px) var(--gutter) calc(clamp(56px, 7vw, 96px) + 36px);
  background: linear-gradient(to bottom, var(--void) 0%, var(--void) 62%, var(--bg) 100%);
  text-align: center;
}

.how::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -300px;
  width: min(1150px, 130vw);
  height: 820px;
  transform: translateX(-50%);
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(closest-side,
    rgba(94, 216, 232, 0.09) 0%,
    rgba(94, 216, 232, 0.045) 55%,
    rgba(94, 216, 232, 0.015) 88%,
    transparent 100%);
}

.how-title {
  font-size: clamp(26px, 3vw, 38px);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.15;
  text-wrap: balance;
}

/* A quicker word rhythm than the shared default, so heading and steps land together (about 1 s). */
.js .how-title .w {
  transition-duration: 600ms;
  transition-delay: calc(var(--i, 0) * 70ms + 60ms);
}

/* The light seam leads the heading in. */
.how-title::before {
  content: "";
  display: block;
  width: 140px;
  height: 1px;
  margin: 0 auto 28px;
  background: linear-gradient(90deg, transparent, rgba(94, 216, 232, 0.9), transparent);
  box-shadow: 0 0 10px 1px rgba(94, 216, 232, 0.35);
  transition: transform 700ms var(--ease), opacity 700ms var(--ease);
}

.js .how-title:not(.is-in)::before {
  opacity: 0;
  transform: scaleX(0.2);
}

/* Glow sits on the parent so it never fights a word's own blur filter. */
.how-title .accent { filter: drop-shadow(0 0 16px rgba(94, 216, 232, 0.35)); }

.how-title .accent .w {
  background: linear-gradient(180deg, #b4f1f8 0%, #5ed8e8 75%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  max-width: 860px;
  margin: clamp(36px, 5vw, 56px) auto 0;
}

.step {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0 clamp(8px, 2vw, 24px);
}

/* Connector: a cyan core brightest at the node, faded toward both tiles. */
.step:not(:last-child)::after {
  content: "";
  position: absolute;
  top: 36px;
  left: calc(50% + 52px);
  right: calc(-50% + 52px);
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(94, 216, 232, 0.5) 30%,
    rgba(94, 216, 232, 0.8) 50%,
    rgba(94, 216, 232, 0.5) 70%,
    transparent 100%);
  filter: drop-shadow(0 0 4px rgba(94, 216, 232, 0.55));
  transform-origin: left;
  transition: transform 380ms var(--ease) calc(var(--d, 0) * 200ms + 300ms);
}

/* Node: outline diamond that grows from a dot and turns in. */
.step:not(:last-child)::before {
  content: "";
  position: absolute;
  top: 29px;
  left: 100%;
  width: 14px;
  height: 14px;
  margin-left: -7px;
  border-radius: 3px;
  background: var(--void);
  border: 1.25px solid rgba(94, 216, 232, 0.85);
  box-shadow: 0 0 10px rgba(94, 216, 232, 0.35), inset 0 0 5px rgba(94, 216, 232, 0.25);
  transform: rotate(45deg) scale(1);
  z-index: 1;
  transition:
    opacity 260ms var(--ease) calc(var(--d, 0) * 200ms + 480ms),
    transform 420ms cubic-bezier(0.3, 1.4, 0.5, 1) calc(var(--d, 0) * 200ms + 480ms);
}

/* Tile: lit cyan glass with rivets, an inner plate and line art that glows. */
.step-icon {
  position: relative;
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  border-radius: 18px;
  border: 1px solid rgba(94, 216, 232, 0.42);
  background: linear-gradient(180deg, rgba(94, 216, 232, 0.2), rgba(94, 216, 232, 0.12)), #0a1217;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    inset 0 0 16px rgba(94, 216, 232, 0.18),
    0 0 28px rgba(94, 216, 232, 0.16);
  transition: opacity 550ms var(--ease), filter 550ms var(--ease), transform 550ms var(--ease);
  transition-delay: calc(var(--d, 0) * 200ms + 120ms);
}

.step-icon svg {
  width: 72px;
  height: 72px;
  fill: none;
  stroke: rgba(94, 216, 232, 0.85);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 0 3px rgba(94, 216, 232, 0.5));
}

.step-icon g * { vector-effect: non-scaling-stroke; }

.step-icon .rivet { fill: rgba(94, 216, 232, 0.5); stroke: none; }

.step-icon .plate { fill: rgba(94, 216, 232, 0.12); stroke: rgba(94, 216, 232, 0.5); }

.step-icon .pins { stroke: rgba(94, 216, 232, 0.6); }

.step-icon .sheen { fill: rgba(236, 235, 239, 0.06); stroke: none; }

.step-icon .ico-dot { fill: rgba(94, 216, 232, 0.6); stroke: none; }

.step-icon .hi { stroke: #a6eef7; stroke-width: 1; opacity: 0.9; }

.step-num {
  margin-top: 18px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
}

.step-title {
  margin-top: 4px;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.step-text {
  max-width: 22em;
  margin-top: 6px;
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1.5;
  text-wrap: balance;
}

.step-num,
.step-title,
.step-text {
  transition: opacity 500ms var(--ease), filter 500ms var(--ease), transform 500ms var(--ease);
}

.step-num { transition-delay: calc(var(--d, 0) * 200ms + 220ms); }

.step-title { transition-delay: calc(var(--d, 0) * 200ms + 250ms); }

.step-text { transition-delay: calc(var(--d, 0) * 200ms + 280ms); }

/* Desktop: the heading is the one trigger. Tiles start while the first words are still clearing:
   tile, text, line draws, node turns in, step by step. */
@media (min-width: 641px) {
  .js .how-title:not(.is-in) ~ .steps .step-icon {
    opacity: 0;
    filter: blur(10px);
    transform: translateY(12px) scale(0.94);
  }

  .js .how-title:not(.is-in) ~ .steps .step-num,
  .js .how-title:not(.is-in) ~ .steps .step-title,
  .js .how-title:not(.is-in) ~ .steps .step-text {
    opacity: 0;
    filter: blur(8px);
    transform: translateY(10px);
  }

  .js .how-title:not(.is-in) ~ .steps .step:not(:last-child)::after { transform: scaleX(0); }

  .js .how-title:not(.is-in) ~ .steps .step:not(:last-child)::before {
    opacity: 0;
    transform: rotate(0deg) scale(0.3);
  }

  /* Second connector: the node appears first, then its line draws out both ways. */
  .step:nth-child(2)::before { transition-delay: 500ms, 500ms; }

  .step:nth-child(2)::after {
    transform-origin: center;
    transition-delay: 620ms;
  }
}

@media (max-width: 640px) {
  .how { padding-bottom: calc(clamp(56px, 7vw, 96px) + 24px); }

  .how::before {
    top: -240px;
    width: 170vw;
    height: 560px;
  }

  /* Steps become a vertical timeline: icon left, text right, line between icons. */
  .steps {
    grid-template-columns: minmax(0, 1fr);
    gap: 28px;
    max-width: 420px;
  }

  .step {
    display: grid;
    grid-template-columns: 72px minmax(0, 1fr);
    column-gap: 18px;
    align-items: start;
    padding: 0;
    text-align: left;
  }

  .step-icon { grid-row: 1 / 4; }

  .step-num { margin-top: 6px; }

  .step-text { text-wrap: pretty; }

  .step:not(:last-child)::after {
    top: 80px;
    bottom: -24px;
    left: 36px;
    right: auto;
    width: 1px;
    height: auto;
    background: linear-gradient(180deg, transparent, rgba(94, 216, 232, 0.7) 50%, transparent);
    transform-origin: top;
  }

  /* The node sits at the middle of the vertical line. */
  .step:not(:last-child)::before {
    top: calc(80px + (100% - 80px + 24px) / 2);
    left: 36px;
    margin-top: -7px;
  }

  /* Each step is its own trigger here, so a step never plays below the fold.
     Fixed delays: every step runs the same short sequence when it arrives. */
  .step-icon { transition-delay: 0ms; }

  .step-num { transition-delay: 100ms; }

  .step-title { transition-delay: 130ms; }

  .step-text { transition-delay: 160ms; }

  .step:not(:last-child)::after { transition-delay: 240ms; }

  .step:not(:last-child)::before { transition-delay: 420ms, 420ms; }

  .js .step:not(.is-in) .step-icon {
    opacity: 0;
    filter: blur(10px);
    transform: translateY(12px) scale(0.94);
  }

  .js .step:not(.is-in) .step-num,
  .js .step:not(.is-in) .step-title,
  .js .step:not(.is-in) .step-text {
    opacity: 0;
    filter: blur(8px);
    transform: translateY(10px);
  }

  .js .step:not(.is-in):not(:last-child)::after { transform: scaleY(0); }

  .js .step:not(.is-in):not(:last-child)::before {
    opacity: 0;
    transform: rotate(0deg) scale(0.3);
  }
}

/* ---------- problem ---------- */

/* A raised sheet: it overlaps the section above and its rounded top edge slides over it. */
.problem {
  position: relative;
  z-index: 1;
  margin-top: -36px;
  padding: var(--section-pad) var(--gutter);
  border-radius: 36px 36px 0 0;
  border-top: 1px solid rgba(94, 216, 232, 0.3);
  box-shadow: 0 -28px 60px rgba(0, 0, 0, 0.45);
  background: linear-gradient(180deg, #1e1d2b 0, var(--bg-2) 260px);
}

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  max-width: var(--maxw);
  margin: clamp(40px, 5vw, 64px) auto 0;
  border-radius: var(--radius);
  overflow: clip;
  border: 1px solid var(--line);
  background: var(--bg-3);
  transition: opacity 800ms var(--ease), transform 800ms var(--ease);
}

/* The panel rises in without blur; the art arrives already lit. */
.js .split:not(.is-in) {
  opacity: 0;
  transform: translateY(24px);
}

.split-art {
  position: relative;
  min-height: 460px;
  overflow: clip;
  background:
    radial-gradient(9% 42% at 50% 78%, rgba(94, 216, 232, 0.16), transparent 70%),
    var(--void);
}

/* Lamps over the top of each orb, at the panel's top corners. */
.split-art::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(20% 24% at 4% 0%, rgba(236, 250, 252, 0.95), rgba(94, 216, 232, 0.35) 45%, transparent 75%),
    radial-gradient(20% 24% at 96% 0%, rgba(236, 250, 252, 0.95), rgba(94, 216, 232, 0.35) 45%, transparent 75%);
  mix-blend-mode: screen;
}

/* Glass lit from above: bright at the top, black at the bottom, a crisp neon rim with bloom. */
.orb {
  position: absolute;
  top: 50%;
  width: 640px;
  height: 640px;
  margin-top: -320px;
  border-radius: 50%;
  background:
    linear-gradient(180deg,
      rgba(170, 238, 246, 0.95) 12%,
      rgba(94, 216, 232, 0.8) 24%,
      rgba(58, 163, 180, 0.55) 42%,
      rgba(14, 45, 51, 0.6) 60%,
      rgba(8, 8, 13, 0.95) 82%);
  box-shadow:
    inset 0 0 0 3px rgba(206, 246, 251, 0.95),
    inset 0 0 16px 4px rgba(94, 216, 232, 0.9),
    inset 0 0 60px 6px rgba(94, 216, 232, 0.22),
    0 0 0 1px rgba(94, 216, 232, 0.9),
    0 0 22px 4px rgba(94, 216, 232, 0.75),
    0 0 70px 14px rgba(94, 216, 232, 0.3);
}

/* Light on the outer side at mid height. */
.orb::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(55% 45% at var(--lx) 42%, rgba(58, 163, 180, 0.6), transparent 72%);
}

/* 80px waist: wide enough for the 66px bubble to fall through. */
.orb-l { right: calc(50% + 40px); --lx: 22%; }

.orb-r { left: calc(50% + 40px); --lx: 78%; }

.bubble {
  position: absolute;
  z-index: 2;
  left: 50%;
  display: grid;
  place-items: center;
  border-radius: 50%;
}

.bubble svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: var(--on-accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.bubble-a {
  top: 16%;
  width: 60px;
  height: 60px;
  margin-left: -78px;
  background: var(--cy);
  box-shadow: 0 0 30px rgba(94, 216, 232, 0.55);
}

.bubble-b {
  top: 13%;
  width: 66px;
  height: 66px;
  margin-left: 6px;
  background: var(--ink);
}

.bubble-c {
  top: 36%;
  width: 62px;
  height: 62px;
  margin-left: -31px;
  background: rgba(94, 216, 232, 0.75);
}

.bubble-c svg { stroke: rgba(19, 19, 31, 0.55); }

.bubble-d {
  top: 74%;
  width: 62px;
  height: 62px;
  margin-left: -31px;
  background: rgba(46, 128, 142, 0.7);
}

.bubble-d svg { stroke: rgba(236, 235, 239, 0.5); }

.js .bubble { animation: bob 6s ease-in-out infinite; }

.js .bubble-b { animation-delay: -2s; }

.js .bubble-c { animation-delay: -4s; }

.js .bubble-d { animation-delay: -1s; }

@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8px); }
}

/* app.js drops the bubbles through the waist; it places each one with a transform.
   will-change gives each bubble its own layer: without it the moving bubbles are painted
   into the art, and every frame re-rasters the orbs' blurred glow (scroll jank). */
.split-art.is-falling .bubble {
  top: 0;
  left: 0;
  margin: 0;
  animation: none;
  will-change: transform;
}

.split-art-label {
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 56%;
  transform: translateX(-50%);
  padding-bottom: 4px;
  color: var(--ink);
  font-size: clamp(26px, 2.8vw, 36px);
  font-weight: 500;
  letter-spacing: -0.02em;
  white-space: nowrap;
  /* Keeps the label readable while the light bubble falls behind it. */
  filter: drop-shadow(0 0 6px rgba(8, 8, 13, 0.9));
  transition: color 700ms var(--ease) 400ms;
}

/* The underline draws left to right, then the text fades in. The label triggers on its
   own, so it plays when it reaches the screen. */
.split-art-label::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1.5px;
  background: var(--ink);
  transform-origin: left;
  transition: transform 600ms var(--ease) 200ms;
}

.js .split-art-label:not(.is-in) { color: transparent; }

.js .split-art-label:not(.is-in)::after { transform: scaleX(0); }

.split-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: clamp(28px, 4vw, 56px);
  background: linear-gradient(180deg, rgba(94, 216, 232, 0.07), rgba(94, 216, 232, 0.02));
}

.split-title {
  font-size: clamp(24px, 2.4vw, 30px);
  font-weight: 500;
  letter-spacing: -0.02em;
}

.fails {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
  margin: 28px 0 36px;
}

.fails li {
  position: relative;
  padding-left: 26px;
  color: var(--ink);
  font-size: 16px;
  line-height: 1.45;
}

.fails li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.45em;
  width: 7px;
  height: 7px;
  border-top: 2px solid var(--cy);
  border-right: 2px solid var(--cy);
  transform: rotate(45deg);
}

.split-body .btn-ghost { margin-top: auto; }

/* Title, then one row at a time, then the button. The title, the list and the button
   each trigger on their own, so on a slow scroll (or a phone, where the body sits below
   the art) the rows play when they arrive, not while still below the fold. When all
   three arrive together the delays keep the order. Only the entrance carries the
   delay, so the button's hover stays instant. No blur filter here: animating blur on
   these rows while the panel scrolls in made the scroll stutter. */
.split-title,
.fails li,
.split-body .btn {
  transition:
    opacity 600ms var(--ease) var(--t, 0ms),
    transform 600ms var(--ease) var(--t, 0ms),
    background 160ms var(--ease),
    color 160ms var(--ease),
    border-color 160ms var(--ease);
}

.fails li { --t: calc(120ms + var(--i, 0) * 110ms); }

.split-body .btn { --t: 800ms; }

.js .split-body:not(.is-in) .split-title,
.js .fails:not(.is-in) li,
.js .split-body .btn:not(.is-in) {
  opacity: 0;
  transform: translateY(8px);
}

.js .split-body .btn:not(.is-in) { transform: translateY(14px); }

/* A keyboard user who tabs here before the entrance has played still sees the button and its focus ring. */
.js .split-body .btn:focus-visible {
  opacity: 1;
  transform: none;
  transition-delay: 0ms;
}

@media (max-width: 640px) {
  .problem {
    margin-top: -24px;
    border-radius: 24px 24px 0 0;
  }
}

/* ---------- services ---------- */

/* The heading sits centred on its own stage; the canvas field streams dots toward it (app.js). */
.services {
  --stage-h: clamp(460px, 82svh, 900px);
  position: relative;
  padding-top: 0;
  background: var(--bg-2);
}

/* The canvas runs 96px past the stage; the band covers that strip. */
.dots {
  position: absolute;
  inset: 0 0 auto 0;
  height: calc(var(--stage-h) + 96px);
  z-index: 0;
  overflow: clip;
  pointer-events: none;
}

.dots-canvas {
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 700ms var(--ease);
}

.dots.is-live .dots-canvas { opacity: 1; }

.services .section-head {
  position: relative;
  z-index: 1;
  min-height: var(--stage-h);
  justify-content: center;
  padding: 0 var(--gutter);
}

/* Words blur in after the pill; the accent line lags one beat. */
#services-title { --words-delay: 200ms; }

#services-title .accent { --words-delay: 320ms; }

/* The tail finishes the heading wherever the band word is not shown: phones, and JS off.
   On desktop with JS the band word does that job, so the tail is kept for screen readers only. */
#services-title .title-tail {
  display: block;
  margin-top: 12px;
  color: var(--cy);
  font-size: clamp(20px, 5vw, 26px);
  line-height: 1.3;
  letter-spacing: -0.01em;
}

@media (min-width: 901px) {
  .js .title-tail {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* Without JS the band word cannot track the scroll, so drop the rail. */
html:not(.js) .band-rail { display: none; }

html:not(.js) .band-inner { grid-template-columns: minmax(0, 1fr); }

.band {
  position: relative;
  z-index: 1;
  margin-top: 0;
  background:
    radial-gradient(80% 60% at 20% 0%, rgba(94, 216, 232, 0.14), transparent 60%),
    linear-gradient(180deg, var(--band-2), var(--band));
  border-top: 1px solid rgba(94, 216, 232, 0.32);
  border-bottom: 1px solid rgba(94, 216, 232, 0.18);
}

/* Lit top edge. Inside the padding box, so the entrance wipe draws it left to right. */
.band::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(94, 216, 232, 0.7) 30%, rgba(94, 216, 232, 0.7) 70%, transparent);
  box-shadow: 0 0 14px rgba(94, 216, 232, 0.35);
}

/* Entrance: the band wipes in from the left (app.js adds .is-in when its top is 60% down
   the screen, or .is-in.is-instant when the visit lands inside it). */
.js .band:not(.is-in) { clip-path: inset(0 100% 0 0); }

.js .band.is-in:not(.is-instant) {
  animation: band-wipe 950ms cubic-bezier(0.5, 0, 0.45, 1) backwards;
}

@keyframes band-wipe {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0 0 0 0); }
}

/* A lit line rides the wipe edge: the overlay's right 2px, moved on the same curve.
   It never passes the band's right edge, so the page gains no sideways scroll. */
.band::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(90deg, transparent calc(100% - 48px), rgba(94, 216, 232, 0.28) calc(100% - 3px), #5ed8e8 calc(100% - 2px));
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 120px, #000 calc(100% - 120px), transparent);
  mask-image: linear-gradient(180deg, transparent, #000 120px, #000 calc(100% - 120px), transparent);
}

.js .band.is-in:not(.is-instant)::after {
  animation: band-edge 950ms cubic-bezier(0.5, 0, 0.45, 1) backwards;
}

@keyframes band-edge {
  0% { transform: translateX(-100%); opacity: 1; }
  80% { opacity: 1; }
  100% { transform: translateX(0); opacity: 0; }
}

.band-inner {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(24px, 5vw, 80px);
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.band-rail {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: clamp(28px, 3.4vw, 52px);
  /* Before it pins, the word sits 200px under the band's top edge, so it is on screen while
     the band wipes in. Pinned, it is still centred on the screen. */
  margin-top: calc(200px - 50vh);
  margin-top: calc(200px - 50svh);
  /* Its empty top half overlaps the heading; it holds nothing to click or select. */
  pointer-events: none;
}

/* The word and the dial drift in from the left while the band wipes in. */
.band-word,
.band-dots { transition: translate 1100ms cubic-bezier(0.22, 1, 0.36, 1) 120ms; }

.js .band:not(.is-in) .band-word,
.js .band:not(.is-in) .band-dots { translate: -56px 0; }

.band-word {
  display: grid;
  justify-items: end;
  text-align: right;
}

.word {
  grid-area: 1 / 1;
  align-self: center;
  color: var(--cy);
  font-size: clamp(36px, 4.4vw, 64px);
  font-weight: 500;
  line-height: 1.02;
  letter-spacing: -0.03em;
  max-width: 8em;
  opacity: 0;
  filter: blur(8px);
  transform: translateY(12px);
  transition: opacity 500ms var(--ease), filter 500ms var(--ease), transform 500ms var(--ease);
}

.word.is-active { opacity: 1; filter: none; transform: none; }

/* A dial: seven dots on an arc bowing toward the text. app.js sets data-k to each dot's
   offset from the active service; the outer two at each end are decorative ghosts. */
.band-dots {
  position: relative;
  width: 40px;
  height: 380px;
}

.dot {
  --d: clamp(26px, 2.4vw, 36px);
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--d);
  height: var(--d);
  margin: calc(var(--d) / -2);
  border-radius: 50%;
  background: var(--cy);
  transform: translate(var(--x), var(--y)) scale(var(--s));
  opacity: var(--o);
  transition: transform 700ms cubic-bezier(0.22, 1, 0.36, 1), opacity 700ms, box-shadow 700ms;
}

.dot[data-k="0"] {
  --x: 0px;
  --y: 0px;
  --s: 1;
  --o: 1;
  box-shadow: 0 0 0 6px rgba(94, 216, 232, 0.10), 0 0 28px rgba(94, 216, 232, 0.55);
}

.dot[data-k="1"] { --x: -30px; --y: 108px; --s: 0.62; --o: 0.55; }

.dot[data-k="-1"] { --x: -30px; --y: -108px; --s: 0.62; --o: 0.55; }

.dot[data-k="2"] { --x: -76px; --y: 168px; --s: 0.3; --o: 0.16; }

.dot[data-k="-2"] { --x: -76px; --y: -168px; --s: 0.3; --o: 0.16; }

.dot[data-k="3"],
.dot[data-k="4"] { --x: -110px; --y: 196px; --s: 0.15; --o: 0; }

.dot[data-k="-3"],
.dot[data-k="-4"] { --x: -110px; --y: -196px; --s: 0.15; --o: 0; }

/* The faint outer dots fade in last on entrance. */
.js .band:not(.is-in) .dot[data-k="2"],
.js .band:not(.is-in) .dot[data-k="-2"] { opacity: 0; }

.js .band.is-entering .dot { transition-delay: 500ms; }

/* Top room must exceed scroll-padding-top (88px) so a jump to the first service pins the rail.
   Bottom room keeps the rail pinned when a jump lands the last service (52vh tall) under the nav. */
.band-steps { padding: max(12vh, 100px) 0 40vh; }

.svc {
  min-height: 52vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 28px 0;
}

/* With the band word on screen, the article title would repeat it. Keep it for screen readers. */
@media (min-width: 901px) {
  .js .svc-title {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .hero-title {
    max-width: none;
    font-size: clamp(52px, min(5.4vw, 8.6vh), 78px);
  }

  .js .svc-lede { margin-top: 10px; font-size: 17px; }
}

/* Inactive services dim only their number and lede; item titles stay lit. */
.svc-num,
.svc-lede { transition: opacity 500ms var(--ease); }

.js .svc:not(.is-active) .svc-num,
.js .svc:not(.is-active) .svc-lede { opacity: 0.75; }

.svc-num {
  color: var(--cy);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
}

.svc-title {
  margin-top: 6px;
  font-size: clamp(24px, 2.4vw, 30px);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.svc-lede {
  max-width: 30em;
  margin-top: 8px;
  color: var(--ink-2);
  font-size: 16px;
}

.svc-items {
  display: grid;
  gap: 22px;
  margin-top: 28px;
}

.svc-items li {
  position: relative;
  padding-left: 20px;
  transition: translate 900ms cubic-bezier(0.22, 1, 0.36, 1) calc(200ms + var(--i, 0) * 60ms);
}

.svc-items li:nth-child(2) { --i: 1; }

.svc-items li:nth-child(3) { --i: 2; }

/* On entrance the items start spread apart and gather. */
.js .band:not(.is-in) .svc-items li { translate: 0 calc(var(--i, 0) * 18px); }

/* Each item has a track and a fill; app.js sets --fill as the item passes the reading line.
   Without JS the fill shows full. */
.svc-items li::before,
.svc-items li::after {
  content: "";
  position: absolute;
  left: 0;
  top: 3px;
  bottom: 3px;
  width: 3px;
  border-radius: 3px;
}

.svc-items li::before { background: rgba(94, 216, 232, 0.16); }

.svc-items li::after {
  background: linear-gradient(180deg, rgba(94, 216, 232, 0.55), #5ed8e8);
  box-shadow: 0 0 10px rgba(94, 216, 232, 0.5);
  transform: scaleY(var(--fill, 1));
  transform-origin: top;
}

/* With JS only the item being read shows its bar; the others keep the bare track. */
.js .svc-items li::after {
  opacity: 0;
  transform: scaleY(var(--fill, 0));
  transition: opacity 300ms var(--ease);
}

.js .svc-items li.is-current::after { opacity: 1; }

.svc-items h4 {
  color: var(--cy);
  font-size: clamp(18px, 1.6vw, 21px);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.3;
}

.svc-items p {
  margin-top: 4px;
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1.5;
  text-wrap: pretty;
}

/* The item being read lights its description; the rest stay muted. */
.js .svc-items p {
  color: var(--muted);
  transition: color 300ms var(--ease);
}

.js .svc-items li.is-current p { color: var(--ink); }

@media (max-width: 900px) {
  .js .band.is-in:not(.is-instant),
  .js .band.is-in:not(.is-instant)::after { animation-duration: 700ms; }

  .svc-items li::before,
  .svc-items li::after { width: 2px; }

  .js .svc:not(.is-active) .svc-num,
  .js .svc:not(.is-active) .svc-lede { opacity: 1; }
}

@media (max-width: 600px) {
  .services { --stage-h: clamp(420px, 68svh, 560px); }
}

/* With motion, the band rides up under the heading and covers the lower streams, which show
   through its unrevealed part until the wipe. Reduced motion keeps the band below the stage. */
@media (min-width: 901px) and (prefers-reduced-motion: no-preference) {
  .js .services .band { margin-top: calc(var(--stage-h) * -0.5 + 95px); }
}

@media (max-width: 900px) and (prefers-reduced-motion: no-preference) {
  .js .services .band { margin-top: calc(var(--stage-h) * -0.25); }
}

/* Reduced motion: the finished state, whether or not app.js has run yet. */
@media (prefers-reduced-motion: reduce) {
  .band::after { display: none; }

  .js .band:not(.is-in) { clip-path: none; }

  .js .band:not(.is-in) .band-word,
  .js .band:not(.is-in) .band-dots,
  .js .band:not(.is-in) .svc-items li { translate: none; }

  .js .band:not(.is-in) .dot[data-k="2"],
  .js .band:not(.is-in) .dot[data-k="-2"] { opacity: var(--o); }

  .js .svc-items li::after {
    opacity: 1;
    transform: none;
  }

  .js .svc-items p { color: var(--ink-2); }
}

/* ---------- track record ---------- */

.record {
  padding: var(--section-pad) var(--gutter);
  background:
    radial-gradient(60% 45% at 50% 60%, rgba(94, 216, 232, 0.07), transparent 70%),
    var(--bg);
}

/* Static hairline above the pill, as the reference lights a short line above its dark headings. */
.record .section-head::before {
  content: "";
  width: 140px;
  height: 1px;
  margin-bottom: 28px;
  background: linear-gradient(90deg, transparent, rgba(94, 216, 232, 0.6), transparent);
  box-shadow: 0 0 12px rgba(94, 216, 232, 0.4);
}

/* The stats hang off one lit connector line that draws left to right, with a diamond node per stat. */
.stats {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 4vw, 56px);
  max-width: var(--maxw);
  margin: clamp(40px, 5vw, 64px) auto clamp(40px, 5vw, 64px);
  padding-top: 28px;
}

.stats::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  /* Brightest under the three diamonds (3%, 37.5%, 72.5% of the row), dimmer between them. */
  background: linear-gradient(90deg,
    transparent 0,
    rgba(94, 216, 232, 0.75) 3%,
    rgba(94, 216, 232, 0.3) 20%,
    rgba(94, 216, 232, 0.75) 37.5%,
    rgba(94, 216, 232, 0.3) 55%,
    rgba(94, 216, 232, 0.75) 72.5%,
    rgba(94, 216, 232, 0.3) 88%,
    transparent);
  filter: drop-shadow(0 0 4px rgba(94, 216, 232, 0.55));
  transform-origin: left;
  transition: transform 1.4s var(--ease) 0.2s;
}

.js .stats:not(.is-in)::before { transform: scaleX(0); }

.stat {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 8px 0 8px clamp(18px, 2vw, 28px);
}

/* Diamond on the line, the same node as the steps connector: 28px of .stats padding above the stat, plus half the diamond. */
.stat::before {
  content: "";
  position: absolute;
  top: -34.5px;
  left: clamp(18px, 2vw, 28px);
  width: 13px;
  height: 13px;
  border: 1.25px solid rgba(94, 216, 232, 0.85);
  border-radius: 3px;
  background: var(--bg);
  box-shadow: 0 0 10px rgba(94, 216, 232, 0.35), inset 0 0 5px rgba(94, 216, 232, 0.25);
  transform: rotate(45deg);
  transition: opacity 0.5s var(--ease), transform 420ms cubic-bezier(0.3, 1.4, 0.5, 1);
  transition-delay: calc(300ms + var(--d, 0) * 350ms);
}

/* Each stat sharpens just after its diamond lands, not before it. */
.js .stat.reveal { transition-delay: calc(var(--d, 0) * 350ms + 150ms); }

.js .stat:not(.is-in)::before {
  opacity: 0;
  transform: rotate(45deg) scale(0.4);
}

.stat-unit {
  margin-left: 0.15em;
  font-size: 0.5em;
  letter-spacing: 0;
}

.stat-num {
  font-size: clamp(40px, 4.6vw, 64px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.04em;
  background: linear-gradient(180deg, #ffffff, #9fe9f3);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 18px rgba(94, 216, 232, 0.28));
}

.stat-label {
  max-width: 24em;
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1.5;
}

.cases {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  max-width: var(--maxw);
  margin: 14px auto 0;
}

/* Glass tile: top-edge light, a cyan bloom in the top-left corner, a frosted inner rim. */
.case {
  --glass-bloom: rgba(94, 216, 232, 0.10);
  padding: clamp(22px, 2.6vw, 30px);
  border: 1px solid rgba(236, 235, 239, 0.10);
  border-radius: var(--radius);
  background:
    linear-gradient(90deg, transparent 12%, rgba(94, 216, 232, 0.5) 50%, transparent 88%) top / 100% 1px no-repeat,
    radial-gradient(60% 55% at 18% 0%, var(--glass-bloom), transparent 70%),
    linear-gradient(180deg, rgba(34, 32, 46, 0.72), rgba(19, 19, 31, 0.6));
  box-shadow:
    inset 0 0 18px rgba(236, 235, 239, 0.06),
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    0 24px 60px -30px rgba(0, 0, 0, 0.8);
  transition: border-color 200ms var(--ease), box-shadow 200ms var(--ease), translate 200ms var(--ease);
}

/* Keeps the .reveal entrance (with its stagger) and adds the hover; the lift uses translate so it never waits on the reveal. */
.js .case {
  transition:
    opacity 900ms var(--ease) calc(var(--d, 0) * 110ms),
    filter 900ms var(--ease) calc(var(--d, 0) * 110ms),
    transform 900ms var(--ease) calc(var(--d, 0) * 110ms),
    border-color 200ms var(--ease),
    box-shadow 200ms var(--ease),
    translate 200ms var(--ease);
}

@media (hover: hover) {
  .case:hover {
    border-color: rgba(94, 216, 232, 0.35);
    translate: 0 -2px;
    box-shadow:
      inset 0 0 18px rgba(236, 235, 239, 0.06),
      inset 0 1px 0 rgba(255, 255, 255, 0.10),
      0 24px 60px -30px rgba(0, 0, 0, 0.8),
      0 0 40px -12px rgba(94, 216, 232, 0.3);
  }
}

.case-tag {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(94, 216, 232, 0.1);
  color: var(--cy);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.case-title {
  margin-top: 16px;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.25;
}

.case-text {
  margin-top: 8px;
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1.55;
}

@media (max-width: 900px) {
  /* The connector turns vertical, down the left edge, like the steps timeline on phones. */
  .stats {
    gap: 0;
    padding-top: 0;
  }

  .stats::before {
    top: 0;
    bottom: 0;
    left: 0;
    right: auto;
    width: 1px;
    height: auto;
    background: rgba(94, 216, 232, 0.5);
    transform-origin: top;
  }

  .js .stats:not(.is-in)::before { transform: scaleY(0); }

  .stat { padding: 20px 0 20px 22px; }

  .stat::before {
    top: 28px;
    left: -6.5px;
  }
}

@media (max-width: 640px) {
  /* Stacked cards would glow as one block at full bloom. */
  .case { --glass-bloom: rgba(94, 216, 232, 0.04); }
}

@media (prefers-reduced-motion: reduce) {
  .case:hover { translate: none; }
}

/* ---------- about ---------- */

.about {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
  gap: clamp(32px, 6vw, 96px);
  align-items: start;
  max-width: calc(var(--maxw) + var(--gutter) * 2);
  margin: 0 auto;
  padding: var(--section-pad) var(--gutter);
  position: relative;
  isolation: isolate;
}

/* Soft cyan pool behind the heading, so the left column is not unlit. */
.about::before {
  content: "";
  position: absolute;
  left: var(--gutter);
  top: calc(var(--section-pad) - 60px);
  width: 600px;
  max-width: 80%;
  height: 400px;
  background: radial-gradient(closest-side, rgba(94, 216, 232, 0.06), transparent);
  z-index: -1;
  pointer-events: none;
}

.about-copy .section-title { max-width: 16ch; }

.about-text {
  max-width: 34em;
  margin-top: 20px;
  color: var(--ink-2);
  font-size: clamp(16px, 1.4vw, 18px);
  text-wrap: pretty;
}

/* Body copy follows the heading's word reveal instead of landing before it. */
.js .about-text.reveal { transition-delay: 500ms; }

/* Twelve words at the shared 110ms stagger land after the body and rows; tighten them. */
#about-title .w { transition-delay: calc(var(--i, 0) * 60ms); }

/* Credentials tile: a glass tile (as .case) holding a cyan rail with a dot per row, a short timeline.
   On entrance the rail draws down and each dot lights as the rail reaches it; "Now" is the active dot. */
.creds {
  --glass-bloom: rgba(94, 216, 232, 0.10);
  position: relative;
  padding: 6px clamp(22px, 2.6vw, 30px);
  border: 1px solid rgba(236, 235, 239, 0.10);
  border-radius: var(--radius);
  background:
    linear-gradient(90deg, transparent 12%, rgba(94, 216, 232, 0.5) 50%, transparent 88%) top / 100% 1px no-repeat,
    radial-gradient(60% 55% at 18% 0%, var(--glass-bloom), transparent 70%),
    linear-gradient(180deg, rgba(34, 32, 46, 0.72), rgba(19, 19, 31, 0.6));
  box-shadow:
    inset 0 0 18px rgba(236, 235, 239, 0.06),
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    0 24px 60px -30px rgba(0, 0, 0, 0.8),
    0 0 90px -40px rgba(94, 216, 232, 0.35);
}

.creds::before {
  content: "";
  position: absolute;
  top: 30px;
  bottom: 30px;
  left: calc(clamp(22px, 2.6vw, 30px) + 3.5px);
  width: 1px;
  background: linear-gradient(180deg, rgba(94, 216, 232, 0.6), rgba(94, 216, 232, 0.15));
  transform-origin: top;
  transition: transform 1.2s var(--ease) 0.15s;
}

.js .creds:not(.is-in)::before { transform: scaleY(0); }

.creds li {
  position: relative;
  display: grid;
  grid-template-columns: 7.5em minmax(0, 1fr);
  gap: 16px;
  padding: 18px 0 18px 24px;
  border-bottom: 1px solid var(--line);
}

.creds li:last-child { border-bottom: 0; }

/* Every dot is centred 4px in from the row's left edge and 29px down, on the rail.
   Trailing dots stay small; the active "Now" dot is about 2.5 times their size. */
.creds li::before {
  content: "";
  position: absolute;
  left: 1px;
  top: 26px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(94, 216, 232, 0.7);
  box-shadow: 0 0 8px rgba(94, 216, 232, 0.4);
  transition:
    width 0.45s var(--ease),
    height 0.45s var(--ease),
    left 0.45s var(--ease),
    top 0.45s var(--ease),
    background 0.45s var(--ease),
    box-shadow 0.45s var(--ease);
}

.js .creds:not(.is-in) li::before {
  width: 6px;
  height: 6px;
  left: 1px;
  top: 26px;
  background: rgba(94, 216, 232, 0.25);
  box-shadow: none;
}

.creds li > span { transition: opacity 0.6s var(--ease), filter 0.6s var(--ease); }

.js .creds:not(.is-in) li > span {
  opacity: 0;
  filter: blur(8px);
}

.creds li:nth-child(1)::before,
.creds li:nth-child(1) > span { transition-delay: 300ms; }

.creds li:nth-child(2)::before,
.creds li:nth-child(2) > span { transition-delay: 580ms; }

.creds li:nth-child(3)::before,
.creds li:nth-child(3) > span { transition-delay: 860ms; }

.creds li:nth-child(4)::before,
.creds li:nth-child(4) > span { transition-delay: 1140ms; }

/* The active dot: larger, with a slow ring that pings outward while on screen. */
.creds li:nth-child(3)::before {
  width: 14px;
  height: 14px;
  left: -3px;
  top: 22px;
  background: var(--cy);
  box-shadow: 0 0 0 5px rgba(94, 216, 232, 0.14), 0 0 22px rgba(94, 216, 232, 0.85);
}

.creds li:nth-child(3)::after {
  content: "";
  position: absolute;
  left: -8px;
  top: 17px;
  width: 24px;
  height: 24px;
  border: 1px solid rgba(94, 216, 232, 0.4);
  border-radius: 50%;
  opacity: 0.5;
}

.js .creds:not(.is-in) li:nth-child(3)::after { opacity: 0; }

.creds.is-in li:nth-child(3)::after {
  animation: creds-halo 2.8s var(--ease) 1.2s infinite both;
  animation-play-state: paused;
}

.creds.is-in.is-live li:nth-child(3)::after { animation-play-state: running; }

@keyframes creds-halo {
  0% { opacity: 0; transform: scale(0.6); }
  40% { opacity: 0.9; }
  100% { opacity: 0; transform: scale(1.6); }
}

.cred-k {
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding-top: 2px;
}

.cred-v {
  color: var(--ink);
  font-size: 16px;
  line-height: 1.45;
  text-wrap: pretty;
}

@media (max-width: 640px) {
  .creds { --glass-bloom: rgba(94, 216, 232, 0.04); }
}

/* ---------- closer ---------- */

.closer {
  position: relative;
  isolation: isolate;
  min-height: 620px;
  display: grid;
  place-items: center;
  padding: var(--section-pad) var(--gutter);
  background: var(--void);
  text-align: center;
  overflow: clip;
}

/* Waist sits on the CTA: bottom inset (420px) + CTA half height (22px)
   + space below it (18px margin + about 20px mail line). */
.closer-copy > .lens {
  inset: -900px -100vw -420px;
  z-index: -1;
  --waist: calc(100% - 480px);
  --gap: 70px;
}

/* Soft fade from the About section, painted above the lens. */
.closer::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 140px;
  background: linear-gradient(to bottom, var(--bg), transparent);
  z-index: 0;
  pointer-events: none;
}

/* The arcs fade into the footer instead of stopping at a flat edge.
   z-index -1 puts it above the lens (earlier in the page) but under the copy, button and mail line. */
.closer::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 120px;
  background: linear-gradient(to top, var(--void), transparent);
  z-index: -1;
  pointer-events: none;
}

.closer-copy {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.closer-title {
  font-size: clamp(40px, 6vw, 84px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.035em;
  text-wrap: balance;
  background: linear-gradient(180deg, #ffffff 30%, #a9a6b4 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.closer-text {
  max-width: 28em;
  margin-top: 32px;
  color: var(--ink-2);
  font-size: clamp(16px, 1.5vw, 19px);
  text-wrap: balance;
}

/* Space above the CTA keeps the subtext clear of the top arc (gap 70px). */
.closer .btn-hero { margin-top: 60px; }

.closer-mail {
  margin-top: 18px;
  color: var(--muted);
  font-size: 14px;
}

/* Words are split into inline blocks, so each word carries the title's gradient itself. */
.closer-title .w {
  background: inherit;
  -webkit-background-clip: text;
  background-clip: text;
}

/* Entrance: the arcs fade up from black and close in on the button.
   The offset uses translate, so it composes with any transform the shared .lens rules set. */
.closer-copy > .lens { transition: opacity 1.4s var(--ease); }

.closer .lens-top,
.closer .lens-bottom { transition: translate 1.4s var(--ease); }

.js .closer-copy:not(.is-in) > .lens { opacity: 0; }

.js .closer-copy:not(.is-in) .lens-top { translate: 0 -40px; }

.js .closer-copy:not(.is-in) .lens-bottom { translate: 0 40px; }

/* Subtext, button and mail line follow the four title words, as the hero's do. */
.js .closer-text.reveal { transition-delay: 450ms; }

/* The button opens from its icon after the title, with the hero CTA's keyframes. */
.js .closer-copy:not(.is-in) .btn-hero { opacity: 0; }

.js .closer-copy.is-in .btn-hero {
  animation:
    cta-open 1.05s cubic-bezier(0.65, 0, 0.35, 1) 0.6s backwards,
    cta-glow 0.4s ease-out 1.65s backwards;
}

/* Keyboard focus shows the button at once, entrance or not. */
.js .closer-copy .btn-hero:focus-visible {
  opacity: 1;
  animation: none;
}

.js .closer-mail.reveal { transition-delay: 700ms; }

/* ---------- footer ---------- */

/* A centred cyan hairline where the closer's bottom arc light lands. */
.footer {
  position: relative;
  padding: 40px var(--gutter) 32px;
  background: var(--void);
}

.footer::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: min(900px, 80%);
  height: 1px;
  transform: translateX(-50%);
  background: linear-gradient(90deg, transparent, rgba(94, 216, 232, 0.45), transparent);
  box-shadow: 0 0 14px rgba(94, 216, 232, 0.3);
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  max-width: var(--maxw);
  margin: 0 auto;
}

.footer-top img { width: 96px; height: auto; }

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0 24px;
}

.footer-links a,
.footer-mail {
  display: inline-block;
  padding: 11px 0;
  color: var(--ink-2);
  font-size: 14px;
  text-decoration: none;
}

.footer-links a:hover,
.footer-mail:hover { color: var(--cy); }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px 24px;
  max-width: var(--maxw);
  margin: 20px auto 0;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.footer-note {
  color: var(--muted);
  font-size: 14px;
}

/* ---------- responsive ---------- */

@media (max-width: 1080px) {
  .nav-links a { padding: 8px 12px; }
}

@media (max-width: 900px) {
  .band-inner {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .band-rail { display: none; }

  .band-steps { padding: 24px 0; }

  .svc {
    min-height: 0;
    padding: 36px 0;
  }

  .svc + .svc { border-top: 1px solid rgba(94, 216, 232, 0.16); }

  .split { grid-template-columns: minmax(0, 1fr); }

  .split-art { min-height: 340px; }

  .about { grid-template-columns: minmax(0, 1fr); }

  .stats,
  .cases { grid-template-columns: minmax(0, 1fr); }

}

@media (max-width: 1100px) {
  .creds li { grid-template-columns: 6em minmax(0, 1fr); }
}

@media (max-width: 820px) {
  .nav { grid-template-columns: auto 1fr; }

  .nav-toggle { display: grid; }

  .nav-links {
    display: none;
    position: absolute;
    top: calc(100% - 4px);
    left: var(--gutter);
    right: var(--gutter);
    flex-direction: column;
    gap: 2px;
    padding: 8px;
    border-radius: 18px;
    background: var(--bg);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
  }

  .nav-links.is-open { display: flex; }

  .nav-links a { padding: 12px 14px; font-size: 16px; }

  /* No backdrop-filter here: it would make the nav the containing block for the
     fixed dim layer below and shrink it to the nav bar. */
  .nav.is-open {
    background: rgba(8, 8, 13, 0.96);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* Dim the page behind the open menu; a tap on it closes the menu (app.js). */
  .nav.is-open::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    background: rgba(8, 8, 13, 0.6);
  }
}

/* With JS off the menu button does nothing, so show the links inline instead. */
@media (max-width: 820px) {
  html:not(.js) .nav-toggle { display: none; }

  html:not(.js) .nav {
    position: absolute;
    grid-template-columns: 1fr auto;
  }

  html:not(.js) .nav-links {
    display: flex;
    position: static;
    grid-column: 1 / -1;
    grid-row: 2;
    flex-direction: row;
    flex-wrap: wrap;
    padding: 4px;
    background: rgba(19, 19, 31, 0.6);
  }

  html:not(.js) .nav-links a { padding: 8px 12px; font-size: 14px; }

  html:not(.js) .hero { padding-top: 150px; }
}

@media (max-width: 640px) {
  /* On phones the hero hugs its content instead of filling the screen. */
  .hero {
    min-height: 0;
    padding-top: 80px;
  }

  .hero-paths { padding-top: 72px; }

  .hero-copy > .lens { --gap: 44px; }

  .lens-top,
  .lens-bottom {
    width: 1300px;
    height: 760px;
    margin-left: -650px;
  }

  .tiles {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    width: min(420px, calc(100vw - var(--gutter) * 2));
  }

  .tile { min-height: 96px; font-size: 13px; }

  .split-art { min-height: 280px; }

  .split-art-label { top: 52%; }

  .bubble-a { top: 10%; }

  .bubble-b { top: 8%; }

  .bubble-c { top: 32%; }

  .bubble-d { top: 78%; }

  .orb { width: 440px; height: 440px; margin-top: -220px; }

  .stat-num { font-size: 40px; }

  .cases { gap: 10px; }

  .creds li { grid-template-columns: minmax(0, 1fr); gap: 4px; }

  .footer-links {
    display: grid;
    grid-template-columns: repeat(2, max-content);
    gap: 0 24px;
  }

  .closer { min-height: 500px; }

  .btn-mag { padding: 15px 16px; font-size: 14px; }

  .nav-logo img { width: 92px; }
}

/* ---------- reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }

  .js .reveal,
  .js .words:not(.is-in) .w {
    opacity: 1;
    filter: none;
    transform: none;
  }
}
