/* ============================================================
   САВКА • НОВА ЕРА / public/nova/bulka.css — вигляд ведучої.

   ЧОМУ ФАЙЛ ВИГЛЯДАЄ САМЕ ТАК:

   • Анімуються ТІЛЬКИ transform і opacity. Ні filter, ні
     backdrop-filter, ні box-shadow у кейфреймах: на сусідніх проєктах
     це давало 15 fps на середньому Android і замерзлий кадр на
     скріншотах. Тінь тут є, але статична — вона нікуди не рухається.

   • Постійний рух (дихання, хвіст, кільце-пульс) — це CSS-кейфрейми.
     JS у них не заходить узагалі; він лише пише числа у --bk-*, а
     кейфрейм читає їх через calc(). Тому «жива» Булка коштує чотири
     присвоєння змінних за кадр, а не перебудову SVG.

   • Усі класи з префіксом .nv- (§1 контракту): нова вітрина живе на
     тому ж домені, що й старий сайт Каті, і жодне правило звідси не
     має права дотягнутись до нього. Селекторів на body/html немає.

   • prefers-reduced-motion не «приглушує», а вимикає рух повністю і
     ставить статичну позу. JS у цьому режимі не заводить rAF взагалі.

   • pointer: coarse — це не «мобільна версія», це палець. Аватар росте,
     а якщо він ще й кнопка (opts.onTap), його область торкання не менша
     за 56 px у кожен бік.
   ============================================================ */

.nv-bulka {
  /* Бренд Каті (§8). Хост може перебити ці токени одним рядком. */
  --bk-pink: #E0589B;
  --bk-pink-soft: #F4A6B8;
  --bk-paper: #FCF8FA;
  --bk-ink: #2B2230;

  /* Розмір ведучої. clamp, бо вона мусить бути помітною і на 360 px,
     і не з'їдати весь монітор на 1600 px. */
  --bk-size: clamp(148px, 24vw, 260px);

  /* Ці чотири пише JS кожен кадр. Значення за замовчуванням критичні:
     без них вітрина без JS показала б поламану позу. */
  --bk-mouth: 0;
  --bk-ear: 0;
  --bk-tail: 0;
  --bk-exhale: 0;

  /* Базовий нахил вух — залежить від стану, задається нижче. */
  --bk-ear-base: 0deg;
  --bk-breath: 4.4s;

  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  line-height: 1.35;
  -webkit-tap-highlight-color: transparent;
}

/* Наше власне [hidden] мусить бити наш же display: інакше бульбашка
   лишиться на екрані після say(''), і це побачить гість, а не ми. */
.nv-bulka [hidden] { display: none !important; }

/* ---------------- сцена ---------------- */

.nv-bk-stage {
  position: relative;
  display: block;
  width: var(--bk-size);
  height: var(--bk-size);
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  touch-action: manipulation;
  cursor: default;
}
button.nv-bk-stage { cursor: pointer; }
button.nv-bk-stage:focus-visible {
  outline: 3px solid var(--bk-pink);
  outline-offset: 6px;
  border-radius: 50%;
}

.nv-bk-art {
  position: relative;
  width: 100%;
  height: 100%;
  transform-origin: 50% 92%;
  animation: nv-bk-breathe var(--bk-breath) ease-in-out infinite;
}

/* Ріжемо по viewBox навмисно. З overflow:visible груди вилазять за
   свою рамку і накривають сусідній блок вітрини — а ми не знаємо, що
   там у app.js. Обрізаний по груди бюст — це ще й потрібна композиція. */
.nv-bk-svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

/* Дихання. Найдешевша ознака життя: одна композитна властивість на
   один елемент, і Булка перестає бути картинкою. */
@keyframes nv-bk-breathe {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-1.2%) scale(1.014); }
}

/* ---------------- кільце-пульс мікрофона ---------------- */

.nv-bk-ring {
  position: absolute;
  left: 50%; top: 52%;
  width: 86%; height: 86%;
  margin: -43% 0 0 -43%;
  border: 2px solid var(--bk-pink);
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}
.nv-bk-is-listen .nv-bk-ring { animation: nv-bk-pulse 2.1s ease-out infinite; }
.nv-bk-is-listen .nv-bk-ring--2 { animation-delay: 1.05s; }

@keyframes nv-bk-pulse {
  0%   { transform: scale(.82); opacity: .55; }
  70%  { opacity: .12; }
  100% { transform: scale(1.24); opacity: 0; }
}

/* ---------------- вуха ----------------
   Дві причини руху — два вузли, і це принципово.

   Зовнішній вузол: базовий нахил від стану (сторч, коли слухає;
   притиснуті, коли спить). Стан міняється рідко, тому тут доречний
   плавний перехід.

   Внутрішній вузол: мікросмик від звуку. Переходу НЕМАЄ навмисно —
   --bk-ear прилітає щокадру, і transition кожного разу перезапускався б
   з нуля. Вухо тоді не реагує на голос, а розмазується із запізненням.
   Спокуса скласти обидва повороти в один transform коштує саме цього. */

.nv-bk-ear {
  transform-box: fill-box;
  transform-origin: 50% 88%;
  transition: transform .28s ease;
}
.nv-bk-ear--l { transform: rotate(var(--bk-ear-base)); }
.nv-bk-ear--r { transform: rotate(calc(0deg - var(--bk-ear-base))); }

.nv-bk-ear-in { transform-box: fill-box; transform-origin: 50% 88%; }
.nv-bk-ear--l .nv-bk-ear-in { transform: rotate(calc(var(--bk-ear) * -8deg)); }
.nv-bk-ear--r .nv-bk-ear-in { transform: rotate(calc(var(--bk-ear) * 8deg)); }

/* ---------------- хвіст ----------------
   Амплітуда — з --bk-tail, тому «радість» від wag() видно одразу, а не
   через перезапуск анімації (перезапуск дає ривок на першому кадрі). */

.nv-bk-tail {
  transform-box: fill-box;
  transform-origin: 22% 86%;
  animation: nv-bk-wag 1.15s ease-in-out infinite;
}
@keyframes nv-bk-wag {
  0%, 100% { transform: rotate(calc(var(--bk-tail) * -7deg)); }
  50%      { transform: rotate(calc(var(--bk-tail) * 9deg)); }
}

/* ---------------- рот ----------------
   Щелепа опускається від верхньої губи. 0.06 знизу — щоб закритий рот
   лишався тонкою темною лінією, а не зникав зовсім. */

.nv-bk-mouth {
  transform-box: fill-box;
  transform-origin: 50% 0%;
  transform: scaleY(calc(.06 + var(--bk-mouth) * .94));
}
.nv-bk-tongue {
  transform-box: fill-box;
  transform-origin: 50% 0%;
  transform: translateY(calc(var(--bk-mouth) * 2px));
}

/* Видих між фразами: тепла хмаринка біля носа. */
.nv-bk-puff {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  opacity: calc(var(--bk-exhale) * .55);
  transform: translate(calc(var(--bk-exhale) * 7px), calc(var(--bk-exhale) * -9px))
             scale(calc(.6 + var(--bk-exhale) * .7));
}

/* ---------------- око і повіка ---------------- */

.nv-bk-lid {
  transform-box: fill-box;
  transform-origin: 50% 0%;
  transform: translateY(-118%);
  transition: transform .09s linear;
}
.nv-bk-blinking .nv-bk-lid,
.nv-bk-is-sleep .nv-bk-lid { transform: translateY(0); }

/* ---------------- іскорка замість правого ока ----------------
   Вона не «затуляє ваду», вона підморгує. Тому іскорка живе тоді, коли
   живе розмова: слухає і говорить. */

.nv-bk-star {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  opacity: .9;
}
.nv-bk-glow { transform-box: fill-box; transform-origin: 50% 50%; }

.nv-bk-is-listen .nv-bk-star,
.nv-bk-is-talk .nv-bk-star { animation: nv-bk-spark 2.6s ease-in-out infinite; }
.nv-bk-is-listen .nv-bk-glow,
.nv-bk-is-talk .nv-bk-glow { animation: nv-bk-glow 2.6s ease-in-out infinite; }

@keyframes nv-bk-spark {
  0%, 62%, 100% { transform: scale(1) rotate(0deg); opacity: .9; }
  74%           { transform: scale(1.35) rotate(28deg); opacity: 1; }
  86%           { transform: scale(.92) rotate(6deg); opacity: .75; }
}
@keyframes nv-bk-glow {
  0%, 62%, 100% { transform: scale(1); opacity: .22; }
  74%           { transform: scale(1.2); opacity: .42; }
}

/* ---------------- лапка ----------------
   Виїжджає тільки в «думаю»: жест «дай подумаю» читається миттєво і
   без слів, а саме він прикриває секунду очікування моделі. */

.nv-bk-paw {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transform: translateY(26px) rotate(4deg);
  opacity: 0;
  transition: transform .34s cubic-bezier(.34, 1.3, .5, 1), opacity .22s ease;
}
.nv-bk-is-think .nv-bk-paw { transform: translateY(-36px) rotate(-11deg); opacity: 1; }

/* ---------------- голова за станами ---------------- */

.nv-bk-head {
  transform-box: fill-box;
  transform-origin: 50% 96%;
  transition: transform .38s cubic-bezier(.34, 1.2, .5, 1);
}
.nv-bk-is-think .nv-bk-head { transform: rotate(-7deg); }   /* нахил «слухаю уважно» */
.nv-bk-is-listen .nv-bk-head { transform: rotate(2.5deg); }
.nv-bk-is-sleep .nv-bk-head { transform: rotate(4deg) translateY(3%); }

/* ---------------- сон ---------------- */

.nv-bk-zzz { opacity: 0; transition: opacity .3s ease; }
.nv-bk-is-sleep .nv-bk-zzz { opacity: 1; }
.nv-bk-z {
  fill: var(--bk-pink);
  font: 700 15px/1 Raleway, system-ui, sans-serif;
  transform-box: fill-box;
  transform-origin: 50% 100%;
  opacity: 0;
}
.nv-bk-is-sleep .nv-bk-z { animation: nv-bk-float 3.6s ease-in-out infinite; }
.nv-bk-is-sleep .nv-bk-z--2 { animation-delay: 1.2s; }
.nv-bk-is-sleep .nv-bk-z--3 { animation-delay: 2.4s; }

@keyframes nv-bk-float {
  0%   { transform: translateY(6px) scale(.7); opacity: 0; }
  25%  { opacity: .9; }
  100% { transform: translateY(-16px) scale(1.15); opacity: 0; }
}

/* ---------------- базовий нахил вух і темп дихання за станами ---------------- */

.nv-bk-is-sleep  { --bk-ear-base: 16deg; --bk-breath: 4.6s; }
.nv-bk-is-listen { --bk-ear-base: -9deg; --bk-breath: 3.1s; }  /* вуха сторч */
.nv-bk-is-think  { --bk-ear-base: -3deg; --bk-breath: 3.4s; }
.nv-bk-is-talk   { --bk-ear-base: -5deg; --bk-breath: 2.5s; }

/* Щічки трохи яскравіші, коли вона говорить — дрібниця, але саме такі
   дрібниці й відрізняють живе від намальованого. */
.nv-bk-cheek { transition: opacity .4s ease; }
.nv-bk-is-talk .nv-bk-cheek { opacity: .68; }
.nv-bk-is-sleep .nv-bk-cheek { opacity: .34; }

/* ---------------- бульбашка з реплікою ---------------- */

.nv-bk-say {
  max-width: min(30ch, 92vw);
  padding: 10px 16px;
  border-radius: 18px 18px 18px 6px;
  background: var(--bk-paper);
  color: var(--bk-ink);
  font: 500 15px/1.4 Raleway, system-ui, sans-serif;
  text-align: center;
  box-shadow: 0 6px 18px rgba(43, 34, 48, .18);
  opacity: 0;
  transform: translateY(6px) scale(.96);
  transition: opacity .22s ease, transform .22s cubic-bezier(.34, 1.3, .5, 1);
}
.nv-bk-said .nv-bk-say { opacity: 1; transform: translateY(0) scale(1); }

/* ---------------- палець ---------------- */

@media (pointer: coarse) {
  .nv-bulka { --bk-size: clamp(172px, 46vw, 280px); }
  /* Кнопкою аватар стає лише з opts.onTap; але якщо став — область
     торкання не має залежати від того, наскільки вдало намальована
     собака. 56 px — це нижня межа, нижче якої палець промахується. */
  button.nv-bk-stage { min-width: 56px; min-height: 56px; }
  .nv-bk-say { font-size: 16px; padding: 12px 18px; }
}

/* ---------------- менше руху ----------------
   Дублюємо клас .nv-bk-calm поруч із медіа-запитом навмисно: JS уже знає
   про режим (він у ньому не заводить rAF), і стилі мусять збігатися з
   поведінкою JS, а не жити своїм життям. */

@media (prefers-reduced-motion: reduce) {
  .nv-bulka *,
  .nv-bulka *::before,
  .nv-bulka *::after {
    animation: none !important;
    transition: none !important;
  }
  .nv-bk-art { transform: none !important; }
  .nv-bk-mouth { transform: scaleY(.06) !important; }
  .nv-bk-ring { opacity: 0 !important; }
  .nv-bk-puff { opacity: 0 !important; }
  .nv-bk-say { opacity: 1; transform: none; }
}

.nv-bk-calm *,
.nv-bk-calm *::before,
.nv-bk-calm *::after {
  animation: none !important;
  transition: none !important;
}
.nv-bk-calm .nv-bk-art { transform: none !important; }
.nv-bk-calm .nv-bk-mouth { transform: scaleY(.06) !important; }
.nv-bk-calm .nv-bk-ring { opacity: 0 !important; }
.nv-bk-calm .nv-bk-puff { opacity: 0 !important; }
.nv-bk-calm .nv-bk-say { opacity: 1; transform: none; }
