/* ==========================================================================
   WAIW landing page — layout & components
   (design tokens live in tokens.css)

   The page is one continuous dark landscape: a fixed terraced-topography
   backdrop runs behind every (transparent) section, joined by hairline
   strata seams, textured with terrain relief and accented in brand red.
   ========================================================================== */

/* ---- Reset & base -------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--t-on-dark);
  background: var(--c-ink);
}

/* One continuous terraced-topography backdrop behind the whole page.
   It's fixed to the viewport, so content scrolls over a stationary
   terrain map — sections stay transparent and never show a seam. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: url("../assets/img/topo-bg.png") center / cover no-repeat;
}

img { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.12;
  margin: 0;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

p { margin: 0; }
a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--c-red);
  outline-offset: 3px;
  border-radius: 2px;
}

::selection { background: var(--c-red); color: #fff; }

.container   { width: min(var(--container),   100% - 2 * var(--gutter)); margin-inline: auto; }
.container-w { width: min(var(--container-w), 100% - 2 * var(--gutter)); margin-inline: auto; }

/* ---- Film grain ------------------------------------------------------------
   Sits just above the fixed topo backdrop and *below* all content, so the
   grain textures the background only — images, text and cards stay clean. */
.grain {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.5;
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- Buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  padding: 0.8em 1.6em;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.25s var(--ease-out), background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.25s;
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }

.btn--primary {
  background: var(--c-red);
  color: #fff;
  box-shadow: 0 8px 28px -10px rgba(224, 51, 65, 0.55);
}
.btn--primary:hover {
  background: var(--c-red-deep);
  box-shadow: 0 12px 34px -10px rgba(224, 51, 65, 0.7);
}

.btn--ghost {
  border-color: var(--line-on-dark);
  color: var(--t-on-dark);
  background: rgba(255, 255, 255, 0.03);
}
.btn--ghost:hover { border-color: rgba(255, 255, 255, 0.45); }

.btn--lg  { padding: 1em 2em; font-size: 1rem; }
.btn--nav { padding: 0.55em 1.3em; font-size: 0.875rem; }

/* ---- Eyebrows, wiggle & section heads --------------------------------------- */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--c-red);
  margin-bottom: 1.1rem;
}
.eyebrow span { opacity: 0.55; margin-right: 0.6em; }

/* keep eyebrows brand-red inside any section theme */
.section .eyebrow { color: var(--c-red); }


/* seismic-wavelet underline drawn after each section title */
.wiggle {
  display: block;
  width: 130px;
  height: 20px;
  margin-top: 1.3rem;
  overflow: visible;
}
.wiggle path {
  stroke: var(--c-red);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 0; /* JS animates 1 → 0 on scroll */
}

.section { position: relative; padding-block: var(--sect-pad); }

/* sections are transparent — the fixed body backdrop runs through all of
   them; identity comes from ghost numerals, glows and content surfaces */
.section--ink,
.section--carbon { background: transparent; }
.section p, .section .lede { color: var(--t-on-dark-muted); }
.section h2, .section h3, .section h4 { color: var(--t-on-dark); }

/* soft red radial glows — circular and fully contained, so they fade to
   nothing before reaching any section edge (edge-anchored gradients clip) */
.section--glow {
  background-image: radial-gradient(46% 32% at 78% 24%, rgba(224, 51, 65, 0.13) 0%, transparent 68%);
}
.section--carbon.section--glow {
  background-image: radial-gradient(52% 38% at 16% 68%, rgba(224, 51, 65, 0.11) 0%, transparent 68%);
}

.section-head { position: relative; max-width: 44rem; margin-bottom: clamp(2.5rem, 6vw, 4.5rem); }
.section-head h2 { font-size: var(--fs-h2); font-weight: 800; }
.section-head .lede { font-size: var(--fs-lede); margin-top: 1.4rem; line-height: 1.55; }

/* oversized ghost numeral behind each section head */
.section-head[data-num]::before {
  content: attr(data-num);
  position: absolute;
  top: -0.35em;
  right: clamp(-14rem, -18vw, -6rem);
  z-index: 0;
  font-family: var(--font-display);
  font-size: clamp(9rem, 18vw, 17rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, 0.07);
  pointer-events: none;
  user-select: none;
}
.section-head > * { position: relative; z-index: 1; }

/* ---- Products: the pair, named in the head ---------------------------------
   Two terms with their definitions, one per product. The rails and the counted
   index are what make "two" register at a glance — the same two sentences ran
   as one lede paragraph and a reviewer read the section as showing a single
   product. Rails borrow the idiom already used by the Research columns and the
   Services aside rather than inventing one.
   `.section-head > *` above already lifts this above the ghost numeral. */
.product-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2.6rem;
  margin: 2.2rem 0 0;
}
.product-pair > div {
  border-left: 1px solid var(--line-on-dark);
  padding-left: 1.4rem;
}
.product-pair__idx {
  display: block;
  margin-bottom: 0.7rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  /* Brand red, like every other index and eyebrow on the page. It does not
     clear AA at 12px — see the ONE RED note in tokens.css for the trade. */
  color: var(--c-red);
}
.product-pair dt {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.5rem, 2vw, 1.75rem);
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--t-on-dark);
}
/* dd carries a 40px inline-start margin by default — zero it or the text
   hangs off its own rail */
.product-pair dd {
  margin: 0.6rem 0 0;
  font-size: 1.0625rem;
  line-height: 1.55;
  color: var(--t-on-dark-muted);
}

/* anchor offset under the fixed header */
section[id], article[id] { scroll-margin-top: calc(var(--header-h) + 1rem); }

/* ---- Strata seams (section transitions drawn as hairline bedding lines) ---- */
.strata-divider {
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: clamp(56px, 8vw, 120px);
  pointer-events: none;
}
.strata-divider svg { width: 100%; height: 100%; display: block; }
.strata-divider path {
  vector-effect: non-scaling-stroke;
  stroke-width: 1.5;
}
.strata-divider .sw { stroke: rgba(255, 255, 255, 0.08); }
.strata-divider .sr { stroke: rgba(224, 51, 65, 0.28); }
/* sections with a seam need breathing room above the content */
.strata-divider ~ .container,
.strata-divider ~ .container-w {
  padding-top: clamp(2rem, 5vw, 3.5rem);
}

.section-terrain {
  position: absolute;
  inset: auto 0 0 0;
  width: 100%;
  height: min(78%, 38rem);
  object-fit: cover;
  opacity: 0.2;
  mix-blend-mode: screen;
  /* fade out symmetrically — it should leave the way it arrived */
  -webkit-mask-image: linear-gradient(to top, transparent 0%, #000 30%, #000 62%, transparent 100%);
  mask-image: linear-gradient(to top, transparent 0%, #000 30%, #000 62%, transparent 100%);
  pointer-events: none;
}

/* ---- Header ---------------------------------------------------------------- */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  transition: background-color 0.3s, box-shadow 0.3s;
}
.site-header.is-scrolled {
  background: rgba(16, 16, 16, 0.78);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.07);
}
.site-header__inner {
  width: min(var(--container-w), 100% - 2 * var(--gutter));
  margin-inline: auto;
  height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 1rem;
  padding-right: 1.6rem;   /* clear of the plus in the corner */
}
.site-header__logo img { width: 107px; height: auto; }

.scroll-progress {
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 100%;
  height: 2px;
  background: var(--c-red);
  transform-origin: 0 50%;
  transform: scaleX(0);
}

.site-nav {
  display: flex;
  align-items: center;
  gap: clamp(1.2rem, 2.5vw, 2.2rem);
}
.site-nav > a:not(.btn) {
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--t-on-dark-muted);
  transition: color 0.2s;
}
.site-nav > a:not(.btn):hover,
.site-nav > a.is-active { color: #fff; }

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 7px;
  width: 44px;
  height: 44px;
  padding: 10px;
  background: none;
  border: 0;
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  height: 2px;
  background: #fff;
  border-radius: 2px;
  transition: transform 0.3s var(--ease-out);
}
.nav-open .nav-toggle span:first-child { transform: translateY(4.5px) rotate(45deg); }
.nav-open .nav-toggle span:last-child  { transform: translateY(-4.5px) rotate(-45deg); }

/* ---- Side label (current section, desktop only) ------------------------------ */
.side-label {
  position: fixed;
  left: 1.1rem;
  bottom: 7rem;
  z-index: 90;
  pointer-events: none;
}
.side-label span {
  display: inline-block;
  transform: rotate(180deg);
  writing-mode: vertical-rl;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--t-on-dark-faint);
  transition: opacity 0.4s;
}
.side-label span::after {
  content: "";
  display: block;
  width: 1px;
  height: 2.4rem;
  margin: 0 auto 0.9rem; /* below the text in vertical writing mode */
  background: var(--c-red);
}
.side-label span:empty { opacity: 0; }

/* ---- Hero ------------------------------------------------------------------ */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
}
.hero__contours {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.hero__terrain {
  position: absolute;
  top: 0;
  right: 0;
  width: min(58vw, 900px);
  height: min(74vh, 720px);
  object-fit: cover;
  opacity: 0.42;
  mix-blend-mode: screen;
  -webkit-mask-image: radial-gradient(115% 100% at 100% 0%, #000 30%, transparent 72%);
  mask-image: radial-gradient(115% 100% at 100% 0%, #000 30%, transparent 72%);
  pointer-events: none;
  will-change: transform;
}
.hero__scrim {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(115% 85% at 16% 48%, rgba(21, 21, 21, 0.9) 0%, rgba(21, 21, 21, 0.5) 45%, rgba(21, 21, 21, 0) 100%),
    linear-gradient(to bottom, rgba(21, 21, 21, 0.45), transparent 26%);
}
.hero__content {
  position: relative;
  z-index: 1;
  padding-block: calc(var(--header-h) + 3rem) 8.5rem;
}
.hero__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--t-on-dark-muted);
  margin-bottom: 1.6rem;
}
.hero__eyebrow .tick {
  width: 2.4rem;
  height: 2px;
  background: var(--c-red);
}
.hero__title { display: block; }
.hero__word {
  display: block;
  font-size: var(--fs-hero);
  font-weight: 800;
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: #fff;
}
.hero__sub {
  display: block;
  margin-top: 1rem;
  font-size: var(--fs-hero-2);
  font-weight: 600;
  color: var(--t-on-dark);
  max-width: 18em;
}
.hero__lede {
  margin-top: 1.6rem;
  max-width: 34em;
  font-size: var(--fs-lede);
  color: var(--t-on-dark-muted);
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin-top: 2.4rem;
}
.hero__scroll {
  position: absolute;
  left: 50%;
  bottom: 5.4rem;
  transform: translateX(-50%);
  z-index: 1;
  width: 24px;
  color: var(--t-on-dark-faint);
  transition: color 0.2s;
}
.hero__scroll:hover { color: var(--c-red); }
.hero__scroll-dot { animation: scroll-dot 2.2s ease-in-out infinite; }
@keyframes scroll-dot {
  0%, 100% { transform: translateY(0); opacity: 1; }
  55%      { transform: translateY(12px); opacity: 0.2; }
  70%      { transform: translateY(0); opacity: 0; }
}

/* looping ticker along the hero's lower edge */
.ticker {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  overflow: hidden;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  background: rgba(15, 15, 15, 0.35);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
.ticker__track {
  display: flex;
  align-items: center;
  gap: 2.2rem;
  width: max-content;
  padding: 0.85rem 0;
  animation: ticker-scroll 38s linear infinite;
}
.ticker span {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--t-on-dark-faint);
  white-space: nowrap;
}
.ticker i { color: var(--c-red); font-style: normal; }
@keyframes ticker-scroll {
  to { transform: translateX(-50%); }
}

/* ---- Products --------------------------------------------------------------- */
.product {
  display: grid;
  grid-template-columns: minmax(20rem, 5fr) 7fr;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
  position: relative;
  z-index: 1;
}
.product + .product { margin-top: var(--sect-pad); }

/* GeoSurreal: tighten only the vertical gap so the scene + pipeline are co-visible */
#geosurreal { row-gap: clamp(0.75rem, 1.5vw, 1.25rem); }

/* The product NAME is the headline. It used to be a 13px pill above a 35px
   tagline — the smallest text in its own block, and smaller than the body copy
   — so the tagline was impersonating the title. Reviewer feedback, and true. */
.product__intro h3 {
  font-size: clamp(2.2rem, 4.4vw, 3.5rem);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0 0 0.7rem;
  font-weight: 800;
}
/* `margin-bottom` is deliberately absent: `.product__intro > p` is (0,1,1) and
   owns it. Setting it here would look like it worked (same value) while
   actually losing, and the next person to change the number would be very
   confused. */
.product__tagline {
  font-size: var(--fs-lede);
  line-height: 1.5;
  margin-top: 0;
}
/* Red, and scoped to beat `.section p` (0,1,1) — a bare `.product__tagline`
   colour would silently lose to it.
   Brand red, the same one the eyebrow above it uses. At 20.8px regular this
   is not WCAG "large text" (that needs 24px, or 18.66px bold), so it answers
   to the 4.5:1 bar and measures ~3.66:1 — see the ONE RED note in tokens.css.
   Neither a size nor a weight nudge fixes that honestly, because --fs-lede
   clamps down to 16.8px and any exemption would evaporate at narrow widths.
   A tagline can take the accent where the NAME above it could not: red titles
   are what this site turns things into on hover (.entry__title, .pub__title),
   so a permanently red heading would teach the wrong thing. A description is
   the same role .svc-hero already plays in red. */
.section .product__tagline { color: var(--c-red); }
.product__intro > p { margin-bottom: 1.4rem; }

.ticks {
  list-style: none;
  margin: 0 0 1.8rem;
  padding: 0;
  display: grid;
  gap: 0.65rem;
}
.ticks li {
  position: relative;
  padding-left: 1.6rem;
  color: var(--t-on-dark-muted);
}
.ticks li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 0.85rem;
  height: 2px;
  background: var(--c-red);
}

/* screenshots (still used by the GeoAssistant .shot--output figure) */
.shot { margin: 0; }
.shot img {
  border-radius: var(--radius);
  box-shadow: 0 30px 70px -22px rgba(0, 0, 0, 0.75);
}
.shot figcaption {
  margin-top: 0.9rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--t-on-dark-faint);
}

/* GeoSurreal live scene + WebGL fallback */
.product__media { position: relative; }
.scene-mount {
  position: relative;
  width: 100%;
  aspect-ratio: 654 / 560;      /* shorter than the old shot so the pipeline is co-visible */
  border-radius: var(--radius);
  overflow: hidden;
}
.scene-mount canvas { display: block; width: 100%; height: 100%; }
.scene-fallback {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #fff;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--radius);
  box-shadow: 0 30px 70px -22px rgba(0, 0, 0, 0.75);
  display: none;                /* shown only in fallback mode */
}
.product__media.is-fallback .scene-fallback { display: block; }
.product__media.is-live .scene-fallback { display: none; }

.scene-controls {
  position: absolute;
  right: 0.9rem;
  bottom: 0.9rem;
  display: inline-flex;
  gap: 0.5rem;
  z-index: 4; /* above the export player (z-index 3) so the arrows stay clickable at stage 4 */
}
.product__media.is-fallback .scene-controls { display: none; } /* no scene → no controls */

/* fallback has no stage driver → show the pipeline underline fully drawn */
.product__media.is-fallback ~ .pipeline,
.product:has(.product__media.is-fallback) .pipeline { --draw: 1; }
.scene-arrow {
  width: 2.6rem;
  height: 2.6rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8rem;
  color: #fff;
  background: rgba(24, 24, 24, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.scene-arrow:hover { background: var(--c-red); border-color: var(--c-red); }
.scene-arrow:active { transform: scale(0.94); }
.scene-arrow:disabled { opacity: 0.35; cursor: default; }
.scene-arrow:disabled:hover { background: rgba(24, 24, 24, 0.62); border-color: rgba(255, 255, 255, 0.18); }

/* ---- Stage-4 close (.geo-player) ------------------------------------------
   Takes the volume's place as it dissolves — the dissolve is a curtain, so
   something has to be behind it.

   NOT a video frame any more. The old placeholder was 16:9 because it held a
   player, which left 212px of dead space (34% of the media block) around a
   button at 1440. This sizes to its content and sits in the space the volume
   vacated, so the emptiness reads as the dissolve rather than as an empty box.

   NOT a button, either. An earlier pass closed on a filled red pill — but the
   product intro's "See it on your data" is a filled red pill too, and at this
   stage both are on screen at once. Two identical CTAs in one view is a
   stutter that weakens both. So the weight lives in the type and the action is
   a quiet ghost link: different shape, different colour, no clone.

   Add .has-video to restore the framed player (see index.html). */
.geo-player {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3;
  opacity: 0;
  /* Rises into place rather than sliding in from the right: the volume
     condenses into its own conclusion, instead of a panel arriving from
     somewhere off-frame for no reason. */
  transform: translateY(1.1rem);
  pointer-events: none;
  /* visibility, not opacity alone. Without it everything in here stays
     keyboard-focusable at stages 0-3 and a keyboard user can tab to an
     invisible link — the same bug the old dead play button had. The delay
     holds it visible for exactly the length of the fade out. */
  visibility: hidden;
  transition: opacity 0.55s var(--ease-out), transform 0.55s var(--ease-out),
              visibility 0s linear 0.55s;
}
.geo-player.is-in {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  visibility: visible;
  transition-delay: 0s;
}
.geo-player__frame {
  position: relative;
  width: 100%;
  display: grid;
  justify-items: center;
  gap: 0.55rem;
  padding-inline: var(--gutter);
  text-align: center;
}

/* The last trace left behind, spanning the full width the volume vacated. It
   runs edge to edge deliberately: an earlier pass sized it to the content and
   it came out 336px against a 325px button, close enough to read as an
   accidental overline. The width relationship has to be obvious.

   Same fading red rule as .feature::before, so it is a mark the site already
   owns — and NOT .wiggle, which is the section-title ornament and would make
   this panel read as a heading. It draws outward from the centre as the panel
   settles. */
.geo-player__trace {
  width: calc(100% + var(--gutter) * 2);
  margin-inline: calc(var(--gutter) * -1);
  margin-bottom: clamp(1.2rem, 2.6vw, 1.9rem);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--c-red) 18%, var(--c-red) 82%, transparent);
  opacity: 0;
  transform: scaleX(0.12);
  transition: transform 0.8s var(--ease-out) 0.1s, opacity 0.45s ease 0.1s;
}
.geo-player.is-in .geo-player__trace { opacity: 0.7; transform: scaleX(1); }

/* The proclamation. A <p>, not a heading — this is a stage of an animation,
   and injecting a heading here would put it in the document outline between
   the product h3 and the pipeline's h4s. Sized just under --fs-h3 so it lands
   without competing with the GeoSurreal title above it. */
/* Scoped to beat `.section p` (0,1,1) — a bare .geo-player__claim is (0,1,0)
   and loses, which silently rendered this "loud" line at 60% white. The same
   trap .product__tagline carries a note about. */
.section .geo-player__claim {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.5rem, 2.3vw, 2rem);
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--t-on-dark);
}
/* The pointer. It carries the link's meaning, which is why the link itself can
   stay short and quiet. */
.section .geo-player__sub {
  margin: 0;
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--t-on-dark-muted);
}
/* A lone ghost button on an empty backdrop reads as disabled at the site's
   resting 14% border, so it gets a little more edge here. Hover is left to
   .btn--ghost so it still behaves like every other ghost button. */
.geo-player__cta {
  border-color: rgba(255, 255, 255, 0.3);
  margin-top: clamp(1rem, 2.2vw, 1.5rem);
  max-width: 100%;
  line-height: 1.35;
}
/* The type group and the link rise a beat after the trace draws. */
.geo-player__claim,
.geo-player__sub,
.geo-player__cta {
  opacity: 0;
  transform: translateY(0.55rem);
  transition: opacity 0.45s ease, transform 0.5s var(--ease-out);
}
.geo-player.is-in .geo-player__claim { opacity: 1; transform: none; transition-delay: 0.26s; }
.geo-player.is-in .geo-player__sub   { opacity: 1; transform: none; transition-delay: 0.34s; }
.geo-player.is-in .geo-player__cta   { opacity: 1; transform: none; transition-delay: 0.42s; }
.geo-player__cta:hover { transform: translateY(-2px); }

/* With a video: the frame comes back and the type sits under it. */
.geo-player.has-video .geo-player__frame { padding-inline: 0; }
.geo-player.has-video .geo-player__trace { display: none; }
.geo-player__video {
  width: 100%;
  aspect-ratio: 16 / 9;
  margin-bottom: clamp(1.2rem, 2.6vw, 1.9rem);
  display: block;
  background: linear-gradient(160deg, #202020, #121212);
  border: 1px solid var(--line-on-dark);
  border-radius: var(--radius);
  box-shadow: 0 30px 70px -22px rgba(0, 0, 0, 0.75);
}

@media (prefers-reduced-motion: reduce) {
  .geo-player,
  .geo-player__trace,
  .geo-player__claim,
  .geo-player__sub,
  .geo-player__cta { transition: none; transform: none; }
  .geo-player__trace { transform: scaleX(1); }
}


/* pipeline strip */
.pipeline {
  grid-column: 1 / -1;
  list-style: none;
  margin: clamp(1rem, 2vw, 1.6rem) 0 0;      /* pulled up: was clamp(3rem,7vw,5.5rem) */
  padding: clamp(1.1rem, 2vw, 1.5rem) 0 0;   /* tighter: was clamp(1.8rem,3.5vw,2.8rem) */
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1.2rem, 3vw, 2.5rem);
  --draw: 0; /* stage-driven on the live path (B2-2); static full in fallback via .is-fallback */
}
.pipeline::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 1px;
  background: var(--line-on-dark);
}
.pipeline::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 2px;
  background: linear-gradient(90deg, var(--c-red), rgba(224, 51, 65, 0.25));
  transform: scaleX(var(--draw));
  transform-origin: 0 50%;
}
.pipeline h4 { font-size: var(--fs-h4); margin: 0.4rem 0 0.5rem; }
.pipeline p { font-size: var(--fs-small); color: var(--t-on-dark-muted); }
.pipeline__num {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  color: var(--c-red);
}

/* pipeline stage emphasis — the active step class (.is-active) is toggled by
   js/geosurreal-scene.js on the live path. Steps stay full-opacity throughout;
   the active one is emphasized by the red number glow + a grown heading (via
   font-size, which animates reliably where a transform transition does not). */
.pipeline li h4 { transition: font-size 0.4s ease; }
.pipeline__num { transition: color 0.45s ease, text-shadow 0.45s ease; }

.pipeline li.is-active h4 { font-size: calc(var(--fs-h4) * 1.18); }
.pipeline li.is-active .pipeline__num {
  color: var(--c-red);
  text-shadow: 0 0 14px rgba(224, 51, 65, 0.65);
}

@media (prefers-reduced-motion: reduce) {
  .pipeline li h4, .pipeline__num { transition: none; }
}

/* GeoAssistant flavour */
.product--assistant {
  grid-template-columns: 1fr;
  gap: clamp(2rem, 4vw, 3rem);
}
.product--assistant .product__intro { max-width: 44rem; }

.prompt-line {
  font-family: var(--font-mono);
  font-size: 0.9375rem;
  color: var(--t-on-dark);
  background: rgba(32, 32, 32, 0.75);
  border: 1px solid var(--line-on-dark);
  border-left: 3px solid var(--c-red);
  border-radius: var(--radius-sm);
  padding: 1rem 1.2rem;
}
.prompt-line span { color: var(--c-red); margin-right: 0.5em; }

.shot--output img { border: 1px solid var(--line-on-dark); }

/* ---- Interactive annotated section (.ixa) -----------------------------------
   Hotspots + tooltips over the GeoAssistant screenshot. Everything is
   absolutely positioned inside .ixa, so the component's footprint is exactly
   the image — zero extra height, no layout shift. All rules are scoped under
   .ixa. Kept in sync with standalone/seismic-section.html. */
.ixa {
  position: relative;
  z-index: 6; /* tooltips float above the capability grid below */
}
.ixa img { display: block; }

/* 44px tap target centred on (and covering) the baked-in yellow marker.
   Minimal-tech badge in the site's chip language: ink disc, hairline red
   ring, red mono digit — inverting to red fill / white digit when active. */
.ixa-btn {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  font-family: var(--font-mono);
  font-weight: 700;
  /* badge & digit scale with the viewport so the disc always hides the
     yellow marker baked into the screenshot (~2.4% of image width) */
  font-size: clamp(0.75rem, 1.1vw, 0.875rem);
  color: var(--c-red);
  transition: color 0.2s;
}
/* the visible disc, behind the digit */
.ixa-btn::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(clamp(24px, 2.75vw, 36px) + 2px);
  height: calc(clamp(24px, 2.75vw, 36px) + 2px);
  transform: translate(-50%, -50%);
  z-index: -1;
  border-radius: 50%;
  background: var(--c-charcoal);
  border: 1.5px solid rgba(224, 51, 65, 0.9);
  transition: background-color 0.2s, box-shadow 0.2s, transform 0.2s var(--ease-out);
}
.ixa-btn:hover,
.ixa-btn:focus-visible,
.ixa-hs.is-open .ixa-btn { color: #fff; }
.ixa-btn:hover::before,
.ixa-btn:focus-visible::before,
.ixa-hs.is-open .ixa-btn::before {
  background: var(--c-red);
  transform: translate(-50%, -50%) scale(1.1);
  box-shadow: 0 0 0 4px rgba(224, 51, 65, 0.18);
}
.ixa-btn:focus-visible { outline: none; } /* the badge ring is the focus indicator */

.ixa-tip {
  position: absolute;
  /* clamp keeps the bubble inside the image at every width */
  left: clamp(6px, calc(var(--x) - 140px), calc(100% - min(280px, 94%) - 6px));
  bottom: calc(100% - var(--y) + 26px); /* above the marker by default */
  width: min(280px, 94%);
  z-index: 40;
  padding: 0.8rem 0.95rem;
  background: #232323;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-left: 3px solid var(--c-red);
  border-radius: var(--radius-sm);
  box-shadow: 0 16px 40px -12px rgba(0, 0, 0, 0.7);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--t-on-dark-muted);
  opacity: 0;
  visibility: hidden;
  transform: translateY(5px);
  transition: opacity 0.2s, transform 0.2s var(--ease-out), visibility 0.2s;
  pointer-events: none;
}
.ixa-tip strong {
  display: block;
  margin-bottom: 0.25rem;
  font-family: var(--font-display);
  font-size: 0.875rem;
  color: var(--t-on-dark);
}
/* markers near the top open downwards instead */
.ixa-tip--below {
  bottom: auto;
  top: calc(var(--y) + 26px);
  transform: translateY(-5px);
}
@media (hover: hover) {
  .ixa-btn:hover + .ixa-tip,
  .ixa-tip:hover { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto; }
}
.ixa-btn:focus-visible + .ixa-tip,
.ixa-hs.is-open .ixa-tip { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto; }

/* Touch devices: the image is small relative to the bubbles, so an anchored
   tooltip would cover (and steal taps from) neighbouring markers. Show it as
   a fixed bottom sheet instead — still zero layout height, always on screen. */
@media (hover: none) {
  .ixa-tip,
  .ixa-tip--below {
    position: fixed;
    left: 12px;
    right: 12px;
    top: auto;
    bottom: 12px;
    width: auto;
    max-width: 34rem;
    margin-inline: auto;
    z-index: 300;
    transform: translateY(8px);
  }
}


/* ---- GeoAssistant: the claims under the diagram ------------------------------
   Three of the site's own cards — --c-surface on a hairline at --radius, the
   same surface the .svc-panel and the diagram's boxes are built from, so the
   row reads as a continuation of the figure above it rather than a new idea.

   The red mark is .ticks li::before verbatim (0.85rem x 2px, --c-red): the
   site already has an accent for "here is a claim", so this borrows it. */
.gas-points {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 2vw, 1.5rem);
}
.gas-point {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--c-surface);
  border: 1px solid var(--line-on-dark);
  border-radius: var(--radius);
  padding: clamp(1.15rem, 2.2vw, 1.6rem);
}
.gas-point::before {
  content: "";
  width: 0.85rem;
  height: 2px;
  background: var(--c-red);
  margin-bottom: 0.3rem;
}
/* h4 already carries --font-display 700 and the tight display line-height. */
.gas-point__title {
  font-size: var(--fs-h4);
  color: var(--t-on-dark);
  text-wrap: balance;
}
/* Scoped past `.section p` (0,1,1), which would otherwise win on the colour. */
.section .gas-point__gloss {
  font-size: var(--fs-small);
  line-height: 1.55;
  color: var(--t-on-dark-muted);
}

/* The lead-in sits with the prompt it introduces, not a section-gap away. */
.gas-lead {
  display: grid;
  gap: 0.9rem;
}


/* ---- GeoAssistant architecture diagram (.gas-diagram) ------------------------
   Two authored SVGs — .gas-desktop and .gas-phone — with CSS choosing between
   them. No JS layout switching, so it works with script disabled and survives
   a resize with nothing to re-run; the hidden one is display:none, which also
   keeps it out of the accessibility tree so nothing is announced twice.

   Behaviour lives in js/gas-diagram-mount.js. Everything here is complete
   without it: this stylesheet never hides anything the reader needs. */
.gas-diagram { position: relative; }
.gas-diagram svg { display: block; width: 100%; height: auto; }
.gas-diagram .gas-phone { display: none; }
/* Zero-sized but NOT display:none — a paint server inside a display:none
   subtree does not resolve, which strips every fill off the visible figure. */
.gas-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Below the desktop width the figure keeps its designed size and the container
   scrolls sideways rather than reflowing — an accepted trade at laptop and
   tablet widths, where the alternative is type under the site's 13px floor.
   82rem is --container-w. */
@media (max-width: 82rem) {
  .gas-diagram { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  /* Needs the .gas-diagram prefix: `.gas-diagram svg` (0,1,1) outranks a bare
     `.gas-desktop` (0,1,0) and the figure would reflow instead of scrolling. */
  .gas-diagram .gas-desktop { width: 1312px; max-width: none; }
}
/* 46rem is the site's own phone breakpoint. Sideways scrolling is not an
   answer at 390px, where the figure would be nearly four screens wide. */
@media (max-width: 46rem) {
  .gas-diagram { overflow-x: visible; }
  .gas-diagram .gas-desktop { display: none; }
  /* Capped and centred: the phone figure's units are ~1px at ~360, so letting
     it fill a 736px tablet would scale the smallest gloss to ~26px. */
  .gas-diagram .gas-phone { display: block; width: 100%; max-width: 440px; margin-inline: auto; }
}

/* ---- hover / focus emphasis ----
   ADDITIVE, not subtractive. Dimming alone made hovering a purely negative
   reward, and because dimmed text still has to clear AA the dim could never go
   far enough to feel like anything. So the hovered thing GAINS a glow — which
   is the site's own emphasis idiom, see .pipeline's active step and .ixa-btn's
   hover ring — and the dim only does the small remaining job.

   Three tiers: rest, .is-lit (related to what is active), and .is-hot (the one
   thing under the pointer). That third tier is what lets a GAS card glow on
   its own while the whole group still lights when an outer box points at it. */
.gas-diagram [data-node] {
  transition: opacity 0.24s ease, stroke 0.24s ease, filter 0.28s ease;
}
.gas-diagram svg[data-active] [data-node]:not(.is-lit)     { opacity: 0.40; }
.gas-diagram svg[data-active] text[data-node]:not(.is-lit) { opacity: 0.76; }

/* The perimeter is the figure's ground truth — it never recedes. It only ever
   lights UP, when Cloud models makes the boundary the point. */
.gas-diagram svg[data-active] [data-node="perimeter"]:not(.is-lit) { opacity: 1; }
/* Same for the key. The `text` type selector is required: without it the
   generic text-dim rule outranks this one on specificity and "GAS provides"
   dims to 2.91:1. */
.gas-diagram svg[data-active] text[data-node^="legend-"]:not(.is-lit) { opacity: 1; }

/* The glow, in the box's own legend colour, radiating from the solid fill.
   Brightness is applied BEFORE the glow so it lifts the box, not the halo. */
.gas-diagram .gas-fill.is-hot[data-tone="gold"]  { filter: brightness(1.12) url(#gas-glow-gold); }
.gas-diagram .gas-fill.is-hot[data-tone="red"]   { filter: brightness(1.12) url(#gas-glow-red); }
.gas-diagram .gas-fill.is-hot[data-tone="plain"] { filter: brightness(1.12) url(#gas-glow-plain); }
.gas-diagram [data-node="perimeter"].is-hot,
.gas-diagram [data-node="perimeter"].is-lit      { filter: url(#gas-glow-plain); }
/* The GAS frame swaps its resting glow for a hotter one rather than stacking a
   second filter on top of it. */
.gas-diagram [data-node="gas"].is-lit            { filter: url(#gas-gasglow-hot); }

/* Edge and fill both lift under the pointer — the same gesture as
   --c-surface-2, the site's "hover / raised surface" token. */
.gas-diagram .gas-edge.is-hot { stroke: rgba(255, 255, 255, 0.88); }
.gas-diagram svg[data-active] .gas-edge.is-lit { stroke: rgba(255, 255, 255, 0.58); }

/* A live connection closes its endpoints: the open node circles fill in. */
.gas-diagram .gas-node.is-lit { fill: rgba(255, 255, 255, 0.9); }

/* Legend entries read as controls: the active one glows in its own colour. */
.gas-diagram text[data-node="legend-red"].is-hot  { filter: url(#gas-glow-red); }
.gas-diagram text[data-node="legend-gold"].is-hot { filter: url(#gas-glow-gold); }

.gas-hit { cursor: pointer; outline: none; }
/* Matches the site's :focus-visible ring. Stroke width is in user units, and
   the two figures have different ones. */
.gas-hit:focus-visible { stroke: var(--c-red); stroke-width: 9; }
.gas-phone .gas-hit:focus-visible { stroke-width: 2.5; }

@media (prefers-reduced-motion: reduce) {
  /* The state still changes, it just arrives instead of easing — a finished
     state, never a frozen half-built one. */
  .gas-diagram [data-node] { transition: none; }
}



/* ---- Services panels ------------------------------------------------------------
   Three panels that hold only a brief at rest and expand on click, one at a
   time. The row is a FIXED-HEIGHT flex container, for two reasons that reinforce
   each other: a collapsed sibling shows its title rotated up a 68px rail, which
   is illegible in a short card; and with the height fixed, expanding animates
   widths only, so nothing below the section can ever move.
   The disclosure logic lives in js/services-panels.js — it only toggles the
   `is-open` / `has-open` classes and ARIA. Every transition below is CSS. */
.svc-row {
  display: flex;
  gap: 1.2rem;
  /* Sized to the tallest panel, measured: content columns run 205 / 266 / 259px,
     plus 51px of heading, the close control and the vertical padding. Set by
     the consulting silhouette, so shrink that before touching this. */
  height: 420px;
  align-items: stretch;
}
.svc-panel {
  position: relative;
  /* own stacking context, so the contour wash can sit at z-index -1: above the
     panel background, below every bit of content, without the content needing
     position:relative of its own. Content that is NOT positioned is what lets
     the toggle stretch its hit area across the whole panel below. */
  isolation: isolate;
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: var(--c-surface);
  border: 1px solid var(--line-on-dark);
  border-radius: var(--radius);
  transition: flex-grow 0.52s var(--ease-out), flex-basis 0.52s var(--ease-out),
              border-color 0.3s, transform 0.25s var(--ease-out);
}
/* contour wash, the site's own motif, filling the space above the brief.
   Offset per panel so the three do not read as clones, and masked so it never
   competes with the text it sits behind. */
.svc-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("../assets/img/topo-bg.png") center / 150% auto no-repeat;
  opacity: 0.16;
  z-index: -1;
  -webkit-mask-image: linear-gradient(to bottom, #000 45%, transparent 92%);
  mask-image: linear-gradient(to bottom, #000 45%, transparent 92%);
  pointer-events: none;
}
.svc-panel:nth-child(2)::before { background-position: 30% 40%; }
.svc-panel:nth-child(3)::before { background-position: 70% 70%; }

/* ---- resting state: title + brief pushed to the foot of the panel ---- */
.svc-panel__head { margin: 0 0 0.55rem; }
.svc-panel__toggle {
  width: 100%;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0;
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  padding-right: 1.5rem;   /* keeps a long title clear of the plus */
}
.svc-panel__title {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--t-on-dark);
}
.svc-panel__brief {
  font-size: var(--fs-small);
  line-height: 1.65;
  color: var(--t-on-dark-muted);
  margin: 0;
  /* The longest brief, MEASURED at the panel width: the three render 92, 100
     and 123px, so they bottom-anchor to different heights and the titles step
     down across the row unless they are all given the tallest one. 8.8em =
     123px at this font-size and leading. Re-measure if the briefs are
     rewritten longer — this is the price of a fixed-height design. */
  min-height: 8.8em;
}
.svc-panel__head { margin-top: auto; }     /* everything sits at the foot */
.svc-panel__head, .svc-panel__brief { padding: 0 1.8rem; }
.svc-panel__brief { padding-bottom: 1.9rem; }
.svc-panel__head { padding-top: 1.8rem; }

/* Panel mark — card art, not a bullet icon. It owns the whole empty face of
   the resting panel, so it is sized and placed to fill it rather than to sit
   politely in a corner.

   Two things make the centring exact rather than approximate:
   * `flex: 1` gives this element the entire gap between the panel's top edge
     and the head, so `place-items: center` centres the mark in that gap on
     both axes — no auto-margin arithmetic, and nothing to re-derive if the
     brief is rewritten longer.
   * `padding-top` MIRRORS the head's own `padding-top` sitting below the
     mark. Without it the mark lands half that padding above true centre,
     which is exactly the 40px error the previous version had — dressed up as
     an optical correction it was far too large to be.

   Horizontally it is centred in the card, not aligned to the text column. At
   this size the mark is a plate above a caption, and a plate hugging one edge
   of a 371px card reads as a mistake however well it lines up with the title.

   Brand red at rest, full red on hover — the same two-step as the plus. */
.svc-panel__icon {
  flex: 1;
  min-height: 0;
  display: grid;
  place-items: center;
  padding: 1.8rem 1.8rem 0;
  color: rgba(224, 51, 65, 0.5);
  transition: color 0.25s;
  pointer-events: none;   /* the toggle's stretched hit area keeps the clicks */
}
/* Stroke weight is set on the artwork at 1 unit in a 64 viewBox, which lands
   at ~2.3px here — the page's own hairline weight. Scaling the mark up
   without thinning the stroke would have made these the heaviest lines on
   the page by some way. */
.svc-panel__icon svg { width: 150px; height: 150px; display: block; }
.svc-panel:hover .svc-panel__icon, .svc-panel:focus-within .svc-panel__icon {
  color: var(--c-red);
}
/* Only while the row is at rest. Open, the body needs the height; collapsed to
   a 68px rail, the head centres itself with `margin: auto 0` and a sibling
   above it would throw that off. `has-open` on the row covers both. */
.svc-row.has-open .svc-panel__icon { display: none; }

.svc-panel:not(.is-open) .svc-panel__toggle::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}
.svc-panel:not(.is-open) { cursor: pointer; }

/* the affordance: the same plus the Products capability blobs use, so the two
   read as one idiom rather than two inventions */
.svc-panel::after {
  content: "+";
  position: absolute;
  top: 1rem;
  right: 1.25rem;
  z-index: 2;
  font-family: var(--font-mono);
  font-size: 1.15rem;
  line-height: 1;
  color: rgba(224, 51, 65, 0.45);
  transition: color 0.25s, transform 0.25s, opacity 0.25s;
  pointer-events: none;
}
.svc-panel:hover::after, .svc-panel:focus-within::after {
  color: var(--c-red);
  transform: rotate(90deg);
}
.svc-panel.is-open::after { opacity: 0; }

.svc-panel:hover, .svc-panel:focus-within {
  transform: translateY(-4px);
  border-color: rgba(224, 51, 65, 0.5);
}

/* ---- expanded ---- */
.svc-row.has-open .svc-panel.is-open { flex: 1 1 auto; border-color: rgba(224, 51, 65, 0.35); }
.svc-panel.is-open { transform: none; }
.svc-panel.is-open .svc-panel__head { margin-top: 0; }
.svc-panel.is-open .svc-panel__brief { display: none; }

.svc-panel__body {
  /* flex: none + zero height while closed, so the body claims NO space and
     `margin-top: auto` on the head can push the title and brief to the foot of
     the panel. With the body still flexed, it ate the free space and the text
     sat at the top with a void beneath it. */
  flex: none;
  height: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0 1.8rem;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity 0.32s var(--ease-out), transform 0.32s var(--ease-out), visibility 0s 0.32s;
}
/* the body arrives BEHIND the width so text never reflows mid-slide */
.svc-panel.is-open .svc-panel__body {
  flex: 1;
  height: auto;
  padding-bottom: 1.6rem;
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0.18s, 0.18s, 0s;
}
/* The open panel is wide and short — roughly 980x360. Running prose the full
   width would be a ~140-character measure, so the width carries a SECOND
   COLUMN instead: prose left at a readable measure, the panel's artifact
   right. That also halves the content's vertical extent, which is what lets
   every panel fit the fixed row height without scrolling. */
.svc-panel__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-right: 0.4rem;
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.7fr);
  gap: 0;
  align-content: start;
}
.svc-panel__main > p:last-child { margin-bottom: 0; }
.svc-panel__main { padding-right: 2.6rem; }
/* a vertical rule between the two columns — a horizontal one above the aside
   read as an underline belonging to nothing */
.svc-panel__aside {
  border-left: 1px solid var(--line-on-dark);
  padding-left: 2.6rem;
}
.svc-panel__aside--art { display: flex; justify-content: center; align-items: flex-start; }
/* the event list carries long names and needs the width the other two asides
   do not — a silhouette is 55px wide and a pulled-out line reflows freely */
#svc-training .svc-panel__scroll { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
#svc-consulting .svc-panel__scroll { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); }
.svc-panel__body p { font-size: var(--fs-small); line-height: 1.7; margin: 0 0 0.9rem; }
.svc-panel__close {
  align-self: flex-start;
  margin-top: 0.6rem;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--t-on-dark-faint);
  transition: color 0.25s;
}
.svc-panel__close:hover, .svc-panel__close:focus-visible { color: var(--c-red); }

/* ---- collapsed rail: the title turns and runs up the panel ---- */
.svc-row.has-open .svc-panel:not(.is-open) { flex: 0 0 68px; }
.svc-row.has-open .svc-panel:not(.is-open) .svc-panel__brief { display: none; }
.svc-row.has-open .svc-panel:not(.is-open) .svc-panel__head {
  margin: auto 0;
  padding: 1.4rem 0;
}
.svc-row.has-open .svc-panel:not(.is-open) .svc-panel__toggle {
  height: 100%;
  justify-content: center;
}
.svc-row.has-open .svc-panel:not(.is-open) .svc-panel__title {
  writing-mode: vertical-rl;
  transform: rotate(180deg);      /* reads bottom-to-top */
  white-space: nowrap;
  color: var(--t-on-dark-muted);
  transition: color 0.25s;
}
.svc-row.has-open .svc-panel:not(.is-open):hover .svc-panel__title { color: var(--t-on-dark); }

/* ---- panel furniture ---- */
.svc-panel__body p.svc-hero {
  font-family: var(--font-display);
  font-size: 1.45rem !important;
  font-weight: 600;
  line-height: 1.3 !important;
  color: var(--c-red);
  /* no rule above and no top offset: the divider is the vertical one between
     the columns, and the line has to start level with the prose beside it */
  margin: 0 !important;
}
.svc-panel__body p.svc-label {
  font-family: var(--font-mono);
  font-size: var(--fs-mono) !important;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-red);
  margin-bottom: 0.7rem !important;
}
.svc-events { margin: 0 0 1.1rem; }
.svc-events > div {
  display: grid;
  grid-template-columns: 3.6rem 1fr;    /* fixed year column, hard left edge */
  gap: 0.9rem;
  padding: 0.32rem 0;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.svc-events dt {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  color: var(--c-red);
  line-height: 1.7;
}
.svc-events dd {
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.7;
  color: var(--t-on-dark);
}
.svc-events dd span { color: var(--t-on-dark-faint); }

/* A MASK over the SOURCE RASTER, which is the whole point: every RGB channel
   in that PNG is zero and the entire drawing — body, lobes, leader rules,
   inset, scale bar and labels — lives in its alpha. Masking a coloured box
   therefore reproduces the artwork exactly, in a colour the theme controls,
   with no editing and no tracing.

   Showing the labels publishes real field names, which CLAUDE.md tells us not
   to do. That was raised twice and overridden deliberately; it is a content
   decision, not an oversight. Aspect is the file's own 1024:1536. */
.svc-ghawar {
  display: block;
  flex: none;
  width: 197px;
  height: 296px;
  background: var(--c-red);
  opacity: 0.9;
  -webkit-mask: url("../assets/img/ghawar-map.png") center / contain no-repeat;
  mask: url("../assets/img/ghawar-map.png") center / contain no-repeat;
  filter: drop-shadow(0 0 26px rgba(224, 51, 65, 0.28));
}
.svc-note { color: var(--t-on-dark-muted); }
/* Inline links inside a panel. The global rule sets a { color: inherit }, so
   without this a link is indistinguishable from the prose around it. */
.svc-panel__body a {
  color: var(--t-on-dark);
  text-decoration: underline;
  text-decoration-color: rgba(224, 51, 65, 0.55);
  text-underline-offset: 3px;
  transition: color 0.2s, text-decoration-color 0.2s;
}
.svc-panel__body a:hover,
.svc-panel__body a:focus-visible { color: var(--c-red); text-decoration-color: var(--c-red); }

/* ---- History timeline ----------------------------------------------------------- */
.section--history .container { position: relative; z-index: 1; }
.timeline-wrap { position: relative; }
.timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2rem;
}
.timeline li {
  position: relative;
  z-index: 1;                /* dots and text sit over the line */
  padding-top: 1.6rem;
  border-top: 1px solid var(--line-on-dark);
}
/* The drawn line takes the rule's job — but only once JS has actually drawn
   one. The border is kept and made transparent rather than removed, so the box
   model is byte-identical and no entry can shift by the 1px it occupies. */
.timeline-wrap.has-signal .timeline li { border-top-color: transparent; }

/* ---- History signal ------------------------------------------------------------- */
.history-signal {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;         /* the event rides above the baseline */
  pointer-events: none;
  z-index: 0;
}
.history-signal__line,
.history-signal__rest {
  stroke: var(--c-red);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.history-signal__rest { stroke-opacity: 0.18; }

.timeline li::before {
  content: "";
  position: absolute;
  top: -4.5px;
  left: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--c-red);
  box-shadow: 0 0 0 4px rgba(224, 51, 65, 0.18);
  transition: opacity 0.25s ease;
}
/* dots dim ahead of the event, but only while a scrub is actually driving
   them — otherwise a degraded page would show four permanently faded dots */
.timeline-wrap.is-scrubbed li:not(.is-lit)::before { opacity: 0.3; }
.timeline li.has-pulsed::before { animation: node-pulse 0.5s ease-out; }
@keyframes node-pulse {
  0%   { box-shadow: 0 0 0 4px rgba(224, 51, 65, 0.18); }
  45%  { box-shadow: 0 0 0 11px rgba(224, 51, 65, 0.30); }
  100% { box-shadow: 0 0 0 4px rgba(224, 51, 65, 0.18); }
}
.timeline__year {
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 500;
  color: #fff;
  display: block;
  /* max-content, not the default full column width. The box is invisible, but
     the .is-current scale(1.1) below scales the BOX, not the glyphs — and a
     322px-wide box at 390px of viewport grew 32px past its column and scrolled
     the whole page sideways. Hugging the text makes the same scale grow ~40px
     to ~44px. The text renders identically either way, since it is left
     aligned and the transform origin is the left edge. */
  width: max-content;
  max-width: 100%;
  margin-bottom: 0.6rem;
  /* scale, never font-size: the year sits above its paragraph, so growing the
     type would shove the body text down as the event passes. A transform does
     not reflow. */
  transform-origin: left center;
  transition: transform 0.35s ease, text-shadow 0.35s ease, opacity 0.35s ease;
}
.timeline p { transition: opacity 0.35s ease; }

/* The entry the event has reached lifts; its neighbours step back — but ONLY
   while the signal is still travelling. `is-settled` lands when the run ends
   and restores everyone to full strength, because a dim that outlives the
   animation stops reading as emphasis and starts reading as a fault.
   Opacity goes on the CHILDREN, not the <li>: GSAP's [data-reveal-group]
   stagger animates the <li>'s own opacity, and a CSS transition on the same
   property would fight it frame by frame. */
.timeline-wrap.is-scrubbed:not(.is-settled) li:not(.is-current) .timeline__year,
.timeline-wrap.is-scrubbed:not(.is-settled) li:not(.is-current) p { opacity: 0.45; }

.timeline-wrap.is-scrubbed li.is-current .timeline__year {
  transform: scale(1.1);
  text-shadow: 0 0 18px rgba(224, 51, 65, 0.5);
}
.timeline p { font-size: var(--fs-small); }
.timeline strong { color: var(--t-on-dark); }

/* ---- Research -------------------------------------------------------------------
   One featured item over three columns. Two structural rules do most of the
   work here and both are load-bearing:

   1. `.rcol__slot` stacks every entry into ONE grid cell, so the cell is
      permanently as tall as the longest entry in it. That is what fixes the
      Papers slot height at every breakpoint without a magic pixel number —
      a slot that sized to the current entry would make the whole row jump
      every 7 seconds and read as a bug.
   2. `.rcol__foot` is pushed down with `margin-top: auto`, so the three
      footers share a baseline however unequal the columns above them are.
   ---------------------------------------------------------------------------- */
.research-top {
  display: grid;
  grid-template-columns: 1fr minmax(0, 0.86fr);
  gap: clamp(2rem, 5vw, 4rem);
  /* Bottom-aligned, not centred. Two reasons that pull the same way: the card
     and the lede then share a baseline, which anchors a card that otherwise
     floats in its column; and dropping the card clears the top of the ghost
     numeral behind it, which is why this section can carry one at all. */
  align-items: end;
  margin-bottom: clamp(2.5rem, 6vw, 4.5rem);
}
/* The numeral paints behind the card by DOM order alone — the head comes
   first and neither element opens a stacking context. Nothing to pin. */
/* the head's own bottom margin belongs to the stacked layout, not this one */
.research-head { margin-bottom: 0; max-width: 34rem; }

/* ---- Featured item ---- */
.feature {
  position: relative;
  padding: clamp(1.6rem, 3vw, 2.2rem);
  background: var(--c-surface);
  border: 1px solid var(--line-on-dark);
  border-radius: var(--radius);
  /* a red hairline along the top edge and a soft bloom beneath it: the
     section's peak, marked without a badge */
  background-image: radial-gradient(120% 80% at 50% 0%, rgba(224, 51, 65, 0.13) 0%, transparent 62%);
  overflow: hidden;
}
.feature::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--c-red) 22%, var(--c-red) 78%, transparent);
  opacity: 0.75;
}
.feature__label { margin-bottom: 0.9rem; }
.feature h3 { font-size: clamp(1.6rem, 2.6vw, 2.1rem); }
.section .feature__blurb { margin-top: 0.7rem; color: var(--t-on-dark-muted); }

.feature__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.6rem;
  margin-top: 1.6rem;
}
.feature__links a {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--t-on-dark-faint);
  transition: color 0.2s;
}
.feature__links a:hover { color: var(--c-red); }

/* ---- The three columns ---- */
.research-cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.2rem;
}
.rcol {
  display: flex;
  flex-direction: column;
  padding: 1.7rem 1.6rem;
  background: var(--c-surface);
  border: 1px solid var(--line-on-dark);
  border-radius: var(--radius);
  transition: border-color 0.25s, background-color 0.25s;
}
.rcol:hover { border-color: rgba(255, 255, 255, 0.22); }
.rcol h3 { font-size: 1.3rem; }
.section .rcol__framing {
  margin-top: 0.5rem;
  font-size: var(--fs-small);
  color: var(--t-on-dark-muted);
}

/* the stacked entry cell — see rule 1 in the block comment above */
.rcol__slot {
  position: relative;
  display: grid;
  margin-top: 1.5rem;
  padding-left: 1.1rem;
}
.rcol__slot > .entry { grid-area: 1 / 1; }

/* Left rule. On the live column it doubles as the dwell rail: the red
   segment fills top-to-bottom over one dwell, so the rotation reads as
   deliberate and you can see the change coming. Driven from JS with the Web
   Animations API — that animation IS the timer, so the rail and the switch
   share one clock and cannot drift apart. */
.rcol__rail {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: rgba(255, 255, 255, 0.13);
  border-radius: 2px;
}
.rcol__rail i {
  display: block;
  height: 100%;
  background: var(--c-red);
  border-radius: 2px;
  transform: scaleY(0);
  transform-origin: top;
}

/* Crossfade, not a slide: sliding implies swipeability and invites gestures
   that are not supported. `visibility` is stepped rather than transitioned so
   inactive entries leave the tab order and the a11y tree the moment they go. */
.entry {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  text-decoration: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.55s var(--ease-out), visibility 0s linear 0.55s;
}
.entry.is-current {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.55s var(--ease-out), visibility 0s;
}
.entry__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.02rem;
  line-height: 1.3;
  color: var(--t-on-dark);
  transition: color 0.2s;
}
.entry:hover .entry__title { color: var(--c-red); }
.entry__meta {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--t-on-dark-faint);
}

/* ---- Rotation indicators ---- */
/* The dot you see is still 7px, but the thing you TAP is 24px — the buttons
   used to be 7x7 and 9x9, which is a coin-flip on a phone. 24px is WCAG 2.2
   SC 2.5.8 (Target Size, Minimum); gap:0 lets neighbours meet exactly, so the
   targets tile without overlapping and no tap lands on the wrong dot.
   The offsets below keep the visible dots where they already were. */
.papers__dots {
  display: flex;
  gap: 0;
  margin-top: 0.45rem;                  /* was 1rem — the button now carries
                                           8.5px of its own padding above */
  padding-left: calc(1.1rem - 8.5px);   /* was 1.1rem, aligned to the entries */
}
.papers__dots button {
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: none;
  display: grid;
  place-items: center;
  cursor: pointer;
}
.papers__dots button::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.22);
  transition: background-color 0.25s, transform 0.25s;
}
.papers__dots button:hover::before { background: rgba(255, 255, 255, 0.45); }
.papers__dots button[aria-current="true"]::before {
  background: var(--c-red);
  transform: scale(1.25);
}
/* the ring now has the 24px box to sit on, so it no longer needs pushing out */
.papers__dots button:focus-visible { outline-offset: -3px; }

/* ---- Column footers ---- */
.rcol__foot {
  margin-top: auto;
  padding-top: 1.8rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--t-on-dark-faint);
  transition: color 0.2s;
}
a.rcol__foot:hover { color: var(--c-red); }

/* ---- Partners ------------------------------------------------------------------- */
.partner-wall {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid var(--line-on-dark);
  border-left: 1px solid var(--line-on-dark);
}
.partner-wall li {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(2rem, 5vw, 3.5rem) 1.5rem;
  border-right: 1px solid var(--line-on-dark);
  border-bottom: 1px solid var(--line-on-dark);
  transition: background-color 0.25s;
}

/* per-logo heights, tuned so the marks carry similar visual weight */
.partner-logo {
  width: auto;
  max-width: min(72%, 300px);
  object-fit: contain;
  opacity: 0.78;
  transition: opacity 0.25s;
}
.partner-wall li:hover .partner-logo { opacity: 1; }
.partner-logo--nvidia      { height: clamp(64px, 7vw, 92px); }
.partner-logo--aws         { height: clamp(46px, 4.8vw, 62px); }
.partner-logo--spe         { height: clamp(48px, 5vw, 66px); }
.partner-logo--dell        { height: clamp(30px, 3.2vw, 44px); }
.partner-logo--eage        { height: clamp(32px, 3.4vw, 46px); }
.partner-logo--thinkonward { height: clamp(26px, 2.8vw, 36px); }
.partner-wall li::before {
  /* survey-grid tick at each cell corner */
  content: "+";
  position: absolute;
  top: -0.62em;
  left: -0.36em;
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 400;
  color: rgba(224, 51, 65, 0.5);
}
.partner-wall li:hover { color: #fff; background: rgba(224, 51, 65, 0.08); }

/* ---- Contact ---------------------------------------------------------------------- */
.contact {
  display: grid;
  grid-template-columns: 5fr 6fr;
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: start;
}
.contact__info h2 { font-size: var(--fs-h2); font-weight: 800; }
.contact__info .lede { margin-top: 1.4rem; font-size: var(--fs-lede); }
.contact__email {
  display: inline-block;
  margin-top: 2rem;
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  font-weight: 700;
  color: var(--t-on-dark);
  text-decoration: none;
  border-bottom: 2px solid var(--c-red);
  padding-bottom: 0.15em;
  transition: color 0.2s;
}
.contact__email:hover { color: var(--c-red); }

.social {
  list-style: none;
  display: flex;
  gap: 1.4rem;
  margin: 1.8rem 0 0;
  padding: 0;
}
.social a {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--t-on-dark-muted);
  text-decoration: none;
}
.social a:hover { color: var(--c-red); }

.contact__form {
  background: var(--c-surface);
  border: 1px solid var(--line-on-dark);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 3vw, 2.5rem);
  display: grid;
  gap: 1.1rem;
}
.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.1rem;
}
.field { display: grid; gap: 0.45rem; }
.field span {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--t-on-dark-muted);
}
.field em { text-transform: none; font-style: normal; opacity: 0.6; }
.field input,
.field textarea {
  font: inherit;
  color: var(--t-on-dark);
  background: var(--c-ink);
  border: 1px solid var(--line-on-dark);
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.9rem;
  transition: border-color 0.2s;
  resize: vertical;
}
.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--c-red);
}
.contact__form .btn { justify-self: start; }
.contact__form-note {
  font-size: var(--fs-small);
  color: var(--t-on-dark-muted);
  min-height: 1.4em;
}

/* ---- Publications (research.html) ------------------------------------------------
   A chronological index, newest first. Dense on purpose: a padded list of
   twenty-two entries reads as thin, a tight one reads as a body of work.
   ---------------------------------------------------------------------------- */

/* Visible only once focused — the first stop for a keyboard user on a page
   whose content is a long list of links behind a nav. */
.skip-link {
  position: absolute;
  left: 1rem;
  top: -4rem;
  z-index: 100;
  padding: 0.7em 1.2em;
  border-radius: var(--radius-sm);
  background: var(--c-red);
  color: #fff;
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 700;
  text-decoration: none;
  transition: top 0.2s var(--ease-out);
}
.skip-link:focus { top: 1rem; }

/* the sub-page has no hero, so its content starts under the fixed header */
.page-sub main { padding-top: var(--header-h); }
.pub-head h1 {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -0.015em;
  margin: 0;
  color: var(--t-on-dark);
}
/* muted, not faint: this line carries information (the links leave the site),
   and faint measures 3.58:1 on the ink background — under AA */
.section .pub-note {
  margin-top: 0.9rem;
  font-size: var(--fs-small);
  color: var(--t-on-dark-muted);
}

.pub-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--line-on-dark);
}
.pub-list li { border-bottom: 1px solid var(--line-on-dark); }

/* One link per row, covering year + title + venue: the whole row is the target,
   and a screen reader reads the three as a single name ("2025, Rust-Powered
   Engine…, ADIPEC (SPE), link") instead of three separate stops. */
.pub {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1.5fr) minmax(0, 0.85fr) 1.5rem;
  align-items: baseline;
  gap: 0 1.5rem;
  padding: 1rem 0.4rem;
  text-decoration: none;
  transition: background-color 0.2s, padding-left 0.2s var(--ease-out);
}
.pub:hover, .pub:focus-visible {
  background: rgba(255, 255, 255, 0.035);
  padding-left: 0.9rem;
}
.pub__year {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  /* Brand red, matching the title it turns red on hover. See tokens.css. */
  color: var(--c-red);
  letter-spacing: 0.06em;
}
.pub__title {
  font-size: 1.02rem;
  line-height: 1.4;
  color: var(--t-on-dark);
  transition: color 0.2s;
}
.pub:hover .pub__title { color: var(--c-red); }
.pub__venue {
  font-size: var(--fs-small);
  line-height: 1.45;
  color: var(--t-on-dark-muted);
}
.pub__mark {
  justify-self: end;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  /* aria-hidden, so WCAG treats it as decorative and exempt — but it is the
     only VISUAL cue that a row leaves the site, so it gets a legible weight
     anyway, matching the venue beside it */
  color: var(--t-on-dark-muted);
  transition: color 0.2s, transform 0.2s var(--ease-out);
}
.pub:hover .pub__mark { color: var(--c-red); transform: translate(2px, -2px); }

.pub-back {
  margin-top: 2.5rem;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.pub-back a { color: var(--t-on-dark-muted); text-decoration: none; transition: color 0.2s; }
.pub-back a:hover { color: var(--c-red); }

@media (max-width: 46rem) {
  /* the venue drops under the title rather than into a column too narrow to
     hold "Fourth EAGE Digitalization Conference & Exhibition" */
  .pub {
    grid-template-columns: 3.6rem 1fr 1.2rem;
    gap: 0.25rem 1rem;
  }
  .pub__title { grid-column: 2; }
  .pub__venue { grid-column: 2; }
  .pub__mark { grid-column: 3; grid-row: 1; }
}

/* ---- Footer ------------------------------------------------------------------------ */
.site-footer {
  position: relative;
  background: var(--c-deep);
  color: var(--t-on-dark-muted);
  padding-top: clamp(3rem, 6vw, 4.5rem);
  overflow: hidden;
}
.site-footer__inner {
  display: grid;
  grid-template-columns: 2fr 1fr 1.4fr;
  gap: 2.5rem;
  padding-bottom: 2.5rem;
}
.site-footer__brand img { width: 89px; margin-bottom: 1rem; }
.site-footer__brand p { font-size: var(--fs-small); }

.site-footer__nav {
  display: grid;
  gap: 0.55rem;
  align-content: start;
}
.site-footer__nav a,
.site-footer__contact a {
  font-size: var(--fs-small);
  color: var(--t-on-dark-muted);
  text-decoration: none;
}
.site-footer__nav a:hover,
.site-footer__contact a:hover { color: #fff; }

.site-footer__contact > a {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.1rem;
  color: var(--t-on-dark);
}
.site-footer__social {
  display: flex;
  gap: 1.2rem;
  margin-top: 1rem;
}
.site-footer__social a {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.site-footer__legal {
  position: relative;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  padding-block: 1.4rem;
  font-size: 0.8125rem;
  color: var(--t-on-dark-faint);
}

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width: 68rem) {
  .side-label { display: none; }
}

@media (max-width: 62rem) {
  .product { grid-template-columns: 1fr; }
  .product__media { margin-top: 1rem; }
  .pipeline { grid-template-columns: repeat(2, 1fr); }
  /* one column from here down: the line has only two honest orientations, and
     a path snaking between grid rows is neither */
  .timeline { grid-template-columns: 1fr; gap: 1.4rem; }
  /* gutter for the vertical line, with the dots moved into it */
  .timeline li { padding-left: 28px; }
  .timeline li::before { left: 8px; }
  /* stacked entries need their divider back — the line runs down the gutter
     beside them, not along them, so it cannot do that job */
  .timeline-wrap.has-signal .timeline li { border-top-color: var(--line-on-dark); }
  /* Research: the head and the featured item stop sharing a row, and the three
     columns stack in source order. Three columns below this width leaves each
     one narrow enough to break a paper title over six or seven lines. */
  .research-top { grid-template-columns: 1fr; }
  .research-head { max-width: none; }
  .research-cols { grid-template-columns: 1fr; }

  /* Services: vertical accordion. Stacked, there is no rail to keep tall and
     no sibling to displace, so the panels size to their content and the body
     animates open with a 0fr->1fr grid row — no magic max-height. */
  .svc-row { display: block; height: auto; }
  .svc-panel { margin-bottom: 1rem; }
  /* stacked, the panels size to their content and there is no free space for a
     mark to fill — it would only add height to every card */
  .svc-panel__icon { display: none; }
  .svc-panel__head { margin-top: 0; }
  .svc-panel.is-open .svc-panel__brief { display: block; }
  .svc-panel__body {
    display: grid;
    grid-template-rows: 0fr;
    padding-bottom: 0;
    transform: none;
    transition: grid-template-rows 0.42s var(--ease-out), opacity 0.3s var(--ease-out),
                visibility 0s 0.42s, padding-bottom 0.42s var(--ease-out);
  }
  .svc-panel.is-open .svc-panel__body {
    grid-template-rows: 1fr;
    padding-bottom: 1.6rem;
    transition-delay: 0s;
  }
  .svc-panel__scroll { overflow: hidden; min-height: 0; padding-right: 0; }
  /* no rails on mobile: nothing rotates, nothing collapses to a strip */
  .svc-row.has-open .svc-panel:not(.is-open) { flex: none; }
  .svc-row.has-open .svc-panel:not(.is-open) .svc-panel__brief { display: block; }
  .svc-row.has-open .svc-panel:not(.is-open) .svc-panel__head { margin: 0; padding: 1.8rem 1.8rem 0; }
  .svc-row.has-open .svc-panel:not(.is-open) .svc-panel__toggle { height: auto; justify-content: space-between; }
  .svc-row.has-open .svc-panel:not(.is-open) .svc-panel__title {
    writing-mode: horizontal-tb;
    transform: none;
    color: var(--t-on-dark);
  }
  .svc-panel__scroll { grid-template-columns: 1fr; gap: 1.4rem; }
  .svc-panel__main { padding-right: 0; }
  .svc-panel__aside {
    border-left: 0;
    border-top: 1px solid var(--line-on-dark);
    padding-left: 0;
    padding-top: 1.4rem;
  }
  .svc-ghawar { display: none; }

  .contact { grid-template-columns: 1fr; }
  .site-footer__inner { grid-template-columns: 1fr 1fr; }
  .section-head[data-num]::before { right: auto; left: -0.06em; opacity: 0.7; }
}

@media (max-width: 46rem) {
  .site-nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.5rem var(--gutter) 1.5rem;
    background: rgba(14, 14, 14, 0.97);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--line-on-dark);
    display: none;
  }
  .nav-open .site-nav { display: flex; }
  .nav-open .site-header { background: rgba(14, 14, 14, 0.97); }
  .site-nav > a:not(.btn) {
    padding: 0.9rem 0;
    font-size: 1.05rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  }
  .site-nav .btn { margin-top: 1.1rem; justify-content: center; }
  .nav-toggle { display: flex; }

  .hero__actions .btn { flex: 1 1 100%; }
  .hero__terrain { width: 100vw; height: 46vh; opacity: 0.3; }
  .hero__scroll { display: none; }
  .hero__content { padding-bottom: 6.5rem; }
  .card-grid, .field-row, .gas-points { grid-template-columns: 1fr; }
  /* the rails stay — they are what distinguishes the two entries once the
     columns are gone */
  .product-pair { grid-template-columns: 1fr; gap: 1.6rem; }
  .pipeline { grid-template-columns: 1fr 1fr; }
  .partner-wall { grid-template-columns: repeat(2, 1fr); }
  .site-footer__inner { grid-template-columns: 1fr; gap: 2rem; }
}

/* ---- Motion preferences -------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001s !important;
    transition-duration: 0.001s !important;
  }
  .ticker__track { animation: none; }
  /* the blanket rule above zeroes durations but not DELAYS, and the panel body
     deliberately waits 0.18s behind the width — without this it would still
     lag on a machine asking for no motion */
  .svc-panel__body { transition-delay: 0s !important; }
}
