/* ==========================================================================
   LANGUAGE RETUNE — a line comes apart, half of it changes language, blackout

   The words that change are the ones already on the page: js/retune.js splits
   each visible line into inline units and swaps some of them for the language
   being chosen. Nothing here positions anything, and nothing is added beside
   the page — which is why scrolling through a language change stays smooth.
   ========================================================================== */

.retune {
  position: fixed;
  inset: 0;
  z-index: 220;
  visibility: hidden;
  pointer-events: none;
}

.is-retuning .retune {
  visibility: visible;
}

.retune__blackout {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: #020108;
  opacity: 0;
  will-change: opacity;
}

/* Inline runs preserve the exact source layout. Characters remain inline, so
   wrapping points do not move when a line is tokenised. */
.rt-source-run,
.rt-word,
.rt-char {
  display: inline;
}

/* Only large type lights up on its bright frames. The halo is a blur the
   browser paints per character, so on body copy it was paying for something
   too small to read as a glow at all — every line still flickers, but the
   expensive half of the effect is spent where it shows. */
.rt-source-run--loud {
  --rt-glow: 0 0 8px rgba(217, 184, 255, 0.9), 0 0 18px rgba(178, 91, 240, 0.6);
}

/* No will-change here on purpose. There is one of these per character, so a
   hint on each asks the compositor for hundreds of layers at once — which is
   what made a language change feel like the whole site straining. Stepped
   opacity needs none of it. */
.rt-flicker-unit {
  animation-duration: var(--nf-duration, 480ms);
  animation-delay: var(--nf-delay, 0ms);
  animation-timing-function: steps(1, end);
  animation-fill-mode: both;
}

.rt-flicker--a { animation-name: rt-signal-a; }
.rt-flicker--b { animation-name: rt-signal-b; }
.rt-flicker--c { animation-name: rt-signal-c; }

/* The other language, standing in the line it is replacing.

   `isolate` is what keeps a half-changed line readable: without it a
   right-to-left fragment dropped into a left-to-right sentence drags the words
   around it into its own order, and the line reshuffles every time another
   word changes. Isolated, each fragment lays itself out in its own direction
   and stays in the slot it was put in.

   The tint is not decoration either — it is what says "this half is the other
   signal" rather than "this word is a typo". */
.rt-new {
  unicode-bidi: isolate;
  color: var(--violet-bright);
  text-shadow: var(--rt-glow, none);
  animation: rt-arrive 420ms steps(1, end) both;
}

/* Arabic dropped into an English line would otherwise be set in whatever the
   Latin stack falls back to. */
.rt-new:lang(ar) {
  font-family: var(--font-ar);
}

/* The tail of the flicker: the page goes, still carrying the half-changed
   wording, a beat before the blackout closes over it. The transition is
   declared here rather than on the elements, so taking the class off snaps the
   page back to full strength behind the blackout instead of fading it in
   underneath one. */
.is-retune-dissolve .hdr,
.is-retune-dissolve #main,
.is-retune-dissolve .footer,
.is-retune-dissolve .gate {
  opacity: 0;
  transition: opacity 320ms cubic-bezier(0.55, 0, 1, 0.45);
}

.is-retune-darken .retune__blackout {
  animation: rt-site-darken 260ms cubic-bezier(0.55, 0, 1, 0.45) both;
}

.is-retune-reveal .retune__blackout {
  animation: rt-site-return 720ms var(--ease) both;
}

/* Signal loss, written in opacity alone. `brightness()` and `drop-shadow()`
   are filter passes the browser runs per element per frame; on a screenful of
   characters that is thousands of passes a second, and it read as the site
   labouring rather than as type breaking up. The dips are also shallower than
   they were: a line that half disappears wavers, one that vanishes strobes. */
@keyframes rt-signal-a {
  0%, 10%, 31%, 56%, 80%, 100% { opacity: 1; text-shadow: none; }
  11%, 18% { opacity: 0.24; }
  19%, 30% { opacity: 1; text-shadow: var(--rt-glow, none); }
  57%, 65% { opacity: 0.4; }
  66%, 79% { opacity: 0.86; text-shadow: var(--rt-glow, none); }
}

@keyframes rt-signal-b {
  0%, 17%, 40%, 62%, 87%, 100% { opacity: 1; text-shadow: none; }
  18%, 24% { opacity: 0.42; }
  25%, 39% { opacity: 0.9; text-shadow: var(--rt-glow, none); }
  63%, 69% { opacity: 0.2; }
  70%, 86% { opacity: 1; text-shadow: var(--rt-glow, none); }
}

@keyframes rt-signal-c {
  0%, 7%, 22%, 47%, 73%, 100% { opacity: 1; text-shadow: none; }
  8%, 13% { opacity: 0.3; }
  14%, 21% { opacity: 1; text-shadow: var(--rt-glow, none); }
  48%, 58% { opacity: 0.46; }
  59%, 72% { opacity: 0.88; text-shadow: var(--rt-glow, none); }
}

/* A fragment tuning in: it catches, drops out once, then holds. */
@keyframes rt-arrive {
  0% { opacity: 0; }
  18% { opacity: 0.85; }
  34% { opacity: 0.12; }
  52% { opacity: 1; }
  68% { opacity: 0.4; }
  100% { opacity: 1; }
}

@keyframes rt-site-darken {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes rt-site-return {
  from { opacity: 1; }
  to { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .rt-flicker-unit,
  .rt-new {
    animation: none !important;
  }
  .is-retune-darken .retune__blackout { animation-duration: 90ms; }
  .is-retune-reveal .retune__blackout { animation-duration: 180ms; }
}
