/* ==========================================================================
   AME · launch homepage experience ("Because sometimes…" opening)
   Feature-switched by AME_CONFIG.launchHero (assets/config.js). When the
   switch is off — or JS never runs — every rule here is inert because the
   .lh section keeps its [hidden] attribute and html.launch-hero is never
   set. One isolated commit family; launchHero:false restores the base
   website exactly.

   One continuous pinned hero: blinking-ellipsis opening → scroll-changed
   centered phrases → branded-circle transition to white (unchanged, per
   approval) → AME fades into the SAME hero and speaks → release into
   "A new kind of introduction."

   Motion contract (AME project rules): every animated value follows one
   continuous scroll-scrubbed curve, smoothstep ramps, no phase cuts.
   Reduced motion renders a finished static frame — no pinning, no wash,
   no autoplay, no blinking.
   ========================================================================== */

/* With the experience on, the base hero section rests — the opening
   replaces its reading of the sentence. Everything else is untouched. */
html.launch-hero .hero { display: none; }

/* PRE-PAINT BOOT. hero-launch.js now loads in <head> and adds the
   launch-hero class before the browser's first paint; this rule displays
   the launch section in that same first frame even though its [hidden]
   attribute has not been removed yet (main() removes it at
   DOMContentLoaded exactly as before). Together with the .hero rule
   above, the first painted frame is the opening stage itself — never a
   flash of the base hero. With JS off the class never exists, [hidden]
   keeps its meaning, and the base website renders exactly. */
html.launch-hero #launch-hero[hidden] { display: block; }

/* FIRST-PAINT CANVAS. index.html declares color-scheme "light", so the
   browser paints its pre-content navigation canvas WHITE — one or two
   blank white frames before a page whose opening (and body background)
   is pure black. Scoping color-scheme dark plus an explicit root
   background to the launch experience makes that canvas black: the very
   first painted frame is then the opening stage's own background, with
   no overlay, timeout or delayed reveal involved. The class is added
   pre-paint by the head boot, so this applies from frame zero; with JS
   off it never applies and the base page keeps its declared light
   scheme. Section backgrounds are all explicit, so mid-page content is
   unaffected. */
html.launch-hero { color-scheme: dark; background: var(--black); }

/* LOCKED HEADER BEHAVIOR — during the hero ceremony the header offers
   no destinations. Navigation and CTAs are invisible, unclickable,
   unfocusable, absent from the accessibility tree, and dead to pointer
   events (visibility:hidden provides all of that while preserving
   layout, so the reveal causes zero shift). The brand mark stays
   visible but non-interactive (JS removes its href until reveal).
   "Step into the future" is the only interactive entry control.
   Once AME completes, .lh-nav-open fades everything back in — and it
   never leaves again for that page load. launchHero:false never adds
   html.launch-hero, so the base website header is untouched. */
html.launch-hero .site-head .site-nav,
html.launch-hero .site-head .head-actions {
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}
html.launch-hero.lh-nav-open .site-head .site-nav,
html.launch-hero.lh-nav-open .site-head .head-actions {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}
@media (prefers-reduced-motion: no-preference) {
  html.launch-hero.lh-nav-open .site-head .site-nav,
  html.launch-hero.lh-nav-open .site-head .head-actions {
    transition: opacity 700ms var(--ease-out);
  }
}
html.launch-hero .site-head .brand[aria-disabled="true"] { cursor: default; }

/* Presentation chrome rests during the ceremony: the header hairline
   and the visible scrollbar stay hidden until AME completes, then
   return together with the navigation reveal (.lh-nav-open). Scrolling
   itself is never affected — wheel, trackpad, touch, and keyboard all
   work normally once the instruction appears; only the scrollbar's
   pixels are hidden. scrollbar-gutter keeps the reserved width
   constant on browsers with classic scrollbars, so nothing shifts
   when it hides or returns. launchHero:false never sets
   html.launch-hero, so the base website is untouched. */
html.launch-hero { scrollbar-gutter: stable; }
html.launch-hero:not(.lh-nav-open) { scrollbar-width: none; }
html.launch-hero:not(.lh-nav-open)::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}
html.launch-hero:not(.lh-nav-open) .site-head::after { opacity: 0; }

/* Screen-reader-only text (the complete sentence in one readable piece). */
.lh .visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

.lh { background: var(--black); color: var(--gray); }

/* Scroll track: ~2.9 viewports of travel; the stage stays pinned. Short
   enough to cross in a few swipes — an overture, not a scroll trap. */
.lh-track { height: 290vh; position: relative; }
.lh-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  display: grid;
  place-items: center;
}

.lh-inner {
  position: relative;
  z-index: 2;
  display: grid;
  place-items: center;
  text-align: center;
  padding-inline: var(--gutter);
  width: 100%;
  max-width: 980px;
}

/* Opening line with its blinking brand-dot ellipsis. The dots ARE the
   ellipsis — one row, attached to the sentence, looping one dot at a
   time until the first scroll. The blink is the scroll cue. */
.lh-open {
  grid-area: 1 / 1;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.1rem, 1.3rem + 3.8vw, 4.2rem);
  line-height: 1.18;
  letter-spacing: -0.01em;
  color: var(--white);
  opacity: 0;
}
.lh-ellipsis {
  display: inline-flex;
  gap: clamp(6px, 0.9vw, 10px);
  margin-left: clamp(10px, 1.4vw, 18px);
  vertical-align: baseline;
}
.lh-ellipsis i {
  width: clamp(9px, 1.1vw, 13px);
  height: clamp(9px, 1.1vw, 13px);
  border-radius: 50%;
  background: var(--chartreuse);
  opacity: 0.25;
}
@media (prefers-reduced-motion: no-preference) {
  .lh:not(.lh-scrolled) .lh-ellipsis i {
    animation: lh-blink 1.8s infinite;
  }
  .lh:not(.lh-scrolled) .lh-ellipsis i:nth-child(2) { animation-delay: 0.3s; }
  .lh:not(.lh-scrolled) .lh-ellipsis i:nth-child(3) { animation-delay: 0.6s; }
}
/* One dot at a time: each dot brightens on its own beat of the loop. */
@keyframes lh-blink {
  0%, 45%, 100% { opacity: 0.25; }
  15%, 30% { opacity: 1; }
}
/* Once scrolling starts the cue has done its job: dots settle solid. */
.lh.lh-scrolled .lh-ellipsis i { opacity: 1; }

/* The changing centered phrases — each occupies the same center slot and
   crossfades as scroll advances. Large, centered, visually dominant. */
.lh-phrases { grid-area: 1 / 1; display: grid; place-items: center; width: 100%; }
.lh-phrase {
  grid-area: 1 / 1;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.1rem, 1.3rem + 3.8vw, 4.2rem);
  line-height: 1.18;
  letter-spacing: -0.01em;
  color: var(--white);
  opacity: 0;
  transform: translateY(18px);
  text-wrap: balance;
}
.lh-phrase em { color: var(--chartreuse); }

/* Entry button: "Step into the future" — the single clickable doorway,
   centered beneath the opening line. Quiet, premium text with a slow
   gentle pulse (never a flashing or conventional play control).
   Activating it is the user gesture that unlocks the chime, the
   "Scroll Down" voiceover, and AME's later voice. */
.lh-cue {
  position: absolute;
  z-index: 4;
  left: 50%;
  top: calc(50% + clamp(84px, 14vh, 150px));
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--gray);
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  white-space: nowrap;
  /* button reset — quiet text, not a control chrome */
  background: transparent;
  border: 1px solid rgba(231, 231, 231, 0.28);
  border-radius: 999px;
  padding: 12px 26px;   /* generous touch target */
  font-family: var(--sans);
  cursor: pointer;
  opacity: 1;
  transition: opacity 600ms var(--ease-out), border-color 250ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .lh-cue:hover { color: var(--white); border-color: rgba(231, 231, 231, 0.55); }
}
@media (prefers-reduced-motion: no-preference) {
  .lh-cue.lh-cue-pulse { animation: lh-cue-pulse 2.6s ease-in-out infinite; }
}
/* Slow, soft breathing — an invitation, not a flash. */
@keyframes lh-cue-pulse {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}
/* Activation: the button fades away, then leaves the layout. */
.lh-cue.lh-cue-leaving { opacity: 0; pointer-events: none; animation: none; }

/* Post-activation instruction (NOT a button, requires no click):
   a subtle downward arrow with "Scroll to meet AME", shown after the
   voiceover finishes; gone the moment scrolling begins. */
.lh-scroll-note {
  position: absolute;
  z-index: 3;
  left: 50%;
  top: calc(50% + clamp(84px, 14vh, 150px));
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--mut-dark);
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 700ms var(--ease-out);
  pointer-events: none;
}
.lh-scroll-note.lh-note-in { opacity: 1; }
.lh-cue-arrow { display: block; }
/* Scrolling has begun: entry guidance is done. */
.lh.lh-scrolled .lh-cue,
.lh.lh-scrolled .lh-scroll-note { display: none; }

/* The AME-colored circles that carry the screen from black to white.
   PRESERVED EXACTLY AS APPROVED — do not modify. */
.lh-circles { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.lh-circles i {
  position: absolute;
  border-radius: 50%;
  width: 24vmax;
  height: 24vmax;
  transform: scale(0);
}
.lh-circles .c1 { left: 8%;  top: 12%; background: var(--chartreuse); }
.lh-circles .c2 { right: 6%; top: 20%; background: var(--teal); }
.lh-circles .c3 { left: 22%; bottom: 8%; background: var(--white); }
.lh-circles .c4 { right: 18%; bottom: 4%; background: var(--chartreuse); }
.lh-circles .c5 { left: 42%; top: 4%;  background: var(--white); }
.lh-circles .c6 { right: 40%; bottom: 26%; background: var(--teal); }
/* The final wash that completes the transition to white. */
.lh-wash {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--white);
  opacity: 0;
  pointer-events: none;
}

/* AME appears INSIDE the same hero — no player frame, no chrome, no
   controls. The video's own white background merges into the white
   stage so she reads as part of the website, not an embed. Aspect
   ratio reserved — no layout shift when the file arrives. */
.lh-video-wrap {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  padding: calc(var(--head-h) + 8px) 0 0;
  opacity: 0;
  pointer-events: none;   /* nothing to click — AME just speaks */
  background: transparent;
}
.lh-video-wrap video,
.lh-video-wrap img.lh-poster {
  grid-area: 1 / 1;   /* poster and video share the cell: identical box */
  display: block;
  width: min(1400px, 100vw);
  max-height: min(86vh, 86svh);
  aspect-ratio: 16 / 9;
  object-fit: contain;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  outline: none;
}
/* A media element that has a source but no decoded frame paints as an
   opaque black rectangle. Until JS confirms a displayable frame the
   element is fully transparent and the identically-sized poster <img>
   behind it carries her first frame — so nothing black can ever reach
   the white stage, whatever the network or decoder is doing. */
.lh-video-wrap video.lh-noframe { opacity: 0; }
.lh-video-wrap img.lh-poster[hidden] { display: none; }
/* While AME is actually speaking, her layer leaves the sticky stage and
   fixes to the viewport over its own white fill. Scroll is never locked
   or consumed — the page moves freely underneath (pointer-events stays
   none on the wrap) — but she remains centered and stable until `ended`
   removes the class. Same padding, same inset: the handoff from the
   pinned stage is pixel-identical, white on white. */
.lh-video-wrap.lh-ame-fixed {
  position: fixed;
  background: var(--white);
}

/* AME-active hard scroll lock (mobile background-scroll fix). While she
   speaks the document must not move: overflow:hidden holds desktop;
   touch-action:none + overscroll-behavior:none stop iOS touch scrolling,
   momentum, and rubber-band overscroll declaratively (the JS also cancels
   touchmove/wheel as a fallback for older WebKit). With scrolling frozen
   the URL bar cannot animate, so no gap ("black line") can open under her
   and no underlying section can slide into view. The class is removed the
   instant AME ends, errors, or the safety timeout fires. */
html.lh-ame-lock,
html.lh-ame-lock body {
  overflow: hidden !important;
  overscroll-behavior: none !important;
  touch-action: none !important;
}
/* Portrait and narrow screens: a full 16:9 letterbox leaves AME tiny.
   Size by BOTH viewport axes and crop only the video's own white side
   margins (object-fit: cover from the center) — AME stays whole,
   centered, and dominant. The container's dimensions come purely from
   the viewport, so nothing shifts when the file arrives. */
@media (max-width: 700px), (max-aspect-ratio: 4/5) {
  .lh-video-wrap video,
  .lh-video-wrap img.lh-poster {
    width: 100vw;
    height: min(64svh, 120vw);
    max-height: none;
    aspect-ratio: auto;
    object-fit: cover;
    object-position: center;
  }
}

/* Discreet corner sound control — shown ONLY when the browser has
   genuinely refused unmuted playback. Never a play screen. Fixed to
   the viewport (not the hero) so it stays reachable while AME keeps
   speaking muted even if the visitor scrolls on. */
.lh-sound {
  position: fixed;
  z-index: 30;
  top: calc(var(--head-h) + 14px);
  right: var(--gutter);
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 560;
  letter-spacing: 0.01em;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(0, 0, 0, 0.25);
  border-radius: 999px;
  padding: 8px 16px;
  cursor: pointer;
}
.lh-sound:hover { border-color: rgba(0, 0, 0, 0.55); }

/* ---------- Reduced motion / static variant ----------
   One finished frame: the complete sentence on black, then the video
   below with controls (user-initiated, never autoplay). No pinning, no
   wash, no blinking. !important so the finished frame wins over any
   inline scrub styles left behind if reduced-motion turns on mid-visit. */
.lh.lh-static .lh-track { height: auto; }
.lh.lh-static .lh-stage {
  position: static;
  height: auto;
  min-height: 60vh;
  padding-block: clamp(96px, 14vw, 160px) clamp(40px, 6vw, 72px);
}
.lh.lh-static .lh-inner { opacity: 1 !important; grid-template-rows: auto auto; }
.lh.lh-static .lh-open { opacity: 1 !important; transform: none !important; grid-area: auto; }
.lh.lh-static .lh-ellipsis i { opacity: 1 !important; animation: none !important; }
.lh.lh-static .lh-phrases { grid-area: auto; margin-top: clamp(20px, 3vw, 34px); }
.lh.lh-static .lh-phrase {
  position: static;
  grid-area: auto;
  opacity: 1 !important;
  transform: none !important;
  font-size: clamp(1.5rem, 1rem + 2.5vw, 2.9rem);
}
.lh.lh-static .lh-circles, .lh.lh-static .lh-wash, .lh.lh-static .lh-sound,
.lh.lh-static .lh-cue, .lh.lh-static .lh-scroll-note { display: none; }
/* Reduced motion: the video sits in flow with native controls restored
   (an operable, user-initiated start is the accessible equivalent). */
.lh.lh-static .lh-video-wrap {
  position: static;
  opacity: 1 !important;
  pointer-events: auto;
  padding: 0 var(--gutter) clamp(48px, 7vw, 80px);
}
.lh.lh-static .lh-video-wrap video { max-height: none; width: min(1060px, 100%); }

@media (max-width: 640px) {
  .lh-circles i { width: 34vmax; height: 34vmax; }
}
