/* A typed radius makes the feathered mask interpolate continuously. */
@property --voice-aperture {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

/* Reserve the same content width with and without a page scrollbar. */
html { scrollbar-gutter: stable; }
#wave-home-design header .wf-brand { view-transition-name: wave-brand; }
::view-transition-group(wave-brand) { z-index: 2; animation: none; }
::view-transition-old(wave-brand) { animation: none; opacity: 0; }
::view-transition-new(wave-brand) { animation: none; }

/* Copy arrives whole, outside the aperture. These are transition-only layers:
   direct loads and unsupported browsers keep the ordinary, visible page. */
#wave-home-design .voice-heading { view-transition-name: voice-heading; }
#wave-home-design .voice-session { view-transition-name: voice-session; }
#wave-home-design .voice-extras { view-transition-name: voice-extras; }
#wave-home-design .voice-footer { view-transition-name: voice-footer; }
#wave-home-design .voice-header nav { view-transition-name: voice-nav; }

::view-transition { background: #f9f9fb; }
::view-transition-group(root) { z-index: 0; animation-duration: 1020ms; }
::view-transition-group(voice-heading),
::view-transition-group(voice-session),
::view-transition-group(voice-extras),
::view-transition-group(voice-footer),
::view-transition-group(voice-nav) { z-index: 1; animation: none; }
::view-transition-old(*), ::view-transition-new(*) { mix-blend-mode: normal; }

/* Enter the new space in one overlapping movement. Only the boundary is
   feathered: the orb and both pages remain sharp. The old world is fully gone
   before teardown; the new mask is fully opaque before the copy settles. */
:root:has(.wave-demo)::view-transition-old(root) {
  animation: voice-world-recede 800ms cubic-bezier(.35, 0, .35, 1) both;
}
:root:has(.wave-demo)::view-transition-new(root) {
  mask-image: radial-gradient(circle at 50% 46%,
    #000 max(0px, calc(var(--voice-aperture) - 32px)),
    transparent var(--voice-aperture));
  mask-repeat: no-repeat;
  animation: voice-world-open 950ms cubic-bezier(.38, 0, .2, 1) both;
}
:root:has(.wave-demo)::view-transition-new(voice-heading),
:root:has(.wave-demo)::view-transition-new(voice-session),
:root:has(.wave-demo)::view-transition-new(voice-extras),
:root:has(.wave-demo)::view-transition-new(voice-footer),
:root:has(.wave-demo)::view-transition-new(voice-nav) {
  animation: voice-world-arrive 760ms cubic-bezier(.22, .61, .36, 1) 260ms both;
}

/* Returning home is quieter than entering the demo. */
:root:has(.wave-home)::view-transition-group(root) { animation-duration: 320ms; }
:root:has(.wave-home)::view-transition-old(root) {
  animation: voice-world-leave 320ms ease-out both;
}
:root:has(.wave-home)::view-transition-old(voice-heading),
:root:has(.wave-home)::view-transition-old(voice-session),
:root:has(.wave-home)::view-transition-old(voice-extras),
:root:has(.wave-home)::view-transition-old(voice-footer),
:root:has(.wave-home)::view-transition-old(voice-nav) {
  animation: voice-world-leave 180ms ease-out both;
}
:root:has(.wave-home)::view-transition-new(root) {
  animation: voice-world-return 320ms ease-out both;
}

@keyframes voice-world-recede {
  from { transform: scale(1); opacity: 1; }
  to { transform: scale(1.035); opacity: 0; }
}
@keyframes voice-world-open {
  from { --voice-aperture: 0px; }
  /* 80vmax covers the farthest corner at any aspect ratio; include the full
     feather so the last animated frame matches the unmasked live page. */
  to { --voice-aperture: calc(80vmax + 32px); }
}
@keyframes voice-world-arrive {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes voice-world-leave { to { opacity: 0; } }
@keyframes voice-world-return { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}
