/* =========================================================================
   Endon — deep navy & sky.
   The palette is the Endon brand system from endonspace.com: deep navy
   ground (#02182E), steel and sky blues, pale blue text. Inter throughout.
   Tokens below are the whole system.
   ========================================================================= */

:root {
  --paper:    #02182E;   /* deep navy ground (brand)        */
  --shell:    #072440;   /* raised surfaces, inputs         */
  --wash:     #0E3151;   /* blue wash (AI bubbles, frames)  */
  --hairline: #1E4160;   /* rules & borders                 */
  --ink:      #EAF3F8;   /* primary text                    */
  --soft:     #CCDEE4;   /* secondary text (brand pale)     */
  --steel:    #488DB4;   /* brand steel blue                */
  --cobalt:   #85C4E4;   /* brand sky accent                */
  --cobalt-deep: #AAD9F0;
  --night:    #01121F;   /* closing band: settles darker than the page */
  --night-text: #EAF3F8;
  --night-soft: #9FB9CC;

  --font-display: "Montserrat", "Inter", "Helvetica Neue", Arial, sans-serif;
  --font-text: "Inter", "Helvetica Neue", Arial, sans-serif;

  --wrap: 66rem;
  --wrap-narrow: 44rem;
  --gutter: clamp(1.25rem, 4vw, 2rem);
  --radius: 12px;
  --ease: cubic-bezier(.4, 0, .2, 1);
}

/* ---------- Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
/* The root carries the page colour too, and declares itself dark. Rubber-
   band overscroll past the top reveals the html background, not the body's,
   and without this it flashed white. color-scheme keeps the browser's own
   canvas, scrollbars and form chrome dark to match. */
html { scroll-behavior: smooth; background: var(--paper); color-scheme: dark; }
body {
  position: relative;
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: 1.0625rem;
  line-height: 1.68;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img { max-width: 100%; display: block; }
/* Selection tints the words only. Some browsers extend a cross-block
   selection to an ancestor's full width, so the paint area is clipped. */
::selection { background: rgba(133, 196, 228, .28); }

a { color: var(--cobalt); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--cobalt-deep); }

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

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.015em;   /* Montserrat is wide; tighter than this cramps it */
  margin: 0;
  text-wrap: balance;
}

.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-link {
  position: absolute; left: 1rem; top: -3rem; z-index: 100;
  background: var(--cobalt); color: var(--paper); padding: .5rem 1rem;
  border-radius: 8px; transition: top .15s ease;
}
.skip-link:focus { top: 1rem; color: var(--paper); }

.wrap {
  max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter);
  /* clip bounds a cross-block selection to the column; the margin leaves
     room for hover glows to paint outside the box */
  overflow: clip; overflow-clip-margin: 3rem;
}
.site-header .wrap { overflow: visible; }
.wrap--narrow { max-width: var(--wrap-narrow); }
.wrap--wide { max-width: 74rem; }

/* ---------- Scroll progress ---------- */
/* A hairline, not a UI element — 2px, low-opacity cobalt, sits above the
   header (which frosts/scrolls independently) so it reads as one continuous
   thread the whole way down rather than another band of chrome. */
.scroll-progress {
  position: fixed; top: 0; left: 0; z-index: 70;
  height: 2px; width: 0%;
  background: var(--cobalt); opacity: .65;
}

/* ---------- Header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  /* transparent over the hero atmosphere; frosts once the page moves */
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color .3s var(--ease), border-color .3s var(--ease);
}
.site-header.is-scrolled {
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom-color: var(--hairline);
}
/* Anchor targets: clear the sticky header on landing, not by luck of
   whatever padding a section happens to have. */
section[id], #waitlist, #partner-form { scroll-margin-top: 5.5rem; }
.site-header .wrap {
  display: flex; align-items: center; gap: 2rem;
  padding-block: .9rem;
}
.brand {
  display: inline-flex; align-items: center; gap: .55rem;
  font-family: var(--font-display); font-weight: 600; font-size: 1.15rem;
  letter-spacing: -0.01em;
  color: var(--ink); text-decoration: none;
}
/* Brand mark: not a static image — a CSS mask filled with a slow gradient
   sweep, same technique the live app uses for its own header logo. The PNG
   only supplies the silhouette (alpha shape); the visible colour is the
   gradient underneath, so it "shimmers" instead of sitting flat white. */
.brand-mark {
  display: inline-block;
  /* own compositor layer: a masked element whose background animates is
     the first thing Safari drops under load, and it blinks out entirely */
  will-change: transform; transform: translateZ(0); backface-visibility: hidden;
  background: linear-gradient(90deg,
    var(--steel), var(--cobalt), var(--soft), var(--cobalt), var(--steel));
  background-size: 300% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
  animation: brand-shimmer 6s linear infinite;
}
@keyframes brand-shimmer {
  from { background-position: 100% 0; }
  to   { background-position: -200% 0; }
}
/* Both header and footer now use the same full wordmark (endon-full.png,
   554x144), just sized differently — no more separate text "Endon" beside
   either one, the wordmark already reads as the word. */
.site-header .brand-mark {
  width: 84px; height: 22px;
  -webkit-mask-image: url(/endon-full.png); mask-image: url(/endon-full.png);
}
.footer-brand .brand-mark {
  width: 100px; height: 26px;
  -webkit-mask-image: url(/endon-full.png); mask-image: url(/endon-full.png);
}

/* Text shimmer: the same moving gradient as .brand-mark, but cut out of
   glyphs via background-clip instead of a mask-image, for plain text that
   has no PNG silhouette to mask against. Two variants for the two kinds of
   ground this sits on. */
.shimmer-text {
  display: inline-block;
  will-change: transform; transform: translateZ(0); backface-visibility: hidden;
  background-size: 300% 100%;
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  animation: brand-shimmer 6s linear infinite;
}
/* Light-on-dark: sits on the navy footer, same palette as the brand mark. */
.shimmer-text {
  background-image: linear-gradient(90deg,
    var(--steel), var(--cobalt), var(--soft), var(--cobalt), var(--steel));
}
/* Dark-on-light: sits on a solid --cobalt button, where that same light
   gradient would wash out — shimmer between dark tones instead so it stays
   readable at every point in the sweep. */
.shimmer-text--on-light {
  background-image: linear-gradient(90deg,
    var(--paper), var(--wash), var(--paper), var(--wash), var(--paper));
}
.nav { display: flex; gap: 1.4rem; margin-left: auto; }
.nav a {
  position: relative; padding-block: .25rem;
  color: var(--soft); text-decoration: none; font-size: .95rem;
  transition: color .25s var(--ease);
}
/* The underline grows from the left on hover and stays under whichever
   section the scroll-spy says you're in, so the nav moves with the page. */
.nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: var(--cobalt);
  transform: scaleX(0); transform-origin: left;
  transition: transform .3s var(--ease);
}
.nav a:hover, .nav a[aria-current="page"], .nav a.is-current { color: var(--cobalt); }
.nav a:hover::after, .nav a[aria-current="page"]::after, .nav a.is-current::after { transform: scaleX(1); }
.header-cta { margin-left: auto; }
.nav + .header-cta { margin-left: 0; }

/* Header CTA: a textless circular icon button pointing at the waitlist,
   filled with the same moving steel → cobalt → soft gradient as the wordmark
   beside it (brand-shimmer, 6s), so the header's two brand surfaces speak the
   same language. Below 700px it's replaced by .mobile-cta.

   Being textless, its entire accessible name comes from the `aria-label` on
   the anchor, and the <svg> inside is aria-hidden. Never ship this without
   that label — there is no visible text to fall back on. It also carries a
   `title`, so a pointer user gets the same words as a screen reader.

   The fill is a plain animated background-position — no filter, no blur, no
   mask — so it costs the compositor roughly nothing even under the sticky
   header's backdrop-filter. Keep it that way. */
.header-cta--icon {
  position: relative; /* for the ::after breathing glow */
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; width: 2.5rem; height: 2.5rem;
  border-radius: 999px; color: var(--paper); text-decoration: none;
  border: 1px solid transparent;
  background: linear-gradient(90deg,
    var(--steel), var(--cobalt), var(--soft), var(--cobalt), var(--steel));
  background-size: 300% 100%;
  transition: transform .2s var(--ease), box-shadow .3s var(--ease);
}
.header-cta--icon svg {
  width: 1.1rem; height: 1.1rem; display: block;
  transition: transform .25s var(--ease);
}

/* The breathing glow lives on ::after rather than on the button itself, so
   its own box-shadow stays free for the hover state. Before, the two shared
   one property and hover had to stop the animation outright
   (animation-play-state: paused) to be seen — which, now that the shimmer is
   in the same list, would have frozen the gradient mid-slide on hover while
   the wordmark next to it kept moving. */
.header-cta--icon::after {
  content: ""; position: absolute; inset: 0;
  border-radius: inherit; pointer-events: none;
  animation: cta-breathe 4.8s ease-in-out .8s infinite;
}
@keyframes cta-breathe {
  0%, 100% { box-shadow: 0 0 0 0 rgba(133, 196, 228, 0),    0 0 12px rgba(133, 196, 228, .10); }
  50%      { box-shadow: 0 0 0 3px rgba(133, 196, 228, .07), 0 0 22px rgba(133, 196, 228, .26); }
}
.header-cta--icon:hover, .header-cta--icon:active {
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .45), 0 8px 22px -2px rgba(72, 141, 180, .55);
}
/* the arrow leans toward where it's taking you */
.header-cta--icon:hover svg, .header-cta--icon:active svg { transform: translateX(2px); }
.header-cta--icon:focus-visible { outline: 2px solid var(--cobalt); outline-offset: 3px; }

/* Once frosted, the header's bottom rule is a slow-moving thread of the
   brand gradient instead of a flat hairline. */
.site-header::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: linear-gradient(90deg,
    transparent 0%, rgba(133, 196, 228, .0) 20%, rgba(133, 196, 228, .55) 50%,
    rgba(133, 196, 228, .0) 80%, transparent 100%);
  background-size: 220% 100%;
  opacity: 0; transition: opacity .4s var(--ease);
  animation: header-thread 9s linear infinite;
  pointer-events: none;
}
.site-header.is-scrolled::after { opacity: 1; }
@keyframes header-thread { from { background-position: 200% 0; } to { background-position: -120% 0; } }

/* The header joins the page's one entrance: its pieces settle in just
   ahead of the hero copy. The resting state is fully visible and the
   animation only supplies the fade-in (fill-mode backwards), so nothing in
   the header can ever be left invisible if an animation is dropped or
   restarted. The pill's gradient shimmer is declared in the same list so this
   rule doesn't replace it — this selector outranks the .header-cta--icon
   block above, so anything that block animates must be repeated here. (Its
   breathing glow is safe: that lives on ::after, which nothing else touches.) */
@keyframes header-in { from { opacity: 0; transform: translateY(-6px); } }
.site-header .brand { animation: header-in .6s cubic-bezier(.2, .6, .2, 1) backwards; }
.site-header .nav   { animation: header-in .6s cubic-bezier(.2, .6, .2, 1) .08s backwards; }
.site-header .header-cta--icon {
  animation: header-in .6s cubic-bezier(.2, .6, .2, 1) .16s backwards,
             brand-shimmer 6s linear infinite;
}

/* Mobile nav toggle: a checkbox + label, not JS — a <details> disclosure
   was tried first, but "always open above 900px" can't reliably override
   a closed <details>' own shadow-tree content-hiding across engines. The
   checkbox has no such internals, so plain sibling selectors just work. */
.nav-toggle-input { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.nav-toggle-btn {
  display: none; cursor: pointer;
  width: 40px; height: 40px; align-items: center; justify-content: center;
  border-radius: 8px;
}
.nav-toggle-input:focus-visible ~ .nav-toggle-btn { outline: 2px solid var(--cobalt); outline-offset: 3px; border-radius: 8px; }
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  display: block; width: 20px; height: 2px; border-radius: 2px;
  background: var(--ink);
  transition: transform .25s var(--ease), opacity .25s var(--ease), background-color .25s var(--ease);
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before, .nav-toggle-bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bars::before { top: -6px; }
.nav-toggle-bars::after { top: 6px; }
.nav-toggle-input:checked ~ .nav-toggle-btn .nav-toggle-bars { background: transparent; }
.nav-toggle-input:checked ~ .nav-toggle-btn .nav-toggle-bars::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle-input:checked ~ .nav-toggle-btn .nav-toggle-bars::after { transform: translateY(-6px) rotate(-45deg); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-text); font-weight: 500; font-size: 1rem;
  padding: .68rem 1.5rem; border-radius: 999px;
  border: 1px solid transparent; cursor: pointer;
  text-decoration: none;
  transition: background-color .2s var(--ease), box-shadow .2s var(--ease), transform .2s var(--ease);
}
.btn--primary { background: var(--cobalt); color: var(--paper); font-weight: 600; }
.btn--primary:hover {
  background: var(--cobalt-deep);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .45),
    0 8px 22px -2px rgba(72, 141, 180, .55);
}
.btn--primary:active { transform: translateY(1px); }
.btn--block { width: 100%; }
.btn:disabled { opacity: .6; cursor: default; }

/* ---------- Hero ----------
   Atmosphere and breathing rates are taken from the Endon app's own hero:
   layered navy washes, a sky dot-grid, and slow blurred orbs that breathe
   on 10s / 13s / 15s cycles. ------------------------------------------- */
.hero {
  position: relative;
  overflow: hidden; /* .hero-figure sits at right:-2rem under 900px, past
    .wrap's own 3rem clip-margin — without a hard clip here it inflates the
    page's horizontal scroll range between 700-900px and below. */
  padding-block: clamp(4rem, 10vw, 7rem) clamp(3.5rem, 8vw, 6rem);
}
.hero .wrap { max-width: var(--wrap); position: relative; z-index: 1; }

/* Full-page ambience: soft steel glows drifting down the page so the ground
   below the hero keeps some depth instead of going flat navy. Plain radial
   gradients, no blur filters, so this costs nothing to paint. */
body::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(56rem 36rem at 92% 30%, rgba(72, 141, 180, .07) 0%, transparent 66%),
    radial-gradient(48rem 32rem at  4% 66%, rgba(72, 141, 180, .06) 0%, transparent 66%);
}
/* Page backdrop: runs from the very top (behind the sticky header) and
   fades out below the hero, so the atmosphere has no hard edges. */
.page-atmos {
  position: absolute; top: 0; left: 0; right: 0;
  height: clamp(38rem, 130vh, 70rem);
  overflow: hidden; pointer-events: none; z-index: 0;
  background:
    radial-gradient(ellipse 130% 65% at 12% 8%,    rgba(2, 47, 86, .85)    0%, transparent 52%),
    radial-gradient(ellipse 60% 100% at 50% 0%,    rgba(72, 141, 180, .10) 0%, transparent 70%),
    radial-gradient(ellipse 120% 200% at 100% 50%, rgba(2, 28, 58, .90)    0%, transparent 55%);
  -webkit-mask-image: linear-gradient(to bottom, #000 52%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 52%, transparent 100%);
}
/* dot grid, faded out toward the edges */
/* Hero ground: no pattern at all. A dot grid read as a dashboard and a
   blended grain both fought the header (mix-blend-mode under a sticky
   backdrop-filter re-composites every scroll frame and made the header
   contents flash). What's left is light, and not much of it: one slow
   diagonal veil drifting across the hero, a broad cast of light behind the
   figure that swells with the rings — the glyph lighting the room it sits
   in — and five soft distant lights. A second veil, a horizon glow and a
   set of near bokeh were all here at one point; together it was too much. */
.page-atmos .veil {
  width: 130rem; height: 26rem; left: -34rem; top: 4rem;
  border-radius: 50%;
  /* elliptical radial, so every edge fades without a blur filter */
  background: radial-gradient(ellipse 50% 50% at 50% 50%,
    rgba(133, 196, 228, .11) 0%, rgba(72, 141, 180, .08) 40%, transparent 72%);
  animation: veil-drift 80s ease-in-out infinite alternate;
}
@keyframes veil-drift {
  from { transform: rotate(-13deg) translateX(-5rem); }
  to   { transform: rotate(-10deg) translateX(9rem); }
}
.page-atmos .cast {
  width: 66rem; height: 66rem; right: -16rem; top: -2rem;
  background: radial-gradient(circle,
    rgba(133, 196, 228, .20) 0%, rgba(72, 141, 180, .10) 34%, transparent 66%);
  animation: cast-breathe 8.4s ease-in-out infinite;
}
@keyframes cast-breathe {
  0%, 100% { opacity: .7; transform: scale(1); }
  50%      { opacity: 1;  transform: scale(1.04); }
}
/* Bokeh: soft out-of-focus lights, a distant city at 2am, not a grid.
   Five of them, drawn as ONE element with stacked radial gradients. Twelve separate spans
   each carrying filter:blur and their own animation was enough compositor
   load that Safari started dropping the masked elements on the page (the
   wordmark, the button text, the glyph blinked). Gradients are already
   soft; no filter is needed. */
.page-atmos .bokeh {
  inset: 0; border-radius: 0;
  background-repeat: no-repeat;
  background-image:
    radial-gradient(circle, rgba(190,224,240,.55) 0%, rgba(133,196,228,.22) 40%, transparent 70%),
    radial-gradient(circle, rgba(190,224,240,.62) 0%, rgba(133,196,228,.25) 40%, transparent 70%),
    radial-gradient(circle, rgba(190,224,240,.44) 0%, rgba(133,196,228,.18) 40%, transparent 70%),
    radial-gradient(circle, rgba(190,224,240,.58) 0%, rgba(133,196,228,.24) 40%, transparent 70%),
    radial-gradient(circle, rgba(190,224,240,.50) 0%, rgba(133,196,228,.20) 40%, transparent 70%);
  background-size: 20px 20px, 11px 11px, 26px 26px, 13px 13px, 17px 17px;
  background-position: 9% 22%, 56% 9%, 41% 76%, 84% 16%, 91% 68%;
  opacity: .5;
  animation: bokeh-breathe 13s ease-in-out infinite alternate;
}
@keyframes bokeh-breathe { from { opacity: .36; } to { opacity: .62; } }

/* Depth: the ground layers slide a few pixels against the pointer, each
   by a different amount. main.js writes --px / --py (−1..1) on .page-atmos;
   `translate` is used rather than `transform` so it composes with each
   layer's own transform animation instead of replacing it. */
@property --px { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --py { syntax: "<number>"; inherits: true; initial-value: 0; }
.page-atmos { --px: 0; --py: 0; transition: --px .9s var(--ease), --py .9s var(--ease); }
.page-atmos .veil  { translate: calc(var(--px) * -10px) calc(var(--py) * -6px); }
.page-atmos .cast  { translate: calc(var(--px) * -16px) calc(var(--py) * -12px); }
.page-atmos .orb--a { translate: calc(var(--px) * -22px) calc(var(--py) * -14px); }
.page-atmos .orb--b { translate: calc(var(--px) * -12px) calc(var(--py) *  -9px); }
.page-atmos .orb--c { translate: calc(var(--px) *  14px) calc(var(--py) *   9px); }
.page-atmos .bokeh      { translate: calc(var(--px) *  -6px) calc(var(--py) *  -5px); }
.page-atmos span { position: absolute; border-radius: 50%; }
.orb--a {
  width: 46rem; height: 46rem; right: -13rem; top: -6rem;
  background: radial-gradient(circle, rgba(72, 141, 180, .26) 0%, transparent 70%);
  filter: blur(72px); animation: breathe-a 10s ease-in-out infinite;
}
.orb--b {
  width: 34rem; height: 34rem; right: 4rem; top: 34rem;
  background: radial-gradient(circle, rgba(133, 196, 228, .20) 0%, transparent 70%);
  filter: blur(60px); animation: breathe-b 13s ease-in-out infinite;
}
.orb--c {
  width: 40rem; height: 40rem; left: -15rem; top: 8rem;
  background: radial-gradient(circle, rgba(72, 141, 180, .16) 0%, rgba(2, 47, 86, .10) 50%, transparent 70%);
  filter: blur(80px); animation: breathe-c 15s ease-in-out infinite;
}
@keyframes breathe-a { 0%, 100% { transform: scale(1); opacity: .55 } 50% { transform: scale(1.03);  opacity: .75 } }
@keyframes breathe-b { 0%, 100% { transform: scale(1); opacity: .50 } 50% { transform: scale(1.04);  opacity: .70 } }
@keyframes breathe-c { 0%, 100% { transform: scale(1); opacity: .45 } 50% { transform: scale(1.035); opacity: .65 } }

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 21rem);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

/* The living mark. ἔνδον means "within", so the figure is built on one idea:
   the glyph is the source and everything else comes out of it. Rings are
   born at the glyph's edge and travel outward, fading; the glyph and its
   halo beat once per emission (every 4.2s, about 14 a minute — a slow,
   settled breath). The
   whole figure tilts a few degrees toward the cursor and its aura brightens
   as the pointer nears — set by main.js through --tx / --ty / --glow, so
   with no JS or no fine pointer it simply sits at rest. */
/* The three pointer values are registered so the browser can interpolate
   them: the tilt and the aura then ease on their own, including back to
   rest when the pointer leaves, without the children needing transitions. */
@property --tx   { syntax: "<angle>";  inherits: false; initial-value: 0deg; }
@property --ty   { syntax: "<angle>";  inherits: false; initial-value: 0deg; }
@property --glow { syntax: "<number>"; inherits: true;  initial-value: 0; }
.hero-figure {
  position: relative; width: 100%; aspect-ratio: 1; justify-self: center;
  --tx: 0deg; --ty: 0deg; --glow: 0;
  transform: perspective(900px) rotateY(var(--tx)) rotateX(var(--ty));
  transition: --tx .7s var(--ease), --ty .7s var(--ease), --glow .5s var(--ease);
  will-change: transform;
}
.hero-figure span {
  position: absolute; inset: 0; margin: auto; border-radius: 50%;
}
/* Outer field: a faint fixed boundary so the emitted rings have somewhere
   to arrive. Its slow sweep arc is the one asymmetric thing here (below). */
.hero-figure .r3 { width: 92%; height: 92%; border: 1px solid rgba(133, 196, 228, .14); }
.hero-figure .r3::after {
  content: ""; position: absolute; inset: -1px; border-radius: 50%;
  background: conic-gradient(from 0deg,
    transparent 0deg, transparent 298deg,
    rgba(133, 196, 228, .6) 328deg, rgba(133, 196, 228, .6) 344deg,
    transparent 360deg);
  -webkit-mask-image: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
  mask-image: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
  animation: r3-sweep 55s linear infinite;
}
@keyframes r3-sweep { to { transform: rotate(360deg); } }

/* Emitted rings: two in flight at any moment, 4.2s apart. Born at the
   glyph's size (24%), they reach the outer field (92%) as they fade. */
.hero-figure .em {
  width: 32%; height: 32%;
  border: 1px solid rgba(133, 196, 228, .75);
  opacity: 0;
  animation: emanate 8.4s cubic-bezier(.2, .5, .3, 1) infinite;
}
.hero-figure .em2 { animation-delay: 4.2s; }
@keyframes emanate {
  0%   { transform: scale(1);    opacity: 0; }
  6%   { opacity: .62; }
  100% { transform: scale(2.875); opacity: 0; }   /* 32% -> 92% */
}

/* Aura: a wide soft field that answers the cursor (--glow) and the email
   field gaining focus. At rest it is invisible. */
.hero-figure .aura {
  width: 70%; height: 70%;
  background: radial-gradient(circle, rgba(133, 196, 228, .34) 0%, transparent 62%);
  filter: blur(14px);
  opacity: var(--glow);
}
.hero-figure.is-listening .aura { opacity: 1; }

/* Halo and glyph beat together on every emission. */
.hero-figure .core {
  width: 42%; height: 42%;
  background: radial-gradient(circle, rgba(133, 196, 228, .55) 0%, transparent 68%);
  filter: blur(10px);
  animation: halo-beat 4.2s cubic-bezier(.33, 0, .2, 1) infinite;
}
@keyframes halo-beat {
  0%   { transform: scale(1);    opacity: .40; }
  14%  { transform: scale(1.28); opacity: .88; }
  46%  { transform: scale(1);    opacity: .40; }
  100% { transform: scale(1);    opacity: .40; }
}

/* The glyph is four stacked copies of the same masked silhouette, so each
   part of the effect can move on its own while the shape stays one thing:
     .glyph-echo    a soft, larger shadow-copy behind, for depth
     .glyph         the letter itself, in the wordmark's moving gradient
     .glyph-within  a white-hot core that brightens on the beat — the light
                    comes from inside the letter, which is what ἔνδον means
     .glyph-mark    just the diacritic (clip-path keeps the top of the PNG):
                    the Greek name for that sign is πνεῦμα, "breathing
                    mark", so it is the part that flares with each breath.
   Every layer keeps the PNG's 352x456 ratio so the mask isn't distorted. */
.hero-figure .glyph,
.hero-figure .glyph-echo,
.hero-figure .glyph-within,
.hero-figure .glyph-mark {
  width: 30%; height: 38.9%;
  border-radius: 0;
  will-change: transform, opacity; backface-visibility: hidden;
  -webkit-mask: url(/epsilon-logo.png) center / contain no-repeat;
  mask: url(/epsilon-logo.png) center / contain no-repeat;
}
.hero-figure .glyph-echo {
  background: var(--steel);
  animation: echo-beat 4.2s cubic-bezier(.33, 0, .2, 1) infinite;
}
@keyframes echo-beat {
  0%   { transform: scale(1.22); opacity: .07; }
  14%  { transform: scale(1.14); opacity: .12; }
  46%  { transform: scale(1.22); opacity: .07; }
  100% { transform: scale(1.22); opacity: .07; }
}
.hero-figure .glyph {
  background: linear-gradient(90deg,
    var(--steel), var(--cobalt), var(--soft), var(--cobalt), var(--steel));
  background-size: 300% 100%;
  animation: brand-shimmer 6s linear infinite, glyph-beat 4.2s cubic-bezier(.33, 0, .2, 1) infinite;
}
.hero-figure .glyph-within {
  background: radial-gradient(circle at 50% 58%,
    rgba(255, 255, 255, .95) 0%, rgba(234, 243, 248, .6) 22%,
    rgba(133, 196, 228, .25) 42%, transparent 64%);
  animation: glyph-beat 4.2s cubic-bezier(.33, 0, .2, 1) infinite,
             within-beat 4.2s cubic-bezier(.33, 0, .2, 1) infinite;
  transition: opacity .4s var(--ease);
}
@keyframes within-beat {
  0%   { opacity: .22; }
  14%  { opacity: .82; }
  46%  { opacity: .22; }
  100% { opacity: .22; }
}
.hero-figure .glyph-mark {
  background: #F4FAFD;
  clip-path: inset(0 0 64% 0);            /* the diacritic lives in the top 36% */
  animation: glyph-beat 4.2s cubic-bezier(.33, 0, .2, 1) infinite,
             mark-flare 4.2s cubic-bezier(.33, 0, .2, 1) infinite;
}
@keyframes mark-flare {
  0%   { opacity: 0; }
  11%  { opacity: .95; }
  38%  { opacity: 0; }
  100% { opacity: 0; }
}
@keyframes glyph-beat {
  0%   { transform: scale(1); }
  14%  { transform: scale(1.06); }
  46%  { transform: scale(1); }
  100% { transform: scale(1); }
}
.hero-figure.is-listening .glyph-within { opacity: 1; animation-play-state: paused; }

/* Focus ripple: focusing the hero waitlist email sends one pulse through the
   figure — "the product is listening" — instead of the rings just sitting
   there while the one CTA on the page gets the visitor's attention. One
   shot per focus (blur resets it so refocusing replays it), not another
   infinite loop. */
.hero-figure .ripple {
  width: 92%; height: 92%;
  border: 1px solid rgba(133, 196, 228, .55);
  opacity: 0;
}
.hero-figure.is-listening .ripple {
  animation: listen-ripple .9s var(--ease) forwards;
}
@keyframes listen-ripple {
  0%   { transform: scale(.82); opacity: .65; }
  100% { transform: scale(1.2); opacity: 0; }
}
.hero-figure.is-listening .core { filter: blur(8px) brightness(1.35); }
@keyframes breathe-core { 0%, 100% { transform: scale(1); opacity: .55 } 50% { transform: scale(1.22); opacity: .9 } }

/* Scroll cue: sits inside .hero (which clips at its own box), so it simply
   scrolls away with the hero rather than needing JS to hide it past that
   point. */
.hero-scroll-cue {
  position: absolute; left: 50%; bottom: clamp(.75rem, 3vw, 1.75rem);
  transform: translateX(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; color: var(--soft);
  animation: hero-cue-breathe 2.6s ease-in-out infinite;
  transition: color .2s var(--ease);
}
.hero-scroll-cue:hover { color: var(--cobalt); }
.hero-scroll-cue svg { width: 20px; height: 20px; }
@keyframes hero-cue-breathe {
  0%, 100% { transform: translateX(-50%) translateY(0); opacity: .5; }
  50% { transform: translateX(-50%) translateY(6px); opacity: 1; }
}

.etym {
  margin: 0 0 1.6rem;
  font-size: clamp(1.05rem, 1.6vw, 1.2rem);
  color: var(--soft);
}
.etym-word {
  font-style: italic; font-weight: 500;
  color: var(--cobalt);
  font-size: 1.3em;
  margin-right: .35rem;
}
.hero h1 {
  font-size: clamp(2.5rem, 6.4vw, 4rem);
  line-height: 1.06;
  letter-spacing: -.03em;
  max-width: 16ch;
}
.hero-sub {
  margin: 1.5rem 0 0;
  font-size: clamp(1.1rem, 1.8vw, 1.25rem);
  color: var(--soft);
  max-width: 46ch;
}

.waitlist { margin-top: 2.2rem; max-width: 32rem; }
.waitlist form { display: flex; gap: .6rem; flex-wrap: wrap; }
.waitlist input[type="email"] {
  flex: 1 1 14rem;
  font: inherit; color: var(--ink);
  background: var(--shell);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: .66rem 1.2rem;
}
.waitlist input[type="email"]::placeholder { color: #6E93AC; }
.waitlist input[type="email"]:focus {
  outline: none; border-color: var(--cobalt);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cobalt) 18%, transparent);
}
.waitlist input[aria-invalid="true"] { border-color: #FCA5A5; }
/* Live, pre-submit hints — quieter than the hard error/success states above,
   since the user hasn't asked for judgment yet by hitting submit. */
.waitlist input.is-valid { border-color: rgba(133, 196, 228, .6); }
.waitlist input.is-invalid-soft { border-color: rgba(252, 165, 165, .45); }
.form-msg { flex-basis: 100%; margin: .35rem 0 0; font-size: .95rem; min-height: 1.4em; }
.form-msg[data-state="err"] { color: #FCA5A5; }
.form-msg[data-state="ok"] { color: var(--cobalt-deep); }
.night .form-msg[data-state="ok"] { color: var(--night-text); }

.hero-count { margin: 1.1rem 0 0; font-size: .98rem; color: var(--soft); }
@keyframes pulse-dot { 0%, 100% { opacity: .55 } 50% { opacity: 1 } }
/* No indicator dot in front of the count: the live number is the accent. */
.hero-count strong { color: var(--cobalt); font-weight: 600; }
.hero-note { margin: .25rem 0 0; font-size: .88rem; color: var(--soft); }

.hero-trust {
  margin: 2.6rem 0 0; padding-top: 1.4rem;
  border-top: 1px solid var(--hairline);
  font-size: .95rem; color: var(--soft);
  max-width: 52ch;
}

/* Scroll reveal — main.js adds .is-in when the element enters view. */
.js .reveal {
  opacity: 0; transform: translateY(14px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.js .reveal.is-in { opacity: 1; transform: none; }

/* Hero load sequence — the page's one entrance. */
.hero-seq > * { opacity: 0; transform: translateY(10px); animation: rise .7s cubic-bezier(.2,.6,.2,1) forwards; }
.hero-seq > *:nth-child(2) { animation-delay: .12s; }
.hero-seq > *:nth-child(3) { animation-delay: .24s; }
.hero-seq > *:nth-child(4) { animation-delay: .36s; }
.hero-seq > *:nth-child(5) { animation-delay: .48s; }
@keyframes rise { to { opacity: 1; transform: none; } }

main, .site-footer { position: relative; z-index: 1; }

/* Alternating ground. A banded section sits on a faintly raised surface with
   hairline rules, so the page reads as a rhythm rather than one flat field.
   The dot grid ties it back to the hero, held inside the band's own edges. */
.band {
  position: relative;
  background: linear-gradient(180deg,
    rgba(72, 141, 180, .075) 0%,
    rgba(72, 141, 180, .028) 100%);
  border-block: 1px solid rgba(133, 196, 228, .12);
}
.band::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(circle at 1.5px 1.5px, rgba(133, 196, 228, .12) 1.5px, transparent 0);
  background-size: 34px 34px;
  -webkit-mask-image: radial-gradient(ellipse 70% 80% at 50% 50%, #000 10%, transparent 78%);
  mask-image: radial-gradient(ellipse 70% 80% at 50% 50%, #000 10%, transparent 78%);
}
.band > .wrap { position: relative; z-index: 1; }

/* ---------- Sections ---------- */
.section { padding-block: clamp(3.6rem, 8vw, 6.2rem); }
.section-title {
  font-size: clamp(1.9rem, 4vw, 2.6rem);
  line-height: 1.14;
  max-width: 22ch;
}
.section-lead {
  margin: 1.1rem 0 0;
  color: var(--soft);
  font-size: 1.1rem;
  max-width: 56ch;
}
.section-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 27rem);
  gap: clamp(1.5rem, 5vw, 4rem);
  align-items: end;
}
.section-head .section-lead { margin: 0; }

/* Editorial split: heading column beside the substance. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 19rem) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
/* Not sticky: the two columns are close in height, so pinning the heading
   buys ~60px of travel and reads as a stutter rather than a follow. */
.split-head .section-title { max-width: 16ch; }
.split-head .section-lead { font-size: 1.02rem; }
.split-note { margin: 1.4rem 0 0; color: var(--soft); font-size: .95rem; }

/* Between: the “space in between” interlude */
.between p.statement {
  font-size: clamp(1.3rem, 2.3vw, 1.65rem);
  line-height: 1.45;
  margin: 0;
}
.between .cite { display: block; margin-top: 1.3rem; color: var(--soft); font-size: 1.05rem; }

/* The moments Endon is built for. The title sits ABOVE its scene rather than
   beside it: these titles ("The one with the friendship diplomacy") are far
   too long for a label column, and side-by-side they wrapped to two ragged
   lines and left a dead gutter across the middle of the section. Stacked,
   each moment reads as one block and the text gets the full measure. */
.moments { list-style: none; margin: 0; padding: 0; }
.moments li {
  padding: 1.5rem 0;
  border-top: 1px solid var(--hairline);
}
.moments li:last-child { border-bottom: 1px solid var(--hairline); }
.moments li { transition: border-color .3s var(--ease); }
.moments li:hover, .moments li:active { border-top-color: rgba(133, 196, 228, .38); }
.moments li:last-child:hover, .moments li:last-child:active { border-bottom-color: rgba(133, 196, 228, .38); }
.moment-when {
  display: block; margin-bottom: .55rem;
  font-family: var(--font-display); font-weight: 600;
  color: var(--cobalt); font-size: 1.02rem; line-height: 1.35;
  transition: transform .3s var(--ease), color .3s var(--ease);
}
.moments li:hover .moment-when, .moments li:active .moment-when { transform: translateX(3px); color: var(--cobalt-deep); }
.moments p { margin: 0; color: var(--soft); }

/* What it is / what it is not */
.contrast {
  margin-top: 2.6rem;
  display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;
}
.contrast > div {
  border: 1px solid var(--hairline); border-radius: 16px;
  padding: 1.8rem 1.7rem;
  transition: border-color .3s var(--ease), box-shadow .3s var(--ease);
}
.contrast > div:hover, .contrast > div:active {
  border-color: rgba(133, 196, 228, .38);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .07),
    inset 0 0 22px rgba(133, 196, 228, .10);
}
.contrast li::before { transition: width .3s var(--ease), background-color .3s var(--ease); }
.contrast > div:hover li::before { width: 1.05rem; }
.contrast .is { background: var(--shell); }
.contrast h3 { font-size: 1.15rem; }
.contrast ul { list-style: none; margin: 1.3rem 0 0; padding: 0; }
.contrast li {
  position: relative; padding: 0 0 0 1.5rem; margin-bottom: .85rem;
  color: var(--soft); font-size: .98rem;
}
.contrast li:last-child { margin-bottom: 0; }
.contrast li::before {
  content: ""; position: absolute; left: 0; top: .68em;
  width: .75rem; height: 1px; background: var(--cobalt);
}
.contrast .isnt li::before { background: var(--night-soft); opacity: .6; }

/* Hard facts under the trust copy */
.specs {
  margin: 3.2rem 0 2.6rem; padding: 1.8rem 0;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 1.4rem 1.6rem;
}
.specs > div { transition: transform .3s var(--ease); }
.specs > div:hover, .specs > div:active { transform: translateY(-2px); }
.specs dt { color: var(--soft); font-size: .85rem; }
.specs dd {
  margin: .3rem 0 0; font-weight: 600; font-size: 1rem;
  transition: color .3s var(--ease);
}
.specs > div:hover dd, .specs > div:active dd { color: var(--cobalt); }
.between p, .between .cite, .hero-sub, .hero-trust, .section-lead,
.tile p, .member .bio, .assurances p {
  overflow: clip; overflow-clip-margin: .3em;
}

/* ---------- Features: bento grid ---------- */
.bento {
  margin-top: 2.8rem;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem;
}
.tile {
  position: relative; overflow: hidden;
  background: var(--shell);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  padding: 1.7rem 1.6rem;
  transition: border-color .25s var(--ease), box-shadow .25s var(--ease),
              transform .25s var(--ease);
}
.tile:hover, .tile:active {
  border-color: rgba(133, 196, 228, .45);
  transform: translateY(-3px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .10),
    inset 0 0 22px rgba(133, 196, 228, .16),
    0 6px 26px rgba(2, 24, 46, .42);
}
.tile h3, .tile > svg:not(.tile-ghost) {
  transition: color .25s var(--ease), transform .25s var(--ease);
}
.tile:hover h3, .tile:active h3 { color: var(--cobalt); }
.tile:hover > svg:not(.tile-ghost) { transform: scale(1.1); }
.tile .tile-ghost { transition: opacity .35s var(--ease), transform .35s var(--ease); }
.tile:hover .tile-ghost { opacity: .11; transform: scale(1.04); }
.tile svg { width: 34px; height: 34px; color: var(--cobalt); }
.tile h3 { font-size: 1.2rem; margin-top: .9rem; }
.tile p { margin: .55rem 0 0; color: var(--soft); font-size: .97rem; max-width: 46ch; }
.tile--wide { grid-column: span 2; }
.tile--tall { grid-column: span 2; grid-row: span 2; padding: 2rem 1.8rem; }
.tile--tall svg { width: 42px; height: 42px; }
.tile--tall h3 { font-size: 1.55rem; margin-top: 1.1rem; }
.tile--tall p { font-size: 1.02rem; }
.tile--deep { background: linear-gradient(180deg, #022F56, #021E3D); }
.tile svg.tile-ghost {
  position: absolute; right: -2.4rem; bottom: -2.6rem;
  width: 13rem; height: 13rem;
  opacity: .07; pointer-events: none;
}
/* Voice tile: a waveform, not a podcast microphone. The bars rise and fall
   on hover, each on its own beat, so the tile "listens" while you're on it. */
.tile .wave path { transform-box: fill-box; transform-origin: center; }
.tile:hover .wave path, .tile:active .wave path { animation: wave-bar 1.1s ease-in-out infinite; }
.tile .wave path:nth-child(2) { animation-delay: .12s; }
.tile .wave path:nth-child(3) { animation-delay: .24s; }
.tile .wave path:nth-child(4) { animation-delay: .06s; }
.tile .wave path:nth-child(5) { animation-delay: .30s; }
.tile .wave path:nth-child(6) { animation-delay: .18s; }
.tile .wave path:nth-child(7) { animation-delay: .36s; }
@keyframes wave-bar { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(.5); } }

.pull {
  margin: clamp(3rem, 7vw, 4.5rem) 0 0;
  padding-top: clamp(2rem, 4vw, 2.8rem);
  border-top: 1px solid var(--hairline);
  font-family: var(--font-display); font-style: italic; font-weight: 500;
  font-size: clamp(1.3rem, 2.4vw, 1.65rem);
  line-height: 1.45; color: var(--soft);
  max-width: 36ch;
  border: 0; padding: 0;
}


/* ---------- Trust & safety ---------- */
.assurances { display: grid; gap: 1.9rem; }
.assurances > div { padding: .15rem 0 .5rem 1.4rem; border-left: 1px solid var(--hairline); }
.assurances > div { transition: border-color .3s var(--ease); }
.assurances > div:hover, .assurances > div:active { border-left-color: var(--cobalt); }
.assurances h3 { transition: color .3s var(--ease); }
.assurances > div:hover h3, .assurances > div:active h3 { color: var(--cobalt); }
.assurances h3 { font-size: 1.3rem; }
.assurances p { margin: .6rem 0 0; color: var(--soft); font-size: .98rem; }

.callout {
  margin-top: clamp(2.6rem, 5vw, 3.6rem);
  background: #CCDEE4; color: #16344E;
  border-radius: var(--radius);
  padding: 1.5rem 1.75rem;
  font-size: .98rem;
  max-width: 62rem;
}
.callout strong { color: #02182E; }
.callout a { color: #02182E; }


/* ---------- Team ---------- */
/* A square, aligned 2x2 — four equal entries, no first-in-line. The second
   row used to be nudged right ("hive cluster"), which was meant to stop the
   block reading as a ranking but mostly read as a misalignment bug; dropped
   at the client's request. The no-hierarchy requirement is carried where it
   always really was: the MARKUP interleaves the two engineers with the two
   non-engineering roles, so no row or column groups "the engineers" against
   "the others". Keep that order. Still never a flat 4-across row or a single
   stack — those do read as a ranking. */
.team-grid {
  margin-top: 3rem;
  display: grid; grid-template-columns: repeat(2, minmax(0, 27rem));
  column-gap: clamp(1.25rem, 2.5vw, 1.75rem);
  row-gap: clamp(1.25rem, 2.5vw, 1.75rem);
  /* centred inside .wrap--wide: the cluster is narrower than the bento above
     it, and left-hugging it left a dead column of band on the right. */
  justify-content: center;
  /* stretch, not start: at tablet widths the longer names wrap to two lines
     and that card grew taller than the one beside it, leaving the row
     visibly uneven. Equal heights + `align-content: center` inside each card
     keeps the pair level whether the name wraps or not. The bloom panel is
     absolutely positioned, so stretching costs it nothing. */
  align-items: stretch;
}
/* Members run HORIZONTALLY — portrait left, text beside it, vertically
   centred. Stacked (portrait above a left-aligned name) the card was hollow:
   a column of short text down the left and an empty right half. Laid out
   across, the same content fills the box and reads as an entry rather than a
   tile. Same shape as the ≤700px rows below, which keeps the two coherent.

   The container is deliberately restrained — one flat surface and a hairline,
   no gradient fill, no lit top accent. A heavier treatment was tried and read
   as boxy. */
.member {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  grid-template-areas:
    "avatar name"
    "avatar role"
    "avatar links";
  column-gap: 1.3rem;
  align-content: center;
  /* No container: no surface, no border, no radius. The members sit straight
     on the band. Padding is kept only so the hover/focus target stays
     comfortable and the bloom panel has something to hang off. Two boxed
     versions were built and both were rejected — a gradient card with a lit
     top accent, then a flat --shell card with a hairline. Don't build a
     third; if this section needs more presence, put it in the panel. */
  padding: .75rem .25rem;
}
.member .frame {
  grid-area: avatar; align-self: center;
  width: 92px; height: 92px; border-radius: 50%;
  overflow: hidden; background: var(--wash);
  border: 1px solid var(--hairline);
  transition: border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.member .frame img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .4s var(--ease);
}
.member:hover .frame, .member:active .frame,
.member:focus-within .frame, .member.is-open .frame {
  border-color: rgba(133, 196, 228, .45);
  box-shadow: 0 0 0 4px rgba(133, 196, 228, .10);
}
.member:hover .frame img, .member:active .frame img,
.member:focus-within .frame img, .member.is-open .frame img { transform: scale(1.06); }
.member .name { transition: color .25s var(--ease); }
.member:hover .name, .member:active .name,
.member:focus-within .name, .member.is-open .name { color: var(--cobalt); }
.member .name {
  grid-area: name; align-self: end;
  font-family: var(--font-display); font-weight: 600; font-size: 1.1rem;
}
.member .role { grid-area: role; color: var(--cobalt); font-size: .92rem; margin-top: .15rem; }
.member .bio { color: var(--soft); font-size: .95rem; margin: 0; }
.member .links {
  grid-area: links; align-self: start;
  margin-top: .6rem; display: flex; gap: .9rem; font-size: .9rem;
}

/* Bio + focus chips bloom in on hover / keyboard focus / (touch) tap — no
   button. The panel is absolutely positioned, so nothing else on the page
   moves; it's drawn over the dimmed neighbours. Below 701px it's shown inline
   for everyone (responsive block) and main.js leaves it alone. */
.member { position: relative; }
.member:hover, .member:focus-within, .member.is-open { z-index: 5; }
/* A proper popover, not a plain box: it detaches from the member with a gap,
   points back at it with a caret, and carries real elevation so it reads as
   floating above the dimmed neighbours rather than as another slab in the
   grid. This is where the section's visual weight now lives, since the
   members themselves have no container. */
.member-panel {
  position: absolute; left: -.4rem; right: -.4rem; top: calc(100% + .55rem);
  padding: 1.1rem 1.25rem 1.2rem;
  background: linear-gradient(180deg, var(--wash) 0%, var(--shell) 62%);
  border: 1px solid rgba(133, 196, 228, .3);
  border-radius: 16px;
  box-shadow:
    inset 0 1px 0 rgba(133, 196, 228, .2),
    0 26px 54px -20px rgba(0, 0, 0, .8),
    0 0 0 1px rgba(2, 24, 46, .6);
  opacity: 0; visibility: hidden;
  transform: translateY(-8px) scale(.985);
  transform-origin: top center;
  transition: opacity .25s var(--ease), transform .25s var(--ease), visibility .25s;
  pointer-events: none;
}
/* The caret is a rotated square wearing two of the panel's own borders, so
   it inherits the same edge. It sits under the panel's top-left, over the
   member it belongs to. */
.member-panel::after {
  content: ""; position: absolute; left: 2.15rem;
  width: 12px; height: 12px;
  background: var(--wash);
  border-left: 1px solid rgba(133, 196, 228, .3);
  border-top: 1px solid rgba(133, 196, 228, .3);
  top: -7px; transform: rotate(45deg);
}
/* Bottom row blooms upward: opening downward would push the panel past
   `.wrap`'s `overflow: clip` and get it cut off (and cover the investor line).
   The caret flips with it — moving to the bottom edge and taking the two
   opposite borders so it still points at its member. */
.team-grid .member:nth-child(3) .member-panel,
.team-grid .member:nth-child(4) .member-panel {
  top: auto; bottom: calc(100% + .55rem);
  transform: translateY(8px) scale(.985);
  transform-origin: bottom center;
  background: linear-gradient(180deg, var(--shell) 38%, var(--wash) 100%);
}
.team-grid .member:nth-child(3) .member-panel::after,
.team-grid .member:nth-child(4) .member-panel::after {
  top: auto; bottom: -7px;
  background: var(--wash);
  border: 0;
  border-right: 1px solid rgba(133, 196, 228, .3);
  border-bottom: 1px solid rgba(133, 196, 228, .3);
}
.member:hover .member-panel,
.member:focus-within .member-panel,
.member.is-open .member-panel {
  opacity: 1; visibility: visible; transform: none; pointer-events: auto;
}
.member-panel .bio { color: var(--ink); }
.member-panel .bio { margin: 0; }
.focus {
  list-style: none; padding: .85rem 0 0; margin: .85rem 0 0;
  border-top: 1px solid rgba(133, 196, 228, .16);
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
}
/* A quiet "Focus" label, as a flex item ahead of the chips, so the row reads
   as labelled data rather than loose tags. The list keeps its
   aria-label="Focus areas", which is what actually names it for assistive
   tech — generated content can't carry aria-hidden, so engines that do
   announce ::before will say it twice. That redundancy is deliberate: a
   reliable accessible name is worth more than avoiding a repeated word. */
.focus::before {
  content: "Focus";
  font-size: .68rem; letter-spacing: .09em; text-transform: uppercase;
  color: var(--steel); margin-right: .15rem;
}
.focus li {
  font-size: .78rem; line-height: 1; color: var(--soft);
  padding: .38rem .62rem;
  background: color-mix(in srgb, var(--paper) 70%, transparent);
  border: 1px solid rgba(133, 196, 228, .22); border-radius: 999px;
}

/* Spotlight: bring one card forward and let the others settle back — opacity
   and a hair of scale, no layout shift. Pure CSS for pointer (:hover) and
   keyboard (:focus-within); main.js adds .is-open for touch (a tap on the
   card). All three arms need :has(); an engine without it just skips the
   dimming. Off below 701px, where every panel shows inline. */
.team-grid .member { transition: opacity .3s var(--ease), transform .3s var(--ease); }
/* With the container gone there is no box to lift or outline, so the lit
   member is signalled by its own parts instead: the portrait ring lights and
   its image scales, the name turns --cobalt, and the panel blooms. The
   spotlight below still settles the other three back. */
.js .team-grid .reveal:not(.is-in) {
  transition-duration: .6s;
  transition-delay: calc(var(--i, 0) * 70ms);
}
@media (min-width: 701px) {
  .team-grid:has(.member:hover) .member:not(:hover),
  .team-grid:has(.member:focus-within) .member:not(:focus-within),
  .team-grid:has(.member.is-open) .member:not(.is-open) {
    opacity: .45;
    transform: scale(.97);
  }
}
.investor-line { margin-top: 3.5rem; color: var(--soft); font-size: .95rem; }

/* ---------- FAQ ---------- */
.faq { border-top: 1px solid var(--hairline); }
.faq details[id] { scroll-margin-top: 5.5rem; }
.faq details { border-bottom: 1px solid var(--hairline); }
.faq summary {
  cursor: pointer; list-style: none;
  padding: 1.05rem 2.2rem 1.05rem 0;
  font-weight: 600; font-size: 1.02rem;
  position: relative;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary { transition: color .2s var(--ease); }
.faq summary:hover { color: var(--cobalt); }
.faq summary::after {
  content: "+";
  position: absolute; right: .2rem; top: 50%;
  transform: translateY(-50%);
  font-size: 1.3rem; font-weight: 400; color: var(--cobalt);
  transition: transform .25s ease;
}
.faq details[open] summary::after { transform: translateY(-50%) rotate(45deg); }
.faq .answer { overflow: hidden; }
.faq details.js .answer { transition: height .3s ease; }
.faq .answer p { margin: 0 0 1.1rem; color: var(--soft); max-width: 62ch; }

/* ---------- Night: final CTA + footer ---------- */
.night { background: var(--night); color: var(--night-soft); }
.final-cta {
  padding-block: clamp(3.5rem, 7vw, 5rem) clamp(2.5rem, 4vw, 3.25rem);
  background: linear-gradient(180deg, #02182E 0%, #01121F 100%);
}
.final-cta h2 {
  color: var(--night-text);
  font-size: clamp(2rem, 4.5vw, 2.9rem);
  line-height: 1.12;
  max-width: 18ch;
}
.final-cta .section-lead { color: var(--night-soft); }
.final-cta .split { grid-template-columns: minmax(0, 27rem) minmax(0, 1fr); align-items: center; }
.final-cta h2 { max-width: none; }
.night .waitlist input[type="email"] { background: #071C31; border-color: rgba(133, 196, 228, .16); }
.night .hero-note { color: var(--night-soft); }
.night a { color: var(--night-text); }
.night a:hover { color: #fff; }

.site-footer { padding-block: 0 1.8rem; }
.footer-top {
  display: grid; grid-template-columns: 2fr 1fr 1fr;
  gap: 2.5rem;
  padding-block: 2.2rem;
  border-top: 1px solid color-mix(in srgb, #fff 14%, transparent);
}
.footer-brand .brand { color: var(--night-text); }
.footer-brand p { margin: .9rem 0 0; font-size: .95rem; max-width: 30ch; }
.footer-social { margin-top: 1rem; display: flex; gap: 1.1rem; font-size: .92rem; }
.footer-col h4 {
  margin: 0 0 .8rem; font-family: var(--font-display);
  font-weight: 600; font-size: .98rem; color: var(--night-text);
}
.footer-col a { display: block; padding: .22rem 0; font-size: .95rem; text-decoration: none; color: var(--night-soft); }
.footer-col a:hover { color: #fff; }
.footer-bottom {
  display: flex; flex-wrap: wrap; gap: .6rem 2rem; justify-content: space-between;
  padding-top: 1.6rem;
  border-top: 1px solid color-mix(in srgb, #fff 14%, transparent);
  font-size: .88rem;
}
.footer-credit { opacity: .55; font-size: .78rem; }
.footer-credit a { color: inherit; text-decoration: none; }
.footer-credit a:hover { color: inherit; opacity: .85; text-decoration: underline; }
/* .footer-credit a's own `color: inherit` outranks a bare .shimmer-text
   class (higher specificity, class+tag beats class alone) and would cancel
   the transparent fill the gradient-clip needs — reassert it at matching
   specificity so frontalnode.io actually shimmers instead of sitting solid. */
.footer-credit a.shimmer-text,
.footer-credit a.shimmer-text:hover { color: transparent; -webkit-text-fill-color: transparent; }

.site-footer { background: var(--night); color: var(--night-soft); padding-top: 1.2rem; }
.site-footer a { color: var(--night-soft); }
.site-footer a:hover { color: #fff; }

/* ---------- Sticky mobile CTA ---------- */
.mobile-cta {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  padding: .75rem var(--gutter) calc(.75rem + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-top: 1px solid var(--hairline);
  transform: translateY(110%);
  transition: transform .3s ease;
  display: none;
}
.mobile-cta .btn { width: 100%; }
.mobile-cta.is-visible { transform: none; }

/* ---------- Subpages ---------- */
.page-head { padding-block: clamp(3rem, 7vw, 4.5rem) 0; }
.page-head h1 { font-size: clamp(2.2rem, 5vw, 3.2rem); line-height: 1.1; }
.page-head .section-lead { margin-top: 1.2rem; }
.back-link { font-size: .92rem; display: inline-block; margin-bottom: 1.6rem; text-decoration: none; }
.back-link:hover { text-decoration: underline; }
.meta { color: var(--soft); font-size: .92rem; margin: .8rem 0 0; }

.legal-body { padding-block: 2.5rem 4rem; }
.page-head + .section { padding-block-start: 2.2rem; }
.legal-body .callout { margin: 0 0 2.4rem; color: #16344E; } /* .legal-body p would otherwise win on
  specificity and repaint this text var(--soft) — identical to the callout's own background */
.legal-body section { margin-top: 2.2rem; }
.legal-body h2 { font-size: 1.4rem; }
.legal-body p, .legal-body li { color: var(--soft); font-size: .99rem; }
.legal-body ul { padding-left: 1.2rem; }

.field { margin-bottom: 1.2rem; }
.field label { display: block; font-weight: 600; font-size: .95rem; margin-bottom: .4rem; }
.field input, .field textarea {
  width: 100%; font: inherit; color: var(--ink);
  background: var(--shell);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: .65rem .9rem;
}
.field textarea { min-height: 9rem; resize: vertical; }
.field input:focus, .field textarea:focus {
  outline: none; border-color: var(--cobalt);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cobalt) 18%, transparent);
}

.notfound { min-height: 60vh; display: flex; align-items: center; padding-block: 4rem; }
.notfound h1 { font-size: clamp(4rem, 12vw, 7rem); font-weight: 700; color: var(--cobalt); line-height: 1; }
.notfound p { color: var(--soft); font-size: 1.1rem; }
.notfound-links { margin-top: 1.5rem; font-size: .92rem; }

/* ---------- Responsive ---------- */
@media (max-width: 1000px) {
  .team-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 2.5rem; }
  .specs { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  /* order, not source order, decides the row: brand, CTA, hamburger last —
     the toggle input/label stay before .header-cta in the DOM (the ~
     combinator needs them before .nav as plain siblings) so desktop's
     natural order (brand, nav links, CTA) needs no override of its own. */
  /* the nav is out of flow here, so the CTA pushes itself right; the
     selector matches the desktop ".nav + .header-cta" so it can win */
  .header-cta, .nav + .header-cta { order: 2; margin-left: auto; }
  .nav-toggle-btn { order: 3; display: flex; }
  .nav {
    display: none; order: 4;
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; gap: 0; margin-left: 0;
    background: color-mix(in srgb, var(--paper) 96%, transparent);
    backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--hairline);
    padding: .25rem var(--gutter) 1rem;
  }
  .nav-toggle-input:checked ~ .nav { display: flex; }
  .nav a {
    padding: 1rem 0; font-size: 1.02rem;
    border-top: 1px solid var(--hairline);
  }
  .nav a:first-child { border-top: 0; }
  .split, .final-cta .split { grid-template-columns: 1fr; gap: 2rem; }
  .section-head { grid-template-columns: 1fr; gap: 1.1rem; align-items: start; }
  .contrast { grid-template-columns: 1fr; }
  .hero-grid { grid-template-columns: 1fr; }
  .hero-figure {
    position: absolute; top: 1.5rem; right: -2rem;
    width: min(17rem, 54%);
    opacity: .45; z-index: -1; pointer-events: none;
  }
  .bento { grid-template-columns: 1fr; }
  .tile--wide, .tile--tall { grid-column: auto; grid-row: auto; }
  .footer-top { grid-template-columns: 1fr 1fr; }
  .footer-brand { grid-column: 1 / -1; }
}
@media (max-width: 700px) {
  /* desktop offsets push most of each orb off a narrow screen */
  .page-atmos .veil { display: none; }
  .page-atmos .cast { width: 40rem; height: 40rem; right: -14rem; top: -4rem; }
  .orb--a { width: 28rem; height: 28rem; right: -9rem; top: -3rem; }
  .orb--b { width: 22rem; height: 22rem; right: -7rem; top: 25rem; }
  .orb--c { width: 24rem; height: 24rem; left: -10rem; top: 7rem; }
  .specs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .split-head .section-title { max-width: none; }
  /* Phones: hairline-separated rows, the same shape as the moments list, so
     the avatar sits beside its name instead of floating above a stack. */
  .team-grid { grid-template-columns: 1fr; gap: 0; }
  .member {
    display: grid;
    grid-template-columns: 64px minmax(0, 1fr);
    grid-template-areas:
      "avatar name"
      "avatar role"
      "panel  panel"
      "links  links";
    column-gap: 1.1rem;
    /* drop the desktop card surface: on phones these are hairline-separated
       rows, the same shape as the .moments list, not four stacked cards */
    padding: 1.5rem 0;
    background: none; border: 0; border-radius: 0; box-shadow: none;
    border-top: 1px solid var(--hairline);
  }
  .member::before { display: none; }
  .member:last-of-type { border-bottom: 1px solid var(--hairline); }
  .member .frame {
    grid-area: avatar; align-self: center;
    width: 64px; height: 64px;
  }
  .member .name { grid-area: name; align-self: end;   margin-top: 0; font-size: 1.05rem; }
  .member .role { grid-area: role; align-self: start; }
  .member .links { grid-area: links; margin-top: .8rem; }
  /* the bloom panel drops back into the flow on phones — always visible, no overlay */
  .member-panel {
    grid-area: panel; position: static;
    margin-top: .9rem; padding: 0;
    background: none; border: 0; border-radius: 0; box-shadow: none;
    opacity: 1; visibility: visible; transform: none; pointer-events: auto;
  }
  /* inline here, so there is nothing for the popover's caret to point at */
  .member-panel::after { display: none; }
  .focus li { background: var(--shell); }
  .header-cta { display: none; }
  .mobile-cta { display: block; }
  /* .mobile-cta sits fixed over the last ~76px of viewport height once shown
     — without this the footer's last row (Privacy/Terms, copyright) scrolls
     in underneath it and is both unreadable and untappable at page bottom. */
  .site-footer { padding-bottom: 6rem; }
  .waitlist form .btn { flex: 1 1 100%; }
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: .01ms !important;
  }
  .hero-seq > *, .js .reveal,
  .site-header .brand, .site-header .nav, .site-header .header-cta { opacity: 1; transform: none; }
  /* The shimmer is cut to one .01ms pass, which would leave the pill holding
     whatever slice of the gradient sits at its end frame. Pin it to a flat
     cobalt instead, so the contrast behind the dark text is known. */
  .header-cta--icon { background: var(--cobalt); }
  .hero-figure { transform: none; }
  .hero-figure .em { opacity: 0; }
  /* keep the spotlight's dimming, drop its scale — a state change, not motion */
  .team-grid:has(.member:hover) .member:not(:hover),
  .team-grid:has(.member:focus-within) .member:not(:focus-within),
  .team-grid:has(.member.is-open) .member:not(.is-open) { transform: none; }
  .member-panel { transform: none; } /* no slide-in; the fade stays */
}
