.bubble {
  --callout-color: var(--color-cream);
  position: absolute;
  z-index: 90;
  width: max-content;
  max-width: min(16rem, 64vw);
  padding: 0;
  color: var(--callout-color);
  font-family: var(--font-sage);
  font-size: clamp(1.05rem, 1.42vw, 2rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: 0.01em;
  text-align: center;
  text-wrap: balance;
  opacity: 0;
  transform: translate3d(-50%, -42%, 0) scale(0.98);
  transform-origin: 50% calc(100% + 2rem);
  pointer-events: none;
  text-shadow: 0 4px 16px rgba(24, 4, 3, 0.34);
  transition:
    opacity 180ms var(--ease-out),
    transform 220ms var(--ease-out);
  will-change: opacity, transform;
}

.bubble.is-visible {
  opacity: 1;
  transform: translate3d(-50%, -50%, 0) scale(1);
}

.bubble-curve {
  --curve-width: clamp(2rem, 3vw, 3.8rem);
  --curve-height: clamp(2.9rem, 4.1vw, 5.1rem);
  --curve-rotate: 9deg;
  --curve-flip: -1;
  --curve-stroke: clamp(2px, 0.18vw, 3px);
  position: absolute;
  z-index: 89;
  width: var(--curve-width);
  height: var(--curve-height);
  border-left: var(--curve-stroke) solid currentColor;
  border-bottom: var(--curve-stroke) solid currentColor;
  border-radius: 0 0 0 100%;
  color: var(--color-cream);
  opacity: 0;
  transform: translate3d(-50%, -8%, 0) rotate(var(--curve-rotate)) scaleX(var(--curve-flip));
  transform-origin: 0 0;
  pointer-events: none;
  filter: drop-shadow(0 3px 8px rgba(24, 4, 3, 0.22));
  transition:
    opacity 180ms var(--ease-out),
    transform 220ms var(--ease-out);
  will-change: opacity, transform;
}

.bubble-curve.is-visible {
  opacity: 0.96;
  transform: translate3d(-50%, 0, 0) rotate(var(--curve-rotate)) scaleX(var(--curve-flip));
}

.bubble-sage {
  max-width: min(20rem, 62vw);
  font-family: var(--font-sage);
  font-style: normal;
}

.bubble-curve-sage {
  --curve-rotate: 7deg;
  --curve-flip: -1;
}

.bubble-astronaut {
  max-width: min(20rem, 62vw);
  font-family: var(--font-sage);
  font-style: normal;
}

.bubble-curve-astronaut {
  --curve-rotate: -7deg;
  --curve-flip: 1;
}

/* Per-character animation scaffolding (shared) — keep words intact when wrapping. */
.bubble-word {
  display: inline-block;
  white-space: nowrap;
}

.bubble-char {
  display: inline-block;
  white-space: pre;
}

/* Container snaps visible so the per-char timing dominates. */
.bubble--bloom.is-visible,
.bubble--type.is-visible {
  transition: opacity 90ms var(--ease-out), transform 180ms var(--ease-out);
}

/* Sage — soft-blur reveal (Apple-keynote spec via pixelpoint/animate-text):
   per-char, 900ms with cubic-bezier(0.22, 1, 0.36, 1), 16px rise + 12px blur. */
.bubble--bloom .bubble-char {
  opacity: 0;
  filter: blur(12px);
  transform: translateY(16px);
  transition:
    opacity 900ms cubic-bezier(0.22, 1, 0.36, 1),
    filter 900ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 900ms cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(var(--i, 0) * 25ms);
}

.bubble--bloom.is-visible .bubble-char {
  opacity: 1;
  filter: blur(0);
  transform: translateY(0);
}

/* Astronaut — typewriter (pixelpoint spec): per-char snap-on with
   steps(1, end) easing. No fade, no rise — each glyph appears instantly. */
.bubble--type .bubble-char {
  opacity: 0;
  transition: opacity 240ms steps(1, end);
  transition-delay: calc(var(--i, 0) * 46ms);
}

.bubble--type.is-visible .bubble-char {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .bubble,
  .bubble-curve {
    transition-duration: 300ms;
    transform: translate3d(-50%, -50%, 0);
  }

  .bubble-curve.is-visible {
    transform: translate3d(-50%, 0, 0) rotate(var(--curve-rotate)) scaleX(var(--curve-flip));
  }

  .bubble--bloom .bubble-char,
  .bubble--type .bubble-char {
    transition-delay: 0ms;
    transform: none;
    filter: none;
  }
}
