/* ============================================================
   Hephaestus Technologies — demo site styles
   Layout study: modern autonomy-brand landing page structure
   ============================================================ */

:root {
  --ink: #12222f;
  --ink-soft: #48606f;
  /* Accent greens, centralised — these were previously scattered as 23 literals
     in four different notations. Saturation is raised well above the old pair,
     which read as dull against black; lightness is deliberately unchanged, so
     only the vividness shifts. The -rgb triplet exists so rgba() calls can
     share the same value. */
  --accent: #00d9a2;              /* eyebrows, primary button */
  --accent-dark: #00b487;         /* primary button hover */
  --accent-bright: #00d9a2;       /* highlights on black */
  --accent-bright-rgb: 79, 248, 209;
  /* Cold counterpart to the accent: the conductor half of the technology
     section is lit in steel so the two tracks read apart at a glance. */
  --steel: #bad6e0;
  --steel-rgb: 186, 214, 224;
  --bg: #ffffff;
  --bg-soft: #f4f7f8;
  --bg-mute: #e9f2f0;
  --line: #dde5e8;
  --radius-lg: 24px;
  --radius-md: 16px;
  --radius-pill: 999px;
  --shadow-card: 0 8px 30px rgba(18, 34, 47, 0.08);
  --header-h: 72px;       /* compact, once scrolled */
  --header-h-tall: 108px; /* at the top of the page */
  --bar: rgba(8, 8, 10, 0.48); /* frosted bar surface — pairs with the blur */
  --font: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 16px);
  /* Paints the overscroll gutter, so rubber-banding past either end
     shows black rather than the white body background. */
  background: #08080a;
}

body {
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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

a { color: inherit; text-decoration: none; }

.container {
  width: min(1200px, 100% - 48px);
  margin-inline: auto;
}

/* ---------- Type ---------- */

h1, h2, h3 { font-weight: 400; letter-spacing: -0.02em; line-height: 1.12; }

.eyebrow {
  display: inline-block;
  color: var(--accent);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.82rem;
  margin-bottom: 14px;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-block;
  font-family: inherit;
  font-weight: 700;
  font-size: 0.98rem;
  padding: 13px 28px;
  border-radius: var(--radius-pill);
  border: 2px solid transparent;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}

.btn:active { transform: scale(0.97); }

.btn-primary { background: var(--accent); color: #08080a; }
.btn-primary:hover { background: var(--accent-dark); }

.btn-ghost { background: rgba(255, 255, 255, 0.14); color: #fff; border-color: rgba(255,255,255,0.7); backdrop-filter: blur(4px); }
.btn-ghost:hover { background: rgba(255, 255, 255, 0.28); }

.btn-lg { padding: 16px 36px; font-size: 1.05rem; }

/* ============================================================
   Header
   ============================================================ */

.site-header {
  --nav-size: 1.11rem;        /* nav text scales with the bar, like the logo */
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--header-h-tall);
  background: transparent;
  z-index: 100;
  transition: height 0.32s ease, background 0.32s ease, box-shadow 0.32s ease;
}

.site-header.scrolled {
  --nav-size: 0.97rem;
  height: var(--header-h);
  background: var(--bar);
  backdrop-filter: blur(12px);
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.35);
}

/* Keep the bar readable when the mobile menu is open over the hero */
.site-header.menu-open { background: var(--bar); backdrop-filter: blur(12px); }

.header-inner {
  width: min(1320px, 100% - 48px);
  margin-inline: auto;
  height: 100%;
  display: flex;
  align-items: center;
  gap: 36px;
}

.logo { display: flex; align-items: center; gap: 10px; color: #fff; flex-shrink: 0; }
.logo-img { height: 44px; width: auto; transition: height 0.32s ease; }
.site-header.scrolled .logo-img { height: 34px; }
.logo-img-footer { height: 40px; }

.main-nav { flex: 1; }

.nav-list { display: flex; justify-content: flex-end; gap: 6px; list-style: none; }

.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 16px;
  font: inherit;
  font-weight: 600;
  font-size: var(--nav-size);
  color: rgba(255, 255, 255, 0.88);
  background: none;
  border: none;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, font-size 0.32s ease;
}

.nav-link:hover { background: rgba(255, 255, 255, 0.12); color: #fff; }

.nav-item { position: relative; }

.header-actions { display: flex; align-items: center; gap: 14px; }

/* ---------- Private-access button + gate dialog ---------- */

.gate-open {
  position: relative;         /* anchors the glow behind it */
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 18px 9px 15px;
  font: inherit;
  font-weight: 600;
  font-size: 0.93rem;
  color: rgba(255, 255, 255, 0.85);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: var(--radius-pill);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

/* Slow green bloom behind the button, to draw the eye without nagging.
   The glow is an outer box-shadow on an empty pseudo-element: a shadow paints
   only beyond the box, so it haloes the pill instead of tinting its middle
   (a radial gradient pools in the centre of a wide shape). Only opacity
   animates, which the compositor handles. */
.gate-open::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;                /* behind the button, above the header bar */
  border-radius: inherit;
  box-shadow: 0 0 17px 2px rgba(var(--accent-bright-rgb), 0.6);
  pointer-events: none;
  animation: forgeGlow 3.6s ease-in-out infinite;
}

/* Intensity only, deliberately no scale: the pill is wide and short, so a
   uniform scale grows it far more horizontally than vertically and the halo
   stops matching the button's outline. */
@keyframes forgeGlow {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 1; }
}

.gate-open svg { width: 15px; height: 15px; }

.gate-open:hover {
  color: #fff;
  background: rgba(var(--accent-bright-rgb), 0.14);
  border-color: rgba(var(--accent-bright-rgb), 0.55);
}

/* Once they are on it, the invitation has done its job — settle to a steady
   glow so it is not still pulsing under the cursor. */
.gate-open:hover::before { animation: none; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .gate-open::before { animation: none; opacity: 0.45; }
}


/* Standalone login page: centres the panel the dialog used to centre itself */
.login {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 30px;
  padding: 32px;
  background: #08080a;
  color: #fff;
}

.login-logo img { height: 42px; width: auto; }

.login .gate-panel { width: min(400px, 100%); }

.gate-panel {
  padding: 34px 32px 28px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-lg);
  background: #0d0f11;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.7);
}

.gate-panel h2,
.gate-title { font-size: 1.5rem; }

.gate-intro { margin-top: 8px; color: rgba(255, 255, 255, 0.58); font-size: 0.95rem; }

.gate-label {
  display: block;
  margin: 26px 0 8px;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

.gate-input {
  width: 100%;
  padding: 12px 16px;
  font: inherit;
  font-size: 1rem;
  color: #fff;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 12px;
  outline: none;
  transition: border-color 0.2s ease;
}

.gate-input:focus { border-color: var(--accent); }


.gate-error {
  min-height: 1.3em;
  margin-top: 9px;
  color: #ff8a8a;
  font-size: 0.88rem;
}

.gate-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 8px; }

.gate-cancel {
  padding: 13px 22px;
  font: inherit;
  font-weight: 600;
  font-size: 0.95rem;
  color: rgba(255, 255, 255, 0.65);
  background: none;
  border: none;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease;
}

.gate-cancel:hover { color: #fff; background: rgba(255, 255, 255, 0.08); }


@media (max-width: 820px) {
  /* the label would crowd the hamburger; keep the padlock only */
  .gate-open { padding: 9px 12px; gap: 0; font-size: 0; }
  .gate-open svg { width: 17px; height: 17px; }
}

.hamburger {
  display: none;
  flex-direction: column;
  gap: 5px;
  background: none;
  border: none;
  padding: 8px;
  cursor: pointer;
}
.hamburger span { width: 24px; height: 2.5px; background: #fff; border-radius: 2px; transition: transform 0.25s ease, opacity 0.25s ease; }
.hamburger.active span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
.hamburger.active span:nth-child(2) { opacity: 0; }
.hamburger.active span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

/* ============================================================
   Hero
   ============================================================ */

/* Full viewport, so the image edge and the stat bar land on the window bottom.
   Deliberately not clipped: the stat bar has to hang below the hero's bottom
   edge as it drifts. z-index lifts that overhang above the next section, which
   would otherwise paint over it as a later sibling. */
.hero {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: flex;
  align-items: center;
  color: #fff;
}

/* dvh tracks the area actually visible, so mobile browser chrome doesn't
   push the stat bar below the fold */
@supports (min-height: 100dvh) {
  .hero { min-height: 100dvh; }
}

.hero-media, .hero-media video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* Paints black behind the footage, so nothing flashes through while the first
   frame is still decoding. isolation seals the video/overlay z-indexes below
   into their own stacking context — without it they outrank the hero copy,
   which carries no z-index of its own, and paint straight over it. */
.hero-media {
  background: #08080a;
  isolation: isolate;
}

/* Loop dissolve. Both clips sit at full opacity and identical geometry; which
   one you see is decided by stacking order, and only the outgoing clip's
   opacity is animated. Fading both at once would dip the composite dark
   halfway through, since two half-transparent layers over black do not add
   back up to one. */
.hero-video { z-index: 1; }
.hero-video.is-front { z-index: 2; }

/* 1.2s must match XFADE in script.js */
.hero-video.is-fading {
  opacity: 0;
  transition: opacity 1.2s linear;
}

.hero-overlay {
  position: absolute; inset: 0;
  z-index: 3;                 /* the videos now carry z-index, so the wash needs one too */
  background: linear-gradient(180deg, rgba(10, 22, 32, 0.55) 0%, rgba(10, 22, 32, 0.25) 45%, rgba(10, 22, 32, 0.65) 100%);
}

.hero-content {
  position: relative;
  width: min(1200px, 100% - 48px);
  margin-inline: auto;
  padding-top: var(--header-h-tall);
}

/* Entrance: each line slides in from the left, staggered. */
@keyframes heroSlideIn {
  from { opacity: 0; transform: translateX(-70px); }
  to   { opacity: 1; transform: translateX(0); }
}

.hero-content > * {
  animation: heroSlideIn 1s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.hero-content h1 { animation-delay: 0.15s; }
.hero-content .hero-sub { animation-delay: 0.32s; }
.hero-content .hero-ctas { animation-delay: 0.48s; }

@media (prefers-reduced-motion: reduce) {
  .hero-content > * { animation: none; }
}

.hero h1 { font-size: clamp(2.6rem, 6.5vw, 4.8rem); }

/* Sizes to the sentence so it stays on one line, and only wraps once the
   viewport is genuinely too narrow. Beats a fixed max-width, which would
   need revisiting every time the wording changes. */
.hero-sub {
  font-size: clamp(1.05rem, 2vw, 1.3rem);
  width: max-content;
  max-width: 100%;
  margin-top: 20px;
  color: rgba(255,255,255,0.92);
}

.hero-ctas { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 34px; }

/* Drifts downward with scroll. The translate is a percentage of the bar's own
   height, so 100% lands its top edge exactly on the image's bottom edge —
   reached as that edge arrives at the top of the window. */
.hero-stat-bar {
  position: absolute;
  inset: auto 0 0 0;
  background: rgba(10, 22, 32, 0.2);
  backdrop-filter: blur(8px);
  padding: 30px 0;
  transform: translateY(calc(var(--bar-progress, 0) * 20%));
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .hero-stat-bar { transform: none; }
}

.hero-stat-inner {
  width: min(1200px, 100% - 48px);
  margin-inline: auto;
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}

.hero-stat-number { font-size: clamp(1.5rem, 3vw, 2.2rem); font-weight: 800; color: var(--accent-bright); }
.hero-stat-text { font-size: 1rem; color: rgba(255,255,255,0.9); }

/* ============================================================
   Technology — the twin track

   One section carrying what used to be three (mission, plasma,
   materials). The two programmes are laid out as mirror images
   either side of a centre spine, so the block reads as "these
   two problems, worked in parallel, meeting in one machine".

   Everything scroll-driven is a custom property written by JS
   from the block's position in the viewport, and every one
   defaults to its finished value so the layout still lands
   correctly if the script never runs:
     --head      0 = heading out, 1 = in
     --converge  0 = halves parked off each outer edge, 1 = met
     --spine     0 = centre line collapsed, 1 = drawn full height

   Each side sets its own --tint (green for plasma, cold steel
   for the conductor), so one set of rules paints both halves.
   ============================================================ */

.twin {
  --head: 1;
  --converge: 1;
  --spine: 1;
  position: relative;
  overflow: hidden;           /* trims the conductor at the page edge */
  padding: 120px 0 110px;
  background: #08080a;
  color: #fff;
}

/* Two slow, out-of-phase washes — one under each half — so neither side of
   the section is sitting on flat black. */
.twin-aura {
  position: absolute;
  inset: -12% -6%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(44% 46% at 17% 40%, rgba(var(--accent-bright-rgb), 0.16), transparent 72%),
    radial-gradient(44% 46% at 83% 46%, rgba(var(--steel-rgb), 0.13), transparent 72%);
  animation: twinAura 15s ease-in-out infinite alternate;
}

@keyframes twinAura {
  from { opacity: 0.6; transform: scale(1); }
  to   { opacity: 1;   transform: scale(1.06); }
}

/* ---------- Heading ---------- */

.twin-head {
  position: relative;
  z-index: 1;
  max-width: 880px;
  text-align: center;
  opacity: var(--head);
  translate: 0 calc((1 - var(--head)) * 26px);
  will-change: opacity, translate;
}

.twin-title {
  font-size: clamp(2rem, 4vw, 3.2rem);
  text-wrap: balance;
}

.twin-lead {
  margin: 20px auto 0;
  max-width: 660px;
  color: rgba(255, 255, 255, 0.68);
  font-size: 1.04rem;
}

/* ---------- The split ---------- */

.twin-split {
  position: relative;
  z-index: 1;
  width: min(1200px, 100% - 48px);
  margin: clamp(54px, 6.5vw, 92px) auto 0;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  column-gap: clamp(30px, 4.4vw, 68px);
  align-items: start;
}

.twin-side {
  position: relative;
  opacity: var(--converge);
  will-change: opacity, translate;
}

/* Each half arrives from its own outer edge, so they meet at the spine */
.twin-side--magnets {
  --tint: var(--steel);
  --tint-rgb: var(--steel-rgb);
  --art-h: 108%;
  /* Centred by eye, not by bounding box. The artwork is a heavy bright loop
     with two thin leads trailing off one side, so its visual weight sits well
     right of its geometric middle (ink bbox 49% of the frame, brightness
     centroid 51.5%, the loop's own body 54.8%). Squaring the bbox on the
     column left it reading right-heavy; -55% puts the loop on the centre line
     and lets the leads run out to the left as a tail. */
  --art-x: -55%;
  grid-column: 1;
  translate: calc((1 - var(--converge)) * -130px) 0;
}

.twin-side--plasma {
  --tint: var(--accent-bright);
  --tint-rgb: var(--accent-bright-rgb);
  --art-h: 92%;               /* the torus is wide; height-fit it to the stage */
  --art-x: -50%;              /* its ink is centred in its own frame already */
  grid-column: 3;
  translate: calc((1 - var(--converge)) * 130px) 0;
}

/* ---------- Centre spine ---------- */

.twin-spine {
  grid-column: 2;
  grid-row: 1;
  align-self: stretch;
  justify-self: center;
  position: relative;
  width: 2px;
  opacity: var(--spine);
}

.twin-spine-line {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    transparent,
    rgba(255, 255, 255, 0.26) 14%,
    rgba(255, 255, 255, 0.5) 50%,
    rgba(255, 255, 255, 0.26) 86%,
    transparent);
  /* draws outward from the middle as the section arrives */
  transform: scaleY(var(--spine));
  transform-origin: 50% 50%;
}

/* A single charge running the length of the line, top to bottom */
.twin-spine-pulse {
  position: absolute;
  left: 50%;
  width: 6px;
  height: 6px;
  margin-left: -3px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 16px 4px rgba(255, 255, 255, 0.45);
  animation: twinSpinePulse 6s ease-in-out infinite;
}

@keyframes twinSpinePulse {
  0%   { top: 4%;  opacity: 0; }
  14%  { opacity: 1; }
  86%  { opacity: 1; }
  100% { top: 96%; opacity: 0; }
}

/* Slowly turning diamond where the two halves meet */
.twin-spine-node {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 15px;
  height: 15px;
  translate: -50% -50%;
  rotate: 45deg;
  background: #08080a;
  border: 1px solid rgba(255, 255, 255, 0.45);
  box-shadow: 0 0 22px rgba(255, 255, 255, 0.14);
  animation: twinNode 18s linear infinite;
}

@keyframes twinNode {
  from { rotate: 45deg; }
  to   { rotate: 405deg; }
}

/* ---------- Artwork stage ---------- */

.twin-stage {
  position: relative;
  height: clamp(200px, 23vw, 330px);
}

.twin-halo {
  position: absolute;
  inset: 2% 4%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(var(--tint-rgb), 0.22), rgba(var(--tint-rgb), 0) 72%);
  animation: twinBreathe 7.5s ease-in-out infinite alternate;
}

@keyframes twinBreathe {
  from { opacity: 0.5; transform: scale(0.92); }
  to   { opacity: 1;   transform: scale(1.08); }
}

/* Absolutely positioned, so its width shrink-wraps the artwork: every overlay
   inside it can then be inset: 0 and line up with the image exactly. */
.twin-art-wrap {
  position: absolute;
  top: 50%;
  left: 50%;
  height: var(--art-h);
  translate: var(--art-x) -50%;
}

.twin-art {
  height: 100%;
  width: auto;
  max-width: none;            /* the global img cap would squash the bleed */
  transition: filter 0.4s ease;
}

.twin-side:hover .twin-art { filter: brightness(1.09); }

/* ---------- Light running through the artwork ----------
   Each sweep layer is masked by the artwork itself, so the moving highlight
   is confined to the plasma / the cable rather than washing over the black
   around it: circulating current on one side, a sheen travelling the length
   of the conductor on the other. */

.twin-sweep {
  position: absolute;
  inset: 0;
  overflow: hidden;
  mix-blend-mode: screen;
  -webkit-mask-image: var(--art-mask);
          mask-image: var(--art-mask);
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

.twin-side--plasma  { --art-mask: url("../media/plasma_ring_1.png"); }
.twin-side--magnets { --art-mask: url("../media/wire.png"); }

/* Plasma: a wedge of brightness sweeping around the ring's centre */
.twin-sweep::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50.5%;                 /* centred on the torus, measured off the artwork */
  width: 240%;
  aspect-ratio: 1;
  translate: -50% -50%;
  background: conic-gradient(
    from 0deg,
    rgba(255, 255, 255, 0) 0deg 38deg,
    rgba(var(--tint-rgb), 0.42) 52deg,
    rgba(255, 255, 255, 0.62) 60deg,
    rgba(var(--tint-rgb), 0.38) 68deg,
    rgba(255, 255, 255, 0) 82deg 360deg);
  animation: twinSpin 6.5s linear infinite;
}

@keyframes twinSpin {
  from { rotate: 0deg; }
  to   { rotate: 360deg; }
}

/* Magnets: one band of light drawn along the cable and away off the edge */
.twin-side--magnets .twin-sweep::before {
  left: 0;
  top: 0;
  width: 46%;
  height: 100%;
  aspect-ratio: auto;
  background: linear-gradient(
    100deg,
    rgba(255, 255, 255, 0) 20%,
    rgba(var(--tint-rgb), 0.55) 44%,
    rgba(255, 255, 255, 0.85) 52%,
    rgba(var(--tint-rgb), 0.5) 60%,
    rgba(255, 255, 255, 0) 84%);
  animation: twinSheen 5s ease-in-out infinite;
}

@keyframes twinSheen {
  from { translate: -120% 0; }
  to   { translate: 340% 0; }
}

/* Field pulses leaving the coil. Positioned in the artwork's own coordinates
   (the loop centre sits at 57% / 51% of it) and carried by the mirror on the
   wrapper, so they stay on the coil rather than beside it. */
.twin-field {
  position: absolute;
  left: 57%;
  top: 51%;
  height: 74%;
  aspect-ratio: 1;
  translate: -50% -50%;
  border-radius: 50%;
  border: 1px solid rgba(var(--steel-rgb), 0.62);
  opacity: 0;
  animation: twinFieldPulse 5.4s ease-out infinite;
}

.twin-field:nth-of-type(2) { animation-delay: 1.8s; }
.twin-field:nth-of-type(3) { animation-delay: 3.6s; }

@keyframes twinFieldPulse {
  0%   { scale: 0.84; opacity: 0; }
  18%  { opacity: 0.7; }
  100% { scale: 1.95; opacity: 0; }
}

/* Sparks riding the tube's centreline. The path is plotted in the artwork's
   own pixel space, so it tracks the ring at every container width; opacity
   drops through the back half of the orbit, where the plasma is behind
   itself. */
.twin-orbit {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.twin-spark {
  fill: #eafff6;
  filter: drop-shadow(0 0 10px rgba(var(--accent-bright-rgb), 0.95));
  offset-path: path("M 1553 448 A 669 216 0 1 1 215 448 A 669 216 0 1 1 1553 448");
  /* Without fill-box, an SVG child scales about the middle of the viewBox, and
     the smaller a spark got the further it drifted off the ring toward it. */
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: twinSpark 8s linear infinite;
}

.twin-spark:nth-child(2) { animation-duration: 11s;   animation-delay: -4s; }
.twin-spark:nth-child(3) { animation-duration: 14.5s; animation-delay: -9s; }

/* The path starts at the right of the ring and runs clockwise, so the first
   half is the near side of the torus and the second half is behind it. */
@keyframes twinSpark {
  0%   { offset-distance: 0%;   opacity: 0.55; scale: 0.9; }
  25%  { opacity: 1;    scale: 1.15; }
  50%  { offset-distance: 50%;  opacity: 0.55; scale: 0.9; }
  75%  { opacity: 0.26; scale: 0.7; }
  100% { offset-distance: 100%; opacity: 0.55; scale: 0.9; }
}

/* ---------- Copy ---------- */

.twin-copy { margin-top: clamp(20px, 2.6vw, 38px); }

.twin-kicker {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--tint);
}

.twin-kicker::before {
  content: "";
  width: 28px;
  height: 1px;
  background: currentColor;
  opacity: 0.8;
}

.twin-name {
  margin-top: 14px;
  font-size: clamp(1.7rem, 2.7vw, 2.4rem);
}

.twin-tag {
  margin-top: 12px;
  color: rgba(255, 255, 255, 0.7);
  font-size: 1.02rem;
  text-wrap: pretty;
}

/* The problem this track exists to solve — the one figure that makes the case */
.twin-problem {
  position: relative;
  overflow: hidden;
  margin-top: 28px;
  padding: 20px 24px;
  border: 1px solid rgba(255, 255, 255, 0.13);
  border-radius: var(--radius-md);
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(var(--tint-rgb), 0.10), rgba(var(--tint-rgb), 0) 62%),
    rgba(255, 255, 255, 0.04);
  transition: border-color 0.3s ease;
}

.twin-problem::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: linear-gradient(180deg, var(--tint), rgba(var(--tint-rgb), 0));
}

.twin-side:hover .twin-problem { border-color: rgba(var(--tint-rgb), 0.42); }

.twin-problem-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
}

.twin-figure {
  display: block;
  margin-top: 8px;
  font-size: clamp(1.55rem, 2.6vw, 2.1rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--tint);
}

.twin-problem p {
  margin-top: 8px;
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.95rem;
  line-height: 1.55;
  text-wrap: pretty;
}

.twin-list {
  list-style: none;
  display: grid;
  gap: 24px;
  margin-top: 34px;
  padding-top: 30px;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}

.twin-item { display: flex; gap: 18px; }

.twin-num {
  flex-shrink: 0;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--tint);
  padding-top: 5px;
}

.twin-item h4 { font-size: 1.14rem; font-weight: 600; line-height: 1.3; }

.twin-item p {
  margin-top: 7px;
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.95rem;
  text-wrap: pretty;
}

/* ---------- What both tracks buy ---------- */

.twin-outcomes {
  position: relative;
  z-index: 1;
  margin-top: clamp(58px, 7vw, 96px);
  padding-top: 36px;
  border-top: 1px solid rgba(255, 255, 255, 0.13);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 30px;
}

/* tinted to the half each figure comes from */
.twin-stat:nth-child(1) { --tint: var(--accent-bright); --tint-rgb: var(--accent-bright-rgb); }
.twin-stat:nth-child(2),
.twin-stat:nth-child(3) { --tint: var(--steel); --tint-rgb: var(--steel-rgb); }

.twin-stat { text-align: center; }

.twin-stat-num {
  display: block;
  font-size: clamp(1.9rem, 3.4vw, 2.7rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--tint);
}

.twin-stat-label {
  display: block;
  margin-top: 6px;
  color: rgba(255, 255, 255, 0.58);
  font-size: 0.94rem;
}

.twin-close {
  grid-column: 1 / -1;
  margin-top: 14px;
  text-align: center;
  color: rgba(255, 255, 255, 0.5);
  font-size: 0.98rem;
}

.twin-close strong { color: #fff; font-weight: 600; }

/* ---------- Stacked: one track after the other ---------- */

@media (max-width: 980px) {
  .twin { padding: 80px 0 76px; }
  .twin-split {
    grid-template-columns: 1fr;
    row-gap: 64px;
    margin-top: 52px;
  }
  .twin-spine { display: none; }
  .twin-head { opacity: 1; translate: none; }
  .twin-side { opacity: 1; translate: none; }
  .twin-side--plasma,
  .twin-side--magnets { grid-column: 1; }
  /* whichever half falls second, rather than naming one of them */
  .twin-side + .twin-side { padding-top: 60px; border-top: 1px solid rgba(255, 255, 255, 0.12); }
  .twin-stage { height: clamp(180px, 44vw, 300px); }
  .twin-side--magnets { --art-h: 116%; --art-x: -55%; }
  .twin-side--plasma { --art-h: 100%; }
  .twin-outcomes { grid-template-columns: 1fr; gap: 26px; text-align: center; }
}

@media (prefers-reduced-motion: reduce) {
  .twin { --head: 1; --converge: 1; --spine: 1; }
  .twin-aura,
  .twin-halo,
  .twin-sweep::before,
  .twin-side--magnets .twin-sweep::before,
  .twin-field,
  .twin-spark,
  .twin-spine-node { animation: none; }
  .twin-spine-pulse { display: none; }
  .twin-sweep { display: none; }
  .twin-field { opacity: 0.4; }
}

/* ============================================================
   Locations — hand-drawn world map

   The map is a real SVG, not an image: coastlines are plotted in an
   equirectangular projection (x from longitude, y from latitude) at
   1000 units wide, cropped vertically to the inhabited band. Because
   the pins use the same projection, their positions are geographically
   correct against the coastlines rather than eyeballed.
   ============================================================ */

.locations {
  --reveal: 1;                /* scrubbed by scroll */
  position: relative;
  padding: 110px 0;
  background: #08080a;
  color: #fff;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}

/* Centred, like the technology section's heading — left-ranged it left a
   large hole to the right of it above a full-width map. */
.locations-head {
  max-width: 760px;
  margin-inline: auto;
  text-align: center;
  opacity: var(--reveal);
  translate: 0 calc((1 - var(--reveal)) * 26px);
  will-change: opacity, translate;
}

.locations-title { font-size: clamp(1.9rem, 3.6vw, 3rem); }

.locations-lead {
  margin: 18px auto 0;
  max-width: 620px;
  color: rgba(255, 255, 255, 0.66);
  font-size: 1.02rem;
}

/* Fades on its own travel through the viewport, not the section's, so the
   fade actually plays while the map is the thing you are looking at. The
   slide does most of the perceptual work — opacity alone reads as nothing. */
.locations-map {
  position: relative;
  margin-top: 56px;
  opacity: var(--map, 1);
  transform: translateY(calc((1 - var(--map, 1)) * 60px));
  will-change: transform, opacity;
}

/* Keeps the map from floating on flat black, the same trick the technology
   section uses behind its artwork. */
.locations-map::before {
  content: "";
  position: absolute;
  inset: -14% -6%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(58% 66% at 50% 46%, rgba(var(--accent-bright-rgb), 0.09), transparent 72%);
  animation: rsAura 13s ease-in-out infinite alternate;
}

@keyframes rsAura {
  from { opacity: 0.55; }
  to   { opacity: 1; }
}

.rs-svg { width: 100%; height: auto; display: block; overflow: visible; }

/* Graticule: structure behind the coastlines, so the map reads as an
   instrument rather than a sticker. Hairlines only. */
.rs-grid line {
  stroke: rgba(255, 255, 255, 0.05);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.rs-grid .rs-equator { stroke: rgba(var(--accent-bright-rgb), 0.13); }

/* non-scaling-stroke keeps hairlines crisp at every container width */
.rs-lands path {
  fill: rgba(var(--accent-bright-rgb), 0.055);
  stroke: rgba(var(--accent-bright-rgb), 0.55);
  stroke-width: 1.1;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* Lifts the coastline off the grid behind it */
.rs-lands { filter: drop-shadow(0 0 7px rgba(var(--accent-bright-rgb), 0.22)); }

/* ---- routes between the sites ---- */

.rs-arc {
  fill: none;
  stroke: rgba(var(--accent-bright-rgb), 0.3);
  stroke-width: 1;
  stroke-dasharray: 3 5;
  vector-effect: non-scaling-stroke;
}

/* One short dash chasing the arc, drawn on a duplicate of the same path.
   pathLength: 100 makes the dash maths independent of the real arc length. */
.rs-flow {
  fill: none;
  stroke: var(--accent-bright);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 5 95;
  vector-effect: non-scaling-stroke;
  filter: drop-shadow(0 0 6px rgba(var(--accent-bright-rgb), 0.9));
  animation: rsFlow 4.6s linear infinite;
}

.rs-flow--b { animation-duration: 3.2s; animation-delay: 1.1s; }

/* Offset climbing rather than falling walks the dash back along the path, so
   the charge runs Rome -> London -> New York against the order the arcs are
   drawn in. */
@keyframes rsFlow {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: 100; }
}

/* ---- city pins: HTML over the SVG, so labels keep a fixed size ---- */

/* No reveal of their own: they sit inside .locations-map, so its opacity and
   slide already carry them, and all three arrive with the map. */
.rs-pin {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: 36px;
  height: 36px;               /* generous hit area around a small dot */
  margin: -18px 0 0 -18px;
  display: grid;
  place-items: center;
  cursor: pointer;
}

.rs-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent-bright);
  box-shadow: 0 0 0 3px rgba(var(--accent-bright-rgb), 0.16), 0 0 14px rgba(var(--accent-bright-rgb), 0.75);
  transition: transform 0.25s ease;
}

.rs-pin:hover .rs-dot,
.rs-pin:focus-visible .rs-dot { transform: scale(1.45); }

.rs-ring {
  position: absolute;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid var(--accent-bright);
  animation: rsPulse 2.8s ease-out infinite;
}

.rs-pin:nth-of-type(2) .rs-ring { animation-delay: 0.9s; }
.rs-pin:nth-of-type(3) .rs-ring { animation-delay: 1.8s; }

@keyframes rsPulse {
  0%   { transform: scale(1);   opacity: 0.6; }
  70%  { transform: scale(3.2); opacity: 0; }
  100% { transform: scale(3.2); opacity: 0; }
}

/* Hover card: city name plus what that site works on. A rounded rect rather
   than a pill, since it now carries two lines. */
.rs-name {
  position: absolute;
  bottom: calc(100% - 4px);
  left: 50%;
  white-space: nowrap;
  padding: 9px 15px 11px;
  border-radius: 13px;
  background: rgba(8, 8, 10, 0.96);
  border: 1px solid rgba(var(--accent-bright-rgb), 0.42);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 0.86rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: left;
  opacity: 0;
  transform: translate(-50%, 6px);
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}

.rs-name .rs-role {
  display: block;
  margin-top: 3px;
  color: rgba(255, 255, 255, 0.58);
  font-size: 0.79rem;
  font-weight: 400;
  letter-spacing: 0;
}

.rs-pin:hover .rs-name,
.rs-pin:focus-visible .rs-name { opacity: 1; transform: translate(-50%, 0); }

/* Lift the hovered pin above its neighbours so a card is never clipped by them */
.rs-pin:hover,
.rs-pin:focus-visible { z-index: 2; }

.rs-pin:focus-visible { outline: none; }

@media (max-width: 900px) {
  .locations { padding: 76px 0; }
  .locations-head { opacity: 1; translate: none; }
  .locations-map { opacity: 1; transform: none; }
  .rs-name { font-size: 0.76rem; padding: 5px 10px; }
}

@media (prefers-reduced-motion: reduce) {
  .locations-head, .locations-map { opacity: 1; transform: none; translate: none; }
  .rs-ring { animation: none; opacity: 0; }
  .locations-map::before, .rs-flow { animation: none; }
  .rs-flow { opacity: 0; }
}

/* ============================================================
   Private page
   ============================================================ */

.private {
  padding: calc(var(--header-h) + 90px) 0 110px;
  background: #08080a;
  color: #fff;
  min-height: 100vh;
}

.private-title { font-size: clamp(2rem, 3.6vw, 3rem); }

.private-lead {
  max-width: 620px;
  margin-top: 18px;
  color: rgba(255, 255, 255, 0.66);
  font-size: 1.02rem;
}

.private-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 24px;
  margin-top: 54px;
}

.private-card {
  padding: 30px 28px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.035);
}

.private-card h2 { font-size: 1.2rem; font-weight: 600; }
.private-card p { margin-top: 8px; color: rgba(255, 255, 255, 0.58); font-size: 0.95rem; }

@media (max-width: 900px) {
  .private { padding: calc(var(--header-h) + 56px) 0 76px; }
  .private-grid { grid-template-columns: 1fr; }
}

/* ============================================================
   Footer
   ============================================================ */

/* Same black as the header and the dark sections, so the page reads as one
   surface and the overscroll gutter below it matches exactly. The hairline
   separates it from the equally black section above, as between the others. */
.site-footer {
  background: #08080a;
  color: rgba(255,255,255,0.85);
  padding: 80px 0 40px;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}

/* One band, no divider: the brand column on the left and the notice on the
   right, aligned to the bottom of the taller side. */
.footer-inner {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 40px;
  flex-wrap: wrap;
}

.logo-light { color: #fff; }

/* No max-width: clamped to 240px it broke the address onto two lines, and
   it is short enough to stand on one. */
.footer-brand p { margin-top: 14px; color: rgba(255,255,255,0.6); }

.footer-contact { font-size: 0.93rem; }
.footer-contact a {
  color: var(--accent);
  transition: color 0.2s ease;
}
.footer-contact a:hover { color: #fff; text-decoration: underline; }

/* Quiet recruiting line, same weight as the contact line above it */
.footer-hiring { font-size: 0.93rem; }

/* Right-hand column: icons above the notice, both ranged right */
.footer-aside {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 20px;
}

.footer-social { display: flex; gap: 10px; }
.footer-social a {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: rgba(255,255,255,0.1);
  transition: background 0.2s ease;
}
.footer-social a:hover { background: rgba(255,255,255,0.22); }
.footer-social svg { width: 18px; height: 18px; }

.footer-meta {
  max-width: 40ch;
  text-align: right;
  font-size: 0.85rem;
  color: rgba(255,255,255,0.55);
}

/* ============================================================
   Responsive
   ============================================================ */

@media (max-width: 820px) {
  /* Pinned in the dropdown menu — text resizing mid-scroll reads as a glitch
     there. Both states, since .scrolled would otherwise still win. */
  .site-header,
  .site-header.scrolled { --nav-size: 1rem; }

  /* Anchored to the header's own bottom edge so it follows the shrink */
  .main-nav {
    position: absolute;
    top: 100%;
    left: 0; right: 0;
    background: var(--bar);
    backdrop-filter: blur(12px);
    box-shadow: 0 12px 24px rgba(0,0,0,0.4);
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
  }
  .main-nav.open { max-height: 80vh; overflow-y: auto; }

  .nav-list { flex-direction: column; justify-content: flex-start; padding: 16px 24px 24px; gap: 2px; }
  .nav-link { width: 100%; justify-content: space-between; padding: 14px 8px; }

  .hamburger { display: flex; }
}

@media (max-width: 560px) {
  /* Stacked, both read better ranged left under the brand block */
  .footer-aside { align-items: flex-start; }
  .footer-meta { max-width: none; text-align: left; }
}
