/* =====================================================================
   SAVE THE DATE — Julia & Tomasz
   ---------------------------------------------------------------------
   >>> KONFIGURACJA — tutaj zmieniasz wszystko, czego zwykle potrzebujesz
   ===================================================================== */
:root {

  /* --- TŁO (rozmyta grafika) ----------------------------------------
     UWAGA: rozmycie, jasność i nasycenie są WYPALONE W PLIKU
     background.webp (1200x600). Przeglądarka nie liczy już filtra
     blur() na pełnym ekranie — to była najdroższa rzecz na stronie.

     Chcesz inne rozmycie/jasność? Puść ponownie
     tools/optimize-images.py na oryginale z _originals/ — NIE
     przywracaj filtra blur() w CSS, bo wraca koszt każdej klatki.
     Przyciemnienie da się nadal regulować na żywo przez
     --bg-tint i --bg-vignette (to zwykłe gradienty, są tanie).     */
  --bg-image-1: url("background.webp");
  --bg-image-2: url("images_upscayl_4x_digital-art-4x.png");  /* zapas, gdyby pliku wyżej zabrakło */
  --bg-scale: 1;               /* miękkie brzegi są już w pliku          */
  --bg-position: 50% 42%;      /* kadrowanie: lewo/prawo, góra/dół      */
  --bg-tint: rgba(28, 32, 52, .06);  /* dodatkowy woal koloru           */
  --bg-vignette: .26;          /* przyciemnienie rogów (0 = brak)       */

  /* --- LIST ---------------------------------------------------------
     --letter-ar to proporcje pliku save_the_date_letter.webp
     (2300 x 1201 = 1.9151). Dzięki niemu pudełko listu ma DOKŁADNIE
     kształt obrazka, więc cień obrysowuje papier, a nie puste pole
     wokół niego. Podmieniasz grafikę na inną? Popraw tę liczbę.      */
  --letter-ar: 1.9151;
  --letter-width: 60vw;        /* ~1/3 powierzchni ekranu (16:9)        */
  --letter-max-width: 1150px;
  --letter-max-height: 62vh;

  /* --- MOOGLE — POZYCJA (przesuwasz go tymi dwiema wartościami) ------
     Wartości są w % szerokości/wysokości LISTU, liczone od jego
     prawego-dolnego rogu. Większe = dalej od rogu, do środka listu.
     Wartości ujemne = moogle wychodzi poza kartkę.                     */
  --moogle-right: -8%;         /* odsunięcie od prawej krawędzi listu   */
  --moogle-bottom: -11%;       /* odsunięcie od dolnej krawędzi listu   */
  --moogle-size: 16%;          /* wielkość = % szerokości listu         */
  --moogle-rotate: -6deg;      /* przechylenie                          */

  /* --- MOOGLE — LATANIE GÓRA/DÓŁ ------------------------------------ */
  --moogle-float-distance: 14px;  /* jak wysoko podlatuje               */
  --moogle-float-speed: 2.6s;     /* czas jednego cyklu                 */
  --moogle-tilt: 3deg;            /* kołysanie podczas lotu             */

  /* --- ZAPROSZENIE (finalna kartka) ---------------------------------
     Szerokość rośnie razem z ekranem, ale nie przekracza --invite-max
     ani wysokości okna. Cała typografia kartki jest w `em`, więc
     wystarczy ruszyć --invite-base, żeby powiększyć CAŁY tekst.      */
  --invite-width: clamp(340px, min(30vw + 180px, 86vh), 980px);
  --invite-base: clamp(12px, min(0.78vw + 4.7px, 2.3vh), 26px);

  /* Głębia perspektywy przy rozkładaniu. MUSI być powiązana z rozmiarem
     kartki: przy rotacji ~90° dalsza krawędź klapki wychodzi ku widzowi
     o całą swoją wysokość, więc stała wartość dawała na dużym ekranie
     dużo mocniejsze zniekształcenie niż na małym (trójkątne "skrzydła"
     na rogach). Mnożnik 6 = klapka szersza o ~6% niezależnie od ekranu.
     Mniejsza liczba = mocniejsza perspektywa. */
  --invite-perspective: calc(var(--invite-width) * 6);

  /* --- MOOGLE-TRĘBACZE (wlatują z boków po rozłożeniu) --------------- */
  --herald-size: 13vw;         /* wielkość                              */
  --herald-inset: 4vw;         /* odsunięcie od krawędzi ekranu         */
  --herald-offset-y: 2vh;      /* przesunięcie w pionie (+ = w dół)     */
  --herald-float-distance: 22px;  /* jak wysoko podlatują               */
  --herald-float-speed: 3.2s;     /* czas jednego cyklu                 */
  --herald-tilt: 4deg;            /* kołysanie w locie                  */

  /* --- PANEL DŹWIĘKU (prawy górny róg) ------------------------------ */
  --audio-top: 18px;           /* odsunięcie od góry ekranu             */
  --audio-right: 18px;         /* odsunięcie od prawej krawędzi         */
  --audio-fg: rgba(255, 255, 255, .92);
  --audio-bg: rgba(18, 20, 32, .62);

  /* --- CZASY ANIMACJI (nadpisywane z script.js — CONFIG) ------------- */
  --t-open: 900ms;             /* placeholder: otwieranie koperty       */
  --t-slide: 1000ms;           /* odjazd listu w lewo                   */
  --t-emerge: 700ms;           /* wyłanianie kartki ze środka listu     */
  --t-hold: 2000ms;            /* pauza — kartka czeka złożona          */
  --t-unfold: 900ms;           /* rozkładanie zaproszenia               */
  --t-herald: 1100ms;          /* wlot moogli z boków                   */

  /* --- KOLORY ------------------------------------------------------- */
  --paper: #f1efe8;
  --ink: #23201b;
  --ink-soft: #6b6355;
  --accent: #8c2f39;
  --bg-1: #1d2433;
  --bg-2: #39304a;
  --bg-3: #6b5570;
}

/* =====================================================================
   RESZTA — raczej nie musisz tu grzebać
   ===================================================================== */

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  background:
    radial-gradient(1200px 700px at 50% 18%, var(--bg-3) 0%, transparent 62%),
    linear-gradient(160deg, var(--bg-1) 0%, var(--bg-2) 55%, var(--bg-1) 100%);
  color: var(--paper);
  font-family: "Courier New", "DejaVu Sans Mono", monospace;
  overflow: hidden;
}

/* --- rozmyta grafika w tle --- */
.bg {
  position: fixed;
  inset: 0;
  z-index: -2;
  background-image: var(--bg-image-1), var(--bg-image-2);
  background-size: cover;
  background-position: var(--bg-position);
  background-repeat: no-repeat;
  /* Żadnego filter: rozmycie/jasność/nasycenie są wypalone w pliku.
     Wcześniej blur(13px) liczony na warstwie wielkości całego ekranu
     był najdroższą operacją na stronie. */
  transform: scale(var(--bg-scale));
  transform-origin: center;
  pointer-events: none;
}

/* --- woal + winieta, żeby list i zaproszenie były czytelne --- */
.bg-tint {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(120% 100% at 50% 45%,
      transparent 30%,
      rgba(0, 0, 0, var(--bg-vignette)) 100%),
    linear-gradient(180deg,
      rgba(0, 0, 0, .22) 0%,
      transparent 35%,
      transparent 65%,
      rgba(0, 0, 0, .30) 100%),
    var(--bg-tint);
}

/* delikatne gwiazdki w tle */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background-image:
    radial-gradient(1.6px 1.6px at 12% 22%, rgba(255,255,255,.55), transparent),
    radial-gradient(1.4px 1.4px at 78% 14%, rgba(255,255,255,.45), transparent),
    radial-gradient(1.8px 1.8px at 62% 76%, rgba(255,255,255,.35), transparent),
    radial-gradient(1.3px 1.3px at 28% 82%, rgba(255,255,255,.40), transparent),
    radial-gradient(1.5px 1.5px at 90% 58%, rgba(255,255,255,.30), transparent);
  /* Gwiazdki są STATYCZNE. Migotanie animowało opacity na warstwie
     wielkości całego ekranu — bez końca, także gdy nic innego się nie
     działo. Na ekranie 2K to ~3,7 mln pikseli składanych w każdej
     klatce przez cały czas. Efekt był ledwie widoczny, koszt stały. */
  opacity: .42;
}

.stage {
  position: relative;
  height: 100%;
  display: grid;
  place-items: center;
  /* Bez perspective: nic w scenie nie obraca się w 3D (list i trębacze
     używają wyłącznie transformacji 2D). Zbędny kontekst 3D tylko
     wymuszał dodatkową warstwę kompozytora na całą wysokość strony. */
}

/* ---------------------------------------------------------------------
   LIST
   ------------------------------------------------------------------ */
.letter-wrap {
  position: relative;
  /* Ograniczamy SZEROKOŚCIĄ — także limit wysokości przeliczamy na
     szerokość. Dzięki temu pudełko nigdy nie traci proporcji obrazka
     i nie powstają puste pasy, na których rysowałby się cień. */
  width: min(
    var(--letter-width),
    var(--letter-max-width),
    calc(var(--letter-max-height) * var(--letter-ar))
  );
  aspect-ratio: var(--letter-ar);
  cursor: pointer;
  transform-origin: 50% 50%;
  outline: none;
}

/* will-change tylko NA CZAS animacji. Na stałe trzymałoby dużą warstwę
   GPU (na 2K to ponad 1150x600 px) przez cały czas, choć list rusza się
   przez niecałe dwie sekundy. */
.stage.is-opening .letter-wrap { will-change: transform, opacity; }

.letter {
  display: block;
  /* wypełnia pudełko co do piksela — pudełko ma już proporcje obrazka,
     więc nic się nie rozciąga i nie ma czego "zawierać" (object-fit) */
  width: 100%;
  height: 100%;
  border-radius: 4px;
  box-shadow:
    0 26px 60px rgba(0,0,0,.55),
    0 2px 0 rgba(255,255,255,.25) inset;
  /* tylko transform — animowanie box-shadow przemalowuje cały list
     przy każdej klatce najechania myszą */
  transition: transform 260ms ease;
}

.letter-wrap:hover .letter,
.letter-wrap:focus-visible .letter {
  transform: translateY(-6px) scale(1.012);
}

/* --- MOOGLE --- */
.moogle {
  position: absolute;
  right: var(--moogle-right);
  bottom: var(--moogle-bottom);
  width: var(--moogle-size);
  height: auto;
  pointer-events: none;   /* JS włącza je w trybie przenoszenia (Shift) */
  user-select: none;
  filter: drop-shadow(0 10px 14px rgba(0,0,0,.45));
  transform-origin: 50% 90%;
  /* własna warstwa GPU: cień policzy się raz, a nie w każdej klatce lotu */
  will-change: transform;
  animation: moogle-float var(--moogle-float-speed) ease-in-out infinite;
}

@keyframes moogle-float {
  0%, 100% { transform: translateY(0) rotate(var(--moogle-rotate)); }
  50% {
    transform:
      translateY(calc(var(--moogle-float-distance) * -1))
      rotate(calc(var(--moogle-rotate) + var(--moogle-tilt)));
  }
}

.hint {
  position: absolute;
  left: 50%;
  bottom: -2.6em;
  transform: translateX(-50%);
  font-size: clamp(11px, 1.1vw, 14px);
  letter-spacing: .14em;
  text-transform: lowercase;
  color: rgba(255,255,255,.6);
  white-space: nowrap;
  animation: hint-pulse 2.4s ease-in-out infinite;
}
@keyframes hint-pulse { 0%,100% { opacity:.35 } 50% { opacity:.9 } }

/* ---------------------------------------------------------------------
   FAZA 1 + 2 — OTWARCIE LISTU, POTEM ODLOT W LEWO

   Obie animacje wiszą na JEDNEJ klasie i tworzą ciągłą oś czasu:
   `letter-away` startuje z opóźnieniem równym --t-open, czyli dokładnie
   wtedy, gdy `envelope-open` się kończy. Dzięki temu nie ma przeskoku
   (wcześniej podmiana animacji na transform w locie gubiła przejście).

   `letter-away` ma fill `forwards`, a NIE `both` — w trakcie opóźnienia
   nie może nadpisywać transformu z `envelope-open`.
   ------------------------------------------------------------------ */
.stage.is-opening .letter-wrap {
  animation:
    envelope-open var(--t-open) cubic-bezier(.34, 1.4, .5, 1) both,
    letter-away var(--t-slide) cubic-bezier(.5, .02, .35, 1) var(--t-open) forwards;
  pointer-events: none;
}

/* otwarcie: list podrywa się, „strzepuje" i zawisa uniesiony */
@keyframes envelope-open {
  0%   { transform: scale(1)     rotate(0deg)   translateY(0);
         filter: brightness(1); }
  16%  { transform: scale(.975)  rotate(-.8deg) translateY(.6%);
         filter: brightness(1); }
  46%  { transform: scale(1.065) rotate(1.1deg) translateY(-2.2%);
         filter: brightness(1.12); }
  72%  { transform: scale(1.035) rotate(-.5deg) translateY(-.8%);
         filter: brightness(1.05); }
  100% { transform: scale(1.05)  rotate(0deg)   translateY(-1%);
         filter: brightness(1); }
}

/* odlot: ten sam punkt startowy co koniec otwarcia -> płynne przejście */
@keyframes letter-away {
  0% {
    transform: scale(1.05) rotate(0deg) translateY(-1%) translateX(0);
    opacity: 1;
  }
  25% {
    transform: scale(1.07) rotate(2deg) translateY(-2%) translateX(4%);
    opacity: 1;
  }
  100% {
    transform: scale(.86) rotate(-9deg) translateY(2%) translateX(-135vw);
    opacity: 0;
  }
}

.stage.is-opening .hint,
.stage.is-flying .hint { opacity: 0; transition: opacity 200ms ease; }

/* ---------------------------------------------------------------------
   FAZA 3 — ZAPROSZENIE WYŁANIA SIĘ ZE ŚRODKA LISTU I ROZKŁADA
   ------------------------------------------------------------------ */
.invite {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  opacity: 0;
  perspective: var(--invite-perspective);
}

.stage.is-emerging .invite,
.stage.is-unfolded .invite { opacity: 1; pointer-events: auto; }

.invite-stack {
  width: var(--invite-width);
  font-size: var(--invite-base);   /* baza dla całej typografii kartki */
  transform: scaleX(.02) scaleY(.55);
  transform-origin: 50% 50%;
  transform-style: preserve-3d;
  /* Nie ma tu ani filter: drop-shadow(), ani will-change. Filtr na
     kontenerze z obracającymi się w 3D dziećmi kazał przeglądarce
     renderować całe poddrzewo do tekstury co klatkę, a will-change
     wymuszało spłaszczenie do jednej warstwy, co psuje preserve-3d
     i spycha składanie klapek z GPU na główny wątek. */
}

.stage.is-emerging .invite-stack,
.stage.is-unfolded .invite-stack {
  transform: scaleX(1) scaleY(1);
  transition: transform var(--t-emerge) cubic-bezier(.2, .9, .25, 1);
}

.panel {
  background: linear-gradient(180deg, #fbf9f3 0%, var(--paper) 100%);
  color: var(--ink);
  padding: 1.5em 2.2em;
  text-align: center;
  backface-visibility: hidden;
  border-left: 1px solid rgba(0,0,0,.06);
  border-right: 1px solid rgba(0,0,0,.06);
}

/* Cień kartki. Klapki obracają się w 3D, a cień na obracanym elemencie
   trzeba przerysowywać w każdej klatce — dlatego w trakcie składania
   go NIE MA, a wchodzi dopiero gdy kartka leży już płasko. */
.panel-top, .panel-bottom { box-shadow: 0 0 0 rgba(0, 0, 0, 0); }

.panel-main { border-block: 1px dashed rgba(0,0,0,.18); }

/* skrzydła — złożone do środka, rozkładają się jak kartka */
.panel-top {
  transform-origin: 50% 100%;
  transform: rotateX(-92deg);
  border-radius: 6px 6px 0 0;
  border-top: 1px solid rgba(0,0,0,.06);
}
.panel-bottom {
  transform-origin: 50% 0%;
  transform: rotateX(92deg);
  border-radius: 0 0 6px 6px;
  border-bottom: 1px solid rgba(0,0,0,.06);
}

/* Rozłożenie + cień. Cień wchodzi z opóźnieniem równym czasowi
   składania, więc w trakcie obrotu klapek nie ma czego przerysowywać. */
.stage.is-unfolded .panel-top {
  transform: rotateX(0deg);
  transition:
    transform var(--t-unfold) cubic-bezier(.34, 1.3, .5, 1),
    box-shadow 320ms ease var(--t-unfold);
  box-shadow: 0 -18px 40px rgba(0, 0, 0, .38);
}
.stage.is-unfolded .panel-bottom {
  transform: rotateX(0deg);
  transition:
    transform var(--t-unfold) cubic-bezier(.34, 1.3, .5, 1) 120ms,
    box-shadow 320ms ease calc(var(--t-unfold) + 120ms);
  box-shadow: 0 22px 45px rgba(0, 0, 0, .45);
}

/* ---------------------------------------------------------------------
   MOOGLE-TRĘBACZE — wlatują z lewej i prawej krawędzi ekranu
   dopiero w momencie rozłożenia zaproszenia, potem latają góra/dół
   ------------------------------------------------------------------ */
.heralds {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--herald-inset);
  pointer-events: none;
}

.herald {
  width: var(--herald-size);
  /* Przesunięcie w pionie przez `top`, NIE przez margin-top: element jest
     centrowany przez align-items:center, a flexbox centruje pudełko razem
     z marginesami, więc margin przesuwałby go tylko o połowę wartości.
     `top` nie koliduje z transformem, którym trębacz wlatuje z boku. */
  position: relative;
  top: var(--herald-offset-y);
  transition: transform var(--t-herald) cubic-bezier(.2, .85, .3, 1);
}

/* pozycja startowa — poza ekranem */
.herald-left  { transform: translateX(-160vw); }
.herald-right { transform: translateX(160vw); }

.stage.is-unfolded .herald-left,
.stage.is-unfolded .herald-right { transform: translateX(0); }

.stage.is-unfolded .herald-right { transition-delay: 180ms; }

.herald img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 12px 18px rgba(0, 0, 0, .35));
  transform-origin: 50% 80%;
  will-change: transform;   /* własna warstwa GPU — cień liczony raz */
}

/* Latanie startuje dopiero po rozłożeniu kartki. Wcześniej trębacze
   machali poza ekranem: dwie animacje mieliły w tle od wejścia na
   stronę, zanim ktokolwiek kliknął list. */
.stage.is-unfolded .herald img {
  animation: herald-float var(--herald-float-speed) ease-in-out infinite;
}

/* prawy trębacz odbity w poziomie, żeby trąbka celowała w kartkę.
   `scale` jest osobną właściwością, więc nie gryzie się z animacją. */
.herald-right img { scale: -1 1; }

/* lewy i prawy latają w przeciwfazie */
.stage.is-unfolded .herald-right img {
  animation-delay: calc(var(--herald-float-speed) / -2);
}

@keyframes herald-float {
  0%, 100% { transform: translateY(0) rotate(calc(var(--herald-tilt) * -1)); }
  50% {
    transform:
      translateY(calc(var(--herald-float-distance) * -1))
      rotate(var(--herald-tilt));
  }
}

/* --- typografia zaproszenia --- */
.eyebrow {
  margin: 0 0 .8em;
  font-size: .8em;
  letter-spacing: .42em;
  text-transform: uppercase;
  color: var(--accent);
}

.names {
  margin: 0;
  font-family: "Georgia", "Times New Roman", serif;
  font-weight: 400;
  font-size: 2.5em;
  line-height: 1.15;
  letter-spacing: .02em;
}
.amp {
  display: block;
  font-style: italic;
  font-size: .62em;
  color: var(--accent);
  margin: .1em 0;
}

.lead {
  margin: 0 0 1.1em;
  font-size: .92em;
  line-height: 1.8;
  color: var(--ink-soft);
}

.date {
  margin: 0;
  font-family: "Georgia", "Times New Roman", serif;
  font-size: 1.7em;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.date-sep { color: var(--accent); margin: 0 .4em; }

.time {
  margin: .6em 0 0;
  font-size: .92em;
  letter-spacing: .2em;
  color: var(--ink-soft);
}

.place {
  margin: 0 0 1.1em;
  font-size: .92em;
  line-height: 1.7;
}
.rsvp {
  margin: 0;
  font-size: .8em;
  line-height: 1.7;
  color: var(--ink-soft);
}
.kupo { color: var(--accent); letter-spacing: .18em; }

.back {
  margin-top: 1.5em;
  padding: .6em 1.5em;
  font-family: inherit;
  font-size: .8em;
  letter-spacing: .16em;
  color: var(--ink-soft);
  background: transparent;
  border: 1px dashed rgba(0,0,0,.28);
  border-radius: 999px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 300ms ease 700ms, color 200ms, border-color 200ms;
}
.stage.is-unfolded .back { opacity: 1; }
.back:hover { color: var(--accent); border-color: var(--accent); }

/* ---------------------------------------------------------------------
   PANEL DŹWIĘKU — prawy górny róg
   ------------------------------------------------------------------ */
.audio-ui {
  position: fixed;
  top: var(--audio-top);
  right: var(--audio-right);
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px;
  border-radius: 999px;
  /* Bez backdrop-filter: rozmywanie tła pod panelem zmuszało
     przeglądarkę do odczytu i przeliczania tego obszaru w KAŻDEJ
     klatce, w której cokolwiek na stronie się ruszało. Nieprzezroczyste
     tło daje ten sam efekt czytelności za darmo. */
  background: var(--audio-bg);
  border: 1px solid rgba(255, 255, 255, .14);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .3);
}

.audio-btn {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--audio-fg);
  cursor: pointer;
  transition: background 180ms ease, color 180ms ease, transform 180ms ease;
}
.audio-btn:hover { background: rgba(255, 255, 255, .16); }
.audio-btn:active { transform: scale(.92); }
.audio-btn:focus-visible { outline: 2px solid rgba(255, 255, 255, .7); outline-offset: 2px; }

.ico {
  width: 19px;
  height: 19px;
  fill: currentColor;
  stroke: none;
}
.ico .wave {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
}

/* przełączanie ikony głośnik / głośnik przekreślony */
.ico-off { display: none; }
.audio-btn.is-muted .ico-on  { display: none; }
.audio-btn.is-muted .ico-off { display: block; }
.audio-btn.is-muted { color: rgba(255, 255, 255, .5); }

/* suwak głośności — zwinięty do zera, rozwija się po kliknięciu w ikonę */
.audio-range {
  width: 0;
  margin: 0;
  opacity: 0;
  pointer-events: none;
  transition: width 260ms cubic-bezier(.2, .85, .3, 1), opacity 200ms ease;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  cursor: pointer;
}
.audio-ui.vol-open .audio-range {
  width: 96px;
  opacity: 1;
  pointer-events: auto;
  margin-inline: 4px 8px;
}

.audio-range::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .3);
}
.audio-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 13px;
  height: 13px;
  margin-top: -4.5px;
  border: 0;
  border-radius: 50%;
  background: var(--audio-fg);
}
.audio-range::-moz-range-track {
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .3);
}
.audio-range::-moz-range-thumb {
  width: 13px;
  height: 13px;
  border: 0;
  border-radius: 50%;
  background: var(--audio-fg);
}

/* delikatne pulsowanie, dopóki przeglądarka nie pozwoli odpalić dźwięku */
.audio-ui.needs-gesture .audio-btn:first-child {
  animation: audio-nudge 1.8s ease-in-out infinite;
}
@keyframes audio-nudge {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, .0); }
  50%      { box-shadow: 0 0 0 6px rgba(255, 255, 255, .12); }
}

/* ---------------------------------------------------------------------
   TRYB PRZENOSZENIA MOOGLA (Shift + przeciągnij) — patrz script.js
   ------------------------------------------------------------------ */
.stage.moogle-drag .moogle {
  pointer-events: auto;
  cursor: grab;
  animation-play-state: paused;
  outline: 2px dashed rgba(255,255,255,.6);
  outline-offset: 4px;
}
.stage.moogle-drag .letter-wrap { cursor: default; }

.moogle-readout {
  position: fixed;
  left: 12px;
  bottom: 12px;
  z-index: 50;
  padding: 8px 12px;
  font-size: 12px;
  line-height: 1.6;
  color: #0f0;
  background: rgba(0,0,0,.8);
  border-radius: 6px;
  white-space: pre;
  pointer-events: none;
}

/* ---------------------------------------------------------------------
   MOBILE / WĄSKIE EKRANY
   ------------------------------------------------------------------ */
@media (max-aspect-ratio: 1/1), (max-width: 820px) {
  :root {
    --letter-width: 92vw;
    --letter-max-height: 48vh;
    --moogle-size: 20%;
  }
  :root {
    --invite-width: min(90vw, 480px);
    --invite-base: clamp(12px, 3.4vw, 17px);
  }

  /* Na wąskim ekranie trębacze siadają na GÓRNYCH ROGACH kartki, na
     wysokości imion — tam tekst jest krótki i wyśrodkowany, więc po
     bokach zostaje puste pole. Niżej zasłanialiby datę i godzinę.
     Offset jest w vw, bo na mobile cała kartka skaluje się szerokością. */
  :root {
    --herald-size: 25vw;
    --herald-inset: 0vw;
    --herald-offset-y: -38vw;   /* w górę, na wysokość imion */
  }
  .heralds { z-index: 3; }      /* przed kartką, nie za nią */

  :root { --audio-top: 12px; --audio-right: 12px; }
  .audio-ui.vol-open .audio-range { width: 76px; }
}

@media (prefers-reduced-motion: reduce) {
  .moogle, .herald img, body::after, .hint { animation: none; }
}
