/* Pixel 81 · hand-written styles. No frameworks. Midnight direction. */

@font-face {
  font-family: "Geist";
  src: url("/fonts/geist-var-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("/fonts/geist-mono-latin.woff2") format("woff2");
  font-weight: 400 600;
  font-display: swap;
}
@font-face {
  font-family: "Story Script";
  src: url("/fonts/story-script-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Instrument Serif";
  src: url("/fonts/instrument-serif-italic-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

:root {
  --bg: #03050d;
  --bg-2: #060c26;
  --navy: #0b1748;
  --royal: #2a4ad6;
  --glow: #5b84ff;
  --ice: #b6cdff;
  --text: #eef2ff;
  --muted: #bcc4de; /* 5:1 or better even over the brightest glow (WCAG AA) */
  --dim: #8a96be;   /* unlit scroll words: 3:1 or better as large text, still clearly dimmer than lit */
  --line: rgba(200, 215, 255, 0.1);
  --glass: rgba(140, 170, 255, 0.06);
  --glass-line: rgba(200, 220, 255, 0.16);

  --pad: clamp(20px, 5vw, 72px);  /* smallest side gutter */
  --max: 1440px;                  /* widest the content gets; backgrounds stay full-bleed */
  --gutter: max(var(--pad), calc((100vw - var(--max)) / 2)); /* side padding that centers the content past --max */
  --section: clamp(104px, 14vw, 210px);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --sans: "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "Geist Mono", ui-monospace, "SF Mono", monospace;
  --script: "Story Script", cursive;              /* accent words across the site */
  --serif: "Instrument Serif", Georgia, serif;     /* only inside the made-up brands' mini sites */

  interpolate-size: allow-keywords;
  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }
html { background: var(--bg); -webkit-text-size-adjust: 100%; scroll-padding-top: 88px; /* keeps focused items clear of the fixed header */ }
@media (prefers-reduced-motion: no-preference) {
  html:not(.smooth) { scroll-behavior: smooth; }
}
body {
  margin: 0;
  background: transparent; /* the fixed .atmo layer (and html) supply the background */
  color: var(--text);
  font-family: var(--sans);
  font-size: 18px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: clip; /* a safety net only: Safari 15 ignores clip, so nothing may rely on it to stay on the page */
}
img, svg, canvas { display: block; max-width: 100%; }
a { color: inherit; }
::selection { background: var(--ice); color: var(--bg); }
:focus-visible { outline: 2px solid var(--ice); outline-offset: 4px; border-radius: 6px; }
/* One offset for every way of reaching a section (menu links, links from other pages, keyboard focus): the
   html scroll-padding above keeps targets clear of the fixed header. "What we do" lands on the first card,
   pinned at the very top, so it cancels that padding. main.js reads these same values for its smooth scroll. */
#services { scroll-margin-top: -88px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: fixed; left: 16px; top: -60px; z-index: 100;
  background: #fff; color: var(--bg); padding: 10px 16px; border-radius: 10px;
  font-weight: 600; text-decoration: none;
}
.skip:focus { top: 16px; }

/* ---------- Atmosphere: one fixed midnight gradient with drifting light ---------- */
.atmo {
  position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none;
  background: linear-gradient(165deg, #03050d 0%, #050a1f 42%, #08123a 78%, #0b1748 100%);
}
.atmo .light { position: absolute; border-radius: 50%; }
.atmo .l1 {
  width: 90vmax; height: 90vmax; right: -30vmax; top: -45vmax;
  background: radial-gradient(circle, rgba(52, 88, 235, 0.55) 0%, rgba(40, 70, 210, 0.18) 38%, transparent 66%);
}
.atmo .l2 {
  width: 70vmax; height: 70vmax; left: -35vmax; top: 30vh;
  background: radial-gradient(circle, rgba(70, 60, 200, 0.22) 0%, transparent 62%);
}
.atmo .l3 {
  width: 80vmax; height: 60vmax; left: 15%; bottom: -40vmax;
  background: radial-gradient(ellipse, rgba(40, 90, 230, 0.28) 0%, transparent 62%);
}
.atmo::after {
  content: ""; position: absolute; inset: 0; opacity: 0.1; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: no-preference) {
  .atmo .l1 { animation: drift-a 26s ease-in-out infinite alternate; }
  .atmo .l2 { animation: drift-b 32s ease-in-out infinite alternate; }
  .atmo .l3 { animation: drift-a 38s ease-in-out infinite alternate-reverse; }
}
@keyframes drift-a { to { transform: translate(-6vmax, 5vmax) scale(1.06); } }
@keyframes drift-b { to { transform: translate(8vmax, -4vmax) scale(0.94); } }

/* ---------- Type ---------- */
.label {
  display: inline-flex; align-items: center; gap: 10px;
  margin: 0 0 24px;
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ice);
}
.label::before {
  content: ""; display: inline-block; flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: var(--ice); box-shadow: 0 0 10px 2px rgba(130, 165, 255, 0.7);
}
.h2 {
  margin: 0;
  font-size: clamp(42px, 5.6vw, 88px);
  font-weight: 500; line-height: 1; letter-spacing: -0.04em;
  text-wrap: balance;
}
.h2-xl { font-size: clamp(52px, 8vw, 128px); }
h1 em, h2 em {
  font-family: var(--script); font-style: normal; font-weight: 400; letter-spacing: 0;
  background: linear-gradient(180deg, #ffffff 10%, #a8c0ff 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  padding: 0 0.12em 0 0.04em; /* room for the script's overhanging strokes */
}
h2 em { font-size: 1.1em; } /* the script's small x-height reads small next to Geist */

/* ---------- Buttons ---------- */
.btn {
  position: relative; isolation: isolate; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 15px 26px; border: 0; border-radius: 999px;
  font: inherit; font-size: 15.5px; font-weight: 600; letter-spacing: -0.01em;
  text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: transform 0.5s var(--ease-out), box-shadow 0.4s ease, background-color 0.3s ease, color 0.3s ease;
  will-change: transform;
}
.btn > span { position: relative; z-index: 1; }
.btn-sm { padding: 10px 18px; font-size: 14px; }
/* White, glowing — the one bright thing on the page */
.btn-glow {
  background: linear-gradient(180deg, #ffffff 0%, #e4eaff 100%);
  color: #050a1f;
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.75),
    inset 0 1px 0 #ffffff,
    0 10px 28px -10px rgba(120, 160, 255, 0.7),
    0 0 46px -6px rgba(110, 150, 255, 0.55);
}
.btn-glow::after {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(150, 180, 255, 0.45) 50%, transparent 65%);
  transform: translateX(-130%);
  transition: transform 0.9s var(--ease-out);
}
.btn-glow:hover {
  box-shadow:
    0 0 0 1px #ffffff,
    inset 0 1px 0 #ffffff,
    0 14px 34px -10px rgba(130, 170, 255, 0.85),
    0 0 80px -4px rgba(120, 160, 255, 0.75);
}
.btn-glow:hover::after { transform: translateX(130%); }
/* Glass */
.btn-glass {
  background: rgba(150, 175, 255, 0.07);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--glass-line), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}
.btn-glass:hover {
  background: rgba(150, 175, 255, 0.13);
  box-shadow: inset 0 0 0 1px rgba(210, 225, 255, 0.36), 0 0 34px -8px rgba(110, 150, 255, 0.55);
}

/* ---------- Header ---------- */
.site-header {
  position: fixed; inset: 0 0 auto; z-index: 50;
  display: flex; align-items: center; gap: 32px;
  padding: 18px var(--gutter);
  border-bottom: 1px solid transparent;
  transition: background-color 0.4s ease, backdrop-filter 0.4s ease, padding 0.4s var(--ease-out), border-color 0.4s ease;
}
.site-header.scrolled {
  background: rgba(4, 8, 26, 0.86);
  backdrop-filter: blur(16px) saturate(1.3); -webkit-backdrop-filter: blur(16px) saturate(1.3);
  border-bottom-color: var(--line);
  padding-block: 12px;
}
.brand { display: inline-flex; align-items: center; padding: 6px 0; text-decoration: none; margin-right: auto; }
.logo { width: clamp(112px, 8.5vw, 144px); height: auto; filter: drop-shadow(0 0 10px rgba(120, 160, 255, 0.35)); }
.nav { display: flex; gap: 30px; }
.nav a { padding: 6px 0; text-decoration: none; font-size: 14.5px; font-weight: 500; color: var(--muted); transition: color 0.2s ease; }
.nav a:hover { color: var(--text); }
@media (max-width: 860px) { .nav { display: none; } }

/* ---------- Intro overlay (plays once per visit) ---------- */
.intro { position: fixed; inset: 0; z-index: 60; width: 100%; height: 100%; pointer-events: none; }
/* Until the intro hands off it covers the screen from the very first paint (boot.js sets .js before then; main.js
   draws the board over this backdrop). The hero is already drawn underneath, so Google counts it as painted right
   away, and main.js replays its rise at the hand-off. Gated on .js so a script failure can't hide the page. */
.js.intro-pending .intro { background: linear-gradient(147deg, #03050d, #08123a); }
.js.intro-pending .nav, .js.intro-pending .site-header .btn { opacity: 0; }
/* The hero's starting pose, held for a single frame at the hand-off (main.js) so its rise plays as designed */
.js.hero-pre .hero-inner > * { opacity: 0; transition: none; }
.js.hero-pre .hero-title .line > * { transform: translateY(110%); transition: none; }

/* ---------- 1. Hero ---------- */
.hero {
  position: relative; overflow: hidden;
  min-height: 100vh; min-height: 100svh;
  display: flex; align-items: center;
  padding: 130px var(--gutter) 90px;
}
/* Dot grid: hidden at rest. Every 7s a soft band of light washes across, revealing the dots as it passes. */
.hero-grid {
  position: absolute; inset: 0; pointer-events: none;
  -webkit-mask-image: radial-gradient(ellipse 90% 85% at 60% 45%, #000 45%, transparent 100%);
  mask-image: radial-gradient(ellipse 90% 85% at 60% 45%, #000 45%, transparent 100%);
}
.hero-grid::after {
  content: ""; position: absolute; inset: 0;
  background-image: radial-gradient(rgba(205, 220, 255, 0.9) 1px, transparent 1.4px);
  background-size: 30px 30px;
  opacity: 0.85;
  -webkit-mask-image: linear-gradient(105deg, transparent 36%, rgba(0, 0, 0, 0.35) 45%, #000 50%, rgba(0, 0, 0, 0.35) 55%, transparent 64%);
  mask-image: linear-gradient(105deg, transparent 36%, rgba(0, 0, 0, 0.35) 45%, #000 50%, rgba(0, 0, 0, 0.35) 55%, transparent 64%);
  -webkit-mask-size: 300% 100%; mask-size: 300% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: 100% 0; mask-position: 100% 0; /* band parked off the left edge */
}
@media (prefers-reduced-motion: no-preference) {
  .hero-grid::after { animation: wash 7s cubic-bezier(0.45, 0, 0.3, 1) 1.6s infinite; }
  .hero.off .hero-grid::after { animation-play-state: paused; }
}
/* 3.5s to cross, then 3.5s of rest */
@keyframes wash {
  0% { -webkit-mask-position: 100% 0; mask-position: 100% 0; }
  50%, 100% { -webkit-mask-position: 0% 0; mask-position: 0% 0; }
}
.hero-inner { position: relative; z-index: 2; max-width: 1100px; }
.hero-inner > * { transition: opacity 1s ease, transform 1s var(--ease-out); }
.hero-title {
  margin: 0;
  font-size: clamp(60px, 8.4vw, 152px);
  font-weight: 500; line-height: 0.9; letter-spacing: -0.055em;
}
.hero-title .line { display: block; overflow: hidden; padding-bottom: 0.06em; }
.hero-title .line > * { display: inline-block; transition: transform 1.2s var(--ease-out); }
.hero-title .line:nth-child(2) > * { transition-delay: 0.08s; }
.hero-title .line:nth-child(3) > * { transition-delay: 0.16s; }
.hero-title em { font-size: 1.08em; }
.hero-sub { margin: 34px 0 36px; max-width: 30em; font-size: clamp(17px, 1.3vw, 19.5px); color: var(--muted); }
.hero-ctas { display: flex; gap: 12px; flex-wrap: wrap; }
.hero-inner > .hero-sub { transition-delay: 0.25s; }
.hero-inner > .hero-ctas { transition-delay: 0.35s; }



/* Hero stage: an empty slot that marks where the paper plane hovers. main.js draws the plane on .plane,
   a canvas over the whole hero, so it can fly in from off screen. */
.plane { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; pointer-events: none; }
.hero-stage { position: relative; pointer-events: none; }
/* Tablets and up (every iPad, even the 744px mini): the plane hovers to the right of the text. */
@media (min-width: 700px) {
  .hero-stage { position: absolute; right: var(--gutter); top: 50%; width: min(40vw, 720px); aspect-ratio: 1 / 0.8; translate: 0 -46%; }
}
/* Tablets: the text keeps to the left part so the plane has the right side to itself. The headline grows a
   little as the screen narrows (desktop's size at 1100px), so it doesn't look small on an iPad. */
@media (min-width: 700px) and (max-width: 1099px) {
  .hero-inner { max-width: 56%; }
  .hero-title { font-size: calc(8.4vw + (1100px - 100vw) * 0.045); }
}
/* Phones: no hover spot. The plane flies across once (main.js) and nothing sits below the text. */
@media (max-width: 699px) {
  .hero { flex-direction: column; align-items: stretch; justify-content: center; }
  .hero-stage { display: none; }
}

@media (max-width: 699px) {
  .hero { padding-top: 120px; }
  .hero-title { font-size: 17.5vw; }
  .hero-ctas { flex-direction: column; }
  .hero-ctas .btn { width: 100%; }
}

/* ---------- 2. Why ---------- */
.why { padding: var(--section) var(--gutter); }
/* Every screen size: a tall runway with a sticky, screen-height stage. The text scrolls up untouched, pins at
   the middle of the screen, fills word by word as you keep scrolling, holds, then the page moves on.
   main.js maps the fill to the pinned scroll. Reduced motion (and very short screens) keep the plain section.
   svh (the screen with the phone's toolbars showing) keeps the pinned text centered and still on phones. */
@media (min-height: 360px) {
  .motion .why { height: 210vh; height: 210svh; padding-block: 0; } /* 110 pinned: ~85 of fill, ~25 of hold */
  .motion .why-pin { position: sticky; top: 0; height: 100vh; height: 100svh; display: flex; align-items: center; }
}
.why-text {
  margin: 0; max-width: 19.5em;
  font-size: clamp(30px, 4.2vw, 66px);
  font-weight: 450; line-height: 1.14; letter-spacing: -0.03em;
  text-wrap: pretty; /* fewer lonely last words */
}
@media (min-width: 700px) and (max-width: 1099px) {
  .why-text { font-size: calc(4.2vw + (1100px - 100vw) * 0.03); } /* bigger on tablets, desktop's size at 1100px */
}
.why-text .w { white-space: nowrap; } /* never break a word at its hyphen */
.why-text em { font-family: var(--script); font-style: normal; font-weight: 400; font-size: 1.08em; color: var(--ice); }
.motion .why-text .w { color: var(--dim); transition: color 0.35s ease, text-shadow 0.35s ease; }
.motion .why-text .w.on { color: var(--text); }
.why-text .w.hl { font-family: var(--script); font-style: normal; font-weight: 400; letter-spacing: 0; font-size: 1.08em; }
.motion .why-text .w.hl.on { color: var(--ice); text-shadow: 0 0 28px rgba(120, 160, 255, 0.55); }

/* ---------- Chips ---------- */
.chip {
  display: inline-block; padding: 0.06em 0.42em 0.12em; border-radius: 0.28em;
  font-weight: 500; line-height: 1.1; letter-spacing: -0.03em; white-space: nowrap;
  background: rgba(140, 170, 255, 0.1); color: var(--text);
  box-shadow: inset 0 0 0 1px rgba(200, 220, 255, 0.24), inset 0 1px 0 rgba(255, 255, 255, 0.1);
  /* No backdrop-filter on the chips or the ship cards: they ride the moving story strip, where a live blur made
     Safari draw a dark bar over the page (Kyle, 2026-10-20). The light behind them is smooth, so it showed nothing. */
}
.chip-bright {
  background: linear-gradient(180deg, #ffffff, #dfe6ff); color: #050a1f;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.8), 0 0 48px -8px rgba(120, 160, 255, 0.75);
}

/* ---------- 3. Story: pinned horizontal scroll ---------- */
.story { position: relative; }
.story-stage { position: relative; overflow: hidden; }
.story-track { display: flex; align-items: center; gap: clamp(64px, 10vw, 180px); width: max-content; padding: 0 var(--gutter); will-change: transform; }
.story-panel { flex: none; }
.story-lead {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(32px, 6vw, 96px); align-items: center;
  width: calc(100vw - var(--gutter) * 2);
}
.chip-stack { display: flex; flex-direction: column; align-items: flex-start; gap: 0.18em; margin: 0 0 32px; font-size: clamp(28px, 3.4vw, 52px); }
.story-lead-text { margin: 0; max-width: 21em; font-size: clamp(20px, 2vw, 30px); font-weight: 450; line-height: 1.3; letter-spacing: -0.02em; color: var(--muted); }
.story-lead-art { position: relative; height: clamp(260px, 32vw, 440px); }
.ship-stack { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; gap: 14px; width: min(100%, 440px); margin-inline: auto; }
.motion .ship-stack[data-reveal] { opacity: 1; transform: none; }
.ship {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; column-gap: 14px;
  padding: 14px 18px 14px 14px; border-radius: 20px;
  background: rgba(150, 175, 255, 0.08);
  box-shadow: inset 0 0 0 1px var(--glass-line), inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 24px 50px -24px rgba(0, 0, 0, 0.7);
  transition: opacity 0.7s ease, translate 0.9s var(--ease-out), scale 0.9s var(--ease-out);
}
.ship:nth-child(2) { margin-left: 10%; transition-delay: 0.35s; }
.ship:nth-child(3) { margin-left: 4%; transition-delay: 0.7s; }
.ship-ic { display: grid; place-items: center; width: 42px; height: 42px; border-radius: 12px; background: linear-gradient(145deg, #7ea0ff, #2a4ad6); box-shadow: 0 0 24px -4px rgba(110, 150, 255, 0.9), inset 0 1px 0 rgba(255, 255, 255, 0.4); }
.ship-ic svg { width: 22px; fill: none; stroke: #fff; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.ship:nth-child(3) .ship-ic svg { fill: #fff; stroke: none; }
.ship b { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.ship em { font-family: var(--mono); font-size: 12px; font-style: normal; color: var(--muted); }
.motion .ship-stack:not(.in) .ship { opacity: 0; translate: 0 24px; scale: 0.96; }

.story-line {
  display: flex; align-items: center; gap: 0.26em; margin: 0;
  font-size: clamp(54px, 7.2vw, 116px);
  font-weight: 500; line-height: 1; letter-spacing: -0.045em; white-space: nowrap;
}
.story-line .w { color: var(--dim); transition: color 0.3s ease; }
.story-line .w.on { color: var(--text); }
.story-line .chip { font-size: 0.8em; margin: 0 0.1em; }
.demo { position: relative; display: inline-block; flex: none; }
.demo-cursor { width: 0.46em; }
.demo-cursor svg { width: 100%; fill: #fff; stroke: #050a1f; stroke-width: 1.4; filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.5)); }
/* A tiny home screen: four apps on glass, the client's lit. The lit one pops in like a fresh install
   when the line reaches it (main.js adds .on; phones and reduced motion start with it on). */
.demo-home {
  display: inline-grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 0.08em;
  width: 0.92em; height: 0.92em; padding: 0.12em; border-radius: 0.26em;
  background: rgba(150, 175, 255, 0.1);
  box-shadow: inset 0 0 0 1px rgba(200, 220, 255, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.15);
}
.demo-home i { border-radius: 0.07em; background: rgba(182, 205, 255, 0.28); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15); }
.demo-home i:first-child {
  background: linear-gradient(150deg, #ffffff, #c9d8ff); box-shadow: 0 0 18px rgba(170, 200, 255, 0.95);
  transform: scale(0.2); opacity: 0;
  transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.25s ease;
}
.demo-home.on i:first-child { transform: none; opacity: 1; }
.demo-bar { width: 1.6em; height: 0.24em; border-radius: 999px; background: rgba(200, 215, 255, 0.12); overflow: hidden; }
.demo-bar i { position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(90deg, #5b84ff, #b6cdff); box-shadow: 0 0 16px #5b84ff; transform-origin: left; transform: scaleX(var(--p, 0)); transition: transform 0.2s linear; }
.demo-toggle { width: 1.1em; height: 0.6em; border-radius: 999px; background: rgba(200, 215, 255, 0.14); transition: background-color 0.35s ease, box-shadow 0.35s ease; }
.demo-toggle i { position: absolute; top: 0.08em; left: 0.08em; width: 0.44em; height: 0.44em; border-radius: 50%; background: #fff; transition: transform 0.45s var(--ease-out); }
.demo-toggle.on { background: var(--glow); box-shadow: 0 0 30px -4px rgba(110, 150, 255, 0.9); }
.demo-toggle.on i { transform: translateX(0.5em); }
@media (prefers-reduced-motion: no-preference) {
  .demo-cursor svg { animation: tap 2.4s var(--ease-out) infinite; transform-origin: 20% 10%; }
}
.story.off .demo-cursor svg { animation-play-state: paused; } /* main.js marks the story off screen */
@keyframes tap { 0%, 60%, 100% { transform: none; } 70% { transform: scale(0.84); } 80% { transform: none; } }

/* Pinned (every screen size with motion): one screen tall plus the track's sideways travel, which main.js measures */
.story.is-pinned { height: calc(100vh + var(--story-extra, 300vh)); height: calc(100svh + var(--story-extra, 300vh)); }
.story.is-pinned .story-stage { position: sticky; top: 0; height: 100vh; height: 100svh; display: flex; align-items: center; }
/* Phones: the opening panel stays side by side as on desktop, just wider than the screen. The words fill most of
   it and the notifications wait to their right (the first one peeks in at the edge), sliding into view as the
   story moves sideways. */
@media (max-width: 699px) {
  .story.is-pinned .story-lead { width: auto; grid-template-columns: 68vw min(330px, 82vw); gap: 28px; }
  .story.is-pinned .story-lead-art { height: 240px; }
  .story.is-pinned .chip-stack { margin-bottom: 20px; }
}
/* Narrow phones: tighter ship cards so their titles stay on one line */
@media (max-width: 380px) {
  .ship { column-gap: 10px; padding: 10px 12px 10px 10px; border-radius: 16px; }
  .ship-ic { width: 34px; height: 34px; border-radius: 10px; }
  .ship-ic svg { width: 18px; }
  .ship b { font-size: 14px; }
  .ship em { font-size: 11px; }
}
@media (max-width: 360px) { .ship em { display: none; } } /* the smallest phones drop the timestamps */
/* Tablets: the opening panel's words a little bigger (desktop's size at 1100px) */
@media (min-width: 700px) and (max-width: 1099px) {
  .chip-stack { font-size: calc(3.4vw + (1100px - 100vw) * 0.02); }
  .story-lead-text { font-size: calc(2vw + (1100px - 100vw) * 0.022); }
}
.story:not(.is-pinned) { padding: var(--section) 0; }
.story:not(.is-pinned) .story-track { flex-direction: column; align-items: flex-start; width: auto; gap: 56px; }
.story:not(.is-pinned) .story-lead { width: auto; grid-template-columns: 1fr; }
.story:not(.is-pinned) .story-lead-art { order: -1; height: 240px; width: 100%; }
.story:not(.is-pinned) .story-line { flex-wrap: wrap; white-space: normal; font-size: clamp(40px, 11vw, 96px); row-gap: 0.18em; }
.story:not(.is-pinned) .story-line .w { color: var(--text); }

/* ---------- 4. Offerings ---------- */
.offers { padding: var(--section) var(--gutter) 0; }
.offer {
  display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(32px, 6vw, 100px); align-items: center;
  padding: clamp(48px, 7vw, 110px) 0;
}
.offer + .offer { border-top: 1px solid var(--line); }
.offer-app .offer-art { order: 2; }
.offer-app { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
.offer-title {
  display: inline-flex; align-items: center; gap: 10px; margin: 0 0 18px;
  font-family: var(--mono); font-size: 13px; font-weight: 500; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ice);
}
.offer-title::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--ice); box-shadow: 0 0 10px 2px rgba(130, 165, 255, 0.7); }
.offer-note { margin: -12px 0 30px; max-width: 30em; font-size: 16px; line-height: 1.6; color: var(--muted); }
.offer-lede { margin: 0 0 32px; max-width: 16em; font-size: clamp(28px, 3vw, 46px); font-weight: 450; line-height: 1.12; letter-spacing: -0.035em; }
/* One column (device over the words) on phones, on portrait screens of any width (iPads standing up), and on
   narrow landscape windows. Side by side on landscape screens 900px and up, and on short landscape screens 560px
   and up (landscape phones, see below); main.js uses the same test. */
@media (max-width: 899px), (max-height: 639px), (max-aspect-ratio: 999/1000) {
  .offer, .offer-app { grid-template-columns: 1fr; }
  .offer-app .offer-art { order: 0; }
}
/* Tablets in portrait (one column, lots of room): bigger words under each device */
@media (min-width: 700px) and (max-width: 1199px) and (max-aspect-ratio: 999/1000) {
  .offer-lede { font-size: min(4.2vw, 46px); }
  .offer-note { font-size: 17px; }
}

/* Every screen size: the three services stack like cards. Each is a full screen that sticks to the top while
   the next slides up over it. The page's glowing background is one fixed layer behind everything, so each
   card carries its own opaque midnight background (glow on its device side), with a rounded top edge,
   a hairline and a shadow so it reads as a card. main.js dims and shrinks the card being covered, and a card
   taller than the screen sticks by its bottom edge instead (main.js sets its top), so all of it shows first. */
.offers { position: relative; padding: 0; }
/* The last card fades into the page instead of ending on a hard line. The fade hangs below the cards,
   outside .offers, so it doesn't lengthen the stack or hold the last card pinned. */
.offers::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 240px; pointer-events: none;
  background: linear-gradient(90deg, #0a133a, #0b1643); /* the card's own bottom edge, darker on the left */
  -webkit-mask-image: linear-gradient(#000, rgba(0, 0, 0, 0.55) 40%, rgba(0, 0, 0, 0.2) 70%, transparent);
  mask-image: linear-gradient(#000, rgba(0, 0, 0, 0.55) 40%, rgba(0, 0, 0, 0.2) 70%, transparent);
}
.offers > .offer {
  position: sticky; top: 0; z-index: 1;
  overflow: hidden; overflow: clip; /* the big words and the phone image's shadow stop at the card's edge, never past the page */
  min-height: 100vh; min-height: 100svh;
  padding: 88px var(--gutter) 40px;
  border-radius: 28px 28px 0 0;
  background:
    radial-gradient(60% 70% at var(--glow-x, 22%) 42%, rgba(52, 88, 235, 0.3), transparent 70%),
    linear-gradient(170deg, #060b22 0%, #08102f 55%, #0b1644 100%);
  box-shadow: 0 -40px 90px -30px rgba(0, 0, 0, 0.9), inset 0 1px 0 rgba(200, 220, 255, 0.16);
}
.offers > .offer-app { --glow-x: 78%; }
/* A short hold: each card rests on screen a moment before the next one starts sliding up */
.offers > .offer + .offer { border-top: 0; margin-top: 25vh; margin-top: 25svh; }
/* An incoming card must never be see-through, so skip the fade-in here */
.motion .offers > .offer[data-reveal] { opacity: 1; transform: none; }
.offer-dim { position: absolute; inset: 0; z-index: 5; border-radius: inherit; background: #02040b; opacity: 0; pointer-events: none; }
/* The covered card shrinks and dims on the graphics chip: its pixels are drawn once, then only scaled and faded, the way
   Framer Motion animates. Chrome on Android redrew the whole card at every new size and flickered (Kyle's Galaxy S25
   Edge, 2026-10-20). While pinch-zoomed the hint goes, so zoomed cards don't hold extra full-resolution copies. */
.offers > .offer { will-change: transform, scale; }
.offer-dim { will-change: opacity; }
.zoomed .offers > .offer, .zoomed .offer-dim { will-change: auto; }
/* Each big word stands still, centered over its device and resting just above it (main.js places it) */
.offers > .offer .offer-art { position: static; }
.offers > .offer .giant { left: var(--gutter); right: auto; top: 92px; transform: none; }
.offers > .offer { --word-top: 92px; } /* the highest a big word may sit, clear of the header (main.js reads it) */

/* Desktop (landscape, 900px and up): device and words side by side, each big word standing still, centered over its device */
@media (min-width: 900px) and (min-height: 640px) and (min-aspect-ratio: 1/1) {
  .offers > .offer { padding: 96px var(--gutter) 56px; border-radius: 32px 32px 0 0; }
  /* The tall phone stands at the bottom of its card and leaves room above it for APPS (phones box is 699x1123:
     height = screen - header clearance - bottom padding - the part of the word that shows above it) */
  .offers > .offer-app .offer-art { align-self: end; }
  .offers > .offer .device-phone { max-width: min(476px, calc((100vh - 148px - 0.704 * clamp(110px, 11.75vw, 240px)) * 0.6224)); }
  .offers > .offer .giant { font-size: clamp(110px, 11.75vw, 240px); }
}
/* Phones, tablets in portrait and short screens: one column, device over the words, with each device capped at
   about 40% of the screen's height so a card fits one screen wherever it can (ratios are each image's width/height) */
@media (max-width: 899px), (max-height: 639px), (max-aspect-ratio: 999/1000) {
  .offers > .offer { align-content: center; gap: clamp(24px, 4vh, 40px); gap: clamp(24px, 4svh, 40px); }
  .offers > .offer .device-mac { max-width: min(100%, calc(40vh * 1.297)); max-width: min(100%, calc(40svh * 1.297)); margin-inline: auto; }
  .offers > .offer .device-phone { max-width: min(476px, calc(40vh * 0.6224)); max-width: min(476px, calc(40svh * 0.6224)); }
  .offers > .offer .flow { max-width: min(100%, calc(40vh * 1.429)); max-width: min(100%, calc(40svh * 1.429)); margin-inline: auto; width: 100%; }
  /* The big word sits above the device as on desktop: sized so WEBSITES spans about the content width, with room
     made above the device for the part of the word that shows (the device overlaps its bottom edge) */
  .offers > .offer .giant { font-size: min(18.5vw, 15vh); font-size: min(18.5vw, 15svh); }
  .offers > .offer .offer-art { padding-top: calc(min(18.5vw, 15vh) * 0.7); padding-top: calc(min(18.5vw, 15svh) * 0.7); }
}
/* Landscape phones and other short, wide screens (under 640px tall): device and words side by side, as on desktop.
   Everything is sized from the screen's height so a whole card fits one screen: each device gets the card's full
   height, less the header clearance and bottom padding (76 + 20px) and the part of its big word that shows above it. */
@media (min-width: 560px) and (max-height: 639px) and (min-aspect-ratio: 1/1) {
  /* Equal columns: the device's size is set by the screen's height anyway, so the words get the rest of the width */
  .offers > .offer, .offers > .offer-app { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: clamp(28px, 5vw, 72px); padding: 76px var(--gutter) 20px; --word-top: 72px; }
  .offers > .offer-app .offer-art { order: 2; }
  .offers > .offer .giant { font-size: min(9.5vw, 15vh); font-size: min(9.5vw, 15svh); }
  .offers > .offer .offer-art { padding-top: calc(min(9.5vw, 15vh) * 0.7); padding-top: calc(min(9.5vw, 15svh) * 0.7); }
  .offers > .offer .device-mac { max-width: min(100%, calc((100vh - 96px - min(9.5vw, 15vh) * 0.7) * 1.297)); max-width: min(100%, calc((100svh - 96px - min(9.5vw, 15svh) * 0.7) * 1.297)); }
  .offers > .offer .device-phone { max-width: min(476px, calc((100vh - 96px - min(9.5vw, 15vh) * 0.7) * 0.6224)); max-width: min(476px, calc((100svh - 96px - min(9.5vw, 15svh) * 0.7) * 0.6224)); }
  .offers > .offer .flow { max-width: min(100%, calc((100vh - 96px - min(9.5vw, 15vh) * 0.7) * 1.429)); max-width: min(100%, calc((100svh - 96px - min(9.5vw, 15svh) * 0.7) * 1.429)); }
  .offer-lede { margin-bottom: 0.7em; font-size: clamp(17px, min(3vw, 6.2vh), 46px); font-size: clamp(17px, min(3vw, 6.2svh), 46px); text-wrap: pretty; }
  .offer-note { margin: -0.2em 0 18px; font-size: clamp(13px, 3.6vh, 16px); font-size: clamp(13px, 3.6svh, 16px); line-height: 1.5; }
}

/* Giant word behind each device (drifts sideways as you scroll) */
.offer-art { position: relative; } /* no perspective here: it would trap the pinned big words inside this box */
.giant {
  position: absolute; left: 50%; top: 50%; z-index: 0;
  transform: translate(-50%, -52%) translateX(var(--gx, 0px));
  font-size: clamp(110px, 16vw, 290px); font-weight: 700; line-height: 0.8; letter-spacing: -0.065em;
  text-transform: uppercase; white-space: nowrap;
  background: linear-gradient(180deg, rgba(170, 196, 255, 0.46) 0%, rgba(110, 150, 255, 0.16) 55%, rgba(90, 130, 255, 0) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  pointer-events: none; user-select: none;
}
.offer-art > .device, .offer-art > .flow { position: relative; z-index: 1; }

/* 3D tilt (driven by main.js) and a soft glare clipped to the device's own shape (the MacBook only, Kyle 2026-10-18) */
.tilt { transform: perspective(1600px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); will-change: transform; }
/* While pinch-zoomed (main.js adds .zoomed to html), no 3D or GPU-only layers: a zoomed iPhone draws each layer at up
   to 15x and can't trim the hidden parts of 3D ones (Kyle's crash report: 1.2GB at full zoom). The devices stop
   leaning and the phone's glow loses its blur until the zoom ends; at normal size nothing changes. */
.zoomed .tilt { transform: none; will-change: auto; }
.zoomed .device-phone::before { filter: none; }
.zoomed .story-track, .zoomed .btn { will-change: auto; }
/* The phone's live app is the worst layer at full zoom (it sits in a 3D transform, so all of it is drawn at 15x):
   while zoomed it rests on its 72% frame. The card dims go too, and over the cards, the fixed background (and the
   second copy of the page it forces), which the opaque cards hide anyway. */
.zoomed .app, .zoomed .app * { animation: none; }
.zoomed .offer-dim { display: none; }
.zoomed-cards .atmo { display: none; }
.zoomed .glass { backdrop-filter: none; -webkit-backdrop-filter: none; } /* the contact form's live blur (~100MB at 5x) */
.glare {
  position: absolute; inset: 0; z-index: 3; pointer-events: none; border-radius: inherit;
  background: radial-gradient(42% 36% at var(--lx, 30%) var(--ly, 22%), rgba(255, 255, 255, 0.28), transparent 72%);
  mix-blend-mode: screen; opacity: 0.9;
}
.device-mac .glare {
  -webkit-mask: url("/images/macbook.webp") center / 100% 100% no-repeat;
  mask: url("/images/macbook.webp") center / 100% 100% no-repeat;
  opacity: 0.6;
}


/* Devices: a photo with a cut-out screen, and live HTML behind it fitted with a perspective transform */
.device { position: relative; isolation: isolate; }
.device-img { position: relative; z-index: 2; width: 100%; height: auto; pointer-events: none; user-select: none; }
.screen { position: absolute; left: 0; top: 0; z-index: 1; overflow: hidden; transform-origin: 0 0; font-family: var(--sans); }
/* The MacBook is a cutout with a see-through screen; a capture of the real hero sits behind it. */
.screen-shot { display: block; width: 100%; height: 100%; }
/* Its shadow, drawn for the dark page: a tight contact shadow under the front edge over a softer pool.
   It sits behind the laptop, so only the part below the base shows. */
.device-mac::before {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  left: -3%; right: -3%; top: 91%; height: 16%;
  background:
    radial-gradient(47% 16% at 50% 50%, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0) 100%),
    radial-gradient(50% 50% at 50% 50%, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0) 100%);
}
.device-phone { max-width: 476px; margin-inline: auto; }
/* The box is just the phones (699x1123 in image pixels). The image carries the whole cast shadow to the
   left (558px more, 1257 wide in all), so it spills out of the box without shrinking the phones. */
.device-phone .device-img { width: calc(100% * 1257 / 699); max-width: none; margin-left: calc(-100% * 558 / 699); }
/* The glass has rounded corners (~53px in the image), so the live screen does too, or its corners poke out.
   main.js lays the screen out at the size it shows, --s times its 430x932 design, so the corners scale with it. */
.screen-app { border-radius: calc(63px * var(--s, 1)) / calc(58px * var(--s, 1)); }
.device-phone::before {
  content: ""; position: absolute; z-index: 0; left: 30%; right: -6%; top: 6%; bottom: 18%; border-radius: 50%;
  background: radial-gradient(ellipse, rgba(70, 110, 255, 0.45), transparent 68%);
  filter: blur(30px);
}

/* Live app (iPhone screen, designed at 430x932 and scaled down to the screen's size by main.js's --s) */
.app {
  position: relative; width: 430px; height: 932px; transform: scale(var(--s, 1)); transform-origin: 0 0;
  padding: 22px 26px 0; color: #eef2ff; overflow: hidden;
  background: radial-gradient(100% 45% at 50% 0%, rgba(50, 85, 230, 0.75) 0%, transparent 70%), #04071a;
}
.app-status { display: flex; justify-content: space-between; align-items: center; font-size: 17px; padding: 6px 10px 0; }
.app-status span { width: 26px; height: 12px; border-radius: 4px; box-shadow: inset 0 0 0 1.5px rgba(238, 242, 255, 0.8); position: relative; }
.app-status span::after { content: ""; position: absolute; inset: 3px 7px 3px 3px; border-radius: 1px; background: #eef2ff; }
.app-hi { margin: 46px 0 2px; font-size: 16px; color: rgba(238, 242, 255, 0.62); }
.app-h { margin: 0; font-size: 38px; font-weight: 600; letter-spacing: -0.03em; }
.app-ring { position: relative; width: 230px; height: 230px; margin: 30px auto 28px; }
.app-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.app-ring circle { fill: none; stroke-width: 11; stroke: rgba(200, 215, 255, 0.1); }
.app-ring .ring-fill { stroke: #8eabff; stroke-linecap: round; stroke-dasharray: 314.2; stroke-dashoffset: 88; filter: drop-shadow(0 0 8px rgba(110, 150, 255, 0.9)); }
.app-ring > span { position: absolute; inset: 0; display: grid; place-content: center; text-align: center; }
.app-ring b { font-size: 50px; font-weight: 600; letter-spacing: -0.04em; line-height: 1; }
.app-ring em { font-style: normal; font-size: 14px; color: rgba(238, 242, 255, 0.6); }
.app-list { display: grid; gap: 12px; }
.app-row { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 14px; padding: 16px 18px; border-radius: 18px; background: rgba(150, 175, 255, 0.08); box-shadow: inset 0 0 0 1px rgba(200, 220, 255, 0.12); font-size: 17px; }
.app-row i { width: 12px; height: 12px; border-radius: 50%; background: #8eabff; box-shadow: 0 0 12px #5b84ff; }
.app-row:nth-child(2) i { background: #fff; box-shadow: 0 0 12px rgba(255, 255, 255, 0.7); }
.app-row:nth-child(3) i { background: #5b84ff; }
.app-row b { font-weight: 500; }
.app-row em { font-style: normal; color: rgba(238, 242, 255, 0.6); font-size: 15px; }
.app-notif { position: absolute; left: 16px; right: 16px; top: 54px; display: flex; gap: 12px; align-items: center; padding: 14px 16px; border-radius: 22px; background: rgba(235, 240, 255, 0.92); color: #050a1f; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4); transform: translateY(-200%); opacity: 0; }
.app-notif i { width: 34px; height: 34px; border-radius: 9px; background: linear-gradient(145deg, #7ea0ff, #2a4ad6); flex: none; }
.app-notif b { display: block; font-size: 15px; }
.app-notif em { font-style: normal; font-size: 14px; color: #3a4466; }
.app-tabs { position: absolute; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-around; padding: 20px 30px 34px; background: rgba(4, 7, 26, 0.85); border-top: 1px solid rgba(200, 215, 255, 0.08); }
.app-tabs i { width: 26px; height: 26px; border-radius: 8px; background: rgba(200, 215, 255, 0.16); }
.app-tabs i.on { background: #fff; box-shadow: 0 0 16px rgba(130, 165, 255, 0.9); }

/* AI agents & automation canvas */
.flow {
  position: relative; aspect-ratio: 600 / 420; border-radius: 28px; overflow: hidden;
  background: radial-gradient(60% 60% at 50% 50%, rgba(50, 85, 230, 0.22), transparent 70%), rgba(6, 11, 36, 0.55);
  box-shadow: inset 0 0 0 1px var(--glass-line), 0 40px 90px -30px rgba(0, 0, 0, 0.6);
  /* No backdrop-filter here or on the labels: the card behind is solid, so a blur shows nothing, and blur on an
     element that also tilts in 3D can make Chrome draw a dark slab over the card (seen 2026-10-18). */
}
.flow::before {
  content: ""; position: absolute; inset: 0;
  background-image: radial-gradient(rgba(200, 215, 255, 0.16) 1px, transparent 1.3px); background-size: 24px 24px;
}
.flow-wires { position: absolute; inset: 0; width: 100%; height: 100%; fill: none; }
.flow-wires path { stroke: rgba(170, 195, 255, 0.18); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.flow-wires .pulse { stroke: #a9c2ff; stroke-width: 2.5; stroke-linecap: round; stroke-dasharray: 26 400; stroke-dashoffset: 26; filter: drop-shadow(0 0 6px #5b84ff); }
.node {
  position: absolute; transform: translate(-50%, -50%);
  padding: 10px 14px; border-radius: 12px; white-space: nowrap;
  font-family: var(--mono); font-size: clamp(10px, 1vw, 13px); letter-spacing: 0.06em; text-transform: uppercase; color: var(--text);
  /* A faint tint on a near-solid base, matched to how the blurred label looked, so the dot grid doesn't show through */
  background: linear-gradient(rgba(150, 175, 255, 0.06), rgba(150, 175, 255, 0.06)), rgba(10, 18, 54, 0.92);
  box-shadow: inset 0 0 0 1px rgba(200, 220, 255, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.n-in1 { left: 14%; top: 21.4%; } .n-in2 { left: 14%; top: 50%; } .n-in3 { left: 14%; top: 78.6%; }
.n-out1 { left: 86%; top: 21.4%; } .n-out2 { left: 86%; top: 50%; } .n-out3 { left: 86%; top: 78.6%; }
/* Phones (portrait and landscape): the diagram is narrow, so the outer labels hug its edges instead of centering on a
   fixed point (centered, WEEKLY REPORT runs past the right edge below about 410px wide), with slightly tighter padding */
@media (max-width: 560px), (min-width: 560px) and (max-height: 639px) and (min-aspect-ratio: 1/1) {
  .node { padding: 8px 10px; }
  .n-in1, .n-in2, .n-in3 { left: 4%; transform: translate(0, -50%); }
  .n-out1, .n-out2, .n-out3 { left: auto; right: 4%; transform: translate(0, -50%); }
}
.node-core {
  left: 50%; top: 50%; display: inline-flex; align-items: center; gap: 10px; padding: 16px 20px; border-radius: 16px;
  background: linear-gradient(180deg, #ffffff, #dfe6ff); color: #050a1f; font-weight: 600;
  box-shadow: 0 0 0 1px #fff, 0 0 60px -6px rgba(110, 150, 255, 0.9);
}
.node-core i { width: 12px; height: 12px; border-radius: 3px; background: linear-gradient(145deg, #5b84ff, #2a4ad6); transform: rotate(45deg); }

/* Screen and canvas animations: play only while on screen */
.offer { --play: paused; }
.offer.on-screen:not(.is-covered) { --play: running; } /* main.js: on screen and not under the next card */
/* Android: the cards' screens hold still (the app rests on its 72% frame, the AI diagram without its pulses). Chrome
   there flickered redrawing those animations as the stacked cards moved, even paused while scrolling; with them
   off it was smooth (Kyle's Galaxy S25 Edge, test #10, 2026-10-20). main.js sets .android. */
.android .offer *, .android .offer *::before, .android .offer *::after { animation: none !important; }
@media (prefers-reduced-motion: no-preference) {
  .ring-fill { animation: ring 6s var(--ease-out) infinite; animation-play-state: var(--play); }
  .app-row { animation: row-in 6s var(--ease-out) infinite; animation-play-state: var(--play); }
  .app-row:nth-child(2) { animation-delay: 0.12s; } .app-row:nth-child(3) { animation-delay: 0.24s; }
  .app-notif { animation: notif 6s var(--ease-out) infinite; animation-play-state: var(--play); }
  .flow-wires .pulse { animation: pulse 2.4s linear infinite; animation-play-state: var(--play); }
  .flow-wires .pulse.late { animation-delay: 1.1s; }
  .node-core { animation: core 2.4s ease-in-out infinite; animation-play-state: var(--play); }
}
@keyframes ring { 0%, 6% { stroke-dashoffset: 314.2; } 40%, 88% { stroke-dashoffset: 88; } 100% { stroke-dashoffset: 314.2; } }
@keyframes row-in { 0%, 10% { opacity: 0; transform: translateY(16px); } 22%, 88% { opacity: 1; transform: none; } 100% { opacity: 0; } }
/* Hidden means fully gone: slid well clear of the screen top and faded out */
@keyframes notif { 0%, 42% { transform: translateY(-200%); opacity: 0; } 50%, 76% { transform: none; opacity: 1; } 86%, 100% { transform: translateY(-200%); opacity: 0; } }
@keyframes pulse { to { stroke-dashoffset: -400; } }
@keyframes core { 50% { box-shadow: 0 0 0 1px #fff, 0 0 90px 0 rgba(110, 150, 255, 1); } }

/* ---------- 8. Contact ---------- */
.contact { position: relative; padding: var(--section) var(--gutter); }
/* The glow's box spans exactly the page's width, so it can never widen the page (Safari 15 has no overflow: clip).
   Its center and size match the old version, an 80vmax square (1400px at most) sticking 10% past the right edge. */
.contact-glow {
  position: absolute; z-index: -1; left: 0; right: 0; top: 0; height: 80vmax; max-height: 1400px; pointer-events: none;
  background: radial-gradient(circle calc(min(80vmax, 1400px) * 0.7071) at calc(110% - min(40vmax, 700px)) min(40vmax, 700px),
    rgba(55, 95, 245, 0.42) 0%, rgba(40, 70, 210, 0.12) 40%, transparent 66%);
}
.contact-inner { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: clamp(40px, 7vw, 120px); align-items: center; } /* the heading sits level with the middle of the form */

.glass {
  background: rgba(8, 15, 48, 0.55);
  box-shadow: inset 0 0 0 1px var(--glass-line), inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 40px 100px -40px rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(24px) saturate(1.3); -webkit-backdrop-filter: blur(24px) saturate(1.3);
}
.contact-form { display: grid; gap: 20px; padding: clamp(24px, 3vw, 40px); border-radius: 28px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.field { display: grid; align-content: start; gap: 8px; margin: 0; padding: 0; border: 0; min-width: 0; } /* start: a neighbor's message never stretches this one */
.field label, .field legend { padding: 0; font-size: 13.5px; font-weight: 500; color: var(--muted); }
.field legend { margin-bottom: 8px; }
.field input, .field select, .field textarea {
  width: 100%; padding: 14px 16px;
  color: var(--text); font: inherit; font-size: 16px;
  background: rgba(150, 175, 255, 0.05);
  border: 1px solid rgba(182, 205, 255, 0.55); border-radius: 14px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
}
.field textarea { resize: vertical; min-height: 140px; }
.field select {
  appearance: none; -webkit-appearance: none; padding-right: 44px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23b6cdff' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 18px center; background-size: 12px 8px;
}
.field select option { background: #0a1233; color: var(--text); }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--ice);
  box-shadow: 0 0 0 4px rgba(90, 130, 255, 0.2), 0 0 30px -10px rgba(110, 150, 255, 0.8);
  background-color: rgba(150, 175, 255, 0.08);
}
.field [aria-invalid="true"] { border-color: #ff7b8a; }
.hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.turnstile:empty { display: none; }
.btn-submit { justify-self: start; }
.btn-submit[disabled] { opacity: 0.6; cursor: progress; }
.form-status { margin: 0; min-height: 1.5em; font-size: 15px; color: var(--muted); }
.form-status.error { color: #ff9aa6; }
.form-fields { display: grid; gap: 6px; }
/* Our own error messages: one under each field, filled in by main.js. Each field keeps a one-line slot for its
   message at all times, so a message appearing or clearing never moves anything (no layout shift). */
.field-error { min-height: 20px; margin: -2px 0 0; font-size: 14px; line-height: 20px; color: #ff9aa6; }
.form-fields > .btn-submit { margin-top: 10px; }
@media (max-width: 360px) { #f-message-error { min-height: 40px; } } /* the longest message takes two lines on the smallest phones */
.field [aria-invalid="true"]:focus { border-color: #ff7b8a; box-shadow: 0 0 0 4px rgba(255, 123, 138, 0.18); }
/* Sent: the fields give way to a thank-you panel in the same card */
.contact-form.sent { align-content: center; }
.contact-form.sent .form-fields { display: none; }
.form-thanks { display: grid; justify-items: center; gap: 22px; padding: 24px 8px; text-align: center; outline: none; }
.form-thanks[hidden] { display: none; }
.thanks-icon {
  display: grid; place-items: center; width: 72px; height: 72px; border-radius: 50%;
  background: linear-gradient(180deg, #ffffff, #dfe6ff);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.8), 0 0 60px -6px rgba(120, 160, 255, 0.9);
}
.thanks-icon svg { width: 32px; fill: none; stroke: #050a1f; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.thanks-text { margin: 0; max-width: 16em; font-size: clamp(24px, 2.4vw, 32px); font-weight: 500; line-height: 1.25; letter-spacing: -0.02em; color: var(--text); text-wrap: balance; }
@media (prefers-reduced-motion: no-preference) {
  .contact-form.sent .form-thanks { animation: thanks-in 0.7s var(--ease-out) both; }
  .contact-form.sent .thanks-icon svg path { stroke-dasharray: 24; animation: check-draw 0.6s 0.25s var(--ease-out) both; }
}
@keyframes thanks-in { from { opacity: 0; transform: translateY(12px) scale(0.98); } }
@keyframes check-draw { from { stroke-dashoffset: 24; } to { stroke-dashoffset: 0; } }
@media (max-width: 899px) { .contact-inner { grid-template-columns: 1fr; } }
/* Phones: name and email stacked, and a full-width send button that's easy to hit */
@media (max-width: 560px) {
  .field-row { grid-template-columns: 1fr; }
  .btn-submit { justify-self: stretch; }
}

/* ---------- Footer ---------- */
.site-footer { padding: clamp(56px, 8vw, 96px) var(--gutter) 28px; border-top: 1px solid var(--line); overflow: hidden; }
.footer-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
.footer-head { margin: 0 0 12px; font-family: var(--mono); font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ice); }
.footer-nav { display: grid; gap: 6px; }
.footer-nav a { padding: 3px 0; text-decoration: none; font-size: 15px; color: var(--muted); width: fit-content; }
.footer-nav a:hover { color: var(--text); }
.wordmark {
  margin: clamp(48px, 8vw, 110px) 0 14px;
  font-size: clamp(64px, 17.5vw, calc(var(--max) * 0.19)); /* fills the content width, never past it */
  font-weight: 700; line-height: 0.8; letter-spacing: -0.06em; white-space: nowrap;
  background: linear-gradient(180deg, rgba(238, 242, 255, 0.95) 0%, rgba(160, 185, 255, 0.4) 55%, rgba(120, 150, 255, 0) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  user-select: none;
}
/* main.js draws the letters into a water-ripple canvas over the text and hides the text itself */
.wordmark.has-water { position: relative; background: none; }
.wordmark-water { position: absolute; pointer-events: none; }
.footer-base { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 24px; }
.legal { margin: 0; font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em; color: var(--muted); }
.legal-place { white-space: nowrap; } /* on narrow phones the location wraps as one piece, never leaving the dot behind */
@media (max-width: 620px) { .footer-cols { grid-template-columns: 1fr; } }

/* ---------- Reveal on scroll ---------- */
@media (prefers-reduced-motion: no-preference) {
  .motion [data-reveal] { opacity: 0; transform: translateY(32px); transition: opacity 0.9s ease, transform 1.1s var(--ease-out); }
  .motion [data-reveal].in { opacity: 1; transform: none; }
}

/* ---------- Reduced motion: everything still, nothing hidden ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
}

/* ---------- 404 ---------- */
.notfound { min-height: 100vh; min-height: 100svh; display: grid; place-content: center; gap: 28px; padding: var(--pad); text-align: center; }
.notfound .label { justify-self: center; margin: 0; }
/* A matching dot on the right. The negative margin cancels the letter spacing after the last character, so both
   dots sit the same distance from the text. */
.notfound .label::after {
  content: ""; display: inline-block; flex: none; width: 6px; height: 6px; border-radius: 50%; margin-left: -0.14em;
  background: var(--ice); box-shadow: 0 0 10px 2px rgba(130, 165, 255, 0.7);
}
.notfound h1 { margin: 0; font-size: clamp(64px, 12vw, 180px); font-weight: 500; line-height: 0.9; letter-spacing: -0.05em; }
.notfound p { margin: 0; color: var(--muted); font-size: 20px; }
.notfound .btn { justify-self: center; }

/* ---------- Accessibility extras ---------- */
/* Pause control for everything that moves on its own (WCAG 2.2.2): a quiet text button in the footer,
   beside the copyright line. Shown only when motion is on. The label says what a click will do. */
.motion-toggle {
  display: inline-flex; align-items: center; gap: 8px; min-height: 24px; padding: 0;
  border: 0; background: none; cursor: pointer;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em; color: var(--muted);
  transition: color 0.2s ease;
}
.motion-toggle:hover { color: var(--text); }
.motion-toggle svg { width: 10px; height: 10px; fill: currentColor; }
.motion-toggle[hidden] { display: none; }
.motion-toggle .i-play, .motion-toggle.is-paused .i-pause { display: none; }
.motion-toggle.is-paused .i-play { display: block; }
.no-motion .motion-toggle { display: none; }
/* Paused: freeze every CSS animation where it is; main.js freezes the canvas plane too. */
.paused *, .paused *::before, .paused *::after { animation-play-state: paused !important; }

/* Viewers who ask their system for more contrast get brighter secondary text and calmer light. */
@media (prefers-contrast: more) {
  :root { --muted: #dfe5f7; --dim: #b8c2e0; }
  .atmo .light { opacity: 0.55; }
  .giant { opacity: 0.6; }
}
