/* Rotating city in the hero chip — six reveal effects, adapted from the
   text-reveals motion specimen. Everything is namespaced `cfx-` so it cannot
   collide with a theme's own .rise / .split classes.

   The animated glyphs are aria-hidden and each effect carries one screen-reader
   copy of the city, so a rotation never reads out twice (or letter by letter).
   Colours come from the host theme via --cfx-accent. */
.cfx {
  display: inline-block;
  position: relative;
  white-space: nowrap;
  /* Sized by its content: padding the city out to a fixed width leaves a gap
     before the em dash that reads as a typo. The chip that contains it reserves
     the room instead (see .hero-chip in each theme), so the sentence recentres
     rather than shoving whatever sits next to it. */
  text-align: left;
}
.cfx-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.cfx-char,
.cfx-word {
  display: inline-block;
  will-change: transform, opacity, filter;
}

/* 1 · rise — letters lift out of a mask, staggered */
.cfx-rise .cfx-mask { display: inline-block; overflow: hidden; vertical-align: bottom; }
.cfx-rise .cfx-char {
  transform: translateY(115%);
  animation: cfxRise 0.62s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: calc(var(--i) * 42ms);
}
@keyframes cfxRise { to { transform: translateY(0); } }

/* 2 · wipe — clip-path sweep led by a scanning bar */
.cfx-wipe .cfx-wipe-text {
  display: inline-block;
  clip-path: inset(0 100% 0 0);
  animation: cfxWipe 0.72s cubic-bezier(0.22, 1, 0.36, 1) 0.08s forwards;
}
.cfx-wipe .cfx-bar {
  position: absolute;
  top: 8%;
  bottom: 8%;
  left: 0;
  width: 3px;
  background: var(--cfx-accent, currentColor);
  transform: scaleY(0);
  animation: cfxBar 0.82s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes cfxWipe { to { clip-path: inset(0 0 0 0); } }
@keyframes cfxBar {
  0% { transform: scaleY(0); left: 0; }
  12% { transform: scaleY(1); left: 0; }
  88% { transform: scaleY(1); left: calc(100% - 3px); }
  100% { transform: scaleY(0); left: calc(100% - 3px); }
}

/* 3 · blur — characters condense out of focus */
.cfx-blur .cfx-char {
  opacity: 0;
  filter: blur(8px);
  transform: scale(1.06);
  animation: cfxBlur 0.7s ease forwards;
  animation-delay: calc(var(--i) * 38ms);
}
@keyframes cfxBlur { to { opacity: 1; filter: blur(0); transform: scale(1); } }

/* 4 · scramble — glyphs churn, then lock (driven by JS) */
.cfx-scramble .cfx-noise { color: var(--cfx-accent, currentColor); }

/* 5 · split — halves slide in from opposite sides */
.cfx-split .cfx-half {
  position: absolute;
  inset: 0;
  opacity: 0;
  animation: cfxHalve 0.62s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.cfx-split .cfx-half.is-top { clip-path: inset(0 0 50% 0); transform: translateX(-24px); }
.cfx-split .cfx-half.is-bottom { clip-path: inset(50% 0 0 0); transform: translateX(24px); animation-delay: 0.06s; }
.cfx-split .cfx-ghost { visibility: hidden; }
@keyframes cfxHalve { to { opacity: 1; transform: translateX(0); } }

/* 6 · cascade — word-by-word tumble (multi-word city names) */
.cfx-cascade .cfx-word {
  opacity: 0;
  transform: translateY(0.5em) rotate(4deg);
  transform-origin: 0 100%;
  animation: cfxTumble 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: calc(var(--i) * 90ms);
}
@keyframes cfxTumble { to { opacity: 1; transform: translateY(0) rotate(0); } }

/* The rotator swaps text instantly under reduced motion (see city-reveal.js);
   this is belt-and-braces for anything already on screen. */
@media (prefers-reduced-motion: reduce) {
  .cfx-char, .cfx-word, .cfx-half, .cfx-wipe-text {
    animation: none !important;
    opacity: 1 !important;
    filter: none !important;
    transform: none !important;
    clip-path: none !important;
  }
  .cfx-bar { display: none; }
}
