/* --- hero 3D scene --- */
/* The scene host is a grid cell of #hero, never an overlay: stacked under the
   text on narrow screens, the right-hand column on wide ones, so the ribbon
   never sits behind the headline. It is transparent, so the molecule field
   and the ::before drift glow show through. */

.hero3d {
  position: relative;
  width: 100%;
  height: clamp(240px, 40svh, 420px);
  overflow: hidden;
  cursor: grab;
  opacity: 0;
  transition: opacity 0.6s ease;
}

/* Drag gestures live on #hero (the canvas sits below it for hit-testing), so
   vertical touch pans keep scrolling the page while horizontal pans orbit. */
#hero:has(.hero3d) {
  touch-action: pan-y;
  align-content: center;
  row-gap: var(--space-3);
}

/* An empty poster placeholder would otherwise claim a grid cell. */
#hero .poster:empty {
  display: none;
}

/* --- hero poster (issue #25) ---
   The poster is an authentic render of this very scene, captured by
   scripts/capture-posters.mjs, and shares the scene host's grid cell UNDER
   it: before boot the poster is the visible scene; the live canvas then fades
   in over its own snapshot, and the poster remains beneath as the permanent
   fallback when 3D never boots (no WebGL, load error, module failure). */
#hero .hero3d,
#hero .poster {
  grid-column: 1;
  grid-row: 2;
}

#hero .hero3d {
  z-index: 1; /* live scene above its own poster snapshot */
}

#hero .poster {
  z-index: 0;
  align-self: stretch; /* fill the row the scene host sizes */
  overflow: hidden;
  cursor: grab; /* pre-boot drags still arm the boot on the section */
  /* The render carries its own backdrop (page colour plus a few field dots);
     feather its edges so it never reads as a box on the live background. */
  -webkit-mask-image: radial-gradient(closest-side, #000 62%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 62%, transparent 100%);
  transition: opacity 0.6s ease;
}

/* Once the live scene is up, its snapshot steps aside — otherwise the
   poster's baked-in backdrop shows around the transparent canvas. It stays
   in place (and visible) whenever 3D never boots. */
#hero:has(.hero3d.is-ready) .poster {
  opacity: 0;
}

#hero .poster picture,
#hero .poster img {
  display: block;
  width: 100%;
  height: 100%;
}

#hero .poster img {
  object-fit: contain; /* keep the whole molecule visible at any cell aspect */
  user-select: none;
  -webkit-user-drag: none;
}

@media (min-width: 960px) {
  #hero:has(.hero3d) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
    column-gap: clamp(1rem, 3vw, 3rem);
    /* Line the text column up with the nav's container edge. */
    padding-inline: max(2rem, (100% - var(--container, 68rem)) / 2 + 0.75rem);
    text-align: left;
  }

  #hero:has(.hero3d) .container {
    margin: 0;
    justify-self: start;
  }

  #hero:has(.hero3d) .hero-tagline {
    margin-inline: 0;
  }

  #hero:has(.hero3d) .hero-links,
  #hero:has(.hero3d) .eyebrow {
    justify-content: flex-start;
  }

  #hero:has(.hero3d) .hero3d,
  #hero:has(.hero3d) .poster {
    grid-column: 2;
    grid-row: 1;
  }

  .hero3d {
    height: min(78svh, 680px);
  }
}

.hero3d.is-ready {
  opacity: 1;
}

.hero3d:active {
  cursor: grabbing;
}

/* Keyboard focus (arrows orbit): keep the ring inside the cell so the
   section's edge never clips it. */
.hero3d:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.hero3d canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Persistent residue caption tracking the Cys 12 anchor point. */
.hero3d-label {
  position: absolute;
  top: 0;
  left: 0;
  padding: 0.12rem 0.6rem;
  border: 1px solid var(--line, rgba(255, 255, 255, 0.08));
  border-radius: 6px;
  background: color-mix(in srgb, var(--bg, #0a0e14) 90%, transparent);
  backdrop-filter: blur(3px);
  color: var(--ink, #e8eef7);
  font-family: var(--font-mono, monospace);
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  white-space: nowrap;
  pointer-events: none;
  will-change: transform;
}

/* Breathing: a compositor-only transform/opacity oscillation on the canvas —
   zero main-thread work, so it cannot add to the render loop's TBT budget.
   is-paused (toggled by hero3d.js when the loop stops) freezes it in phase,
   and reduced motion gets the plain static frame per the site contract. */
.hero3d.is-ready canvas {
  animation: hero3d-breathe 7s ease-in-out infinite;
}

.hero3d.is-paused canvas {
  animation-play-state: paused;
}

@keyframes hero3d-breathe {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.012); opacity: 0.94; }
}

@media (prefers-reduced-motion: reduce) {
  .hero3d.is-ready canvas {
    animation: none;
  }
}

