/* =========================================================================
   FUNNY DICKS — общие стили
   Файл: css/style.css
   Содержит: переменные цветов, шрифты, сброс, базовую типографику.
   Используется на всех страницах.
   ========================================================================= */

/* ---- CSS-переменные: фирменные цвета из references/STYLE.md ---- */
:root {
  /* Фирменная четвёрка */
  --sky-blue:    #7EC8F0;  /* небесно-голубой (фоны, небо) */
  --blue:        #1E5FB4;  /* насыщенный синий (контраст) */
  --yellow:      #FFC400;  /* насыщенный жёлтый (акценты, солнце) */
  --red:         #D92626;  /* красный (важные элементы) */

  /* Дополнительные */
  --pink:        #FF6FAE;  /* тёплый розовый (баннеры) */
  --cream:       #F4E8D0;  /* кремовый (нейтральный) */
  --maroon:      #7A1E2B;  /* бордовый (театральные фоны) */
  --skin:        #F1C27D;  /* телесный (стволы и яйца шаров-пенисов) */
  --brown:       #894613;  /* средний шоколадный (баннер WELCOME TO) */

  /* Тёмные для текста */
  --ink:         #1a1a2e;  /* почти чёрный, но с синевой */

  /* Шрифты */
  --font-display: 'Lilita One', 'Arial Black', sans-serif;
  --font-body:    'Nunito', 'Segoe UI', sans-serif;
}

/* ---- Сброс (обнуляем стандартные отступы браузера) ---- */
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* scrollbar-gutter: stable УБРАН (Раунд 22). Раньше стоял «stable всегда» для
     фикса Раунда 7 (кнопка mute прыгала при появлении/исчезновении скроллбара).
     НО: на ПК с классическими скроллбарами gutter РИСУЕТ пустую полосу sky-blue
     даже на главной, где скролла нет → бесил Мишу + перекрывал кнопку mute.
     Кнопка теперь привязана к 100vw (не к right) → её позиция от наличия
     скроллбара не зависит → прыжков при переходе нет и без gutter. Карточки в
     галерее проверены — выезд работает (flyCardsIn измеряет позиции в onComplete
     перехода, когда скроллбар уже стабилен). */
}

/* ---- Базовый body ---- */
body {
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--sky-blue);
  overflow-x: hidden;       /* чтобы анимации не давали горизонтальный скролл */
  min-height: 100vh;
  min-height: 100dvh;       /* дышит с адресной строкой (фолбэк выше на 100vh) */
  line-height: 1.5;
}

/* 🐛 ПОЛОСА ПРОКРУТКИ НА ГЛАВНОЙ (ПК) — Раунд 22. Истинный корень:
   body имеет overflow-x:hidden → по спеке CSS Overflow Module вторая ось
   (overflow-y) АВТОМАТОМ становится auto → body превращается в СКРОЛЛ-
   КОНТЕЙНЕР (та же семья грабель, что Раунд 13 для .gallery-page и Раунд 20f
   для body.season-open). На скролл-контейнер распространяется глобальный
   ::-webkit-scrollbar-track { background: var(--sky-blue) } → РИСУЕТСЯ
   ПОЛОСА sky-blue даже когда скролла нет. Это она бесит Мишу на главной.
   Замер (CDP на ПК): html.clientHeight=1036 (gutter НЕ занимает место на
   overlay-скроллбарах Windows 11), а полоса всё равно видна — значит рисует
   её body-скроллбар (подтверждено инъекцией html=red/body=lime → полоса lime).
   ФИКС: спрятать body-скроллбар ПОЛНОСТЬЮ (width:0 + scrollbar-width:none).
   Реальный скроллит <html> (documentElement) — body как скролл-контейнер
   нам вообще не нужен. Глобальный ::-webkit-scrollbar остаётся на html, как
   было. Только на главной — без разницы (там html.scrollHeight==clientHeight,
   скролла нет), но на всякий случай не задеваем работу html-скроллбара в
   галерее: html имеет overflow:visible → html не скролл-контейнер → его
   ::-webkit-scrollbar к нему не применяется. Поэтому прячем body, оставляя
   «глобальный» скроллбар-стиль для любого возможного реального контейнера. */
body::-webkit-scrollbar {
  width: 0;
  height: 0;
}
body {
  scrollbar-width: none;   /* Firefox */
}
/* 🐛 РАУНД 20f: пока открыт сезон — БЛОКИРУЕМ скролл body. Без этого галерея
   (position:relative в потоке, когда активна) порождает скролл body → палец в
   сезоне скроллит НЕ season-view (короткий, не скроллится), а галерею под ним.
   Симптом Миши: «сезон прокручен вниз → галерея тоже внизу». Класс вешает JS
   в openSeason onComplete, снимает в goBack onComplete. overflow:hidden на
   обе оси (вместо hidden-x + auto-y по дефолту) = body не скроллится совсем. */
body.season-open {
  overflow: hidden;
}

/* 🐛 РАУНД «НЫРОК»: глобальный сброс браузерного outline (:focus-visible).
   После клика на .season-cta фокус переходит по дереву — браузер рисует
   серый контур 2.4px (currentColor = var(--ink), тёмно-синий) на body/main/
   seasons-grid/gallery-content. Они под курсором в правой-нижней части
   карточки → контур виден как «полоска буквой Г». Проявлялся при наведении на
   кнопку после клика. Гасим глобально — наш UI не использует outline-фокус
   (свой hover/active-стиль у каждой кнопки). */
*:focus, *:focus-visible {
  outline: none !important;
}

/* ╔══════════════════════════════════════════════════════════════════════╗
   ║  ЕДИНОЕ SPA: экраны-слои.                                          ║
   ║  Оба <main> (#home-screen, #gallery-screen) живут в DOM всегда.    ║
   ║  Активный — .is-active. Неактивный спрятан через visibility:hidden ║
   ║  (НЕ display:none — ломает getBoundingClientRect у летающих клонов).║
   ║  transition на opacity/visibility сглаживает переключение.         ║
   ╚══════════════════════════════════════════════════════════════════════╝ */
main[data-screen] {
  transition: opacity 0.4s ease, visibility 0s linear 0.4s;
  /* Анимация перехода между экранами (Этап 4 настраивает точнее через JS). */
}

/* 🎬 РЕЖИМ SLIDE-ПЕРЕХОДА (бесшовный переход главная↔галерея).
   Когда body несёт .is-transitioning, CSS НЕ прячёт уходящий экран — он
   остаётся видимым (opacity:1, visibility:visible), чтобы GSAP мог его
   увезти вбок (xPercent). Без этого правило :not(.is-active) сразу гасило
   бы opacity:0 → slide был бы невиден. main.js снимает .is-transitioning
   в конце анимации. */
body.is-transitioning main[data-screen] {
  opacity: 1 !important;
  visibility: visible !important;
  position: absolute;       /* поверх потока, чтобы оба экрана могли ехать */
  top: 0;
  left: 0;
  width: 100%;
  z-index: 4;               /* выше птиц(z:3) → карточки перекрывают птиц */
}
body.is-transitioning main[data-screen]:not(.is-active) {
  pointer-events: none;     /* уходящий не ловит клики во время отъезда */
}

main[data-screen].is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.4s ease, visibility 0s;
  /* Экран ПОВЕРХ общего слоя .sky (z:0) и птиц #birds (z:3). Без z-index
     fixed-слои .sky/#birds всплывали над position:relative экраном → облака
     ложились поверх .bg-photos (пульсация) и птицы — поверх карточек галереи.
     z:4 поднимает экран над небом И птицами → карточки перекрывают птиц. */
  z-index: 4;
}

main[data-screen]:not(.is-active) {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* Абсолютное позиционирование, чтобы неактивный экран не занимал место в
     потоке и не создавал пустой высоты/скролла поверх активного.
     ⚠️ content-visibility:hidden был попробован (−41% recalcStyle в покое),
     но УБРАН: давал рывок фото при возврате на главную — браузер «забывал»
     layout скрытого экрана, и при материализации фото перескакивали в свою
     kenburns-позицию. Лаг перехода оказался на GPU (см. STATUS round3-screens),
     а не на стилях → выигрыш content-visibility не стоил рывка. */
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 0;
}

/* ╔══════════════════════════════════════════════════════════════════════╗
   ║  🎬 A/B-ЭКСПЕРИМЕНТ: slide экранов на CSS-transition (с композитора). ║
   ║  Раньше slide делал GSAP: gsap.to(screen,{xPercent,opacity,duration})║
   ║  GSAP пишет стиль через JS на каждый кадр → main thread пересчитывает║
   ║  UpdateLayoutTree (Recalculate Styles) ~636 раз за переход (см. брифы║
   ║  funnydicks-lag-round3*). Нативный CSS-transition выполняется на     ║
   ║  compositor-потоке — НЕ трогает main thread → UpdateLayoutTree падает║
   ║  (по гипотезе модели funnydicks-lag-round3-response.md).             ║
   ║                                                                      ║
   ║  Класс .is-sliding вешается на экран в момент старта slide (tl.add в ║
   ║  flyToGallery/flyToHome, ~0.25с таймлайна). Снимается в onComplete. ║
   ║  transform/opacity задаются инлайном в JS (translateX(±115%) и т.п.)║
   ║  → transition их плавно интерполирует на композиторе.                ║
   ║                                                                      ║
   ║  ДВЕ кривые (как было в GSAP):                                       ║
   ║   - УХОДЯЩИЙ экран: power3.inOut → cubic-bezier(0.645,0,0.355,1)     ║
   ║     (набирает скорость и к концу, симметрично)                       ║
   ║   - ВЪЕЗЖАЮЩИЙ экран: power3.out → cubic-bezier(0.215,0.61,0.355,1)  ║
   ║     (быстрый старт, мягкое торможение)                               ║
   ╚══════════════════════════════════════════════════════════════════════╝ */
main[data-screen].is-sliding--out {
  transition:
    transform 0.9s cubic-bezier(0.645, 0, 0.355, 1),
    opacity 0.9s cubic-bezier(0.645, 0, 0.355, 1);
}
main[data-screen].is-sliding--in {
  transition:
    transform 0.9s cubic-bezier(0.215, 0.61, 0.355, 1),
    opacity 0.9s cubic-bezier(0.215, 0.61, 0.355, 1);
}

/* contain: ИЗЪЯТО из A/B-теста. Ломало позиционирование fixed-клонов
   (position:fixed внутри contain-контейнера начинает считаться от него, а не от
   viewport → клон улетал вниз). Модель советовала как «безрисковую подстраховку»,
   но на практике оказалось НЕ безрисковым. Если вернёмся к contain — нужен
   contain: style только (без layout), либо вынос клонов за пределы экранов.
   Сейчас не используется; CSS-transition (.is-sliding--*) остаётся. */

/* ---- Типографика: заголовки всегда display-шрифтом ---- */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400;         /* Lilita One — уже жирная, вес не нужен */
  line-height: 1.05;
  letter-spacing: 1px;
  text-transform: uppercase;
}

h1 { font-size: clamp(3rem, 10vw, 8rem); }
h2 { font-size: clamp(2rem, 5vw, 4rem); }
h3 { font-size: clamp(1.5rem, 3vw, 2.5rem); }

p {
  font-size: clamp(1rem, 1.5vw, 1.2rem);
}

a {
  color: inherit;
  text-decoration: none;
}

/* Убираем системную подсветку тапа на мобиле (серый/голубой прямоугольник
   поверх SVG-кнопок). Без этого на кастомных кнопках-пенисах и болтах пасхалки
   может мелькать штатная подсветка, конфликтующая с нашим .flash-red-фидбеком
   при тапе. Распространяем на все элементы — болты пасхалки это <span>,
   а не <a>/<button>, поэтому узкого правила недостаточно. */
* {
  -webkit-tap-highlight-color: transparent;
}

img {
  max-width: 100%;
  display: block;
}

/* ---- Полоса прокрутки (мультяшная) ---- */
::-webkit-scrollbar {
  width: 14px;
}
::-webkit-scrollbar-track {
  background: var(--sky-blue);
}
::-webkit-scrollbar-thumb {
  background: var(--red);
  border-radius: 10px;
  border: 3px solid var(--sky-blue);
}
::-webkit-scrollbar-thumb:hover {
  background: var(--yellow);
}

/* ---- Утилита: центрирование ---- */
.center {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ---- Анимация конфетти (общая, используется на обеих страницах) ---- */
@keyframes float-slow {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-20px) rotate(8deg); }
}

@keyframes wiggle {
  0%, 100% { transform: rotate(-3deg); }
  50%      { transform: rotate(3deg); }
}

/* ---- Звёзды-конфетти ПОЛНОСТЬЮ УБРАНЫ (HTML + CSS) ----
   52 элемента .star с animation:wiggle infinite ели ресурс style recalc,
   хотя были скрыты через display:none на .confetti. Chrome всё равно держит
   animationName в computed style для детей display:none-родителя → каждый
   кадр инвалидировал стиль. Удалено в рамках охоты на лаг перехода (Раунд 3).
   @keyframes wiggle ОСТАВЛЕН — нужен для .letter-img (пенис-I) и .bolt-pig. */

/* ---- Край шатра цирка-шапито (общий для обеих страниц) ----
   Полосатая красно-жёлтая полоса с волнистыми «зубцами» (scallops) снизу.
   SVG растягивается на всю ширину, высота фиксированная. */
.tent-edge {
  position: fixed;        /* FIXED — висит вверху окна, не двигается при скролле */
  top: 0;
  left: 0;
  width: 100%;
  height: 150px;          /* viewBox 1200×160: ~56px полосы + ~94px висюльки */
  z-index: 100;           /* выше контента, чтобы всегда был виден сверху */
  pointer-events: none;
}

.tent-edge svg {
  width: 100%;
  height: 100%;
  display: block;
  /* БЕЗ drop-shadow! Раньше тут была тень — при качании висюлек она
     просвечивала в зазорах между ними и выглядела чёрной полосой/стыком.
     Шатёр теперь «отделяется» от фона просто за счёт контраста цветов. */
  overflow: hidden;
}

/* 🚩 ВИСЮЛЬКИ-ВЫМПЕЛЫ (тряпочки шатра). Болтаются на ветру как бахрома.
   Полосы (rect) стоят неподвижно — это «карниз» шатра. Качаются только
   висюльки (path), каждая вокруг своей верхней точки (точки крепления).
   transform-box: fill-box → transform-origin считается по bounding box
   самой висюльки. 50% 0% = верхний центр висюльки. */
.tent-edge svg path {
  transform-box: fill-box;
  transform-origin: 50% 0%;
  animation: tent-sway 3.5s ease-in-out infinite;
}

/* 🔀 АСИНХРОН: каждая висюлька качается со СВОЕЙ скоростью и задержкой,
   поэтому они НИКОГДА не выстраиваются в видимую синхронную волну и выглядят
   как случайный ветер, а не как строй. Длительности — взаимно несоразмерные
   числа секунд (2.3 / 2.7 / 3.1 / 3.5 / 3.9 / 4.1 / 4.5 / 4.9 / 5.3 / 5.7),
   цикл повторяется редко → эффект хаоса, как от настоящего ветра. */
.tent-edge svg path:nth-of-type(1)  { animation-duration: 3.5s; animation-delay: 0.0s;  }
.tent-edge svg path:nth-of-type(2)  { animation-duration: 4.1s; animation-delay: 0.4s;  }
.tent-edge svg path:nth-of-type(3)  { animation-duration: 2.7s; animation-delay: 0.9s;  }
.tent-edge svg path:nth-of-type(4)  { animation-duration: 4.9s; animation-delay: 0.2s;  }
.tent-edge svg path:nth-of-type(5)  { animation-duration: 3.1s; animation-delay: 1.1s;  }
.tent-edge svg path:nth-of-type(6)  { animation-duration: 5.3s; animation-delay: 0.6s;  }
.tent-edge svg path:nth-of-type(7)  { animation-duration: 3.9s; animation-delay: 0.3s;  }
.tent-edge svg path:nth-of-type(8)  { animation-duration: 2.3s; animation-delay: 1.4s;  }
.tent-edge svg path:nth-of-type(9)  { animation-duration: 4.5s; animation-delay: 0.8s;  }
.tent-edge svg path:nth-of-type(10) { animation-duration: 5.7s; animation-delay: 0.1s;  }

/* Колыхание висюльки — ПЛАВНЫЙ маятник туда-обратно (как тряпка на ветру).
   Две опорные точки + ease-in-out → мягкое, без рывков. Асинхронность даёт
   не «рваность» кадра, а то, что у каждой висюльки своя длительность
   (см. :nth-of-type выше) → соседки качаются вразнобой, как от хаотичного ветра.
   Амплитуда зависит от ВЕТРА (CSS-переменная --wind): вверху страницы = 1
   (лёгкое покачивание, ±8°), при прокрутке JS поднимает до ~3.5 (размашисто).
   @property нужен, чтобы браузер ПЕРЕСЧИТЫВАЛ keyframes при изменении --wind. */
@property --wind {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

@keyframes tent-sway {
  0%, 100% { transform: rotate(calc(-5deg * var(--wind))); }
  50%      { transform: rotate(calc( 5deg * var(--wind))); }
}

@media (max-width: 600px) {
  .tent-edge { height: 110px; }
}

/* ---- Воздушные шары (общие для обеих страниц) ----
   Контейнер: <div class="balloons"> со шарами <div class="balloon b1"> внутри.
   Цвет/позицию/скорость задаём модификаторами в hero.css и gallery.css.
   Форма: мультяшный силуэт пениса (головка + тело + мошонка + верёвочка). */
.balloons {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;          /* поверх облаков (z-index:1), под звёздами (z-index:2) */
}

/* ---- Облака (общие для обеих страниц) ----
   Контейнер: <div class="clouds"> с облаками <div class="cloud c1"> внутри.
   Мультяшное белое облако: тело-эллипс + два кружка (::before/::after) сверху.
   Размеры заданы в em, поэтому меняя font-size на модификаторе (.c1, .c2...)
   масштабируем всё облако целиком — без конфликта с transform анимации.
   Облака плывут по небу ОЧЕНЬ медленно (контраст быстрым шарам). */
.clouds {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;          /* позади шаров и звёзд, над фоном неба */
  overflow: hidden;
}

/* 🐦 ПТИЦЫ В НЕБЕ — fixed-слой SVG (как облака, но всегда в видимой области
   окна, даже при скролле длинной галереи). z-index:3 = ПОВЕРХ облаков(1)/
   шаров(2) — птицы летают ВЫШЕ облаков в небе (физично!), поэтому видны
   сквозь/над ними. pointer-events:none — не ловит клики. Под контентом(10). */
#birds {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;        /* фолбэк */
  height: 100dvh;       /* дышит с адресной строкой мобилы (см. .sun-glow выше) */
  pointer-events: none;
  z-index: 3;
}

.cloud {
  position: absolute;
  width: 6em;
  height: 2em;
  font-size: 26px;          /* базовый масштаб (переопределяется модификатором) */
  background: #fff;
  border-radius: 2em;
  opacity: 0.9;
  /* мягкая голубоватая тень снизу — облако «парит» над небом */
  filter: drop-shadow(0 6px 6px rgba(30, 95, 180, 0.10));
  animation: cloud-drift 70s linear infinite;
}

/* Левый бугорок облака */
.cloud::before {
  content: "";
  position: absolute;
  width: 2.4em;
  height: 2.4em;
  top: -1.2em;
  left: 0.8em;
  background: #fff;
  border-radius: 50%;
}

/* Правый бугорок облака (побольше) */
.cloud::after {
  content: "";
  position: absolute;
  width: 3.2em;
  height: 3.2em;
  top: -1.8em;
  right: 0.6em;
  background: #fff;
  border-radius: 50%;
}

/* Дрейф облака: медленно плывёт слева направо через весь экран.
   По пути слегка поднимается и опускается (±8px) — лёгкое покачивание.
   Один transform — поэтому конфликтов с другими анимациями нет.
   ⚠️ СТАРТ −70vw: облако (до ~48vw шириной на мобиле) при старте ПОЛНОСТЬЮ
   скрыто за левым краем (правый край −70+48 = −22vw, за экраном). Раньше
   было −20vw → при перезапуске цикла правый край облака торчал в кадре →
   облако «возникало из ниоткуда», а не выплывало. Это та же логика, что у
   balloon-rise: старт за краем гарантирует чистое «выплывание» каждый раз. */
@keyframes cloud-drift {
  0%   { transform: translateX(-70vw) translateY(0); }
  25%  { transform: translateX(-5vw)  translateY(-8px); }
  50%  { transform: translateX(50vw)  translateY(0); }
  75%  { transform: translateX(85vw)  translateY(6px); }
  100% { transform: translateX(120vw) translateY(0); }
}

/* Сам «шар»: мультяшный силуэт пениса.
   Форма задаётся через SVG-маску (встроена в CSS как data: URI) — настоящие
   эллипсы и круги, поэтому края идеально ОКРУГЛЫЕ (не угловатые, как у polygon):
   красная головка-«лопата» (вытянута вверх, ry > rx) → широкий ствол →
   два больших круглых яйца → хвостик-верёвочка.
   top:0 — вертикаль целиком управляется анимацией balloon-rise. */
.balloon {
  position: absolute;
  top: 0;
  width: 64px;
  height: 140px;
  /* Поток: шар поднимается снизу экрана и уходит за верхнюю границу.
     Покачивание влево/вправо задано прямо в keyframes balloon-rise. */
  animation: balloon-rise 12s linear infinite;
  /* SVG-маска силуэта пениса, встроенная прямо в CSS как data: URI.
     ВАЖНО: внешняя маска url(file.svg) НЕ работает при открытии страницы
     через file:// (двойной клик) — Chrome блокирует её по CORS, и шар
     становится невидимым. Inline data: URI решает проблему.
     Маска рисуется настоящими эллипсами/кругами → края округлые.
     preserveAspectRatio="none" + mask-size:100% 100% → форма тянется
     под любой width/height шара.
     Координаты (viewBox 0 0 100 240):
       - головка: эллипс rx=40 ry=50 → ВЫТЯНУТА ВВЕРХ как лопата (ry>rx),
         центрирована вверху;
       - ствол: rect x30..70 (ширина 40 — широкий), длинная колонна;
       - яйца: два круга r=30 (большие, размером с головку).
       (Верёвочка-хвостик убрана по просьбе Миши.) */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 240' preserveAspectRatio='none'%3E%3Cg fill='%23fff'%3E%3Cellipse cx='50' cy='50' rx='40' ry='50'/%3E%3Crect x='30' y='84' width='40' height='78'/%3E%3Ccircle cx='34' cy='184' r='30'/%3E%3Ccircle cx='66' cy='184' r='30'/%3E%3C/g%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 240' preserveAspectRatio='none'%3E%3Cg fill='%23fff'%3E%3Cellipse cx='50' cy='50' rx='40' ry='50'/%3E%3Crect x='30' y='84' width='40' height='78'/%3E%3Ccircle cx='34' cy='184' r='30'/%3E%3Ccircle cx='66' cy='184' r='30'/%3E%3C/g%3E%3C/svg%3E");
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  /* Слои фона (снизу вверх по z-порядку слоёв):
     1) СТВОЛ + ЯЙЦА — ТЕЛЕСНЫЙ цвет, одинаковый у всех шаров.
     2) КРАСНАЯ ГОЛОВКА — радиальный эллипс, повторяющий форму головки
        из маски (чтобы красный не «наезжал» на ствол).
     3) ВОЛОСЫ на яйцах — россыпь тёмных точек.
     4) БЛИКИ на головке — как у настоящего воздушного шара.
     ВАЖНО: первый слой в списке — самый ВЕРХНИЙ (рисуется поверх остальных).
     Поэтому блики и волосы стоят ПЕРВЫМИ, иначе красный их перекроет.
     (Корональная полоса на стыке головка↔ствол убрана по просьбе Миши.) */
  background-color: var(--skin);     /* телесный: ствол и яйца (одинаково у всех) */
  background-image:
    /* 4а) основной мягкий блик на головке (большой, как у шара) */
    radial-gradient(ellipse 26% 30% at 33% 15%, rgba(255,255,255,0.9), transparent 60%),
    /* 4б) маленький яркий блик-точка рядом (добавляет «глянца») */
    radial-gradient(circle 5px at 42% 11%, rgba(255,255,255,1), transparent 70%),
    /* 3) ВОЛОСЫ на яйцах: россыпь тёмных точек-завитков.
       Каждая точка — отдельный radial-gradient, расставленный вручную
       только по области яиц (нижняя часть шара, y ~68–86%).
       Левое яйцо (центр ~32% x), правое яйцо (центр ~68% x).
       Радиус 2px → тоненькие, как волоски. */
    radial-gradient(circle 2px at 22% 70%, rgba(50,25,8,0.8), transparent 60%),
    radial-gradient(circle 2px at 32% 68%, rgba(50,25,8,0.8), transparent 60%),
    radial-gradient(circle 2px at 42% 72%, rgba(50,25,8,0.8), transparent 60%),
    radial-gradient(circle 2px at 26% 80%, rgba(50,25,8,0.8), transparent 60%),
    radial-gradient(circle 2px at 38% 84%, rgba(50,25,8,0.8), transparent 60%),
    radial-gradient(circle 2px at 58% 72%, rgba(50,25,8,0.8), transparent 60%),
    radial-gradient(circle 2px at 68% 68%, rgba(50,25,8,0.8), transparent 60%),
    radial-gradient(circle 2px at 78% 70%, rgba(50,25,8,0.8), transparent 60%),
    radial-gradient(circle 2px at 62% 80%, rgba(50,25,8,0.8), transparent 60%),
    radial-gradient(circle 2px at 74% 84%, rgba(50,25,8,0.8), transparent 60%),
    /* 2) красная головка: РАДИАЛЬНЫЙ градиент-эллипс, повторяющий форму
       головки из маски (эллипс rx40%/ry20.8% по центру 50%/20.8%).
       Координаты взяты прямо из viewBox маски (100×240): cx=50, cy=50,
       rx=40, ry=50 → 50%, 20.8%, 40%, 20.8% от размера элемента.
       Так красный лежит СТРОГО по форме головки и НЕ наезжает на ствол
       (в отличие от простой горизонтальной заливки). */
    radial-gradient(ellipse 40% 20.8% at 50% 20.8%, var(--red) 96%, transparent 100%);
  /* no-repeat для всех слоёв: точки-волосы стоят точно там, где заданы. */
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

/* Анимация полёта шара: снизу вверх через весь экран.
   Старт за нижним краем (110vh), уход за верхний (-30vh).
   По пути шар покачивается влево/вправо и наклоняется — как на ветру. */
@keyframes balloon-rise {
  0%   { transform: translateY(110vh) translateX(0)    rotate(-3deg); }
  25%  { transform: translateY(80vh)  translateX(20px) rotate(2deg);  }
  50%  { transform: translateY(50vh)  translateX(-15px) rotate(-2deg); }
  75%  { transform: translateY(20vh)  translateX(15px)  rotate(3deg);  }
  100% { transform: translateY(-30vh) translateX(0)     rotate(-3deg); }
}

/* Версия анимации для МОБИЛЫ (используется в @media ниже).
   ПРОБЛЕМА: на мобиле весь контейнер .balloons масштабируется scale(0.7) с
   transform-origin: center top. Стартовый кадр translateY(110vh) после
   умножения на 0.7 даёт ~77vh — это ВНУТРИ видимой области, поэтому шар
   «появлялся» на экране, а не вылетал снизу.
   РЕШЕНИЕ: умножаем все значения translateY на 1/0.7 ≈ 1.43, чтобы после
   сжатия контейнера они снова оказались за краями экрана. X-покачивание и
   наклон оставляем как есть (они не зависят от масштаба по вертикали). */
@keyframes balloon-rise-mobile {
  0%   { transform: translateY(158vh) translateX(0)    rotate(-3deg); }
  25%  { transform: translateY(114vh) translateX(20px) rotate(2deg);  }
  50%  { transform: translateY(72vh)  translateX(-15px) rotate(-2deg); }
  75%  { transform: translateY(29vh)  translateX(15px)  rotate(3deg);  }
  100% { transform: translateY(-43vh) translateX(0)     rotate(-3deg); }
}

/* (Мошонка и верёвочка встроены прямо в SVG-маску выше —
   отдельные ::before / ::after больше не нужны.) */

/* (.star правила удалены — звёзды убраны полностью, см. комментарий у .confetti) */

/* ---- Уважение к настройкам анимаций (для доступности) ---- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   ⬇⬇⬇  Раньше это были отдельные файлы: hero.css, gallery.css, bg-photos.css.
   Сейчас всё в одном style.css, чтобы SPA-роутер (который при переходе между
   страницами НЕ меняет <head>) гарантированно имел под рукой стили ОБЕИХ
   страниц. Раньше gallery.css подключался только в <head> gallery.html →
   при переходе через SPA он не грузился → галерея «ломалась» (без рамок,
   без эффектов, без полёта кнопки). Один общий CSS полностью это лечит.
   ========================================================================== */

/* ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████
   ████  БЫЛО: css/bg-photos.css — фоновое слайдшоу (только на главной)  █████
   ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████ */

/* Идея: ОДНО фото за раз полностью заливает экран. Оно медленно
   проявляется (fade-in), держится, потом затухает (fade-out) — и въезжает
   следующее. И так по кругу: фото 1 → 4 → 6 → 10 → 1 → ...

   Слои (снизу вверх): небо → .bg-photos (z-index:0) → облака(1) →
   шары(2) → звёзды(3) → контент(10). Фото — под всем остальным.

   НАСТРОЙКИ (по просьбе Миши):
     • По 5 секунд на каждое фото → цикл = 20 секунд.
     • Сильное киноподъезжание камеры (Ken Burns): scale 1.10 → 1.55.
     • ПЛАВНЫЙ перекрёстный переход (crossfade).

   КАК РАБОТАЕТ БЕЗ JS:
     • 4 фото лежат друг на друге (position:absolute, на весь экран).
     • У каждого своя анимация opacity (bg-crossfade) со сдвигом по фазе.
     • Дополнительно фото медленно "наезжает" камерой (bg-kenburns).

   Порядок: p1 (0-5с) → p4 (5-10с) → p6 (10-15с) → p10 (15-20с) → снова p1. */

/* ---- Контейнер: на весь экран, под облаками ----
   ⚠️ Перенесён из .hero в общий слой .sky на <body>, чтобы фото лежали ПОД
   облаками (как раньше) и не пульсировали в такт crossfade. Проявляются ТОЛЬКО
   на главной через класс .sky--home на body (вешает/снимает роутер при смене
   экрана). На галерее скрыты → фото главной не просвечивают через карточки. */
.bg-photos {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  opacity: 0;            /* по умолчанию скрыты (на галерее) */
  transition: opacity 0.4s ease;
}
body.sky--home .bg-photos {
  opacity: 1;            /* на главной — проявлены */
}

/* 🚀 ЛАГ-ФИКС Раунд 5 (ЗАМОРОЗКА АНИМАЦИИ НА ПЕРЕХОДЕ — ПОБЕДА НАД ЛАГОМ):
   Трасса показала: лаг перехода = 81% инвалидаций от АНИМАЦИИ фото (kenburns
   scale + crossfade opacity молотят каждый кадр) → композитор дробит экран
   на ~50 плиток 640×640 и пересоздаёт их (418 OnCreateSharedImage, 696мс
   суммарно при возврате). Заморозка ИМЕННО НА ВРЕМЯ ПЕРЕХОДА
   (body.is-transitioning) → фото замирают на 2с перехода → экран стабилен →
   перерисовок нет → лаг уходит. Миша подтвердил: «плавность ИДЕАЛЬНАЯ».
   В onComplete is-transitioning снимается → анимация возобновляется. */
body.is-transitioning .bg-photo,
body.is-transitioning .bg-photo img {
  animation-play-state: paused;
}

/* ╔══════════════════════════════════════════════════════════════════════╗
   ║  🧊 ЗАМОРОЗКА CSS-АНИМАЦИЙ НА НЕВИДИМЫХ ЭКРАНАХ (исследование         ║
   ║     перфоманса галерея↔сезон, 2026-07-24).                            ║
   ║                                                                       ║
   ║  Корень: неактивный <main> скрыт через visibility:hidden (НЕ          ║
   ║  display:none — ломает getBoundingClientRect у клонов). Но по спеке   ║
   ║  CSS visibility:hidden НЕ останавливает CSS-анимации (в отличие от    ║
   ║  display:none). Значит на скрытой главной вечно тикают: footer-wave   ║
   ║  (29 букв), balloon-rise (3 шара), cta-beckon-right, wiggle — впустую,║
   ║  пока активна галерея. CDP-замер: getAnimations() на .char скрытого   ║
   ║  home-экрана → playState:"running". Это объясняет парадокс            ║
   ║  «idle ХУЖЕ перехода» и 41% всех инвалидаций от футера (87 букв =      ║
   ║  3 футера × 29, тикают все).                                          ║
   ║                                                                       ║
   ║  Лечение — «простой вариант» (модель п.0): animation-play-state:      ║
   ║  paused для всего поддерева невидимого экрана. По тому же паттерну,   ║
   ║  что заморозка фото выше. ВИЗУАЛЬНО НИЧЕГО НЕ МЕНЯЕТСЯ — морозится    ║
   ║  только то, чего физически не видно.                                  ║
   ║                                                                       ║
   ║  «Системный» вариант content-visibility:hidden НЕ ИСПОЛЬЗУЕМ — уже    ║
   ║  пробовали (см. комментарий в main[data-screen]:not(.is-active) ниже, ║
   ║  style.css ~158): давал рывок фото при возврате (браузер «забывал»    ║
   ║  layout скрытого экрана).                                             ║
   ╚══════════════════════════════════════════════════════════════════════╝ */

/* (1) НЕВИДИМЫЙ <main>-экран. Главная в покое, пока активна галерея (и
   наоборот). Морозим ВСЕ анимации поддерева: footer-wave, balloon-rise,
   cta-beckon-right, wiggle. Видимый экран (.is-active) не затронут. */
main[data-screen]:not(.is-active) * {
  animation-play-state: paused;
}
/* ⚠️ ИСКЛЮЧЕНИЕ: во время slide-перехода главная↔галерея ОБА экрана видны
   одновременно ~0.9с (body.is-transitioning глушит opacity:0 уходящего).
   НЕ морозим в этом окне — иначе уходящий экран встанет в моменте, пока
   въезжает, что читается как рывок. Тот же принцип, что у заморозки фото
   (пауза только во время is-transitioning, не наоборот) — но здесь
   переворачиваем: is-transitioning = РАБОТАЕТ, без него = пауза. */
body.is-transitioning main[data-screen] * {
  animation-play-state: running;
}

/* (2) ГАЛЕРЕЯ ПОД ОТКРЫТЫМ СЕЗОНОМ. Сезон (season-view z:40) перекрывает
   галерею (gallery-content z:10) целиком. Под оверлеем вечно тикают
   footer-wave галерейного футера + season-ken-burns на карточках — впустую.
   Морозим. Сезон живёт на <body> (season-view/season-footer/back-dick) ВНЕ
   .gallery-content → его анимации НЕ затронуты, back-dick-breathe дышит. */
body.season-open .gallery-content * {
  animation-play-state: paused;
}
/* ⚠️ ИСКЛЮЧЕНИЕ: переход галерея↔сезон (кроссфейд через slide). body.season-
   open ставится СИНХРОННО при клике в openSeason (main.js:837), т.е. ДО
   начала анимации въезда сезона. Если бы паузили сразу — галерея встала бы
   в моменте под въезжающим сезоном → рябь. Поэтому во время всего таймлайна
   перехода висит body.is-season-transitioning → НЕ паузим галерею. Класс
   ставится в начале openSeason/goBack, снимается в onComplete — когда сезон
   осел и галерея под ним действительно невидима. */
body.is-season-transitioning.season-open .gallery-content * {
  animation-play-state: running;
}

/* (3) СЕЗОН, КОГДА ЗАКРЫТ (в покое). season-footer и .back-dick скрыты
   (opacity:0; visibility:hidden), но back-dick-breathe крутится infinite
   ВСЕГДА (style.css ~3340), и footer-wave на .season-footer .char тоже.
   Морозим, пока сезон закрыт. */
body:not(.season-open) .season-footer *,
body:not(.season-open) .back-dick {
  animation-play-state: paused;
}

/* 🐛 ОТКРЫТЫЙ КОСМЕТИЧЕСКИЙ БАГ (не критичный, плавность важнее):
   Симптом (Миша): при клике «в галерею» фото на мгновение резко показывается
   в КРАЙНЕМ положении kenburns (scale 1.55, translate -5%) — т.к. paused
   морозит фото в текущей фазе. При возврате — плавно (фото проявляется из
   opacity:0, маскируя фазу). Пробовали (НЕ сработало): animation:none + статичный
   transform + transition — правило применилось, но эффект не изменился
   (вероятно конфликт с will-change / compositor layer). Отложено для свежей
   головы. Возможные подходы: (1) JS-сброс transform в момент клика до старта
   перехода, (2) сделать kenburns через JS/GSAP вместо CSS-анимации, (3)
   уменьшить амплитуду kenburns (scale 1.10→1.30 вместо 1.55), чтобы крайняя
   фаза была ближе к центру и слёт был незаметен. */

/* 🚀 ЛАГ-ФИКС (Раунд 4, оставлен): фото убираем из layout полностью, когда
   мы НЕ на активной главной. Заморозка анимации (animation-play-state:paused
   постоянно в галерее) НЕ помогала возврату (Раунд 4), но заморозка НА
   ПЕРЕХОДЕ выше — новое лечение. А это правило освобождает GPU-память в покое.
   Когда sky--home снимается (уход в галерею), фото плавно затухают (transition
   opacity 0.4с у .bg-photos), потом display:none. На главной — снова в layout. */
body:not(.sky--home):not(.is-transitioning) .bg-photo {
  display: none;
}

/* ---- Слой одного фото: на весь экран ----
   Раунд 15: в контейнере ВСЕГО 2 узла. JS меняет их классы в 3 состояния:
     .cur     — видимое фото (opacity 1) + его Ken Burns анимация
     .fading  — затухающее (opacity 1→0 за FADE_MS) — БЫВШЕЕ .cur, скоро станет .next
     .next    — скрытое (opacity 0), готовит следующее фото

   ПОСЛЕДОВАТЕЛЬНЫЙ fade (НЕ crossfade!): старая ПОЛНОСТЬЮ исчезает,
   потом новая начинает появляться. Миша: «одна фотка полностью исчезала,
   потом уже следующая начинала проявляться». */
.bg-photo {
  position: absolute;
  /* ⚠️ Раунд 9 (сохраняется): фото прижаты К НИЗУ, высота 100svh. */
  top: auto;
  bottom: 0;
  left: 0;
  right: 0;
  height: 100svh;
  opacity: 0;
  transition: opacity 3.4s ease-in-out;
  pointer-events: none;
}

/* КАРТИНКА: заливает ВЕСЬ экран (object-fit: cover), полупрозрачная 30%.
   ⚠️ БЕЗ transform в базовом правиле! Раньше было `transform: scale(1.10)`
   здесь — это давало СКАЧОК масштаба при swap (когда .cur→.next, transform
   моментально с 1.30 на 1.10 = «фотка скачет в первоначальный размер»).
   Теперь transform живёт ТОЛЬКО в keyframes → на .next transform = none
   (scale 1.0, фото ровно по экрану, без полоски). Скачок теперь = 1.0↔анимация,
   но он происходит когда opacity=0 → НЕ ВИДЕН. */
.bg-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.15;              /* ×1.5 ещё тусклее (просил Миша) — было 0.22 */
  transform-origin: center center;
  will-change: transform;
}

/* Видимое фото + Ken Burns.
   ⚠️ Длительность 9с = весь цикл показа (SHOW_MS в JS). Раньше было 6с →
   фотка замирала за 3с до затухания, и старт fade-out читался как «рывок».
   Теперь Ken Burns покрывает ВСЁ время показа → фото постоянно медленно
   подъезжает, нет мёртвой статики. */
.bg-photo.cur  { opacity: 1; }
.bg-photo.cur img {
  animation: bg-kenburns 9s ease-out forwards;
}

/* 🆕 МЕДЛЕННЫЙ ЗАЖИГ ПРИ ЗАГРУЗКЕ. По умолчанию .cur opacity:1 (видно сразу).
   body.bg-booting (вешает initBgSlideshow, снимает после первого swap) —
   у .cur opacity стартует с 0 и плавно разгорается до 1 за FADE_MS (3.4с).
   Та же длительность, что и обычный crossfade → ощущение такое же плавное. */
body.bg-booting .bg-photos .bg-photo.cur {
  opacity: 0;
  animation: bg-first-load 3.4s ease-in-out forwards;
}
@keyframes bg-first-load {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}
/* Затухающее — opacity 1→0 за 1.5с (transition). Ken Burns ПРОДОЛЖАЕТСЯ (не
   отменяется!) — иначе при потере класса .cur анимация сбрасывалась к scale 1.0
   и был виден СКАЧОК «фото отскакивает в первоначальный размер». Теперь фото
   доигрывает подъезд до конца пока затухает → движение плавное. */
.bg-photo.fading { opacity: 0; }
.bg-photo.fading img {
  animation: bg-kenburns 9s ease-out forwards;
}
/* Скрытое, готовит следующее. transform = none (фото ровно по экрану),
   opacity 0, без анимации. */
.bg-photo.next { opacity: 0; }
.bg-photo.next img { animation: none; }

/* Заморозка на переходе (Раунд 5): фото замирают на ~2с анимации экранов. */
body.is-transitioning .bg-photo.cur img,
body.is-transitioning .bg-photo.fading img {
  animation-play-state: paused;
}

/* ---- Ken Burns: плавный подъезд камеры. БЕЗ translate (не обнажает край),
   амплитуда 1.10→1.30 (маленькая, края всегда перекрыты с запасом). ---- */
@keyframes bg-kenburns {
  0%   { transform: scale(1.10); }
  100% { transform: scale(1.30); }
}

/* ---- Уважение к настройкам анимаций (доступность) ----
   Если пользователь попросил меньше движения — просто статично
   показываем фото без Ken Burns. */
@media (prefers-reduced-motion: reduce) {
  .bg-photo.cur img { animation: none; }
}

/* ---- 🆕 ПЕРЕКЛЮЧЕНИЕ desktop / mobile (2 независимых набора фото) ----
   Граница 768px = «телефон». По умолчанию (широкий экран) виден desktop-набор
   .bg-photos--d, mobile-набор .bg-photos--m целиком display:none → его фото
   не грузятся и не занимают GPU-слои. На телефоне (≤768px) — наоборот. */
.bg-photos--m { display: none; }

@media (max-width: 768px) {
  .bg-photos--d { display: none; }
  .bg-photos--m { display: block; }
}

/* ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████
   ████  БЫЛО: css/hero.css — стили афиши (страница 1, index.html)  █████████
   ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████ */

/* ---- Сцена: на весь экран ---- */
.hero {
  position: relative;
  min-height: 100vh;    /* фолбэк для старых браузеров */
  min-height: 100dvh;   /* дышит с адресной строкой на мобиле/в ТГ — футер всегда виден */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 20px 380px;    /* padding-top 40px: шатёр СНЯТ (проект «сезонов»),
                                 сверху больше ничего не висит → небольшой отступ
                                 от верхнего края окна. Раньше было 165px (150px
                                 шатра + 15px зазор), когда шатёр висел сверху.
                                 padding-bottom 380px: большой отступ снизу => контент
                                 (баннер+заголовок) центрируется flex'ом ВЫШЕ по экрану.
                                 Кнопка «В ГАЛЕРЕЮ» подстраивается сама (positionCtaCenter
                                 в js/main.js считает центр между заголовком и футером). */
  overflow: hidden;
  /* Небо/солнце/градиент убраны — теперь рисуются общим слоем .sky на <body>,
     чтобы при slide-переходе двигался только контент, а фон стоял (бесшовный).
     .hero прозрачна → небо из .sky видно сквозь неё. */
  background: transparent;
}

/* (Старый плоский ::before удалён — теперь край шатра рисует SVG .tent-edge) */

/* ---- Воздушные шары (форма/анимация — в общем css/style.css) ----
   Здесь — только цвета, горизонтальные позиции и скорость каждого шара.
   У каждого свои animation-duration (скорость) и animation-delay.
   ВНИМАНИЕ: в галерее (gb*) задержки ОТРИЦАТЕЛЬНЫЕ (шары сразу висят).
   На главной (b*) — ПОЛОЖИТЕЛЬНЫЕ + fill-mode: backwards → шары ВЫЛЕТАЮТ
   снизу из-за края при загрузке (по очереди), а не висят сразу.
   См. отдельное правило .hero .balloon ниже. */
/* Цвета (жёлтый/розовый/синий...) убраны: теперь ствол и яйца у ВСЕХ шаров
   одинаковые телесные (--skin), а головка красная. Здесь — только позиция
   по горизонтали, размер и скорость полёта. */
.balloon.b1  { left: 8%;   animation-duration: 12s; }
.balloon.b2  { left: 6%;   animation-duration: 14s; width: 85px; height: 180px; }
.balloon.b3  { right: 10%; animation-duration: 10s; width: 60px; height: 125px; }
.balloon.b4  { right: 8%;  animation-duration: 16s; width: 95px; height: 200px; }
.balloon.b5  { left: 45%;  animation-duration: 9s;  width: 55px; height: 115px; }
.balloon.b6  { left: 40%;  animation-duration: 13s; width: 50px; height: 105px; }
/* Новые шары b7–b12 — для большей плотности потока */
.balloon.b7  { left: 22%;  animation-duration: 11s; width: 58px; height: 120px; }
.balloon.b8  { left: 68%;  animation-duration: 15s; width: 78px; height: 165px; }
.balloon.b9  { left: 55%;  animation-duration: 10s; width: 48px; height: 100px; }
.balloon.b10 { left: 33%;  animation-duration: 13s; width: 66px; height: 140px; }
.balloon.b11 { left: 78%;  animation-duration: 9s;  width: 52px; height: 110px; }
.balloon.b12 { left: 15%;  animation-duration: 14s; width: 62px; height: 130px; }

/* 🎈 ПОВЕДЕНИЕ ШАРОВ НА ГЛАВНОЙ: вылет снизу при загрузке.
   Положительные animation-delay → анимация ещё не началась при загрузке,
   шар сидит на стартовом кадре (translateY(110vh), за нижним краем — невидим).
   animation-fill-mode: backwards нужен, чтобы шар ПРИДЕРЖИВАЛСЯ стартового
   кадра в течение задержки (без этого он показался бы в обычной позиции).
   Задержки расставлены по очереди (0.3 → 2.4с), чтобы шары вылетали не кучей,
   а по одному. Галерея (gb*) НЕ затронута — там шары сразу висят. */
.hero .balloon.b1 { animation-delay: 0.3s;  animation-fill-mode: backwards; }
.hero .balloon.b4 { animation-delay: 2.6s;  animation-fill-mode: backwards; }
.hero .balloon.b8 { animation-delay: 6.5s;  animation-fill-mode: backwards; }

/* ---- Облака на главной ----
   Каждое облако: своя высота (top), размер (font-size) и скорость
   дрейфа (animation-duration). ОТРИЦАТЕЛЬНЫЕ задержки, чтобы при
   загрузке облака уже были на разных позициях по горизонтали, а не
   выстраивались в кучу слева. */
.cloud.c1 { top: 12%;  font-size: 24px; animation-duration: 75s; animation-delay: 0s;   }
.cloud.c2 { top: 28%;  font-size: 34px; animation-duration: 90s; animation-delay: -25s; }
.cloud.c3 { top: 8%;   font-size: 20px; animation-duration: 60s; animation-delay: -45s; }
.cloud.c4 { top: 45%;  font-size: 30px; animation-duration: 85s; animation-delay: -10s; }
.cloud.c5 { top: 20%;  font-size: 26px; animation-duration: 70s; animation-delay: -55s; }

/* (Позиции звёзд главной .star.s1-s20 удалены — звёзды убраны полностью) */

/* ---- Центральный блок: заголовок + кнопка ---- */
.hero-content {
  position: relative;
  z-index: 10;
  text-align: center;
  max-width: 1200px;          /* увеличено, чтобы FUNNY DICKS не переносилось */
  width: 100%;
}

/* ===== Плашка-приглашение WELCOME TO + 🥚 ПАСХАЛКА v2 «баннер на двух болтах» =====
   ДВУХФАЗНАЯ: фаза 1 — кликни по поросёнку 5 раз → откручивается (растёт) →
   отваливается и улетает вниз → баннер повисает на правом болте вертикально
   с качанием маятника → на месте поросёнка остаётся дырочка-крестик.
   Фаза 2 — правый болт становится кликабельным, кликни 3 раза → откручивается
   → улетает вниз → баннер падает ПОЛНОСТЬЮ вниз → на месте болта дырочка-крестик.
   Тень «живая»: drop-shadow на невращающихся слоях-родителях → едет вниз с
   объектом при падении, но не крутится вместе с ним (как от солнца).
   Механика протестирована в _design-tests/easter-egg-banner.html.

   АРХИТЕКТУРА СЛОЁВ (чтобы НЕ конфликтовать transform-анимациями — грабля из
   STATUS.md): каждый слой делает ТОЛЬКО ОДНО. Трансформы перемножаются по цепочке.
   Левый болт: .bolt-pig(клик+падение+тень) → .pig-wobble(рост) → .pig-badge(вращение)
   Правый болт: .bolt-x-wrap(клик+падение+тень) → .bolt-x-wobble(рост) → .bolt-x(доворот)
   Пульс-слой (.pig-pulse/.bolt-x-pulse) оставлен в HTML для совместимости, но НЕ
   используется (Миша просил без отскока — только плавный рост через transition). */

/* НЕвращающийся слой-обёртка с «живой» тенью баннера. drop-shadow рисует тень
   по финальному силуэту в фиксированном направлении экрана (вправо-вниз, от
   солнца) — НЕ крутится вместе с баннером при провисании/падении. */
.hero-tag-shadow-wrap {
  display: inline-block;
  filter: drop-shadow(6px 6px 0 rgba(26,26,46,0.4));
  margin-bottom: 20px;
}
.hero-tag {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  background: var(--brown);          /* коричневый: не сливается с красными буквами FUNNY DICKS */
  color: #fff;
  font-family: var(--font-display);
  font-size: clamp(0.9rem, 1.5vw, 1.2rem);
  padding: 8px 8px;                  /* симметрично — по болту с каждой стороны */
  border-radius: 50px;
  /* box-shadow УБРАН — тень теперь на родителе .hero-tag-shadow-wrap (drop-shadow). */
  letter-spacing: 2px;
  transform: rotate(-7deg);           /* наклон баннера */
  /* Точка вращения = центр ПРАВОГО болта. При провисании баннер крутится вокруг
     него → левый край падает вниз. Правый болт 52px, padding-right 8px → 34px. */
  transform-origin: calc(100% - 34px) 50%;
  position: relative;                 /* для точек-проколов (position:absolute) */
  will-change: transform;
}
/* Чёрные точки-проколы: остриё болта, вышедшее снизу баннера. Сидят на баннере
   (position:absolute), а НЕ на болтах → остаются, когда болты отваливаются.
   Позиция = центр каждого болта по горизонтали, чуть ниже центра по вертикали
   (прокол выходит снизу плашки). padding 8 + половина болта 52 = 34px.
   СКРЫТЫ изначально (opacity:0) — появляются только когда болт отваливается
   (класс .show-left-hole / .show-right-hole вешает JS при отвале).
   Центрирование: translate(-50%, …) — иначе left/right ставит КРАЙ точки, и она
   уезжает вбок на полсвоей ширины. */
.bolt-hole {
  position: absolute;
  top: 50%;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ink);
  pointer-events: none;
  z-index: 2;
  opacity: 0;                         /* скрыт, пока болт не отвалился */
  transition: opacity 0.25s ease;
}
.bolt-hole--left  { left: 34px;  transform: translate(-50%, -50%); }
.bolt-hole--right { right: 34px; transform: translate(50%, -50%);  }
.hero-tag.show-left-hole  .bolt-hole--left,
.hero-tag.show-right-hole .bolt-hole--right { opacity: 1; }
/* Фаза 1: баннер провисает на правом болте (маятник с затуханием). */
.hero-tag.sagging {
  transform: rotate(-7deg);
  animation: hero-tag-sag 2.6s cubic-bezier(0.4, 0, 0.5, 1) forwards;
}
@keyframes hero-tag-sag {
  0%   { transform: rotate(-7deg); }
  16%  { transform: rotate(-104deg); }
  28%  { transform: rotate(-78deg);  }
  40%  { transform: rotate(-97deg);  }
  52%  { transform: rotate(-84deg);  }
  64%  { transform: rotate(-94deg);  }
  76%  { transform: rotate(-87deg);  }
  88%  { transform: rotate(-92deg);  }
  100% { transform: rotate(-90deg);  }
}
/* Контр-вращение правого болта: компенсирует вращение баннера при провисании,
   чтобы болт оставался СТАТИЧНЫМ (не качался вместе с баннером).
   ВАЖНО про старт: до провисания у болта transform:none (0°). Баннер наклонён
   на -7° → болт визуально тоже на -7°. Контр-вращение должно стартовать с 0°
   (а НЕ с +7°!), иначе скачок в начале. Формула: counter = -banner - 7.
   Визуальный угол болта = banner + counter = -7° на ВСЕХ кадрах → не меняется. */
.hero-tag.sagging .bolt-x-wrap {
  animation: bolt-x-counter-sag 2.6s cubic-bezier(0.4, 0, 0.5, 1) forwards;
}
@keyframes bolt-x-counter-sag {
  0%   { transform: rotate(0deg); }
  16%  { transform: rotate(97deg);  }
  28%  { transform: rotate(71deg);  }
  40%  { transform: rotate(90deg);  }
  52%  { transform: rotate(77deg);  }
  64%  { transform: rotate(87deg);  }
  76%  { transform: rotate(80deg);  }
  88%  { transform: rotate(85deg);  }
  100% { transform: rotate(83deg);  }
}
/* Фаза 2: правый болт отвалился → баннер падает ПОЛНОСТЬЮ вниз.
   ВАЖНО: translateY ПЕРЕД rotate. rotate(-90deg) поворачивает систему координат →
   translateY после него идёт ВБОК. Сначала сдвиг (вниз), потом поворот. */
.hero-tag.dropping {
  animation: hero-tag-drop 1.4s cubic-bezier(0.5, 0, 0.75, 0.4) forwards;
}
@keyframes hero-tag-drop {
  0%   { transform: translateY(0) rotate(-90deg); }
  100% { transform: translateY(115vh) rotate(-90deg); }
}

.hero-tag-label { padding: 0 6px; white-space: nowrap; }

/* --- Дырочки-крестики на стене (следы от открученных болтов) ---
   position: fixed по координатам центра болта (JS, через getBoundingClientRect).
   Появляются с лёгкой анимацией. Просто тёмный полупрозрачный крест «х». */
.hole-mark {
  position: fixed;
  z-index: 1;
  pointer-events: none;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  animation: hole-appear 0.3s ease 0.15s forwards;
}
.hole-mark svg { width: 100%; height: 100%; }
.hole-mark line {
  stroke: rgba(26,26,46,0.55);
  stroke-width: 3.5;
  stroke-linecap: round;
}
@keyframes hole-appear {
  0%   { opacity: 0; transform: scale(0.3); }
  100% { opacity: 1; transform: scale(1); }
}

/* --- Левый болт: поросёнок (кликабельный) --- */
.bolt-pig {
  display: inline-flex;
  cursor: pointer;
  user-select: none;
  flex-shrink: 0;
  position: relative;
  /* «Живая» тень на самом внешнем слое: при падении едет вниз вместе с объектом,
     не вращается (вращение — на внутреннем .pig-badge через WAAPI). */
  filter: drop-shadow(0 5px 0 rgba(26,26,46,0.4));
}
.pig-pulse { display: inline-flex; }   /* слой оставлен, пульс НЕ используется */
.pig-wobble {
  display: inline-flex;
  transform-origin: 50% 50%;
  transform: scale(1);
  transition: transform 0.25s ease;    /* плавный рост без отскока */
}
/* Медальон с поросёнком. Вращение управляется через Web Animations API (JS),
   чтобы при смене направления поросёнок продолжал с текущего угла, а не прыгал. */
.pig-badge {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  overflow: hidden;
  background: #fff;
  border: 4px solid var(--yellow);
  flex-shrink: 0;
}
.pig-badge img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}
/* Поросёнок отвалился — падает вниз. rotate убран из keyframes (тень не должна
   крутиться). Поросёнок крутится через WAAPI на внутреннем .pig-badge. */
.bolt-pig.detached {
  position: fixed;
  z-index: 500;
  pointer-events: none;
  animation: bolt-pig-fall 1.8s cubic-bezier(0.55, 0, 0.85, 0.4) forwards;
}
@keyframes bolt-pig-fall {
  0%   { transform: translate(0, 0); }
  100% { transform: translate(0, 115vh); }
}

/* --- Правый болт: крестик --- */
.bolt-x-wrap {
  display: inline-flex;
  cursor: default;
  user-select: none;
  flex-shrink: 0;
  position: relative;
  filter: drop-shadow(0 5px 0 rgba(26,26,46,0.4));   /* «живая» тень */
}
.bolt-x-wrap.active { cursor: pointer; }
/* .inactive убран — opacity НЕ менялся (Миша: «тон цвета не должен меняться»).
   Единственная разница между inactive/active = курсор (default vs pointer). */
.bolt-x-pulse { display: inline-flex; }   /* слой оставлен, пульс НЕ используется */
.bolt-x-wobble {
  display: inline-flex;
  transform-origin: 50% 50%;
  transform: scale(1);
  transition: transform 0.25s ease;
}
.bolt-x {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--cream);
  border: 4px solid var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transform: rotate(0deg);            /* угол ставится через JS по кликам */
  transition: transform 0.3s ease;
}
.bolt-x svg { width: 26px; height: 26px; }
.bolt-x line {
  stroke: var(--ink);
  stroke-width: 5;
  stroke-linecap: round;
}
/* Правый болт отвалился — падает вниз. rotate убран (тень не должна крутиться).
   Вращение в полёте — на внутреннем .bolt-x через WAAPI. */
.bolt-x-wrap.detached {
  position: fixed;
  z-index: 500;
  pointer-events: none;
  animation: bolt-x-fall 1.5s cubic-bezier(0.55, 0, 0.85, 0.4) forwards;
}
@keyframes bolt-x-fall {
  0%   { transform: translate(0, 0); }
  100% { transform: translate(0, 115vh); }
}

/* На мобильных делаем медальоны чуть меньше */
@media (max-width: 600px) {
  .pig-badge { width: 42px; height: 42px; border-width: 3px; }
  .bolt-x { width: 42px; height: 42px; border-width: 3px; }
  .bolt-x svg { width: 20px; height: 20px; }
  .bolt-x line { stroke-width: 4; }
  /* Точка вращения баннера = центр ПРАВОГО болта. На мобиле болт 42px → центр на
     29px от края (padding 8 + половина 42 = 29). На ПК было 34px (под болт 52px).
     Если оставить 34 → точка вращения уезжает на 5px правее центра болта → при
     провисании болт «катается» по дуге (шатается). 29px = точно в центре болта →
     болт стоит как вкопанный, как на ПК. */
  .hero-tag { transform-origin: calc(100% - 29px) 50%; }
  /* Точки-проколы тоже сдвигаем под центр моб. болтов (29px от края, не 34). */
  .bolt-hole--left  { left: 29px;  transform: translate(-50%, -50%); }
  .bolt-hole--right { right: 29px; transform: translate(50%, -50%);  }
}

/* ---- Огромный заголовок FUNNY DICKS ----
   Уменьшен макс. размер + white-space: nowrap чтобы НЕ переносилось.
   Каждая буква разноцветная (цвета заданы ниже через nth-child). */
.hero-title {
  font-size: clamp(2.8rem, 11vw, 7.5rem);   /* было 14vw/11rem — слишком крупно */
  color: var(--cream);                       /* базовый (для пробела) */
  white-space: nowrap;                        /* ЗАПРЕЩАЕМ ПЕРЕНОС */
  letter-spacing: 2px;
  line-height: 0.95;
  margin-top: 40px;                           /* 🆕 зазор от баннера с поросёнком.
                                                 Раньше заголовок прилипал к баннеру.
                                                 На мобиле больше (см. @media ниже). */
  margin-bottom: 50px;                        /* отступ под заголовком. Раньше создавал место
                                                 под кнопку в flex-потоке, но кнопка теперь
                                                 position:absolute (прибита к низу) → этот отступ
                                                 просто зазор заголовка с пустотой. Оставил 50px
                                                 (историческое значение), на раскладку не влияет. */
  /* Тёмная обводка букв для читаемости (мультяшный контур) */
  -webkit-text-stroke: 2px var(--ink);
  paint-order: stroke fill;
  /* Цветная тень для объёма */
  text-shadow: 5px 5px 0 var(--blue);
}

/* Каждая буква в отдельном span для GSAP stagger-анимации + цвета */
.hero-title .char {
  display: inline-block;
}

/* Каждое слово — неразрывный блок. Перенос строки возможен только МЕЖДУ
   словами (между .word), внутри слова никогда. Это убирает отрыв буквы S
   от DICKS на узких экранах (Z Fold и т.п.) — та же техника, что в .gallery-logo.
   Пробел-разделитель между словами делаем через margin у .word. */
.hero-title .word {
  display: inline-block;
  white-space: nowrap;
  margin: 0 0.2em;
}

/* 🆕 На мобиле заголовок сильнее отходит от баннера (Миша: «опустить вниз,
   чтобы между FUNNY DICKS и баннером с поросёнком был зазор»). На ПК 40px,
   на телефоне 80px — заголовок визуально уходит к центру, ближе к кнопке. */
@media (max-width: 768px) {
  .hero-title { margin-top: 80px; }
}

/* ЯВНЫЕ цветовые классы (как в gallery-logo). Раньше был nth-child по
   дочерним .char — но с обёрткой .word буквы стали внуками → счётчик сломался
   бы. Явные классы надёжны и не зависят от вложенности. */
.hero-title .c-yellow { color: var(--yellow); }
.hero-title .c-red    { color: var(--red); }
.hero-title .c-pink   { color: var(--pink); }
.hero-title .c-cream  { color: var(--cream); }

/* «Буква» I — картинка пениса вместо буквы.
   Размер подгоняем под высоту букв, делаем узким, чтобы не разрывать слово.
   vertical-align держит картинку на базовой линии текста.
   Отрицательные margin пододвигают соседние буквы (D и C) ближе. */
.hero-title .letter-img {
  display: inline-block;
  height: 0.85em;            /* чуть ниже заглавных букв — яйца не выпирают */
  width: auto;
  vertical-align: baseline;  /* НЕ двигаем вертикально через align — он ломает соседей (буква S уезжала) */
  position: relative;        /* двигаем пенис ТОЛЬКО через top — не влияет на строку */
  top: 0.1em;                /* приопускаем, чтобы верхушка совпадала с буквами */
  margin: 0 0.05em 0 0.02em; /* слева +0.02em (D не заходит), справа +0.05em (C ближе, тень яйца не достаёт) */
  filter: drop-shadow(4px 4px 0 var(--blue)); /* тень как у букв */
  animation: wiggle 3.5s ease-in-out infinite;
  transform-origin: center bottom;
}
/* Класс-глушитель wiggle на время GSAP-прилёта (как .intro у кнопки).
   wiggle = transform-анимация → конфликтовала бы с GSAP-transform прилёта.
   На время .arriving отключаем wiggle, GSAP довозит букву, потом класс
   снимается → wiggle возвращается и буква качается как обычно. */
.hero-title .letter-img.arriving {
  animation: none;
}

/* РАЗНОЦВЕТНЫЕ БУКВЫ теперь через явные классы .c-yellow/.c-red/.c-pink/.c-cream
   прямо на каждом span.char в HTML. См. блок .hero-title .c-* выше.
   (Раньше был nth-child по прямым детям, но с обёрткой .word буквы стали внуками.) */

/* ---- Подзаголовок ---- */
.hero-sub {
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 3vw, 2.2rem);
  color: var(--blue);
  margin-top: 25px;
  margin-bottom: 40px;
  letter-spacing: 2px;
  text-shadow: 2px 2px 0 var(--cream);
  -webkit-text-stroke: 1px var(--ink);
  paint-order: stroke fill;
}

/* ---- Кнопка-вход (ПЕНИС) ----
   Форма = единый SVG <path> (обводка stroke только по внешнему контуру,
   без швов на стыке яиц/ствола). Цвет/hover/тени — как у прежней таблетки.
   Головка ВПРАВО (в галерею). При hover стрелка → дёргается вправо.

   ПОЗИЦИОНИРОВАНИЕ: position: absolute. Точное вертикальное положение
   (ЦЕНТР между заголовком и футером) задаёт JS-функция positionCtaCenter()
   при загрузке и при ресайзе. Здесь — только горизонтальный центр (left:50%
   + margin-left:-145px) и стартовый top:50% как заглушка до отработки JS.
   Никакого transform в базовом правиле — чтобы не конфликтовать с дыханием
   (cta-pulse scale) и анимациями прилёта/полёта. */
.hero-cta {
  position: absolute;
  top: 50%;                 /* заглушка, JS перетрёт точным центром */
  left: 50%;
  margin-left: -145px;      /* половина ширины SVG (290px) → горизонтальный центр */
  z-index: 10;              /* ПОВЕРХ шаров (z-index:2) и облаков (z-index:1):
                               чтобы летящие вверх шары-пенисы не перекрывали кнопку */
  display: inline-block;
  cursor: pointer;
  text-decoration: none;
  /* 🌞 ТЕНЬ ПО ФИЗИКЕ-ОПТИКЕ. Кнопка ДВИГАЕТСЯ ВДОЛЬ фона (рывки вбок), не
     приближается к свету → тень СТАТИЧНА (не меняется). Привязана к элементу
     через filter → автоматически двигается с кнопкой синхронно при рывках.
     Вниз-вправо (солнце слева-сверху). Жёсткая кромка + мягкий ореол.
     При hover НЕ меняется (боковое движение не меняет тень). */
  filter:
    drop-shadow(4px 8px 0 rgba(26,26,46,0.38))
    drop-shadow(6px 12px 12px rgba(0,0,0,0.18));
  /* «Заманивающий рывок» вправо (в сторону стрелки →) с затухающей инерцией.
     Цикл 1с: рывок → перелёт → отскок → устаканилось → пауза. Идёт ВСЕГДА, в т.ч. при hover. */
  animation: cta-beckon-right 1s linear infinite;
}

/* HOVER: только смена цвета (путь + текст краснеют, см. ниже). Тень НЕ меняется —
   кнопка двигается ВДОЛЬ фона, не приближается к свету. Рывок ПРОДОЛЖАЕТСЯ.
   ⚠️ Не трогаем transform (управляется рывком) и filter (статичен). */
.hero-cta:hover {
  /* тень та же — намеренно пусто тут по filter/transform, меняется только цвет .dick-path */
}

.hero-cta:active {
  /* Тень НЕ меняется при тапе — только цвет (.dick-path краснеет через :hover).
     Раньше здесь был filter с меньшей тенью → скачок при тапе. Убрано. */
}

/* Глушим дыхание (cta-pulse) на время приезда слева при загрузке.
   Класс .intro вешает JS при загрузке (вне зависимости от GSAP), а после
   окончания анимации — снимает, возвращая дыхание.
   Приезд сделан на ЧИСТОМ CSS (не зависит от GSAP), чтобы работал всегда. */
.hero-cta.intro {
  animation: cta-arrive 1.4s cubic-bezier(0.34, 1.4, 0.5, 1) forwards;
}

/* Приезд кнопки слева при загрузке сайта: затухающий двойной отскок.
   Из-за левого края → тормозит, проскакивает правее центра → отскакивает левее
   (вдвое меньше) → обратно вправо (ещё вдвое меньше) → устаканивается в 0.
   Амплитуда осцилляций: +40 → −18 → +8 → 0 (каждый раз ~вдвое меньше).
   translateX(-100vw) = старт точно за левым краем экрана.
   Timing-function задаём ПО ФАЗАМ (без overshoot-кривых, которые сами дают рывок):
     • прилет (0→55%): ease-out — плавно тормозит к точке проскока
     • отскоки (55→86%): ease-in-out — плавные качели
     • устаканивание (86→100%): ease-out — мягко оседает */
@keyframes cta-arrive {
  0% {
    transform: translateX(-100vw);
    opacity: 0;
    animation-timing-function: cubic-bezier(0.3, 1, 0.2, 1); /* ещё дольше держит разгон, тормозит в самом конце */
  }
  /* 55%: прилетела, проскочила правее центра (+40px) */
  55% {
    transform: translateX(40px);
    opacity: 1;
    animation-timing-function: ease-in-out; /* плавный отскок влево */
  }
  /* 72%: отскок левее центра (−18px, вдвое меньше) */
  72% {
    transform: translateX(-18px);
    opacity: 1;
    animation-timing-function: ease-in-out; /* плавный возврат вправо */
  }
  /* 86%: обратно правее (+8px, ещё вдвое меньше) */
  86% {
    transform: translateX(8px);
    opacity: 1;
    animation-timing-function: cubic-bezier(0.3, 0.7, 0.4, 1); /* мягкое оседание */
  }
  /* 100%: устаканилась в центре */
  100% {
    transform: translateX(0);
    opacity: 1;
  }
}

/* SVG-силуэт пениса. Тень перенесена на внешнюю обёртку .hero-cta (см. выше),
   чтобы не масштабироваться вместе с анимациями transform. */
.hero-cta .dick-body {
  display: block;
  width: 290px;
  height: auto;
}
.hero-cta .dick-path {
  fill: var(--yellow);
  stroke: var(--ink);
  stroke-width: 3;          /* Раунд 19: было 4 → 3 (чуть тоньше, как у BACK/сезонных) */
  stroke-linejoin: round;
  transition: fill 0.2s ease;
}
.hero-cta:hover .dick-path { fill: var(--red); }

/* Текст на стволе. Ствол ≈ x 74..205 из 300 → центр ~44%.
   🐛 GPU-фикс от дрожания при рывках/дыхании кнопки. */
.hero-cta .dick-label {
  position: absolute;
  top: 50%;
  left: 44%;
  transform: translate(-50%, -50%) translateZ(0);
  font-family: var(--font-display);
  font-size: 1.1rem;
  letter-spacing: 1.5px;
  white-space: nowrap;
  z-index: 5;
  pointer-events: none;
  color: var(--ink);
  will-change: transform;
  backface-visibility: hidden;
  transition: color 0.2s ease;
}
.hero-cta:hover .dick-label { color: #fff; }

/* Стрелка → у головки (правый край ~82%). При hover дёргается вправо.
   GPU-фикс (translateZ в оба состояния, чтобы при переходе hover→покой не было скачка слоя). */
.hero-cta .dick-arrow {
  position: absolute;
  top: 50%;
  left: 82%;
  transform: translate(-50%, -50%) translateZ(0);
  font-family: var(--font-display);
  font-size: 1.25rem;
  z-index: 5;
  pointer-events: none;
  color: var(--ink);
  will-change: transform;
  backface-visibility: hidden;
  transition: transform 0.2s ease, color 0.2s ease;
}
.hero-cta:hover .dick-arrow {
  color: #fff;
  transform: translate(-50%, -50%) translateX(8px) translateZ(0);
}

/* ---- ПОЛИРОВКА ЦВЕТА В ПОЛЁТЕ/ПРИЛЁТЕ ----
   На МОБИЛЕ после тапа браузер «удерживает» состояние наведения (sticky hover):
   курсора нет, поэтому :hover не снимается и держит красный весь полёт.
   На ПК курсор уходит → :hover снимается → кнопка снова жёлтая, бага нет.
   Решение: пока летит (.flying) или прилетает (.arriving), ПРИНУДИТЕЛЬНО
   держим жёлтый цвет пениса и тёмный текст/стрелку. Стоит ПОСЛЕ :hover →
   перебивает прилипший ховер на мобиле. На ПК результат тот же (жёлтый).

   САМ ОТКЛИК КРАСНЫМ при тапе делает JS (см. initCtaFlight в main.js):
   при клике красит в красный на ~0.45с (фаза замаха), потом снимает →
   эти правила вступают и держат жёлтый до конца полёта. */
.hero-cta.flying .dick-path,
.hero-cta.arriving .dick-path { fill: var(--yellow); }

.hero-cta.flying .dick-label,
.hero-cta.arriving .dick-label { color: var(--ink); }

.hero-cta.flying .dick-arrow,
.hero-cta.arriving .dick-arrow {
  color: var(--ink);
  transform: translate(-50%, -50%);
}

/* Краткий красный отклик при тапе на мобиле (где :hover не даёт эффекта).
   Вешает JS (initCtaFlight) на ~0.45с в момент клика: тапнул → красная
   (видно «нажал!») → класс снялся → .flying держит жёлтый. На ПК тоже
   работает (просто дубль с :hover, не мешает). Стоит ПОСЛЕ .flying →
   перебивает жёлтый на время отклика. */
.hero-cta.flash-red .dick-path { fill: var(--red); }
.hero-cta.flash-red .dick-label { color: #fff; }
.hero-cta.flash-red .dick-arrow { color: #fff; transform: translate(-50%, -50%) translateX(8px); }

/* Дыхание кнопки (СТАРОЕ — сейчас не используется, оставлено как памятник).
   Раньше кнопки «дышали» scale(1)↔scale(1.04). Миша попросил заменить на
   «заманивающее движение» — рывок в сторону стрелки (см. cta-beckon ниже). */
@keyframes cta-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.04); }
}

/* 🌞 ДЫХАНИЕ С ТЕНЬЮ (физика-оптика). Дыхание = приподнимание над фоном.
   ВДОХ (масштаб растёт, кнопка оторвалась к свету) → тень ОТДАЛЯЕТСЯ + РАССЕИВАЕТСЯ:
   длиннее, мягче (blur ↑), светлее (alpha ↓).
   ВЫДОХ (масштив возвращается, кнопка прижалась к фону) → тень ПРИБЛИЖАЕТСЯ + УПЛОТНЯЕТСЯ:
   короче, резче (blur ↓), темнее (alpha ↑).
   transform и filter в ОДНОЙ анимации → идеальная синхронность.
   Используется кнопками BACK (.back-btn). */
@keyframes back-breathe {
  0%, 100% {   /* выдох — прижата к фону, тень плотная/резкая */
    transform: scale(1);
    filter:
      drop-shadow(3px 6px 0 rgba(26,26,46,0.42))
      drop-shadow(4px 8px 6px rgba(0,0,0,0.20));
  }
  50% {   /* вдох — оторвалась к свету, тень рассеялась */
    transform: scale(1.04);
    filter:
      drop-shadow(5px 9px 0 rgba(26,26,46,0.30))
      drop-shadow(7px 13px 13px rgba(0,0,0,0.13));
  }
}

/* «ЗАМАНИВАЮЩИЙ РЫВОК» — анимация покоя кнопок (вместо дыхания).
   Идея Миши: кнопка не дышит, а ПОДМАНИВАЕТ — делает рывок в сторону стрелки,
   возвращается с ИНЕРЦИЕЙ (затухающее колебание: пролетает положение и обратно,
   как маятник/пружина), потом стоит паузу, снова рывок.
   - cta-beckon-right: для «TO GALLERY» (стрелка →, рывок вправо).
   - cta-beckon-left:  для «BACK» (стрелка ←, рывок влево).

   Цикл 1 СЕКУНДУ: ~0.35с движение (рывок + затухание), ~0.65с пауза стоя.
   Затухание: рывок 12px → перелёт в обратную сторону ~4.5px (инерция) →
   малый отскок ~1.5px → устаканилось 0. Каждая следующая амплитуда ≈ 1/3
   предыдущей — классическое затухание. */
@keyframes cta-beckon-right {
  0%   { transform: translateX(0); }
  10%  { transform: translateX(12px); }    /* рывок вправо */
  22%  { transform: translateX(-4.5px); }  /* перелёт влево (инерция) */
  31%  { transform: translateX(1.5px); }   /* малый отскок вправо */
  36%  { transform: translateX(0); }       /* устаканилось */
  100% { transform: translateX(0); }       /* стоит до конца цикла */
}

@keyframes cta-beckon-left {
  0%   { transform: translateX(0); }
  10%  { transform: translateX(-12px); }   /* рывок влево */
  22%  { transform: translateX(4.5px); }   /* перелёт вправо (инерция) */
  31%  { transform: translateX(-1.5px); }  /* малый отскок влево */
  36%  { transform: translateX(0); }       /* устаканилось */
  100% { transform: translateX(0); }       /* стоит */
}

/* ---- Анимация полёта кнопки при клике ----
   Сценарий по просьбе Миши: при клике кнопка
     1) отбегает ВЛЕВО («замахивается»),
     2) ДОЛГО и ПЛАВНО тормозит по инерции до полной остановки
        (как мяч, докатывающийся до края — никакой резкой стены),
     3) РЕЗКО разгоняется ВПРАВО и улетает за экран.

   ГЛАВНЫЙ СЕКРЕТ ПЛАВНОСТИ: animation-timing-function задаётся ОТДЕЛЬНО
   для каждого сегмента прямо в keyframes:
     • разгон (0→24%):   ease-in — набирает скорость
     • торможение (24→48%): ОЧЕНЬ мягкий ease-out (cubic-bezier с «длинным
       хвостом») — кнопка быстро въезжает в торможение и потом ДОЛГО
       докатывается до нуля. Это убирает «удар в стену».
     • почти-стоп (48→58%): плавное оседание
     • полёт (58→100%): резкий разгон

   Фаза торможения занимает ~24% длительности (~240мс при 1.0s) — это в 4
   раза дольше прежнего, так что плавность реально видна глазом. */
@keyframes cta-fly-away {
  /* ЗАМАХ ПРОТИВ ЧАСОВОЙ (в минус) → КУВЫРОК ПО ЧАСОВОЙ (в плюс, до 90°).
     4 кадра, без промежуточного 75% (он давал рывок: скачок позиции 45vw
     + смена направления вращения + ease-in налагались → дёрганье в середине).
     Разворот вращения проходит ПЛАВНО через 0° в фазе оседания (58%). */
  0% {
    transform: translateX(0) scale(1) rotate(0deg);
    opacity: 1;
    animation-timing-function: cubic-bezier(0.4, 0, 0.7, 0.4); /* ease-in: разгон замаха */
  }
  /* 0–24%: разгон влево, лёгкий наклон ПРОТИВ часовой */
  24% {
    transform: translateX(-55px) scale(0.96) rotate(-4deg);
    opacity: 1;
    animation-timing-function: cubic-bezier(0, 0.85, 0.15, 1); /* мягкий ease-out */
  }
  /* 24–48%: пик замаха, мягкий наклон ПРОТИВ часовой */
  48% {
    transform: translateX(-85px) scale(0.93) rotate(-8deg);
    opacity: 1;
    animation-timing-function: ease-in-out; /* плавное оседание */
  }
  /* 48–58%: оседание. ВРАЩЕНИЕ ПЛАВНО ПРОХОДИТ ЧЕРЕЗ 0° (разворот завершён).
     timing-function мягкая (ease-in-out), чтобы смена направления не дёргала. */
  58% {
    transform: translateX(-74px) scale(1.07) rotate(0deg);
    opacity: 1;
    animation-timing-function: cubic-bezier(0.4, 0, 0.7, 0.4); /* разгон полёта */
  }
  /* 58–100%: полёт за экран + кувырок ПО ЧАСОВОЙ до 90°. Головка 3ч → 6ч(вниз).
     Вторые 90° докрутятся в галерее (back-btn-fly-in): 6ч → 9ч(влево). */
  100% {
    transform: translateX(130vw) scale(0.7) rotate(90deg);
    opacity: 0;
  }
}

/* Класс вешается JS-ом при клике. Пока летит — дыхание отключаем.
   Timing-function задаётся внутри keyframes (по сегментам), поэтому здесь
   не указываем. */
.hero-cta.flying {
  animation: cta-fly-away 1s forwards;
  pointer-events: none;   /* чтобы не нажать повторно во время полёта */
}

/* ---- Анимация ПРИЛЁТА кнопки «В ГАЛЕРЕЮ» на главную (справа → налево на место) ----
   Сценарий: мы вернулись из галереи (кнопка «НАЗАД» улетела ВЛЕВО за экран).
   Здесь «В ГАЛЕРЕЮ» продолжает движение влево — вылетает СПРАВА за экраном и
   мягко тормозит на своём месте. Ощущение: кнопка «пробила» границу страниц.

   Зеркало back-btn-fly-in ниже (из бывшего gallery.css). */
@keyframes cta-fly-in {
  /* СТАРТ +90°: головка внизу (6ч) — продолжение сальто после back-btn-fly-away.
     Прилетает справа, докручивается +90°→0°: головка 6ч → 3ч(вправо) = покой В ГАЛЕРЕЮ.
     ВАЖНО: САЛЬТО ЗАВЕРШАЕТСЯ РОВНО В МОМЕНТ ПЕРЕЛЁТА (55%) — дальше только
     отскок позиции БЕЗ вращения. Раньше вращение «дотягивалось» в отскоке →
     тело уже стояло, а пенис ещё крутился = рывок. */
  0% {
    transform: translateX(130vw) scale(0.7) rotate(90deg);
    opacity: 0;
    animation-timing-function: cubic-bezier(0.2, 0.6, 0.4, 1); /* разгон снаружи */
  }
  /* 0–55%: быстрый въезд в кадр СПРАВА. ВРАЩЕНИЕ ЗАВЕРШАЕТСЯ ЗДЕСЬ (0°),
     когда кнопка в крайней точке перелёта (−20px). */
  55% {
    transform: translateX(-20px) scale(1.06) rotate(0deg);
    opacity: 1;
    animation-timing-function: cubic-bezier(0, 0.85, 0.15, 1); /* мягкое торможение */
  }
  /* 55–100%: ЗАТУХАЮЩИЕ ОСЦИЛЛЯЦИИ позиции БЕЗ вращения (как при первом приезде).
     Амплитуда: −20 → +10 → −4 → 0 (каждый раз ~вдвое меньше). Направление то же,
     что и при первом приезде: кнопка прилетает справа, проскакивает влево,
     отскакивает вправо и т.д. Устаканивается в центре. */
  72% {
    transform: translateX(10px) scale(0.99) rotate(0deg);
    opacity: 1;
    animation-timing-function: ease-in-out; /* плавный отскок */
  }
  86% {
    transform: translateX(-4px) scale(1) rotate(0deg);
    opacity: 1;
    animation-timing-function: ease-in-out; /* плавный возврат */
  }
  100% {
    transform: translateX(0) scale(1) rotate(0deg);
    opacity: 1;
  }
}

/* Класс вешается JS-ом при прилёте (после возврата из галереи). */
.hero-cta.arriving {
  animation: cta-fly-in 1.1s forwards;
  pointer-events: none;   /* пока летит — не кликабельна */
}

/* ---- Низ страницы: мелкая подпись ----
   Разноцветная (каждая буква в span.char, цвет чередуется по кругу:
   жёлтый / красный / розовый / кремовый — по 4 цвета из палитры STYLE.md).
   Чтобы буквы читались на голубом фоне — добавляем тёмную обводку. */
.hero-footer {
  position: absolute;
  bottom: 2px;          /* почти в самой нижней кромке экрана (было 20px, потом 4px) */
  /* ⚠️ Раунд 25: ширина = ВЕСЬ VIEWPORT (100vw), НЕ ширина родителя.
     Раньше было left:0; right:0 → ширина = родитель. Но родитель галереи у́же
     главной: галерея длинная → есть скроллбар (~14px), который сжирает место
     справа; на главной скролла нет. nowrap-контент «© FUNNY DICKS · ALL RIGHTS
     RESERVED» на галерее не влезал → последние буквы обрезались через
     overflow:hidden. Симптом: «на главной футер нормальный, в галерее буквы
     пропадают». Лечим: растягиваем футер на 100vw и выравниваем по ОКНУ
     (left:50% от родителя + margin-left:-50vw сносит к левому краю viewport).
     vw не зависит от скроллбара → ширина одинакова на всех экранах.
     text-align:center работает честно: контент короче 100vw → помещается. */
  left: 50%;
  margin-left: -50vw;
  width: 100vw;
  right: auto;
  z-index: 10;
  box-sizing: border-box;
  padding: 0 8px;       /* боковой запас под раздувание букв в волне (scale 1.7) */
  overflow: hidden;     /* страховка: если что-то всё же вылезет — не порождаем полосу */
  font-family: var(--font-display);
  /* Адаптивный размер: сжимается на узких экранах, чтобы вся строка
     «© FUNNY DICKS · ALL RIGHTS RESERVED» гарантированно влезала.
     vw-составляющая делает шрифт пропорциональным ширине окна → на мобиле
     (320–400px) мелкий, на десктопе — 0.85rem. Раньше было фиксированное
     0.85rem → последняя D в RESERVED обрезалась на узких экранах. */
  font-size: clamp(0.5rem, 3vw, 0.85rem);
  letter-spacing: 3px;
  text-align: center;
  white-space: nowrap;
  -webkit-text-stroke: 0.6px var(--ink);
  paint-order: stroke fill;
}

.hero-footer .char {
  display: inline-block;
  transform-origin: center;                 /* растём из центра буквы */
  animation: footer-wave 3.5s linear infinite;  /* 3.5с = волна 2с + пауза ~1с */
}

/* Буква I — картинка пениса внутри span.char (как в заголовке FUNNY DICKS).
   img лежит ВНУТРИ .char, поэтому участвует в волне через scale родителя.
   Цвет ей не нужен (она цветная) — но порядок :nth-child не сбивается
   (span остаётся на своём месте). */
.hero-footer .letter-img {
  display: inline-block;
  height: 0.95em;           /* под высоту мелких букв футера */
  width: auto;
  vertical-align: -0.15em;  /* стоит на базовой линии, как буквы */
  margin: 0 -0.12em;        /* подтягиваем соседние буквы (D/C, S/N, R/G) */
}
/* Чередуем 4 цвета по кругу */
.hero-footer .char:nth-child(4n+1) { color: var(--yellow); }
.hero-footer .char:nth-child(4n+2) { color: var(--red);    }
.hero-footer .char:nth-child(4n+3) { color: var(--pink);   }
.hero-footer .char:nth-child(4n+4) { color: var(--cream);  }

/* 🌊 Бегущая волна в футере: каждая буква по очереди «набухает» и возвращается.
   animation-delay выставляет JS (initFooterWave) — равномерно распределяет 2с
   между всеми буквами. Активная фаза — первые 10% цикла (~0.35с), дальше буква
   в покое. Это даёт «горб» шириной ~6 букв, плавно катящийся слева направо.
   Цикл 3.5с = волна 2с (от старта 1-й до старта последней) + пауза ~1с. */
@keyframes footer-wave {
  0%   { transform: scale(1); }
  5%   { transform: scale(1.7); }
  10%  { transform: scale(1); }
  100% { transform: scale(1); }
}

/* ---- Адаптив: на мобильных делаем шары меньше ----
   ВАЖНО: масштабируем контейнер .balloons целиком, а не отдельные .balloon.
   Если бы масштабировали каждый шар через transform: scale(), анимация
   balloon-rise (тоже использующая transform) перезаписала бы масштаб. */
@media (max-width: 600px) {
  .balloons { transform: scale(0.7); transform-origin: center top; }
  /* Шары на мобиле летят по увеличенной траектории (см. @keyframes
     balloon-rise-mobile выше) — иначе после scale(0.7) они стартуют не
     за краем, а внутри видимой области и видны при появлении.
     Продолжительность/delay остаются от модификаторов (.b1, .gb1...). */
  .balloon { animation-name: balloon-rise-mobile; }

  /* 🎈 РАЗНОС ШАРОВ на мобиле, чтобы не слипались.
     На узком экране %-позиции десктопа давали почти одинаковые абсолютные
     координаты (b8 на 68% + его ширина 78px сталкивались с b4 справа).
     Здесь раскидываем три шара в три чёткие зоны: b1 — левый край, b8 —
     центр, b4 — правый край. Между ними гарантированный зазор даже с учётом
     покачивания translateX(±20px) в keyframes. Десктоп не трогаем. */
  .hero .balloon.b1 { left: 5%; }
  .hero .balloon.b8 { left: 38%; }
  .hero .balloon.b4 { right: 5%; }

  .hero-title {
    font-size: clamp(2rem, 13vw, 4rem);   /* на узких экранах ещё меньше */
    -webkit-text-stroke: 1.5px var(--ink);
  }
  .hero-footer { letter-spacing: 1px; }   /* font-size уже адаптивный через clamp() выше */
}

/* ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████
   ████  БЫЛО: css/gallery.css — стили галереи (страница 2)  ████████████████
   ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████ */

.gallery-page {
  position: relative;          /* чтобы звёзды позиционировались относительно страницы */
  min-height: 100vh;
  padding: 0 20px 0;          /* ⚠️ БЕЗ нижнего паддинга. Раньше было padding:0 20px 60px,
                                 но 60px снизу создавали дырку под футером галереи:
                                 .hero-footer прилеплен к низу .gallery-content
                                 (position:absolute; bottom:2px), а .gallery-content
                                 кончается вместе с .gallery-page за вычетом паддинга →
                                 футер зависал на ~73px выше низа страницы. Без паддинга
                                 футер встаёт у самой кромки. Кнопка BACK (fixed к окну,
                                 bottom:60px) НЕ конфликтует — проверено замером:
                                 overlap футер↔кнопка = 0px (Раунд 8). */
  overflow-x: clip;            /* 🐛 ВАЖНО: clip, НЕ hidden! По спеке CSS Overflow,
                                 если одна ось overflow-x:hidden/scroll/auto, а
                                 другая visible → visible АВТОМАТОМ становится auto.
                                 Это превращало .gallery-page в скролл-контейнер со
                                 СВОИМ красным ::-webkit-scrollbar (глобальное
                                 правило). В покое контент влезал → полосы не было.
                                 НО при открытии сезона карточки осыпаются вниз,
                                 .gallery-page раздувался → переполнение → вспыхивала
                                 красная полоса справа. overflow-x:clip обрезает
                                 горизонталь так же, но НЕ делает элемент скролл-
                                 контейнером → overflow-y остаётся visible →
                                 скроллит body (как и должно быть). Поддержка:
                                 Chrome 90+/Firefox 81+/Safari 16+. */
  /* Небо/солнце/градиент убраны — рисуются общим слоем .sky на <body>. Галерея
     прозрачна → небо из .sky видно сквозь неё, и при slide-переходе двигается
     только контент (карточки), а фон стоит → бесшовный переход. */
  background: transparent;
}

/* ☀️ СОЛНЦЕ-ЗАСВЕТ — отдельный fixed-слой (как облачка и шатёр), лежит поверх неба,
   под контентом. position: fixed → статичен при скролле + градиент считается по ОКНУ
   (не растягивается на длину документа). Работает везде, включая iOS (в отличие от
   background-attachment: fixed). */
/* ██████████████████████████████████████████████████████████████████████████
   🌤️ ЕДИНЫЙ ФОН НЕБА (.sky на <body>) — общий для обоих экранов.
   Вынесен из экранов, чтобы при slide-переходе главная↔галерея двигался
   только КОНТЕНТ, а небо стояло на месте → бесшовный фон.
   Fixed, под птицами(z:3) и контентом(z:10). Лёгкий параллакс-сдвиг в
   сторону перехода делается GSAP (transform на .sky). */
.sky {
  position: fixed;
  top: 0;
  left: 0;
  /* ⚠️ width: 100vw (НЕ 100%!). Для fixed-элемента width:100% считается от
     documentElement, и тянется за documentElement.scrollWidth — который
     раздувается при slide-переходе (галерея стоит за правым краем через
     translateX(115%) → scrollWidth растёт с 375 до ~807px на мобиле). Тогда
     .sky растягивается до 807px, .bg-photos (width:100% от .sky) тоже, фото
     (width:100% от .bg-photos) пересчитывает object-fit:cover → СКАЧОК фото
     на ~33px влево при клике «в галерею» (баг Раунда 6, корень был ЗДЕСЬ, а не
     в Ken Burns). 100vw жёстко привязано к viewport → .sky всегда 375px,
     фото не пересчитывается → скачка нет. Проверено замером на телефоне
     (diag-sky-width-test.js): width:100vw убирает скачок до 0px. */
  width: 100vw;
  height: 100vh;        /* фолбэк для совсем старых браузеров */
  height: 100lvh;       /* large viewport: всегда максимум (с запасом под адресную
                           строку мобилы). 100dvh «дышит» с адр.строкой и в момент
                           её задвигания НЕ дорастает до низа → снизу щель с голым
                           body (лазурь). 100lvh = самая большая высота → щели нет.
                           Проверено на телефоне инъекцией (Раунд 8). */
  z-index: 0;            /* под шарами экранов(2), птицами(3), контентом(10) */
  pointer-events: none;
  overflow: hidden;
  /* ⚠️ БЕЗ will-change: transform. С will-change браузер держит .sky на отдельном
     композитном слое — и при reflow экранов (is-transitioning ставит им
     position:absolute) этот слой ПЕРЕСОЗДАЁТСЯ → CSS-анимация cloud-drift на
     детях-облаках сбрасывалась к 0% («телепорт облака в начало»). Без
     will-change .sky — обычный fixed-слой, его анимации живут непрерывно. */
}

/* 🌤️ Вертикальный градиент неба: лазурь вверху → светлеет книзу. */
.sky .sky-gradient {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, #6bb8e6 0%, var(--sky-blue) 35%, #a8d5f0 70%, #d4ecf7 100%);
}

/* ☀️ Осветлённое небо — проявляется при скролле галереи вниз (JS opacity 0→1).
   Эффект «приближаемся к земле, небо светлеет». Лежит поверх .sky-gradient. */
.sky .sky-gradient-light {
  position: absolute;
  inset: 0;
  opacity: 0;            /* JS (initSkyParallax) поднимает при скролле */
  background: linear-gradient(180deg, #95cbea 0%, #9ed3ef 35%, #c0e2f5 70%, #e8f5fc 100%);
}

/* ☀️ Солнце-засвет в левом верхнем углу. Поверх градиентов неба. */
.sky .sun-glow {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 0% 0%, rgba(255, 253, 235, 1) 0%, rgba(255, 240, 160, 1) 4%, rgba(255, 210, 60, 0.9) 10%, rgba(255, 196, 0, 0.45) 18%, transparent 30%);
}

/* ☁️ Облака (белые, мультяшные, медленно плывут). z-index не нужен — порядок
   в DOM после солнца → рисуются поверх. */
.sky .clouds {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
/* Позиции/скорость общих облаков (префикс sc).
   ⚡ Верхнее облако плывёт ЗНАЧИМО быстрее нижнего (ветер сильнее на высоте —
   честная физика). Раньше было 78с/92с (разница ~15%) — глазом почти не
   читалось. Сейчас 55с/100с (разница ~45%) — явный контраст скоростей. */
.cloud.sc1 { top: 13%;  font-size: 24px; animation-duration: 55s; animation-delay: 0s;    }
.cloud.sc2 { top: 30%;  font-size: 33px; animation-duration: 100s; animation-delay: -35s;  }

/* (.gallery-page .confetti удалён — звёзды убраны полностью) */

/* ---- Воздушные шары в галерее ----
   Контейнер FIXED: галерея — длинная прокручиваемая страница, поэтому
   привязываем поток к ОКНУ браузера, а не к высоте документа. Тогда
   шары всегда летят в видимой области, как живой фон при любом скролле.
   z-index:0 — под карточками (у .gallery-grid выше), но над фоном. */
.gallery-page .balloons {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

/* Позиции и скорость шаров галереи (префикс gb).
   Цвета убраны — ствол/яйца у всех телесные, головка красная (как на главной). */
.balloon.gb1  { left: 5%;   animation-duration: 12s; animation-delay: 0s;   }
.balloon.gb2  { left: 18%;  animation-duration: 14s; animation-delay: -4s;  width: 80px; height: 170px; }
.balloon.gb3  { right: 12%; animation-duration: 10s; animation-delay: -7s;  width: 60px; height: 125px; }
.balloon.gb4  { right: 5%;  animation-duration: 16s; animation-delay: -2s;  width: 90px; height: 190px; }
.balloon.gb5  { left: 48%;  animation-duration: 9s;  animation-delay: -5s;  width: 55px; height: 115px; }
.balloon.gb6  { left: 38%;  animation-duration: 13s; animation-delay: -9s;  width: 50px; height: 105px; }
.balloon.gb7  { left: 28%;  animation-duration: 11s; animation-delay: -1s;  width: 58px; height: 120px; }
.balloon.gb8  { left: 65%;  animation-duration: 15s; animation-delay: -6s;  width: 72px; height: 152px; }
.balloon.gb9  { left: 80%;  animation-duration: 10s; animation-delay: -10s; width: 48px; height: 100px; }
.balloon.gb10 { left: 12%;  animation-duration: 13s; animation-delay: -8s;  width: 64px; height: 135px; }

/* ---- Контейнер лого галереи (прокручивается вместе со страницей) ----
   Простой блок-заголовок: не sticky, не fixed — едет со скроллом.
   Текст: THE FUNNY DICKS GALLERY. */
.gallery-logo-wrap {
  position: relative;
  z-index: 10;            /* выше шаров/звёзд фона, но под модалкой */
  text-align: center;
  margin-top: 95px;      /* шатёр СНЯТ (проект «сезонов»). Лого чуть ниже верхнего
                             ряда кнопок (mute 🔊 + НАЗАД). Раньше было 175px (150px
                             шатра + 25px зазор), когда шатёр висел сверху. */
  margin-bottom: 40px;
  padding: 0 20px;
}

.gallery-logo {
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 4vw, 3rem);
  color: var(--cream);
  /* Разрешаем перенос МЕЖДУ словами (но не внутри — см. .word ниже).
     Раньше nowrap держал всё в одну строку → на мобиле налезало на кнопку
     НАЗАД или разрывало DICKS после картинки-члена. */
  white-space: normal;
  letter-spacing: 2px;
  line-height: 1.1;
  /* Тёмная обводка как у заголовка главной */
  -webkit-text-stroke: 1.5px var(--ink);
  paint-order: stroke fill;
  text-shadow: 3px 3px 0 var(--blue);
}

/* Каждое слово — неразрывный блок. Перенос строки возможен только МЕЖДУ
   словами (между .word), внутри слова никогда. Это убирает рваный разрыв
   DICKS прямо после картинки-пениса на мобиле. */
.gallery-logo .word {
  display: inline-block;
  white-space: nowrap;
  margin: 0 0.15em;   /* лёгкий отступ между словами вместо старых span.space */
}

/* РАЗНОЦВЕТНЫЕ БУКВЫ в "THE FUNNY DICKS GALLERY".
   Раньше раскраска шла через nth-child (считала детей .gallery-logo), но
   теперь слова обёрнуты в .word → буквы стали внуками → счётчик сломался
   бы. Перешли на ЯВНЫЕ цветовые классы на каждой букве — надёжно и не
   зависит от того, как обёрнуты слова.
   Палитра: жёлтый/красный/розовый/кремовый (фирменная). */
.gallery-logo .c-cream  { color: var(--cream);  }
.gallery-logo .c-yellow { color: var(--yellow); }
.gallery-logo .c-red    { color: var(--red);    }
.gallery-logo .c-pink   { color: var(--pink);   }

/* Буквы THE и GALLERY — чуть мельче и полегче, чтобы FUNNY DICKS
   оставалось главным акцентом в строке. */
.gallery-logo .char.small {
  font-size: 0.62em;
  opacity: 0.9;
}

/* «Буква» I — картинка-пенис в логотипе (та же картинка, меньше) */
.gallery-logo .letter-img {
  display: inline-block;
  height: 0.85em;
  width: auto;
  vertical-align: baseline;  /* НЕ двигаем через align — ломает соседей */
  position: relative;        /* двигаем ТОЛЬКО через top */
  top: 0.1em;
  margin: 0 0.04em 0 0.02em; /* слева +0.02em (D не заходит), справа +0.04em (C ближе) */
  filter: drop-shadow(3px 3px 0 var(--blue));
  animation: wiggle 3.5s ease-in-out infinite;
  transform-origin: center bottom;
}

/* Пробел между словами в логотипе */
.gallery-logo .space {
  display: inline-block;
  width: 0.4em;
}

/* ---- Кнопка НАЗАД: ФИКСИРОВАННАЯ, в НИЖНЕМ правом углу (thumb zone) ----
   position: fixed; bottom — висит в ПРАВОМ НИЖНЕМ углу окна и НЕ двигается
   при прокрутке. Так её можно нажать из любого места галереи.
   Перенесена ВНИЗ для мобил: «thumb zone» — зона большого пальца. Сайтом
   пользуются в основном с телефонов, тянуться в правый верх неудобно.
   z-index: 60 — выше карточек (10) и лого, но ниже модалки (1000).
   Раньше висела в правом верхнем (под шатром на 174px, потом на 78px). */
.back-btn {
  position: fixed;
  bottom: 60px;          /* ПЕРЕНЕСЕНО ВНИЗ: «thumb zone» — зона большого пальца
                            на мобиле. Сайтом пользуются в основном с телефонов,
                            тянуться в правый верхний угол неудобно. */
  right: 20px;
  z-index: 60;
  display: inline-block;
  cursor: pointer;
  text-decoration: none;
  /* 🌞 ДЫХАНИЕ С ТЕНЬЮ (физика-оптика) через общую анимацию back-breathe:
     transform (масштаб) и filter (тень) в ОДНОЙ анимации → идеальная синхронность.
     Вдох (растёт) → тень рассеивается; выдох (возвращается) → тень уплотняется.
     Отзеркаливание scaleX(-1) у .dick-body НЕ конфликтует (отдельный элемент со своим transform). */
  animation: back-breathe 2s ease-in-out infinite;
}

/* HOVER: только смена цвета (.dick-path краснеет). Дыхание + тень ПРОДОЛЖАЮТСЯ.
   ⚠️ НЕ трогаем transform/filter — ими управляет анимация. */
.back-btn:hover {
  /* меняется только цвет пениса/текста/стрелки (см. ниже) */
}

.back-btn:active {
  animation: none;   /* по нажатию — глушим дыхание, прижимаем тенью */
  filter:
    drop-shadow(2px 4px 0 rgba(26,26,46,0.45))
    drop-shadow(3px 6px 6px rgba(0,0,0,0.20));
}

/* SVG-силуэт. Отражаем scaleX(-1) → головка ВЛЕВО (домой).
   Текст/стрелка в обёртке — не отражаются, читаются нормально.
   Тень перенесена на внешнюю обёртку .back-btn (см. выше). */
.back-btn .dick-body {
  display: block;
  width: 210px;
  height: auto;
  transform: scaleX(-1);                /* головка влево */
}
.back-btn .dick-path {
  fill: var(--yellow);
  stroke: var(--ink);
  stroke-width: 3;
  stroke-linejoin: round;
  transition: fill 0.2s ease;
}
.back-btn:hover .dick-path { fill: var(--red); }

/* Текст на стволе. SVG отзеркален, но текст лежит в обёртке поверх — не отражён.
   🐛 GPU-фикс от дрожания при scale-дыхании кнопки (тот же приём, что у .sound-icon
   в mute): will-change + translateZ(0) + backface-visibility держат текст в стабильном
   GPU-слое → не пересчитывается каждый кадр → не дрожит. */
.back-btn .dick-label {
  position: absolute;
  top: 50%;
  left: 56%;            /* правее центра: у BACK головка СЛЕВА (отзеркалена),
                           стрелка занимает левую часть → текст сдвигаем вправо.
                           Зеркально к .hero-cta (там left:44%, головка справа). */
  transform: translate(-50%, -50%) translateZ(0);
  font-family: var(--font-display);
  font-size: 0.9rem;
  letter-spacing: 1px;
  white-space: nowrap;
  z-index: 5;
  pointer-events: none;
  color: var(--ink);
  will-change: transform;
  backface-visibility: hidden;
  transition: color 0.2s ease;
}
.back-btn:hover .dick-label { color: #fff; }

/* Стрелка ← у головки. У отзеркаленного SVG головка СЛЕВА → стрелка на 18%.
   GPU-фикс (тот же, что у надписи). */
.back-btn .dick-arrow {
  position: absolute;
  top: 50%;
  left: 18%;
  transform: translate(-50%, -50%) translateZ(0);
  font-family: var(--font-display);
  font-size: 1.05rem;
  z-index: 5;
  pointer-events: none;
  color: var(--ink);
  will-change: transform;
  backface-visibility: hidden;
  transition: transform 0.2s ease, color 0.2s ease;
}
.back-btn:hover .dick-arrow {
  color: #fff;
  transform: translate(-50%, -50%) translateX(-5px);
}

/* ---- ПОЛИРОВКА ЦВЕТА В ПОЛЁТЕ/ПРИЛЁТЕ (зеркало .hero-cta выше) ----
   Sticky hover на мобиле держит красный весь полёт. Пока летит (.flying) или
   прилетает (.arriving) — держим жёлтый. Сам красный отклик при тапе делает
   JS (см. initBackFlight в main.js) через класс .flash-red ниже. */
.back-btn.flying .dick-path,
.back-btn.arriving .dick-path { fill: var(--yellow); }

.back-btn.flying .dick-label,
.back-btn.arriving .dick-label { color: var(--ink); }

.back-btn.flying .dick-arrow,
.back-btn.arriving .dick-arrow {
  color: var(--ink);
  transform: translate(-50%, -50%);
}

/* Краткий красный отклик при тапе (зеркало .hero-cta.flash-red выше).
   Вешает JS (initBackFlight) на ~0.45с: тапнул → красная → жёлтая в полёте. */
.back-btn.flash-red .dick-path { fill: var(--red); }
.back-btn.flash-red .dick-label { color: #fff; }
.back-btn.flash-red .dick-arrow { color: #fff; transform: translate(-50%, -50%) translateX(-5px); }

/* ---- Анимация полёта кнопки «НАЗАД» при клике (зеркало «В ГАЛЕРЕЮ») ----
   Сценарий по просьбе Миши: при клике кнопка
     1) отбегает ВПРАВО («замахивается»),
     2) ДОЛГО и ПЛАВНО тормозит по инерции (мяч у края),
     3) РЕЗКО разгоняется ВЛЕВО и улетает за экран.

   ПЛАВНОСТЬ: timing-function задаётся по сегментам прямо в keyframes
   (разгон / долгое мягкое торможение / оседание / резкий полёт).
   Зеркало анимации .hero-cta.flying выше (из бывшего hero.css). */
@keyframes back-btn-fly-away {
  /* ЗАМАХ ПО ЧАСОВОЙ (в плюс) → КУВЫРОК ПРОТИВ ЧАСОВОЙ (в минус, до −90°).
     Зеркало cta-fly-away: 4 кадра, без промежуточного 75% (давал рывок).
     🌞 ТЕНЬ: filter на каждом кадре (как в back-breathe). .flying перебивает
     back-breathe → без filter здесь тень пропадала бы на время вылёта. */
  0% {
    transform: translateX(0) scale(1) rotate(0deg);
    opacity: 1;
    filter:
      drop-shadow(3px 6px 0 rgba(26,26,46,0.42))
      drop-shadow(4px 8px 6px rgba(0,0,0,0.20));
    animation-timing-function: cubic-bezier(0.4, 0, 0.7, 0.4); /* ease-in: разгон замаха */
  }
  /* 0–24%: разгон вправо, лёгкий наклон ПО часовой */
  24% {
    transform: translateX(55px) scale(0.96) rotate(4deg);
    opacity: 1;
    filter:
      drop-shadow(4px 7px 0 rgba(26,26,46,0.38))
      drop-shadow(5px 10px 9px rgba(0,0,0,0.17));
    animation-timing-function: cubic-bezier(0, 0.85, 0.15, 1); /* мягкий ease-out */
  }
  /* 24–48%: пик замаха, мягкий наклон ПО часовой. Кнопка «оторвалась» → тень
     рассеялась (длиннее/мягче/светлей). */
  48% {
    transform: translateX(85px) scale(0.93) rotate(8deg);
    opacity: 1;
    filter:
      drop-shadow(5px 9px 0 rgba(26,26,46,0.30))
      drop-shadow(7px 13px 13px rgba(0,0,0,0.13));
    animation-timing-function: ease-in-out; /* плавное оседание */
  }
  /* 48–58%: оседание. ВРАЩЕНИЕ ПЛАВНО ПРОХОДИТ ЧЕРЕЗ 0° (разворот завершён). */
  58% {
    transform: translateX(74px) scale(1.07) rotate(0deg);
    opacity: 1;
    filter:
      drop-shadow(5px 9px 0 rgba(26,26,46,0.30))
      drop-shadow(7px 13px 13px rgba(0,0,0,0.13));
    animation-timing-function: cubic-bezier(0.4, 0, 0.7, 0.4); /* разгон полёта */
  }
  /* 58–100%: полёт за экран + кувырок ПРОТИВ ЧАСОВОЙ до −90°.
     Головка 9ч(влево) → 6ч(вниз). Вторые 90° докрутятся на главной (cta-fly-in). */
  100% {
    transform: translateX(-130vw) scale(0.7) rotate(-90deg);
    opacity: 0;
    filter:
      drop-shadow(3px 6px 0 rgba(26,26,46,0.42))
      drop-shadow(4px 8px 6px rgba(0,0,0,0.20));
  }
}

/* Класс вешается JS-ом при клике. Timing-function — внутри keyframes. */
.back-btn.flying {
  animation: back-btn-fly-away 1s forwards;
  pointer-events: none;   /* чтобы не нажать повторно во время полёта */
}

/* ---- Анимация ПРИЛЁТА кнопки «НАЗАД» в галерею (слева → направо на место) ----
   Сценарий: мы пришли из главной (кнопка «В ГАЛЕРЕЮ» улетела ВПРАВО за экран).
   Здесь «НАЗАД» продолжает движение вправо — вылетает СЛЕВА за экраном и
   мягко тормозит на своём месте. Ощущение: кнопка «пробила» границу страниц.

   Timing-function по сегментам:
     • разгон снаружи (0→55%): быстрый въезд в кадр
     • мягкое торможение (55→80%): докатывается по инерции, чуть пружинит
     • оседание (80→100%): встаёт на место */
@keyframes back-btn-fly-in {
  /* СТАРТ −90°: головка внизу (6ч) — продолжение сальто после cta-fly-away.
     Прилетает слева, докручивается −90°→0°: головка 6ч → 9ч(влево) = покой НАЗАД.
     ВАЖНО: САЛЬТО ЗАВЕРШАЕТСЯ РОВНО В МОМЕНТ ПЕРЕЛЁТА (55%) — дальше только
     отскок позиции БЕЗ вращения. Раньше вращение «дотягивалось» в отскоке →
     тело уже стояло, а пенис ещё крутился = рывок.
     🌞 ТЕНЬ: filter на каждом кадре (как в back-breathe). .arriving перебивает
     back-breathe → без filter здесь тень пропадала бы на время прилёта. */
  0% {
    transform: translateX(-130vw) scale(0.7) rotate(-90deg);
    opacity: 0;
    filter:
      drop-shadow(3px 6px 0 rgba(26,26,46,0.42))
      drop-shadow(4px 8px 6px rgba(0,0,0,0.20));
    animation-timing-function: cubic-bezier(0.2, 0.6, 0.4, 1); /* разгон снаружи */
  }
  /* 0–55%: быстрый въезд в кадр СЛЕВА. ВРАЩЕНИЕ ЗАВЕРШАЕТСЯ ЗДЕСЬ (0°),
     когда кнопка в крайней точке перелёта (+20px). */
  55% {
    transform: translateX(20px) scale(1.06) rotate(0deg);
    opacity: 1;
    filter:
      drop-shadow(5px 9px 0 rgba(26,26,46,0.30))
      drop-shadow(7px 13px 13px rgba(0,0,0,0.13));
    animation-timing-function: cubic-bezier(0, 0.85, 0.15, 1); /* мягкое торможение */
  }
  /* 55–100%: ЗАТУХАЮЩИЕ ОСЦИЛЛЯЦИИ позиции БЕЗ вращения (как при первом приезде).
     Зеркало cta-fly-in: кнопка прилетает слева, проскакивает вправо (+20),
     отскакивает влево (−10), обратно (+4) → устаканивается в центре. */
  72% {
    transform: translateX(-10px) scale(0.99) rotate(0deg);
    opacity: 1;
    filter:
      drop-shadow(4px 7px 0 rgba(26,26,46,0.38))
      drop-shadow(5px 10px 9px rgba(0,0,0,0.17));
    animation-timing-function: ease-in-out; /* плавный отскок */
  }
  86% {
    transform: translateX(4px) scale(1) rotate(0deg);
    opacity: 1;
    filter:
      drop-shadow(3px 6px 0 rgba(26,26,46,0.42))
      drop-shadow(4px 8px 6px rgba(0,0,0,0.20));
    animation-timing-function: ease-in-out; /* плавный возврат */
  }
  100% {
    transform: translateX(0) scale(1) rotate(0deg);
    opacity: 1;
    filter:
      drop-shadow(3px 6px 0 rgba(26,26,46,0.42))
      drop-shadow(4px 8px 6px rgba(0,0,0,0.20));
  }
}

/* Класс вешается JS-ом при прилёте (после перехода из главной).
   Дыхание (если было) перебивается animation-shorthand. После окончания
   JS снимет класс — тогда вернутся обычные hover/active. */
.back-btn.arriving {
  animation: back-btn-fly-in 1.1s forwards;
  pointer-events: none;   /* пока летит — не кликабельна */
}

/* ---- Заголовок-описание секции ---- */
.gallery-intro {
  position: relative;        /* над потоком шаров (z-index ниже .balloons) */
  z-index: 10;
  text-align: center;
  margin-bottom: 40px;
}

.gallery-intro h2 {
  color: var(--blue);
  font-size: clamp(2rem, 5vw, 3.5rem);
  text-shadow: 3px 3px 0 var(--cream);
  margin-bottom: 10px;
  -webkit-text-stroke: 1px var(--ink);
  paint-order: stroke fill;
}

.gallery-intro p {
  font-family: var(--font-display);
  color: var(--red);
  letter-spacing: 2px;
  font-size: clamp(0.9rem, 1.5vw, 1.1rem);
  -webkit-text-stroke: 0.5px var(--ink);
  paint-order: stroke fill;
}

/* (Звёзды галереи .star.gs1-gs16 удалены — звёзды убраны полностью) */

/* ---- Сетка галереи ---- */
.gallery-grid {
  position: relative;        /* над потоком шаров */
  z-index: 10;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 24px;
  max-width: 1200px;
  margin: 0 auto;
  /* Доп. отступ сверху: пенис-кнопка НАЗАД (с яйцами) выступает ниже прежней
     таблетки и задевала верхние карточки. Сдвигаем сетку вниз.
     На мобиле кнопка меньше (см. media), но отступ тут один на все экраны. */
  padding-top: 36px;
}

/* ---- Карточка ---- */
.card {
  position: relative;
  background: #fff;
  border: 5px solid var(--ink);
  border-radius: 18px;
  overflow: hidden;
  cursor: pointer;
  box-shadow: 0 6px 0 var(--ink), 0 10px 20px rgba(0,0,0,0.15);
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.25s ease;
  /* стартовое состояние для GSAP-анимации появления */
  opacity: 0;
}

.card:nth-child(4n+1) { background: var(--yellow); }
.card:nth-child(4n+2) { background: var(--pink); }
.card:nth-child(4n+3) { background: var(--red); }
.card:nth-child(4n+4) { background: var(--blue); }

.card:hover {
  transform: translateY(-12px) rotate(-2deg) scale(1.03);
  box-shadow: 0 14px 0 var(--ink), 0 20px 35px rgba(0,0,0,0.25);
  z-index: 10;
}

.card-image-wrap {
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: var(--cream);
}

.card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}

.card:hover img {
  transform: scale(1.08);
}

/* Подпись снизу карточки */
.card-label {
  font-family: var(--font-display);
  text-align: center;
  padding: 10px 8px;
  color: var(--ink);
  font-size: 1rem;
  letter-spacing: 1px;
  border-top: 4px solid var(--ink);
}

/* ============================================================
   ПОСАДОЧНАЯ СТРАНИЦА: ПОСТЕРЫ СЕЗОНОВ
   Метафора — «сезоны как у сериала»: каждый сезон = афиша-постер.
   Визуальный язык наследуем у карточек галереи (.card): толстая
   обводка var(--ink), тень-«ступенька», скругление, мультяшность.
   Сетка авто-перенос: при добавлении новых сезонов карточки сами
   встанут на новые строки — ничего подкручивать не надо.
   ============================================================ */
.seasons-grid {
  position: relative;
  z-index: 10;
  display: grid;
  /* auto-fill + minmax: карточки переносятся сами при любом числе сезонов.
     320px минимум — постеры крупные, хорошо читаются. На широком экране
     влезет до 3 колонок (3×320=960 < max-width 1200), на узком — меньше. */
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 28px;
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px 20px 60px;
}

/* ---- Карточка сезона (постер + табличка снизу) ---- */
.season-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--cream);
  border: 4px solid var(--ink);
  border-radius: 20px;
  overflow: hidden;
  /* Тень под 45° — свет «светит» сверху-слева, тень падает вниз-вправо.
     Эффект объёма: карточка «оторвана» от фона. Тень УВЕЛИЧЕНА для бóльшего
     объёма. Состав: тяжёлая мультяшная кромка (сдвиг по X и Y, без размытия) +
     мягкий полупрозрачный ореол (сдвиг больше + blur + растяжение). */
  box-shadow: 10px 10px 0 rgba(26, 26, 46, 0.4),
              16px 16px 28px rgba(0, 0, 0, 0.22);
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
              box-shadow 0.25s ease,
              /* 🎈 translate сглаживает СКАЧОК при запуске парения: после перехода
                 (снятие is-transitioning) парение стартует с произвольной фазы, а
                 карточка стояла в translate(0). transition на translate плавно
                 «введёт» её в фазу за 0.5с вместо перескока. На саму анимацию
                 парения не влияет (animation перекрывает transition). */
              translate 0.5s ease-out;
  /* пока постеры не кликабельны (страницы сезонов — шаг 4) */
  cursor: default;
}

/* 🎈 ПАРЕНИЕ карточек в покое. Каждая карточка медленно «дышит» смещением
   (плавает на пару пикселей влево-вправо/вверх-вниз), как подвешенная в воздухе.
   Амплитуда/ритм/фаза — ИНДИВИДУАЛЬНЫЕ для каждой карточки через CSS-переменные
   --float-px (пиксели размаха, по умолчанию 5px), --float-dur (период, ~9-13с),
   --float-delay (сдвиг фазы). Задаются ОДИН раз при загрузке в initSeasonCards
   (как --tilt) → у каждой карточки «свой характер», а не синхрон.
   ⚠️ КЛЮЧЕВОЙ ТРЮК: парение анимирует ОТДЕЛЬНОЕ CSS-свойство `translate`, НЕ `transform`!
   `translate` (как и `rotate`/`scale`) — это самостоятельное свойство, браузер
   складывает его с `transform`. А значит парение (translate) и рост карточки при
   наведении (transform: scale+rotate) НЕ КОНФЛИКТУЮТ → при :hover карточка
   растёт ПЛАВНО через transition, а не перескоком. Раньше парение было на transform,
   и `animation:none` при hover срубало его мгновенно → перескок роста (репорт Миши).
   Поддержка `translate` — Chrome 104+ / Firefox 72+ / Safari 14.1+. Xiaomi Chrome Миши ок.
   ⚠️ БЕЗ will-change/translateZ — GPU-слой замыливает текст названия сезона на ПК
   (Раунд 27). Заморозка: парение паузится, когда галерея не видна (Фикс 1) —
   animation-play-state:paused срабатывает на `animation` независимо от того, какое
   свойство она анимирует (translate тут). Тикает ТОЛЬКО на активной галерее.
   Подкрутить амплитуду → одно число FLOAT_PX в main.js. */
@keyframes season-float {
  0%   { translate: 0 0; }
  25%  { translate: var(--float-px, 5px) calc(var(--float-px, 5px) * -0.7); }
  50%  { translate: 0 calc(var(--float-px, 5px) * -1); }
  75%  { translate: calc(var(--float-px, 5px) * -1) calc(var(--float-px, 5px) * -0.4); }
  100% { translate: 0 0; }
}
.season-card {
  animation: season-float var(--float-dur, 9s) ease-in-out infinite;
  animation-delay: var(--float-delay, 0s);
}

/* 🎈 ВЫДЕЛЕННАЯ карточка замирает (перестаёт парить). При :hover (ПК) /
   .is-active (мобила, тап) парение выключаем — карточка растёт/наклоняется
   через transform, парение ей мешало бы визуально.
   Поскольку парение теперь на ОТДЕЛЬНОМ свойстве `translate`, его гашение
   (animation:none) НЕ мешает transition на `transform` → рост при наведении
   остаётся плавным (исправление перескока).
   .is-flying — выбранная карточка в полёте (оригинал) И клон #diveClone (на него
   тоже вешается is-flying, main.js:1094). Клон зумится GSAP-transform → парение
   на translate ему не мешает напрямую, НО клон ещё и position:fixed + GSAP ставит
   translate-property → конфликт возможен. Гасим для надёжности. */
.season-card:hover,
.season-card.is-active,
.season-card.is-flying {
  animation: none;
}
/* 🛬 ПРИ ОСЫПАНИИ (переход галерея↔сезон) парение глушим у ВСЕХ карточек на время
   реальной анимации. body.is-season-transitioning = кроссфейд галерея↔сезон. В этом
   окне GSAP анимирует transform карточек (осыпание/сборка из центра) — CSS-анимация
   парения перебила бы его. Класс снимается в onComplete → парение возобновляется.
   ТЗ Миши: «остальные отваливаются, по возврату снова парят».
   ⚠️ НЕ гасим при slide главная↔галерея (body.is-transitioning)! Парение должно
   идти НЕПРЕРЫВНО (карточки парят всегда, когда галерея видна). Если глушить на
   slide и потом снимать — карточки скачут в свои случайные фазы (прыжок, репорт
   Миши). И НЕ привязываемся к .no-hover (страховка hover, снимается по touchstart)
   и к float-starting — оба давали прыжок при снятии. */
body.is-season-transitioning .season-card {
  animation: none;
}
/* 🚨 ИСКЛЮЧЕНИЕ: .is-confirming (мигание карточки при выборе) должно работать
   ДАЖЕ когда карточка is-flying (а она именно в этот момент и летит — класс
   вешается в openSeason ПЕРЕД is-confirming). Правила выше гасят animation
   через .is-flying/hover — а мигание у нас на animation (opacity). Без этого
   исключения .is-flying перебивал бы season-confirm-flash → карточка не моргала.
   Специфичность: .season-card.is-confirming (0,2,0) > .season-card.is-flying (0,2,0),
   но .is-flying стоит ПОЗЖЕ в исходнике → выигрывает. Выносим исключение сюда
   (после всех animation:none) и повышаем специфичность через body — гарантия.
   ⏱️ animation-delay 0.3с: по просьбе Миши моргание стартует НЕ СРАЗУ после
   тапа по TO SEASON, а с задержкой 0.3с (карточка «замечает» выбор, потом
   реагирует). JS-снятие класса — через 0.3+0.8=1.1с (см. openSeason, main.js). */
body .season-card.is-confirming {
  animation: season-confirm-flash 0.2s steps(1, end) 0.7s 5 !important;
}

.season-card:hover,
.season-card.is-active {
  /* Карточка растёт (scale 1.12), отрывается от фона к зрителю и чуть наклоняется.
     🎲 Угол наклона — ИНДИВИДУАЛЬНЫЙ для каждой карточки через CSS-переменную
     --tilt (задаётся один раз при загрузке в initSeasonCards, случайный ±6°).
     Запас -1.5deg — если переменная почему-то не задалась.
     ⚠️ .is-active ВАЖЕН для мобилы (Раунд 12): на тачскрине при тапе вешается
     .is-active через JS. Раньше карточка росла через ЛИПКИЙ :hover от тапа, но
     при висящем .no-hover (страховка от залипания) :hover блокируется → карточка
     НЕ росла (репорт Миши: «карточка при выборе перестала скейлиться»). Теперь
     рост привязан и к .is-active → честно работает при тапе, без зависимости от
     :hover. ПК получает то же правило через настоящий :hover. */
  transform: translateY(-10px) rotate(var(--tilt, -1.5deg)) scale(1.12);
  /* ТЕНЬ ПО ФИЗИКЕ. Сцена: солнце вверху-слева-ВПЕРЕДИ (ближе к зрителю, чем
     карточка); карточка дальше; фон ещё дальше. При hover карточка ОТРЫВАЕТСЯ
     к зрителю = приближается к фронтальному солнцу → по ходу лучей тень на
     заднем фоне ПОЛЗЁТ ВНИЗ (геометрия: чем ближе объект к фронтальному свету,
     тем ниже его тень на дальнем фоне). Плюс солнце слева → тень вправо.
     Итог: тень смещается ВНИЗ-ВПРАВО, УГОЛ КРУТИТСЯ ВНИЗ (с 45° до ~56° —
     вертикаль Y растёт сильнее горизонтали X). Дополнительно: карточка
     оторвалась от фона → больше расстояния → тень длиннее и мягче (blur ↑);
     рассеялась по большей площади → светлеет (alpha ↓).
     Состав: жёсткая кромка (контактная тень) + мягкий ореол (диффузная, вытянутая
     вниз-вправо). Обе на одном угле ~56°, чтобы читались как одна тень. */
  box-shadow: 13px 19px 4px rgba(26, 26, 46, 0.37),
              22px 32px 48px rgba(0, 0, 0, 0.17);
  z-index: 10;
}

/* 🥶 БЛОКИРОВКА HOVER после возврата из сезона / на время полёта (Раунд 23).
   JS (blockGalleryHover / resetGalleryHover) вешает .no-hover на .seasons-grid.
   Курсор может стоять на карточке → :hover мгновенно активировал бы её.
   Раньше блок перебивал только часть hover-эффектов (transform, cta, зум) →
   бейдж SEASON и подпись всё равно исчезали, Ken Burns зум продолжался.
   РЕШЕНИЕ: pointer-events:none на карточках (только для настоящей мыши).
   Тогда :hover физически не срабатывает — карточка остаётся в ПОЛНОМ покое
   до первого движения мыши (когда JS снимет .no-hover). На мобиле (hover:none)
   правило не применяется — там двухтаповая модель, кнопка через .is-active. */
@media (hover: hover) and (pointer: fine) {
  .seasons-grid.no-hover .season-card { pointer-events: none; }
}
.seasons-grid.no-hover .season-card:hover,
.seasons-grid.no-hover .season-card:hover .season-cta,
.seasons-grid.no-hover .season-card:hover .season-poster img,
.seasons-grid.no-hover .season-card.is-active {
  /* ВАЖНО: задаём ТЕ ЖЕ значения, что в покое → transition плавно вернёт
     карточку в дефолт, а :hover не сработает. */
  transform: none;
  box-shadow: 10px 10px 0 rgba(26, 26, 46, 0.4), 16px 16px 28px rgba(0, 0, 0, 0.22);
  z-index: auto;
}
.seasons-grid.no-hover .season-card:hover .season-cta { opacity: 0; transform: scale(0.3); }

/* ✈️ ЗАМОРОЗКА карточки при вылете клона (.is-flying). При старте перехода
   openSeason вешает .is-flying на карточку. Задача класса — держать бейдж
   скрытым (чтобы при потере :hover он не проступил под улетевшей cta) и
   ЗАМОРОЗИТЬ Ken Burns на текущем кадре (НЕ сбрасывать!).
   ⚠️ Раунд «НЫРОК»: РАНЬШЕ было `animation: none` — это СБРАСЫВАЛО Ken Burns в
   начало → картинка внутри карточки перескакивала из «зум под курсором» в
   исходное состояние → виден скачок перед нырком. Лечим `animation-play-state:
   paused` — анимация встаёт НА ТЕКУЩЕМ КАДРЕ, без сброса → зум продолжается с
   той же точки на клоне (мы копируем innerHTML в clone — анимация пойдёт с
   того же кадра, только на клоне). */
.season-card.is-flying .season-meta-default { opacity: 0; }
.season-card.is-flying .season-poster img { animation-play-state: paused; }

/* 🤿 РАУНД «НЫРОК»: стили для КЛОНА карточки (#diveClone), который делает зум.
   Клон создаётся JS в openSeason Ф3' и goBack (через card.cloneNode(true)).
   Кладётся position:fixed на body, z-index:45.
   ⚠️ Корень «полоски буквой Г»: это box-shadow карточки (10×10px ступенька +
   мягкий ореол). При зуме в 5× тень превращается в жирную полосу по низу и
   правому краю → читалась как «полоска буквой Г под кнопкой-пенисом». На клоне
   при зуме объём не нужен → убираем box-shadow и hover (на всякий случай). */
.season-card.dive-clone {
  box-shadow: none !important;
  cursor: default;
  /* Фикс 2 перфоманса: клон зумится ×1→×3.93 в Ф3 «нырка». Без will-change GPU
     пересоздаёт текстуру каждый кадр (OnCreateSharedImage 22-27мс в трейсе).
     will-change даёт клону постоянный GPU-слой заранее → GPU зумит уже готовую
     текстуру, не пересоздавая. Замыливание (ClearType, Раунд 27) НЕ страшно:
     на клоне нет мелкого текста (.season-cta вырезан), только название сезона,
     а на телефоне ClearType нет в принципе. */
  will-change: transform, opacity;
}
.season-card.dive-clone:hover {
  transform: none;
  box-shadow: none;
}
/* У клона НЕТ интерактивной кнопки (JS вырезает .season-cta при создании), но
   если она осталась — глушим её отображение. Симметрия: на клоне не нужны
   ни тень-ступенька кнопки, ни сама кнопка (она «улетает» как пенис-клон #dickClone). */
.season-card.dive-clone .season-cta { display: none !important; }

/* ✅ ПОДТВЕРЖДЕНИЕ ВЫБОРА. При клике на TO SEASON карточка 4 раза моргает
   (исчезает↔появляется целиком, вместе с обводкой и тенью), как сигнал
   «выбор принят». Анимация идёт 0.8с (4 × 0.2с, укладывается в Ф1 выбора
   openSeason, до отрыва клона). Класс вешается и снимается в openSeason.
   steps(1, end) = СТРОБОСКОП (резкий щелчок видим↔невидим, без плавного
   угасания) — читается как дискретный сигнал, а не как «затухание».
   Раньше (Раунд 18) мигала только обводка (border-color чёрный↔золотой) —
   по запросу Миши заменено на моргание всей карточки.
   ⚠️ САМО ПРАВИЛО animation живет ВЫШЕ (после body.is-season-transitioning),
   потому что его приходится защищать !important'ом от .is-flying (он гасит
   animation через animation:none). Здесь оставлен только @keyframes. */
@keyframes season-confirm-flash {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0; }
}

/* Постер — обёртка под картинку. Горизонтальный 4:3 (под широкоформатные фото). */
.season-poster {
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--ink);   /* тёмный фон, пока грузится картинка */
  border-bottom: 4px solid var(--ink);
}

.season-poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Точка, к которой зумит картинка. Задаётся ИНДИВИДИДУАЛЬНО для каждого сезона
     инлайн-стилем на карточке (style="--zoom-x: 65%; --zoom-y: 30%; --zoom-scale: 1.6").
     По умолчанию центр (50% 50%), масштаб 1.5. */
  transform-origin: var(--zoom-x, 50%) var(--zoom-y, 50%);
  /* Плавный старт/стоп цикла зума при входе/выходе из hover */
  transition: opacity 0.4s ease;
}

/* 🔁 ЦИКЛИЧЕСКИЙ ЗУМ Ken Burns: зум до --zoom-scale → мягкий возврат к 1 → снова,
   по кругу, пока карточка активна (hover на ПК / .is-active на мобиле).
   Точка зума (--zoom-x/--zoom-y) = transform-origin → картинка «наезжает» на нужную деталь.
   Цикл 12с: ~6с наезжает (медленный, как камера), ~6с отъезжает. ease-in-out = мягко. */
@keyframes season-ken-burns {
  0%   { transform: scale(1); }
  50%  { transform: scale(var(--zoom-scale, 1.5)); }
  100% { transform: scale(1); }
}

/* Включаем цикл зума при наведении (ПК) / .is-active (мобила). */
.season-card:hover .season-poster img,
.season-card.is-active .season-poster img {
  animation: season-ken-burns 12s ease-in-out infinite;
}

/* Табличка-паспорт под постером: название (всегда видно) + слот переключения
   (бейдж/описание по умолчанию ↔ кнопка TO SEASON / бейдж SOON при hover). */
.season-info {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 14px 12px 18px;
  text-align: center;
}

/* Бейдж (год/тип сезона). Мелкий, с обводкой — как ценник/марка.
   Лежит внутри .season-meta-default. */
.season-badge {
  align-self: center;
  font-family: var(--font-body);
  font-weight: 900;
  font-size: 0.72rem;
  letter-spacing: 2px;
  color: var(--ink);
  background: var(--yellow);
  border: 2px solid var(--ink);
  border-radius: 999px;
  padding: 2px 12px;
  margin-bottom: 4px;
  box-shadow: 0 2px 0 var(--ink);
}

/* Название сезона — «окошко» с одной строкой и скрытием переполнения.
   Короткие названия центрируются (text-align: center). Длинные —
   превращаются в бегущую строку (см. JS-детектор + .is-marquee ниже).
   Название ОСТАЁТСЯ видимым при наведении (просьба Миши) — исчезает только
   бейдж+описание, сменяясь кнопкой. */
.season-name {
  font-family: var(--font-display);
  font-size: 1.7rem;
  color: var(--ink);
  -webkit-text-stroke: 0.5px var(--ink);
  line-height: 1.3;
  text-shadow: 2px 2px 0 rgba(217, 38, 38, 0.25);
  width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-align: center;   /* ← короткие названия стоят по центру карточки */
  order: -1;            /* название всегда НАД слотом .season-meta */
}

/* =====================================================================
   СЛОТ ПЕРЕКЛЮЧЕНИЯ: бейдж+описание ↔ кнопка TO SEASON / бейдж SOON.
   Контейнер с фиксированной высотой + position:relative → два слоя внутри
   абсолютно позиционированы, перекрывают друг друга. При hover один fade'ится,
   другой проявляется → ВЫСОТА КАРТОЧКИ НЕ СКАЧЕТ (главное требование).
   ===================================================================== */
.season-meta {
  position: relative;
  width: 100%;
  /* Фиксированная высота под оба состояния. Кнопка-пенис (~70px высоты SVG)
     и бейдж+описание должны оба влезать без скачков. */
  min-height: 70px;
  margin-top: 2px;
}

/* Слой 1: бейдж + описание (виден по умолчанию). */
.season-meta-default {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  opacity: 1;
  transform: scale(1);
  transform-origin: center;
  /* СХЛОПЫВАНИЕ в центр при hover: scale → 0.3 (превращается в точку) + fade. */
  transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.55, 0, 0.65, 1);
}

/* Слой 2: кнопка-пенис TO SEASON (скрыта в покое, проявляется при hover).
   Та же мультяшная форма, что у TO GALLERY/BACK, но КОМПАКТНАЯ — карточки
   узкие. SVG-силуэт + текст на стволе + стрелка у головки.
   ЭФФЕКТ «ПОЯВЛЕНИЕ ИЗ ЦЕНТРА»: в покое сжата в точку (scale 0.3) + прозрачна,
   при hover вырастает из центра (как будто материализуется из точки, куда
   схлопнулся бейдж). transform-origin: center = та же центральная точка слота.
   ТЕНЬ ПО ФИЗИКЕ-ОПТИКЕ: тень на ВНЕШНЕЙ обёртке .season-cta (а не на .dick-body),
   чтобы она НЕ масштабировалась вместе с scale кнопки → держит честную физику.
   Пока кнопка сжата в точку (только появилась, прижата к фону) — тень МАЛЕНЬКАЯ,
   РЕЗКАЯ, ТЁМНАЯ. Когда выросла (scale 1, оторвалась к зрителю/свету) — тень
   БОЛЬШАЯ, МЯГКАЯ, СВЕТЛАЯ, сильнее вниз (солнце вверху-слева-ВПЕРЕДИ → чем ближе
   объект к фронтальному свету, тем ниже его тень на дальнем фоне). */
.season-cta {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  transform-origin: center;
  /* скрыто в покое: сжато в точку + прозрачное. */
  opacity: 0;
  transform: scale(0.3);
  /* 🐛 РАУНД «НЫРОК»: ГАШЕНИЕ «РАМКИ-УГЛА» (буквой Г снизу-справа от кнопки).
     После тапа на кнопке появляется box-shadow — копия тени .season-card
     (10px 10px 0 rgba(26,26,46,.4)). НИ в CSS, НИ в inline, НИ в GSAP-коде её
     нет — это GSAP-«протечка» через _gsap-кеш при анимации карточки/клона
     (та же семья грабель, что cloneNode в Ф3' копировал тень на клон — см.
     main.js ~1041). !important перебивает любое протекание. Проверено на
     телефоне через трассу (box-shadow держался 2с после тапа). */
  box-shadow: none !important;
  /* 🐛 ФИКС «ОДИН ТАП = ПЕРЕХОД» НА МОБИЛЕ: <a> с opacity:0 остаётся кликабельной!
     .season-cta сидит absolute inset:0 поверх ВСЕЙ карточки. В покое scale(0.3)
     сжимает зону клика до центра (как раз «где рожается кнопка»). Тап туда → клик
     ловила скрытая ссылка → openSeason → переход в сезон за 1 тап.
     pointer-events:none в покое пускает тап СКВОЗЬ скрытую кнопку → к карточке →
     1-й тап = .is-active (оживление), кнопка проявляется → 2-й тап по ней = переход.
     На ПК hover тоже переключает в auto, так что мышь кликает как раньше. */
  pointer-events: none;
  /* 🐛 ФИКС SUBPIXEL SNAP: пока scale анимируется, браузер рисует на GPU
     («как картинку»). В момент scale=1 переключается на CPU-рендер текста
     → надпись/стрелка дёргаются на пиксель вниз. will-change + backface-
     visibility ДЕРЖАТ элемент в GPU-слое постоянно → режим не переключается,
     рывка нет. Применяем к обёртке И к текстовым детям. */
  will-change: transform;
  backface-visibility: hidden;
  /* ПОЯВЛЕНИЕ из центра. ⚠️ БЕЗ filter здесь — тень живёт на .dick-wrap (см.
     ниже), одна и постоянная. Принцип Миши: тень НЕ меняется при hover, только
     если кнопка растёт/отрывается. Раньше filter тут + при hover давали скачок
     («ступеньку») и «тень не видна при наведении» —filter на весь .season-cta
     (position:absolute на карточку) ненадёжно отрисовывал тень. Теперь тень
     прямо на пенисе, постоянная, проявляется вместе с opacity. */
  transition: opacity 0.4s ease 0.18s,
              transform 0.45s cubic-bezier(0.16, 1, 0.3, 1) 0.18s;
}
/* Состояние «оторвалась к зрителю»: кнопка ПРОЯВЛЯЕТСЯ (opacity 1) и РАСТЁТ
   из точки (scale 0.3 → финал). ТЕНЬ НЕ ТРОГАЕМ — она постоянная на .dick-wrap.
   Раньше тут меняли filter → скачок; но opacity+transform обязаны быть (без них
   после 1-го цикла сброса cta кнопка не проявлялась при hover → казалась «не
   кликабельной»).
   🐛 РАУНД 14f+14h: финальный scale 0.893 (= 1/1.12) при И :hover, И .is-active.
   Карточка получает transform: scale(1.12) (от :hover — мышь ПК или залипший
   hover на мобиле после тапа) → её потомок cta растёт в 1.12 раза. Без
   компенсации cta на экране = 150×1.12 = 168px (больше BACK=150). Со scale 0.893
   cta на экране = 150×0.893×1.12 ≈ 150px = BACK на выдохе.
   ⚠️ Раунд 14h: раньше стояло в :hover 0.893, в .is-active 1.0 — но на мобиле
   при тапе срабатывают ОБА правила (sticky hover + JS .is-active), а .is-active
   идёт ПОЗЖЕ в файле → побеждало его scale(1) → компенсация не работала, cta
   оставалась 168-170px. ЛЕЧЕНИЕ: оба правила → scale(0.893). Замер Миши
   подтвердил cta=170 (а не 150) — компенсация реально не применялась. */
.season-card:hover .season-cta,
.season-card.is-active .season-cta {
  opacity: 1;
  transform: scale(0.893);
}

/* 🐛 КЛИКАБЕЛЬНОСТЬ кнопки — раздельно для ПК и мобилы.
   В покое .season-cta { pointer-events:none } (см. выше) — скрытая кнопка не
   ловит тап. Проявившаяся кнопка должна снова стать кликабельной:
   • На МОБИЛЕ — только через .is-active (JS-двухтаповка). НЕ через :hover! —
     Chrome Android активирует :hover прямо в момент тапа по карточке → тот же
     тап ушёл бы в .season-cta → openSeason → «один тап = переход» (Раунд 12).
   • На ПК — :hover (мышь). media query (hover:hover)+(pointer:fine) отсекает тач. */
.season-card.is-active .season-cta { pointer-events: auto; }
@media (hover: hover) and (pointer: fine) {
  .season-card:hover .season-cta { pointer-events: auto; }
}

/* Обёртка, плотно облегающая SVG-пенис. Делаем её inline-block + position:relative,
   чтобы надпись/стрелка позиционировались %-ами ИМЕННО от ширины пениса,
   а не от всего контейнера .season-cta (который на всю карточку и шире SVG).
   Раньше стрелка с left:78% считалась от ~300px карточки → улетала за пенис.
   ⚠️ Раунд 27 (ЧЁТКОСТЬ ТЕКСТА НА ПК): раньше здесь был will-change: transform
   И filter: drop-shadow. Любой filter/will-change/translateZ ВЫНОСИТ элемент
   на GPU-слой → на Windows там отключается ClearType (субпиксельное сглаживание
   шрифта) → надпись TO SEASON читалась «замыленной». На телефоне ClearType нет,
   поэтому баг виден только на ПК. Тень (drop-shadow) ПЕРЕНЕСЕНА на сам SVG
   (.dick-body ниже) — визуально та же тень от формы пениса, но текст больше
   не «обёрнут» фильтром. will-change и translateZ(0) убраны: без них обёртка
   не держит собственный GPU-слой → текст рендерится чётче. translateX(2.6%)
   оставлен (центрирование SVG), он слой не создаёт. */
.season-cta .dick-wrap {
  position: relative;
  display: inline-block;
  line-height: 0;            /* убирает щель под inline-элементом */
  /* 🐛 РАУНД 14g: translateX(2.6%) — компенсирует асимметрию SVG-формы пениса
     в viewBox 0 0 300 130. По замеру на экране форма смещена ВЛЕВО на ~11px
     (gap_left=89, gap_right=100 при SVG=210px): яйца ближе к левой рамке
     карточки, головка дальше от правой. translateX(2.6%) сдвигает обёртку
     вправо на 5.5px (= 2.6%×210) → gap_left≈gap_right, форма по центру.
     % считаются от ширины wrap → пропорционально на мобиле (150px) и десктопе. */
  transform: translateX(2.6%);
}

/* SVG-силуэт пениса. ⚠️ Раунд 14: 210px/stroke 3 — УНИФИЦИРОВАНО с .dick-clone,
   .back-dick и #backBtn. Раньше 160px/stroke 7 → клон при отрыве рос 160→180,
   приземлении в back-dick давал скачок. Все 4 пенис-кнопки теперь одного размера
   и обводки → переход бесшовный.
   ⚠️ Раунд 27: filter: drop-shadow ПЕРЕНЕСЁН сюда с .dick-wrap (тень от формы
   пениса визуально та же), чтобы текст .dick-label не был «обёрнут» фильтром
   → не выносился на GPU-слой → не терял ClearType на ПК. */
.season-cta .dick-body {
  display: block;
  width: 210px;
  height: auto;
  /* ТЕНЬ — одна и постоянная, от формы пениса. Честная физика: кнопка оторвана
     от фона, солнце вверху-слева-впереди → мягкая тень вниз-вправо. НЕ меняется
     при hover (принцип Миши: тень = только если кнопка растёт). Проявляется
     вместе с opacity родителя. ТА ЖЕ тень, что у .back-dick и .dick-clone →
     клон/back-dick/cta визуально неразличимы по тени. (Раунд 27: перенесена
     с .dick-wrap сюда — см. комментарий в .dick-wrap.) */
  filter:
    drop-shadow(4px 10px 1px rgba(26, 26, 46, 0.28))
    drop-shadow(6px 16px 16px rgba(0, 0, 0, 0.15));
}
.season-cta .dick-path {
  fill: var(--yellow);
  stroke: var(--ink);
  stroke-width: 3;          /* УНИФИЦИРОВАНО с .dick-clone, .back-dick, #backBtn */
  stroke-linejoin: round;
  transition: fill 0.25s ease;
}
/* Текст на стволе. Ствол (с яйцами) занимает ~x 31..243 из 300 viewBox →
   центр ствола ≈ x=137 из 300 = 46%. Надпись стоит посередине ствола.
   transform: translate(-50%,-50%) центрирует её по этой точке. */
.season-cta .dick-label {
  position: absolute;
  top: 50%;
  left: 46%;
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 0.62rem;
  letter-spacing: 0.5px;
  white-space: nowrap;
  z-index: 5;
  pointer-events: none;
  color: var(--ink);
  transition: color 0.25s ease;
}
/* Стрелка → ВНУТРИ головки. Головка = правый эллипс, центр ≈ x=243 из 300 = 81%.
   Ставим на 81% — это внутри контура головки, стрелка не вылезает за пенис. */
.season-cta .dick-arrow {
  position: absolute;
  top: 50%;
  left: 81%;
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 0.78rem;
  z-index: 5;
  pointer-events: none;
  color: var(--ink);
  transition: transform 0.2s ease, color 0.25s ease;
}
/* Hover самой кнопки: краснеет, стрелка дёргается вправо.
   БЕЗ сдвига самого тела — Миша просил убрать «съезжание» кнопки при наведении.
   ⚠️ Hover на .dick-wrap (плотно облегает пенис), а НЕ на .season-cta (тот на
   всю карточку → краснел бы при наведении на любую часть карточки).
   ⚠️ Раунд 19: обёрнуто в @media (hover:hover) and (pointer:fine) — на мобиле
   :hover ЗАЛИПАЕТ после тапа (тачскрин не шлёт mouseleave → красный держится
   «навсегда»). Красный отклик на тап даёт .flash-red ниже (через JS, на 0.45с),
   потом .flying/.arriving держат жёлтый — как у .hero-cta/.back-btn. */
@media (hover: hover) and (pointer: fine) {
  .season-cta .dick-wrap:hover .dick-path { fill: var(--red); }
  .season-cta .dick-wrap:hover .dick-label,
  .season-cta .dick-wrap:hover .dick-arrow { color: #fff; }
  .season-cta .dick-wrap:hover .dick-arrow { transform: translate(-50%, -50%) translateX(4px); }
}

/* ПОЛИРОВКА ЦВЕТА В ПОЛЁТЕ/ПРИЛЕЁТЕ (зеркало .hero-cta). На мобиле :hover
   залипает (см. выше) → пока летит (.flying) или прилетает (.arriving),
   ПРИНУДИТЕЛЬНО держим жёлтый цвет пениса. Стоит ПОСЛЕ hover-правил →
   перебивает прилипший ховер. На ПК результат тот же. */
.season-cta.flying .dick-path,
.season-cta.arriving .dick-path { fill: var(--yellow); }
.season-cta.flying .dick-label,
.season-cta.arriving .dick-label { color: var(--ink); }
.season-cta.flying .dick-arrow,
.season-cta.arriving .dick-arrow {
  color: var(--ink);
  transform: translate(-50%, -50%);
}

/* Краткий красный отклик при тапе (зеркало .hero-cta.flash-red). Вешает JS
   (клик по .season-cta → openSeason) на ~0.45с в момент клика: тапнул →
   красная (видно «нажал!») → класс снялся → .flying держит жёлтый до конца
   полёта. Стоит ПОСЛЕ .flying → перебивает жёлтый на время отклика. */
.season-cta.flash-red .dick-path { fill: var(--red); }
.season-cta.flash-red .dick-label { color: #fff; }
.season-cta.flash-red .dick-arrow { color: #fff; transform: translate(-50%, -50%) translateX(4px); }

/* Пенис-SOON для заглушек-сезонов (вместо кнопки). Тёмный (var(--ink)) — тот же
   цвет, что у прежних бейджей. Некликабельный (стрелки нет, переходить некуда).
   Та же анимация появления из центра, что у .season-cta. */
.season-soon-badge {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transform-origin: center;
  /* скрыто в покое: сжато в точку (как кнопка-пенис) → появляется из центра. */
  opacity: 0;
  transform: scale(0.3);
  /* Тень в покое — та же физика, что у .season-cta (см. выше). */
  filter:
    drop-shadow(2px 4px 0 rgba(26,26,46,0.45))
    drop-shadow(2px 5px 4px rgba(0,0,0,0.22));
  transition: opacity 0.4s ease 0.18s,
              transform 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.18s,
              filter 0.5s ease 0.18s;
}
/* «Оторвался к зрителю» — тень растёт/мягчеет/светлеет/ползёт вниз (как у CTA). */
.season-card:hover .season-soon-badge,
.season-card.is-active .season-soon-badge {
  filter:
    drop-shadow(4px 10px 1px rgba(26,26,46,0.28))
    drop-shadow(6px 16px 16px rgba(0,0,0,0.15));
}
/* SVG + обёртка — те же размеры/форма, что у кнопки TO SEASON.
   will-change + translateZ(0) — тот же фикс subpixel snap, что у .season-cta. */
.season-soon-badge .dick-wrap {
  position: relative;
  display: inline-block;
  line-height: 0;
  will-change: transform;
  transform: translateZ(0);
}
.season-soon-badge .dick-body {
  display: block;
  width: 210px;
  height: auto;
}
/* Тёмная заливка + тёмная обводка — «выключенный» пенис (нет сезона). */
.season-soon-badge .dick-path {
  fill: var(--ink);
  stroke: var(--ink);
  stroke-width: 3;
  stroke-linejoin: round;
}
/* Надпись SOON — кремовая (читается на тёмном). По центру ствола, без стрелки. */
.season-soon-badge .dick-label {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 0.7rem;
  letter-spacing: 1px;
  white-space: nowrap;
  z-index: 5;
  pointer-events: none;
  color: var(--cream);
}

/* ПЕРЕКЛЮЧЕНИЕ при наведении (ПК :hover) / тапе (.is-active на мобиле).
   default → СХЛОПЫВАЕТСЯ в точку (scale 0.3 + fade), cta/soon → ВЫРАСТАЕТ из точки. */
.season-card:hover .season-meta-default,
.season-card.is-active .season-meta-default {
  opacity: 0;
  transform: scale(0.3);
}
/* Кнопка/бейдж проявляются: из точки (scale 0.3) → финал.
   Раунд 14f+14h: оба правила (hover и is-active) → scale 0.893 (компенсация
   scale карточки 1.12 при hover-sticky на мобиле). См. подробности выше. */
.season-card:hover .season-cta,
.season-card.is-active .season-cta,
.season-card:hover .season-soon-badge,
.season-card.is-active .season-soon-badge {
  opacity: 1;
  transform: scale(0.893);
}

/* Текст внутри окошка. БЕЗ padding, чтобы ширина измерялась чисто. */
.name-text {
  display: inline-block;
}

/* Бегущая строка: JS вешает .is-marquee на РОДИТЕЛЯ, когда текст не помещается.
   text-align: left — старт слева, чтобы было куда уезжать влево.
   Длительность НЕ фиксирована — JS ставит её индивидуально в --marquee-duration
   (по длине текста), чтобы ВСЕ названия ехали с одинаковой СКОРОСТЬЮ, а не одно и
   то же время. Иначе короткий текст ползёт как улитка, длинный несётся. */
.season-name.is-marquee { text-align: left; }
.season-name.is-marquee .name-text {
  animation: name-marquee var(--marquee-duration, 9s) linear infinite;
}

/* Точный сдвиг влево задаёт JS через переменную --travel (в пикселях) —
   насколько текст должен уехать, чтобы показать свой конец. */
@keyframes name-marquee {
  0%, 12%   { transform: translateX(0); }                       /* пауза, читаем начало */
  82%, 94%  { transform: translateX(var(--travel, -50%)); }     /* уехали, читаем конец */
  100%      { transform: translateX(0); }                       /* возврат */
}

/* Уважаем настройку «уменьшить движение» (доступность). */
@media (prefers-reduced-motion: reduce) {
  .season-name.is-marquee .name-text { animation: none; }
}

/* Подзаголовок сезона — мелко, разряженно, без давления */
.season-tagline {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--ink);
  opacity: 0.7;
  letter-spacing: 0.5px;
  margin: 0;
}

/* ---- Адаптив постеров на мобиле ---- */
@media (max-width: 600px) {
  .seasons-grid {
    /* 140px минимум → на узком экране (360–375px) помещается по 2 карточки в ряд.
       Раньше было 160px — на некоторых мобилах вставали по одной. */
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 14px;
    padding: 12px 12px 40px;
  }
  .season-name     { font-size: 1.2rem; }
  .season-badge    { font-size: 0.6rem; letter-spacing: 1.5px; padding: 1px 8px; }
  .season-tagline  { font-size: 0.72rem; }
  .season-info     { padding: 10px 8px 12px; }
  /* Кнопки-пенисы и слот переключения — мельче на мобиле, карточки узкие.
     ⚠️ Раунд 14: ВСЕ пенис-кнопки (cta, soon, back-dick, clone, backBtn)
     на мобиле — ОДНОГО размера 150px. Раньше хаос: cta 160, soon 120, bd 140,
     bb 210 → на переходе скачки. Унификация = бесшовный переход на телефоне.
     Все 4 пенис-кнопки на мобиле = 150px (= BACK на выдохе). */
  .season-meta       { min-height: 52px; }
  .season-cta .dick-body,
  .season-soon-badge .dick-body { width: 150px; }
  .season-cta .dick-label { font-size: 0.7rem; }   /* 14f: = .back-btn */
  .season-cta .dick-arrow { font-size: 0.64rem; }
  .season-soon-badge .dick-label { font-size: 0.56rem; }
}

/* ---- Модальное окно (lightbox) ---- */
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(26, 26, 46, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 30px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}

.lightbox.open {
  opacity: 1;
  visibility: visible;
}

.lightbox-content {
  position: relative;
  max-width: 90vw;
  max-height: 85vh;
  background: #fff;
  border: 6px solid var(--yellow);
  border-radius: 20px;
  padding: 15px;
  box-shadow: 0 0 0 6px var(--ink), 0 30px 60px rgba(0,0,0,0.5);
  transform: scale(0.5);
  transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.lightbox.open .lightbox-content {
  transform: scale(1);
}

.lightbox-content img {
  max-width: 100%;
  max-height: 70vh;
  border-radius: 10px;
  display: block;
}

.lightbox-caption {
  font-family: var(--font-display);
  text-align: center;
  padding-top: 12px;
  color: var(--ink);
  letter-spacing: 1px;
  font-size: clamp(1rem, 2vw, 1.3rem);
}

.lightbox-close {
  position: absolute;
  top: -25px;
  right: -25px;
  width: 55px;
  height: 55px;
  border-radius: 50%;
  background: var(--red);
  color: #fff;
  border: 4px solid var(--ink);
  font-family: var(--font-display);
  font-size: 1.4rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 0 var(--ink);
  transition: transform 0.15s ease, background 0.15s ease;
}

.lightbox-close:hover {
  background: var(--yellow);
  color: var(--ink);
  transform: rotate(90deg) scale(1.1);
}

/* ---- Адаптив галереи ---- */
@media (max-width: 600px) {
  .gallery-grid {
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 14px;
  }
  .card-label { font-size: 0.85rem; padding: 8px 4px; }
  .lightbox-close { top: -15px; right: -10px; width: 45px; height: 45px; }
}

/* ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████
   ████  МОБИЛЬНЫЕ ФИКСЫ (8 июля 2026)  ██████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████
   ██████████████████████████████████████████████████████████████████████████

   Список багов, которые тут лечатся (симптомы с телефона):
   1) Галерея НЕ прокручивается / клики по карточкам НЕ работают /
      музыка НЕ запускается. Причина: фоновые слои (.balloons, .clouds)
      position: fixed; inset:0 на тач-экранах «съедают» касания, несмотря на
      pointer-events:none. На десктопе всё ок (мышь «пробивает» none), на мобе — нет.
      → ЛЕЧЕНИЕ: на узких экранах просто ВЫКЛЮЧАЕМ эти слои в галерее (display:none).
         Шары/облака — декорация, скролл и клики важнее. На главной оставляем.

   2) FUNNY DICKS на главной смещён вправо (не отцентрован).
      Причина: white-space:nowrap + широкая картинка-пенис (415×404, почти квадрат)
      дают асимметрию, плюс отрицательные margin тянут вправо.
      → ЛЕЧЕНИЕ: убираем nowrap на мобе (разрешаем перенос), уменьшаем картинку,
         центрируем надёжнее.

   3) Кнопка «НАЗАД» в галерее наезжает на лого THE FUNNY DICKS GALLERY.
      Причина: лого длинное + nowrap → на узком экране упирается в фиксированную
      кнопку справа.
      → ЛЕЧЕНИЕ: разрешаем лого переноситься (white-space: normal) на мобе.

   4) Облака «в одном положении» — анимация cloud-drift использует translateX(120vw),
      что на узком экране = быстро улетает и подолгу невидим. Не критично, но
      при display:none фоновых слоёв (п.1) проблема отпадает сама собой. */
@media (max-width: 600px) {

  /* === ФИКС 1: галерея — фоновые слои ===
     Шары (.balloons) остаются выключенными: они тяжёлые и сильнее ломали тач.
     Облака (.clouds) — FIXED, привязаны к ВЕРХНЕЙ ПОЛОСЕ экрана (как кнопка
     «НАЗАД»). Висят на месте при скролле, как просил Миша. БЕЗОПАСНОСТЬ: слой
     .sky на <body> имеет pointer-events:none → НЕ «съедает» касания по карточкам
     → старый баг со скроллом (8 июля) не вернётся. Облака могут быть по всей
     высоте (раньше их ограничивали полосой 40vh именно из-за тач-проблемы). */
  .gallery-page .balloons { display: none; }
  /* Подстраховка: разрешаем тач-скролл везде явно (iOS Safari любит это) */
  body {
    touch-action: pan-y pan-x;
    -webkit-overflow-scrolling: touch;
  }

  /* === ФИКС 2: FUNNY DICKS — центрируем надёжно на узком экране === */
  .hero-title {
    /* разрешаем перенос МЕЖДУ словами (FUNNY / DICKS на разных строках), но не
       внутри — каждая буква обёрнута в .word { white-space: nowrap }, поэтому
       DICKS всегда держится цельным блоком (S больше не отрывается). */
    white-space: normal;
    /* уменьшаем, чтобы гарантированно влезло */
    font-size: clamp(2rem, 12vw, 3.2rem);
    /* центрируем: text-align centre + контент уже в .hero-content (text-align:center) */
    text-align: center;
    margin-bottom: 100px;     /* чуть меньше отступ на мобе */
  }
  /* Картинка-пенис (буква I): на мобе делаем поменьше + обнуляем отступы,
     чтобы D-I-C-K-S гарантированно влезало в одну строку (S не падала вниз). */
  .hero-title .letter-img {
    height: 0.8em;
    margin: 0;
  }

  /* === ФИКС 3: лого галереи — на мобе чуть мельче, чтобы влезало ===
     (Перенос между словами уже разрешён в базовом .gallery-logo + .word.
     Здесь только подгоняем размер под узкий экран.) */
  .gallery-logo {
    font-size: clamp(1.4rem, 7vw, 2.2rem);   /* мельче на мобе */
    padding: 0 16px;          /* отступы по бокам, чтобы под кнопку НАЗАД не лезло */
  }
  /* Сам блок лого. Шатёр СНЯТ (проект «сезонов»). Лого ниже верхнего ряда
     кнопок на мобиле (mute 🔊 top:8px + высота 40px + НАЗАД top:60px + зазор). */
  .gallery-logo-wrap {
    margin-top: 20px;        /* НАЗАД перенесена вниз → лого можно поднять к верху.
                                Оставляем небольшой отступ от кнопки mute 🔊 сверху. */
  }

  /* === Кнопка НАЗАД (пенис): на мобе меньше, в НИЖНЕМ правом углу (thumb zone) ===
     Перенесена вниз для удобства большого пальца. Размер остаётся уменьшенным
     (SVG-кнопка шире прежней таблетки → уменьшаем .dick-body). */
  .back-btn {
    bottom: 50px;             /* thumb zone — зона большого пальца (приподнята) */
    right: 12px;
    top: auto;                /* сбрасываем top, используем bottom */
  }
  .back-btn .dick-body { width: 150px; }   /* было 210px на десктопе */
  .back-btn .dick-label { font-size: 0.7rem; }
  .back-btn .dick-arrow { font-size: 0.8rem; }

  /* 🐛 УБРАН overflow-y:auto у .gallery-page (мобильный медиа-запрос).
     Этот блок был «подстраховкой», НО делал .gallery-page ОТДЕЛЬНЫМ
     скролл-контейнером — со своим красным скроллбаром (через глобальный
     ::-webkit-scrollbar). В покое контент влезал → полосы не было. НО при
     открытии сезона карточки осыпаются вниз → .gallery-page раздувается
     (1095→3634px) → переполнение → вспыхивал красный скроллбар справа.
     И так же при закрытии сезона (обратный переезд). На мобиле body сам
     отлично скроллит (overlay-серый), отдельный скролл не нужен. */
}


/* ============================================================================
   📐 АДАПТИВ ПО ВЫСОТЕ ОКНА (только для главной .hero)
   ----------------------------------------------------------------------------
   Базовый padding-bottom: 380px поднимает FUNNY DICKS высоко на БОЛЬШИХ
   экранах (16:9, ~900px+ высоты). Но при Windows-масштабе 125/150/175%
   или на нестандартных ноутах (1920×1200@175% → окно браузера ~589px
   высоты) контент 771px не помещается → появляется прокрутка.

   Решение: несколько ступеней @media (max-height). На больших экранах эти
   правила НЕ срабатывают → раскладка как сейчас (0 изменений для большинства).
   На низких окнах padding-bottom уменьшается → контент помещается без скрутки.
   Широкие правила (max-width:600px) не трогаем — они по другой оси.
   ============================================================================ */

/* Средне-низкие окна (~700–900px высоты): чуть опускаем, чтобы не прижимало */
@media (max-height: 900px) and (min-width: 601px) {
  .hero { padding-bottom: 300px; }
}

/* Низкие окна (~580–700px — например ноут 1920×1200 с Windows-масштабом 175%):
   контент плотный, ещё уменьшаем отступ. Без этого — прокрутка ~180px. */
@media (max-height: 700px) and (min-width: 601px) {
  .hero { padding-bottom: 180px; }
}

/* Очень низкие окна (меньше 580px): гарантированно убираем прокрутку,
   контент центрируется по-честному в маленьком окне. */
@media (max-height: 580px) and (min-width: 601px) {
  .hero { padding-bottom: 80px; }
}


/* =========================================================================
   🎬 ОБОЗРЕНИЕ СЕЗОНА (.season-view) + БЕСШОВНЫЙ ПЕРЕХОД галерея → сезон.
   v110→v111. Архитектура: галерея (.gallery-content) и сезон (.season-view)
   существуют в одном DOM одновременно. При клике по карточке:
     1) выбранную карточку «выбирает свет» (прожектор, .season-glow);
     2) остальные осыпаются вниз с вращением;
     3) .gallery-content уезжает ВЛЕВО, .season-view въезжает СПРАВА;
     4) атмосферный фон (.season-atmosphere) проявляется под контент сезона;
     5) клон-пенис (.dick-clone) отрывается от .season-cta, делает сальто,
        летит к .back-dick и превращается в «В ГАЛЕРЕЮ».
   Логика в js/main.js (openSeason / goBack). Прототип: _design-tests/.
   ========================================================================= */

/* Обёртка контента галереи. По умолчанию ничего не делает (нужна для анимации
   отъезда влево). position:relative, z-index как у обычного потока галереи. */
.gallery-content {
  position: relative;
  z-index: 10;
  will-change: transform, opacity, filter;
}

/* 🌟 СВЕЧЕНИЕ ВЫБОРА карточки (прожектор). Инъецируется JS: на выбранную
   карточку вешается <div class="season-glow"> поверх. Яркое многослойное
   сияние + расходящийся свет позади. По умолчанию opacity:0. */
.season-glow {
  position: absolute;
  inset: -10px;
  border-radius: 28px;
  pointer-events: none;
  opacity: 0;
  z-index: 4;
  box-shadow:
    0 0 0 6px var(--yellow),
    0 0 36px 8px rgba(255, 196, 0, 0.95),
    0 0 70px 18px rgba(255, 196, 0, 0.65),
    0 0 120px 30px rgba(255, 196, 0, 0.35),
    inset 0 0 36px 6px rgba(255, 255, 210, 0.7);
}
/* Радиальная подсветка позади выбранной карточки (расходящийся свет). */
.season-glow--back {
  inset: -70px;
  border-radius: 50px;
  z-index: -1;
  box-shadow: none;
  background: radial-gradient(circle at 50% 45%,
    rgba(255, 210, 90, 0.55),
    rgba(255, 210, 90, 0.15) 45%,
    transparent 70%);
}

/* ── ОВЕРЛЕЙ СЕЗОНА ── */
.season-view {
  position: fixed;
  /* ⚠️ Раунд 8 (для сезонов): НЕ inset:0! На Chrome Android inset:0 для fixed
     «дышит» с адресной строкой (= 100dvh): при её задвигании не дорастает до
     низа → снизу щель с голым body (лазурь). Та же грабля, что у .sky Раунд 8.
     Лечим явно: width:100vw + height:100lvh (= максимум, с запасом под строку).
     top/left:0 вместо inset — то же самое, но читаемее рядом с явными w/h. */
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;       /* фолбэк */
  height: 100lvh;      /* large viewport — всегда максимум, щели снизу нет */
  z-index: 40;
  overflow-y: auto;
  overflow-x: hidden;
  /* скрыт в покое */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
.season-view.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
/* 🐛 ФИКС ЛИШНЕЙ КРАСНОЙ ПОЛОСЫ ПРОКРУТКИ в сезоне. Глобальный ::-webkit-scrollbar
   (строка ~200) красит thumb в --red для ВСЕХ скролл-контейнеров. .season-view —
   отдельный overflow-y:auto поверх галереи → получал СВОЙ красный скроллбар:
     • мобила: вместо лёгкого серого overlay Chrome всплывал жирный красный;
     • ПК: появлялась ВТОРАЯ красная полоса рядом с галерейной.
   Прячем классический скроллбар именно у .season-view — сам скролл остаётся
   рабочим (палец/колесо/тач), просто не рисуем полосу. На мобиле при этом
   возвращается нативный overlay-скролл Chrome. */
.season-view::-webkit-scrollbar { width: 0; height: 0; }
.season-view { scrollbar-width: none; }   /* Firefox: тоже спрятать */

/* Атмосферный фон сезона — fixed внутри оверлея, под контентом.
   Класс atm-* ставится JS из data-atm карточки. Проявляется opacity 0→1.
   ⚠️ Раунд 20: position:fixed работает честно ТОЛЬКО если .season-view не имеет
   inline transform. Любой transform делает родителя containing block для fixed-
   детей → атмосфера скроллится вместе с контентом → снизу лазурь. GSAP ставит
   transform при анимации въезда, но clearProps в onComplete его снимает → в
   покое (когда сезон можно скроллить) атмосфера стоит как вкопанная.
   ⚠️ Раунд 8 (для сезонов): НЕ inset:0, а width:100vw + height:100lvh — иначе
   при задвигании адресной строки fixed-слой не дорастает до низа → лазурь. */
.season-atmosphere {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;       /* фолбэк */
  height: 100lvh;      /* large viewport — максимум, щели снизу нет */
  z-index: -1;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s linear;
}
/* Old Gold — тёплый закат */
.season-atmosphere.atm-old-gold {
  background:
    radial-gradient(ellipse 80% 60% at 70% 20%, rgba(255, 200, 80, 0.55), transparent 60%),
    linear-gradient(180deg, #b35900 0%, #e67e22 35%, #f5b041 65%, #ffd97a 100%);
}
/* New Wave — ночь со звёздами (как у Galina; разноцветные атмосферные фоны
   будут отличаться позже — пока единое звёздное небо по запросу Миши).
   Звёзды = статичные radial-gradient на ОДНОМ слое (35 шт., белый/голубой/
   тёплый, разный размер) — дёшево: один paint, ноль DOM-узлов (раньше 52
   DOM-звезды .star жрали style recalc — см. STATUS:279). Метеор — отдельный
   элемент .meteor, запускается JS изредка (см. main.js initMeteors). */
.season-atmosphere.atm-new-wave {
  background:
    radial-gradient(1.14px 1.14px at 6.7% 78.6%, #fff, transparent),
    radial-gradient(1.54px 1.54px at 18.0% 81.0%, #fff, transparent),
    radial-gradient(2.21px 2.21px at 14.0% 81.5%, #fff, transparent),
    radial-gradient(1.22px 1.22px at 72.8% 53.0%, #fff8dc, transparent),
    radial-gradient(1.59px 1.59px at 84.2% 13.5%, #fff, transparent),
    radial-gradient(1.96px 1.96px at 71.1% 10.3%, #fff, transparent),
    radial-gradient(1.98px 1.98px at 30.2% 69.6%, #fff, transparent),
    radial-gradient(0.99px 0.99px at 48.6% 75.4%, #cfe, transparent),
    radial-gradient(1.48px 1.48px at 15.1% 39.8%, #fff8dc, transparent),
    radial-gradient(1.47px 1.47px at 56.6% 14.6%, #fff, transparent),
    radial-gradient(1.61px 1.61px at 3.0% 91.1%, #fff, transparent),
    radial-gradient(1.35px 1.35px at 71.8% 73.1%, #cfe, transparent),
    radial-gradient(0.83px 0.83px at 34.9% 28.0%, #fff, transparent),
    radial-gradient(1.82px 1.82px at 92.9% 69.9%, #fff, transparent),
    radial-gradient(2.13px 2.13px at 44.3% 8.9%, #cfe, transparent),
    radial-gradient(2.11px 2.11px at 21.1% 71.6%, #cfe, transparent),
    radial-gradient(1.21px 1.21px at 45.7% 74.3%, #cfe, transparent),
    radial-gradient(1.37px 1.37px at 19.2% 43.5%, #fff8dc, transparent),
    radial-gradient(0.83px 0.83px at 16.6% 27.2%, #fff, transparent),
    radial-gradient(1.57px 1.57px at 69.1% 68.1%, #fff8dc, transparent),
    radial-gradient(2.02px 2.02px at 71.1% 81.7%, #cfe, transparent),
    radial-gradient(1.41px 1.41px at 68.7% 5.0%, #cfe, transparent),
    radial-gradient(1.07px 1.07px at 34.9% 81.5%, #fff, transparent),
    radial-gradient(0.86px 0.86px at 28.1% 53.0%, #fff, transparent),
    radial-gradient(2.30px 2.30px at 75.5% 79.0%, #fff, transparent),
    radial-gradient(0.97px 0.97px at 31.4% 80.3%, #cfe, transparent),
    radial-gradient(1.38px 1.38px at 93.0% 88.0%, #fff, transparent),
    radial-gradient(1.81px 1.81px at 15.4% 22.8%, #cfe, transparent),
    radial-gradient(1.89px 1.89px at 54.3% 47.1%, #cfe, transparent),
    radial-gradient(1.04px 1.04px at 64.7% 60.7%, #cfe, transparent),
    radial-gradient(1.04px 1.04px at 87.7% 20.4%, #fff, transparent),
    radial-gradient(1.77px 1.77px at 60.6% 83.2%, #cfe, transparent),
    radial-gradient(1.31px 1.31px at 45.3% 64.0%, #fff, transparent),
    radial-gradient(0.94px 0.94px at 5.3% 65.1%, #fff, transparent),
    radial-gradient(0.80px 0.80px at 33.2% 12.7%, #cfe, transparent),
    radial-gradient(ellipse 100% 70% at 50% 100%, rgba(40, 30, 80, 0.6), transparent 70%),
    linear-gradient(180deg, #070b26 0%, #12173f 50%, #1d1f4a 100%);
}

/* ☄️ МЕТЕОР — полоса-хвост, летит по диагонали сверху-вправо → вниз-влево.
   Яркая головка спереди, тающий хвост позади (по направлению движения — физично).
   Запускается изредка JS (initMeteors). Сам элемент живёт в .season-atmosphere
   (fixed, поверх звёзд, под контентом сезона). Скрыт по умолчанию. */
.meteor {
  position: absolute;
  top: 0;
  left: 0;
  width: 140px;
  height: 2px;
  /* Головка (слева, ~15% длины) яркая, хвост тает вправо. Метеор летит
     ВЛЕВО-ВНИЗ → головка впереди, хвост тянется позади по линии полёта.
     Физично: след ионизированного воздуха тянется ЗА метеором. */
  background: linear-gradient(90deg, #fff 0%, rgba(220,240,255,0.9) 15%, rgba(255,255,255,0.15) 60%, transparent 100%);
  border-radius: 50%;
  filter: drop-shadow(0 0 4px rgba(200,230,255,0.9));
  /* Поворот вокруг ЛЕВОГО конца (головки). ⚠️ Угол считает JS (atan2 от вектора
     движения) — так хвост лежит ровно на траектории при ЛЮБОМ угле полёта. */
  transform-origin: left center;
  transform: translate(9999px, 0) rotate(var(--meteor-angle, -28deg));
  opacity: 0;
  pointer-events: none;
}
/* У New Wave atmosphere position:relative, чтобы .meteor (absolute) лежал внутри. */
.season-atmosphere { position: fixed; }

/* 🌙 Спящий месяц — парит в правом верхнем углу New Wave. Показан только когда
   открыт New Wave (body.season-new-wave). По умолчанию скрыт. */
.moon-sleep {
  position: absolute;
  top: 88px;                          /* ниже MUTE (48px + отступ), не налезает */
  right: 70px;                        /* левее MUTE, не под ней */
  width: 110px;                       /* небольшой, не перекрывает заголовок */
  display: none;                       /* показываем только для New Wave (ниже) */
  pointer-events: none;
  z-index: 2;
}
.moon-sleep__img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 4px 10px rgba(0,0,0,0.45));  /* мягкая тень под месяцем */
}
/* Парение на отдельном свойстве `translate` (как у карточек, Раунд 29) — НЕ
   конфликтует с transform фильтра. Лёгкое покачивание вверх-вниз + дрейф вбок. */
body.season-new-wave .moon-sleep {
  display: block;
  /* ⚠️ Парение НЕ на .moon-sleep (контейнере)! Перенесено на .moon-sleep__img
     внутри. Причина: repositionMoon() измеряет контейнер getBoundingClientRect
     и ставит top=... чтобы разойтись с заголовком. Если бы парение было на
     контейнере — translate (±6px) влиял бы на измеренную позицию → в момент
     перехода атмосферы 0→1 месяц проявлялся бы в фазе парения (-6px), а при
     возврате translate в 0,0 опускался → видимый рывок «вниз-влево». Контейнер
     стоит как вкопанный, парит только картинка внутри. */
}
/* Парение картинки месяца — через GSAP (JS), НЕ через CSS animation.
   Причина: CSS-анимация в одном-DOM SPA капризно ведёт себя при повторных
   «появлениях» элемента (через opacity/visibility родителя) — перезапускается
   в случайные моменты, давая рывки. GSAP же полностью контролируется из JS:
   создаём tween при входе, убиваем при выходе. Каждый вход = новый tween. */
@keyframes moon-float {
  0%, 100% { translate: 0 0; }
  25%      { translate: 3px -4px; }
  50%      { translate: -2px -6px; }
  75%      { translate: -4px -2px; }
}

/* 🌙 ZZZ — призрачные полупрозрачные буквы. Стоят в строчке (z z z, каждая
   следующая крупнее и чуть выше — как сон-комикс). «Волна с хвостом»: z1
   разгорается ярче всех, потом z2 разгорается (z1 тускнеет), потом z3 (z1,z2
   тусклые), потом всё гаснет и заново. Буквы НЕ двигаются — только меняют
   opacity (свой «вижн»). */
.moon-zzz {
  position: absolute;
  font-family: var(--font-display);
  font-weight: 100;                    /* thin — максимально тонкие, призрачные */
  color: rgba(220, 235, 255, 0.8);
  text-shadow:
    0 0 3px rgba(200, 225, 255, 0.35),
    0 0 6px rgba(180, 210, 255, 0.2);   /* слабое свечение, без обводки */
  opacity: 0;
  line-height: 1;
  user-select: none;
  /* ⚠️ Сама анимация — на отдельном классе .is-waving (см. ниже), НЕ в базовом
     правиле. Так repositionMoon() может перезапустить её при повторном входе
     в сезон (toggle класса → animation-delay срабатывает заново). */
}
/* Класс для включения волны ZZZ. Вешается JS (restartMoonAnimations) через
   setTimeout — после проявления атмосферы. ⚠️ LONGHAND (animation-name и т.д.),
   НЕ shorthand `animation:` — иначе сбросит animation-delay отдельных Z
   (.moon-zzz--2/--3), и все три Z загорятся разом. */
.moon-zzz.is-waving {
  animation-name: moon-zzz-wave;
  animation-duration: 4s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
/* Раскладка строчки: z1 слева-внизу, z2 правее-выше, z3 ещё правее-выше.
   left/top — в долях от контейнера .moon-sleep. */
.moon-zzz--1 { font-size: 1.0rem; left: 86%; top: 6px; }
.moon-zzz--2 { font-size: 1.4rem; left: 92%; top: -6px; }
.moon-zzz--3 { font-size: 1.9rem; left: 100%; top: -20px; }
/* Сдвиг ФАЗЫ между Z: z1 на пике первой, z2 через 0.24с, z3 ещё через 0.24с.
   Так яркая Z ползёт слева направо, а за ней тянется длинный тающий хвост.
   ⚠️ Это РАЗНИЦА фаз (не базовая задержка!). Базовый старт (после проявления
   атмосферы) задаётся через setTimeout(1500) в JS — класс .is-waving вешается
   на все Z одновременно, а эти 0.24с создают «волну». CSS animation-delay тут
   работает (класс между входами снимается → каждый раз чистый старт). */
.moon-zzz--2.is-waving { animation-delay: 0.24s; }
.moon-zzz--3.is-waving { animation-delay: 0.48s; }

/* 🌊 Волна с хвостом + ПАУЗА. Цикл 4.0с = ВОЛНА ~1.5с (в первых 37%) +
   ПАУЗА 2.5с (всё скрыто, 37..100%). Ритм: вспышка z→z→z с ДЛИННЫМ ХВОСТОМ →
   тишина → снова вспышка.
   ПЛАВНОСТЬ + ПЕРЕКРЫТИЕ: каждая Z разгорается мягко, держит пик ~6%, потом
   ДОЛГО тает (всю оставшуюся волну). Когда z2 на пике — z1 ещё видна (тает),
   когда z3 — z1,z2 тают → эффект «волна оставляет за собой длинный хвост». */
@keyframes moon-zzz-wave {
  /* 0..6% — ЭТА Z разгорается и на пике (акцент). */
  0%    { opacity: 0; }
  2.5%  { opacity: 0.5; }       /* мягкий полдень разгорания */
  6%    { opacity: 0.95; }      /* пик */
  /* 6..37% — ДОЛГИЙ ХВОСТ: плавно тает всю оставшуюся волну, пока горят
     следующие Z. Линейно-мягкое угасание. */
  12%   { opacity: 0.55; }
  20%   { opacity: 0.3; }
  28%   { opacity: 0.12; }
  37%   { opacity: 0; }         /* полностью погасла к концу волны */
  /* 37..100% — ПАУЗА: всё скрыто, «человек спит». */
  100%  { opacity: 0; }
}
/* На мобиле чуть меньше. MUTE: top:8px, 40×40, left:calc(100vw-48px). Месяц
   прижимаем под MUTE к ПРАВОМУ краю (right:8px) — НЕ в центр, где заголовок.
   Высота: MUTE кончается на 48px, месяц ставим сразу под ним (top:52px). */
@media (max-width: 768px) {
  .moon-sleep { width: 72px; top: 52px; right: 8px; }
  .moon-zzz--1 { font-size: 0.8rem; }
  .moon-zzz--2 { font-size: 1.1rem; }
  .moon-zzz--3 { font-size: 1.5rem; }
}

/* 🔥 Костёр-сценка (camping.webp) в New Wave. Слева, чуть ниже месяца — не зеркально
   напротив, а диагональю (месяц вверху-справа, костёр левее-ниже). Показан только
   когда открыт New Wave (body.season-new-wave). По умолчанию скрыт.
   ⚠️ Парение НЕ на контейнере .campfire, а на картинке .campfire__img внутри — та же
   схема, что у месяца (Раунд 36): контейнер стоит как вкопанный, парит содержимое.
   Это позволяет в будущем делать repositionCampfire() через getBoundingClientRect,
   не опасаясь, что translate парения испортит замер. */
.campfire {
  position: absolute;
  /* ⚠️ top/left здесь — лишь fallback (до отработки repositionScene). JS ставит
     костёр ОТНОСИТЕЛЬНО заголовка: на ПК слева по центру, на мобиле выше-слева. */
  top: 150px;
  left: 40px;
  width: 320px;                       /* размер на ПК (пропорции 720×359 ≈ 2:1) */
  display: none;                      /* показываем только для New Wave (ниже) */
  pointer-events: none;
  z-index: 2;
}
.campfire__img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 6px 14px rgba(0,0,0,0.5));  /* мягкая тень под сценкой */
}
body.season-new-wave .campfire {
  display: block;
}
/* На мобиле — меньше и прижат к левому краю, ниже MUTE/месяца. */
@media (max-width: 768px) {
  .campfire { width: 195px; top: 110px; left: 8px; }
}
/* Движение метеора задаётся JS через Web Animations API (см. initMeteors) —
   угол поворота и трансляция считаются из ОДНОГО вектора (dx,dy), чтобы хвост
   лежал ровно на траектории. CSS @keyframes не используется. */
/* Galina — ночь со звёздами */
.season-atmosphere.atm-galina {
  background:
    radial-gradient(1.5px 1.5px at 12% 18%, #fff, transparent),
    radial-gradient(1px 1px at 28% 42%, #fff, transparent),
    radial-gradient(2px 2px at 47% 12%, #fff, transparent),
    radial-gradient(1px 1px at 63% 33%, #cfe, transparent),
    radial-gradient(1.5px 1.5px at 78% 22%, #fff, transparent),
    radial-gradient(1px 1px at 88% 48%, #fff, transparent),
    radial-gradient(1px 1px at 8% 60%, #fff, transparent),
    radial-gradient(2px 2px at 38% 65%, #cfe, transparent),
    radial-gradient(1px 1px at 72% 70%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 92% 78%, #fff, transparent),
    radial-gradient(ellipse 100% 70% at 50% 100%, rgba(40, 30, 80, 0.6), transparent 70%),
    linear-gradient(180deg, #070b26 0%, #12173f 50%, #1d1f4a 100%);
}
/* Circus — красно-жёлтый купол сверху */
.season-atmosphere.atm-circus {
  background:
    linear-gradient(180deg, transparent 30%, rgba(255, 200, 100, 0.25) 60%, #ffb47a 100%),
    repeating-conic-gradient(from -90deg at 50% -5%,
      var(--red) 0deg 18deg,
      var(--yellow) 18deg 36deg);
  -webkit-mask: linear-gradient(180deg, #000 0 45%, transparent 70%);
          mask: linear-gradient(180deg, #000 0 45%, transparent 70%);
}

/* Контент сезона (въезжает справа). */
.season-inner {
  position: relative;
  z-index: 2;
  max-width: 1100px;
  margin: 0 auto;
  padding: 80px 20px 30px;
  will-change: transform, opacity;
}

/* Шапка сезона: бейдж + название + тэглайн */
.season-head {
  padding: 18px 22px;
  background: rgba(244, 232, 208, 0.92);
  border: 5px solid var(--ink);
  border-radius: 18px;
  box-shadow: 8px 8px 0 rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: baseline;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 28px;
}
.season-head .badge {
  font-family: var(--font-display);
  font-size: 0.8rem;
  color: #fff;
  background: var(--red);
  border: 3px solid var(--ink);
  border-radius: 10px;
  padding: 4px 10px;
  letter-spacing: 1px;
}
.season-head .title {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 5vw, 2.8rem);
  line-height: 1;
  margin: 0;
}
.season-head .tag {
  font-size: 0.9rem;
  opacity: 0.7;
}

/* ── 🌊 ЗАГОЛОВОК NEW WAVE (шрифтом главной, разноцветные буквы) ──
   Образец стиля — .gallery-logo в галерее («THE FUNNY DICKS GALLERY»):
   те же обводка + разноцветные буквы + картинка-пенис вместо I.
   Скрыт по умолчанию, показывается ТОЛЬКО когда открыт New Wave
   (body.season-new-wave). Тогда же классическая шапка .season-head
   прячется (см. body.season-new-wave ниже). */
.season-hero-title {
  display: none;                       /* по умолчанию скрыт */
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 5vw, 3rem); /* как у gallery-logo */
  color: var(--cream);
  text-align: center;
  margin: 0 0 32px;
  padding: 0 12px;
  white-space: normal;
  letter-spacing: 2px;
  line-height: 1.1;
  -webkit-text-stroke: 1.5px var(--ink);
  paint-order: stroke fill;
  text-shadow: 3px 3px 0 var(--blue);
}
.season-hero-title .word {
  display: inline-block;
  white-space: nowrap;
  margin: 0 0.15em;
}
.season-hero-title .c-cream  { color: var(--cream);  }
.season-hero-title .c-yellow { color: var(--yellow); }
.season-hero-title .c-red    { color: var(--red);    }
.season-hero-title .c-pink   { color: var(--pink);   }
.season-hero-title .char.small {
  font-size: 0.62em;
  opacity: 0.9;
}

/* 🌊 NEW WAVE: прячем классическую шапку, показываем заголовок.
   Класс body.season-new-wave вешает JS при открытии New Wave (по data-atm). */
body.season-new-wave .season-head        { display: none; }
body.season-new-wave .season-hero-title  { display: block; }

/* 🌀 SPIRAL-КАРУСЕЛЬ (New Wave). Галерея сезона как вращающаяся спираль галактики.
   ВПЕРВЫЕ в ветке portfolio/spiral. По умолчанию СКРЫТА. Для New Wave:
   прячем сетку-заглушки, показываем спираль. Удобно переключать направление
   (другой переход в master) — одна строка ниже. */
body:not(.season-new-wave) .spiral-carousel { display: none; }
body.season-new-wave .spiral-carousel { display: block; }
body.season-new-wave .season-grid     { display: none; }

/* Сцена-спираль. Fixed-центрирование в видимой области окна, 3D-перспектива. */
.spiral-carousel {
  position: fixed;
  left: 50%; top: 60vh;     /* ниже центра окна — не залезает на заголовок */
  width: 0; height: 0;
  perspective: 1500px;
  perspective-origin: 50% 50%;
  z-index: 10;
}
/* Слайд-картинка спирали. Позиционируется ОТ центра сцены. */
.spiral-carousel .spiral-slide {
  position: absolute;
  left: 50%; top: 50%;
  /* --spiral-w/--spiral-h задаёт JS (адаптив под экран). */
  width: var(--spiral-w, 180px);
  height: var(--spiral-h, 250px);
  margin-left: calc(var(--spiral-w, 180px) / -2);
  margin-top:  calc(var(--spiral-h, 250px) / -2);
  border-radius: 14px;
  overflow: hidden;
  background: #111827;
  /* Каёмка в стиле New Wave — голубой неон. */
  box-shadow:
    0 0 0 1px rgba(125,211,252,.3),
    0 8px 30px rgba(0,0,0,.6);
  backface-visibility: hidden;
  will-change: transform, opacity;
  transform-origin: center center;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;   /* спирали крутятся сами — тап не нужен */
}
.spiral-carousel .spiral-slide img {
  width: 100%; height: 100%;
  object-fit: cover;             /* боковые — обрезка (мелким не страшно) */
  display: block;
  pointer-events: none;
  -webkit-user-drag: none;
}
/* Активный (центральный) слайд: картинка ЦЕЛИКОМ (contain), без обрезки.
   По краям — тёмный фон карточки (для неквадратных пропорций). */
.spiral-carousel .spiral-slide.is-active img {
  object-fit: contain;
  object-position: center;
}
/* Активный (центральный) слайд — ярче, чуть крупнее ореол. */
.spiral-carousel .spiral-slide.is-active {
  box-shadow:
    0 0 0 2px rgba(240,171,252,.6),
    0 0 50px rgba(240,171,252,.3),
    0 12px 40px rgba(0,0,0,.7);
}
@media (max-width: 768px) {
  .season-hero-title { margin: 0 0 24px; }
}

/* Сетка заглушек-картинок внутри сезона */
.season-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 16px;
}
.season-shot {
  aspect-ratio: 1 / 1;
  border: 4px solid var(--ink);
  border-radius: 14px;
  overflow: hidden;
  background: var(--cream);
  box-shadow: 4px 4px 0 rgba(26, 26, 46, 0.4);
}
.season-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ── ФУТЕР СЕЗОНА (Раунд 20h) ──
   Fixed к окну (на <body>, как .back-dick). Скрыт по умолчанию (visibility:
   hidden + opacity:0), проявляется через body.season-open. ⚠️ НЕ child .season-
   view (height:100lvh=776) — иначе bottom:2px = top:757, что ВНЕ видимого
   viewport 720 → «мелькает» при въезде. Fixed к окну = всегда в видимом низу.
   z-index:55 — ниже #backDick (60) и season-view (40)... стоп, выше season-view
   (футер должен быть ВИДЕН поверх сезона). z-index:50 → выше season-view(40),
   ниже back-dick(60). */
.season-footer {
  position: fixed !important;       /* перебиваем absolute из .hero-footer */
  top: auto !important;
  bottom: 2px !important;
  /* Раунд 25: та же ширина, что у остальных футеров — честные 100vw от окна.
     left/right:0 на fixed = почти то же, но 100vw + центрирование через
     left:0 дают гарантированно ту же ширину, что у absolute-футеров главной/
     галереи (там 100vw через margin-left:-50vw) → надпись стоит одинаково. */
  left: 0 !important;
  right: auto !important;
  width: 100vw !important;
  margin-left: 0 !important;
  z-index: 50;
  padding: 0 8px;
  /* скрыт по умолчанию, проявляется через body.season-open (ниже) */
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.3s ease;
}
body.season-open .season-footer {
  visibility: visible;
  opacity: 1;
}

/* ── КНОПКА-ПЕНИС «В ГАЛЕРЕЮ» (.back-dick), фиксированная внизу справа ──
   ЗЕРКАЛИТСЯ целиком (scaleX -1 на SVG) → головка слева, стрелка ←. */
.back-dick {
  position: fixed;
  right: 24px;
  bottom: 60px;            /* Раунд 14c: УРОВНЯНО с .back-btn (тоже 60px) →
                              нет скачка при swap клон→back-dick и кнопка TO
                              GALLERY стоит на той же высоте, что BACK. */
  z-index: 60;
  text-decoration: none;
  cursor: pointer;
  opacity: 0;           /* проявляется после прилёта клона */
  /* 🐛 Раунд 21: pointer-events:none в покое! .back-dick стоит НА ТОМ ЖЕ месте
     (bottom:60px; right), что и #backBtn, И идёт в DOM ПОЗЖЕ (на body, после
     </main>). При равном z-index:60 она рисуется ПОВЕРХ #backBtn. opacity:0 НЕ
     отключает клики → невидимая кнопка перехватывала тапы по видимой BACK →
     звался goBack(), который в галерее молча выходит (seasonBusy=false,
     seasonTimeline=null). Симптом: «кнопка BACK в галерее не кликабельная».
     Та же семья грабель, что Раунд 12 (.season-cta). Включаем клики только при
     открытом сезоне — тогда BACK спрятан и нужна именно TO GALLERY. */
  pointer-events: none;
  /* ТЕНЬ — та же физика, что у клона и у cta:hover (оторвана от фона, солнце
     вверху-слева-впереди → мягкая тень вниз-вправо). Без неё при crossfade
     клон→back-dick тень бы пропадала (у клона drop-shadow, у back-dick нет). */
  filter:
    drop-shadow(4px 10px 1px rgba(26, 26, 46, 0.28))
    drop-shadow(6px 16px 16px rgba(0, 0, 0, 0.15));
  /* дыхание, как у .back-btn / .hero-cta */
  animation: back-dick-breathe 2.6s ease-in-out infinite;
}

/* Кликабельна ТОЛЬКО в открытом сезоне (когда проявляется и видна).
   В покое pointer-events:none → тап проходит СКВОЗЬ невидимую кнопку к #backBtn. */
body.season-open .back-dick { pointer-events: auto; }
.back-dick .dick-wrap {
  position: relative;
  display: inline-block;
  width: 210px;            /* Раунд 14: УНИФИЦИРОВАНО с .dick-clone и #backBtn */
  height: 91px;            /* 210 × (130/300) ≈ 91 */
  line-height: 0;
  transform: translateZ(0);
}
.back-dick .dick-body {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  transform: scaleX(-1);   /* зеркалим форму → головка влево */
}
.back-dick .dick-path {
  fill: var(--yellow);
  stroke: var(--ink);
  stroke-width: 3;          /* Раунд 14: УНИФИЦИРОВАНО с .dick-clone и #backBtn */
  stroke-linejoin: round;
  transition: fill 0.25s ease;
}
.back-dick:hover .dick-path { fill: var(--red); }
.back-dick .dick-label {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 0.72rem;
  letter-spacing: 0.5px;
  white-space: nowrap;
  color: var(--ink);
  transition: color 0.25s ease;
  z-index: 5;
  pointer-events: none;
}
.back-dick:hover .dick-label { color: #fff; }
.back-dick .dick-arrow {
  position: absolute;
  top: 50%;
  left: 18%;
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--ink);
  z-index: 5;
  pointer-events: none;
  transition: transform 0.2s ease, color 0.25s ease;
}
.back-dick:hover .dick-arrow { color: #fff; transform: translate(-50%, -50%) translateX(-4px); }

@keyframes back-dick-breathe {
  /* Раунд 14f: ДЫХАНИЕ как у #backBtn (back-breathe) — scale 1↔1.04 + тень
     рассеивается/уплотняется. Раньше тут был translateY(0↔-4px) → кнопка
     «ездила» вверх-вниз, а не дышала. Миша: «должна дышать как back».
     filter дублируем в каждом кадре (как в back-breathe) — иначе анимация
     перебивала бы постоянную тень .back-dick и тень пропадала бы. */
  0%, 100% {   /* выдох — прижата к фону, тень плотная */
    transform: scale(1);
    filter:
      drop-shadow(4px 10px 1px rgba(26, 26, 46, 0.28))
      drop-shadow(6px 16px 16px rgba(0, 0, 0, 0.15));
  }
  50% {   /* вдох — оторвалась к свету, тень рассеялась */
    transform: scale(1.04);
    filter:
      drop-shadow(6px 13px 1px rgba(26, 26, 46, 0.20))
      drop-shadow(8px 20px 20px rgba(0, 0, 0, 0.10));
  }
}

/* ── КЛОН-ПЕНИС для полёта галерея↔сезон (.dick-clone) ──
   Fixed, поверх обеих сцен. Отрывается от .season-cta, делает сальто, летит
   к .back-dick, превращается в «В ГАЛЕРЕЮ». */
.dick-clone {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 70;
  pointer-events: none;
  opacity: 0;
  will-change: transform, opacity;
  /* ТЕНЬ — ТОЧНО ТАКАЯ ЖЕ, как у .back-dick и .season-cta .dick-wrap. Все три
     должны быть визуально неразличимы по тени → при crossfade клон→back-dick
     (прилёт в сезон) и при отрыве cta→клон (вылет) НЕТ скачка тени. Раньше тут
     была другая тень (0 8px) → скачок при прилёте. Принцип Миши: одна тень. */
  filter:
    drop-shadow(4px 10px 1px rgba(26, 26, 46, 0.28))
    drop-shadow(6px 16px 16px rgba(0, 0, 0, 0.15));
}
.dick-clone .dick-wrap {
  position: relative;
  display: inline-block;
  width: 210px;            /* Раунд 14: УНИФИЦИРОВАНО с .back-dick и #backBtn */
  height: 91px;
  line-height: 0;
}
.dick-clone .dick-body {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.dick-clone .dick-path {
  fill: var(--yellow);
  stroke: var(--ink);
  stroke-width: 3;          /* Раунд 14: УНИФИЦИРОВАНО с .back-dick и #backBtn */
  stroke-linejoin: round;
}
/* Слой надписи клона — крутится МАЯТНИКОМ отдельно от формы (см. JS).
   На GPU-слое (will-change + translateZ) — убирает subpixel snap текста. */
.dick-clone .dick-text {
  position: absolute;
  inset: 0;
  will-change: transform;
  transform: translateZ(0);
}
.dick-clone .dick-label {
  position: absolute;
  top: 50%;
  left: 50%;              /* УНИФИЦИРОВАНО с .back-dick (там 50%) — ИДЕАЛЬНОЕ совпадение позиций надписи при crossfade клон→back-dick. Раньше было 46%/48% → перескакивало на 2-4%. */
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 0.72rem;
  letter-spacing: 0.5px;
  white-space: nowrap;
  color: var(--ink);
  z-index: 5;
  pointer-events: none;
}
.dick-clone .dick-arrow {
  position: absolute;
  top: 50%;
  left: 81%;
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--ink);
  z-index: 5;
  pointer-events: none;
}

/* ╔══════════════════════════════════════════════════════════════════════╗
   ║  🍆 КЛОН-ПЕНИС для бесшовного перехода главная↔галерея (Этап 4).   ║
   ║  Fixed, поверх обоих экранов. Отрывается от #heroCta (главная) или  ║
   ║  #backBtn (галерея) при клике, делает сальто, летит к цели на       ║
   ║  другом экране, превращается в неё. Мотив — как .dick-clone (сезон).║
   ║                                                                      ║
   ║  Размер SVG = 290px (как #heroCta). При прилёте в #backBtn (210px)  ║
   ║  JS плавно масштабирует (scale 0.72). Тень — ОДНА И ПОСТОЯННАЯ       ║
   ║  (как у сезонного клона и .back-dick):                               ║
   ╚══════════════════════════════════════════════════════════════════════╝ */
.home-clone {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 70;              /* поверх экранов, как сезонный .dick-clone */
  pointer-events: none;
  opacity: 0;
  will-change: transform, opacity;
  filter:
    drop-shadow(4px 10px 1px rgba(26, 26, 46, 0.28))
    drop-shadow(6px 16px 16px rgba(0, 0, 0, 0.15));
}
.home-clone .dick-wrap {
  position: relative;
  display: inline-block;
  width: 290px;            /* размер как у #heroCta */
  height: 126px;           /* 290 × (130/300) ≈ 126 */
  line-height: 0;
}
.home-clone .dick-body {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.home-clone .dick-path {
  fill: var(--yellow);
  stroke: var(--ink);
  stroke-width: 3;         /* как у #heroCta (не 7, как у сезонного). Раунд 19: 4→3 */
  stroke-linejoin: round;
}
/* Слой надписи крутится МАЯТНИКОМ отдельно от формы (см. JS). На GPU-слое. */
.home-clone .dick-text {
  position: absolute;
  inset: 0;
  will-change: transform;
  transform: translateZ(0);
}
.home-clone .dick-label {
  position: absolute;
  top: 50%;
  left: 44%;               /* как у #heroCta (ствол центр ~44%) */
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 1.1rem;       /* как у #heroCta */
  letter-spacing: 1.5px;
  white-space: nowrap;
  color: var(--ink);
  z-index: 5;
  pointer-events: none;
}
.home-clone .dick-arrow {
  position: absolute;
  top: 50%;
  left: 82%;               /* как у #heroCta (головка справа) */
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 1.25rem;
  color: var(--ink);
  z-index: 5;
  pointer-events: none;
}

/* ── Мобильная адаптация сезона ── */
@media (max-width: 600px) {
  .season-inner { padding: 70px 14px 110px; }
  /* 🌊 NEW WAVE: опускаем контент ниже на мобиле — даём место костру-сценке
     над заголовком (костёр ~97px высоты + зазор). Без этого костр лез на NEW. */
  body.season-new-wave .season-inner { padding-top: 130px; }
  .season-head { padding: 14px 16px; gap: 10px; }
  .season-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
  .back-dick { right: 14px; bottom: 50px; }   /* Раунд 14c: уровнять с .back-btn (50px на мобиле) */
  /* Раунд 14: УНИФИКАЦИЯ под 150px — вместе с .season-cta/.season-soon (см.
     media-query выше) и #backBtn (правило ниже). На мобиле ВСЕ 4 пенис-кнопки
     одного размера → нет скачков на переходе. */
  .back-dick .dick-wrap,
  .dick-clone .dick-wrap { width: 150px; height: 65px; }
  #backBtn .dick-body { width: 150px; }
  .back-dick .dick-label,
  .dick-clone .dick-label { font-size: 0.6rem; }
  .back-dick .dick-arrow,
  .dick-clone .dick-arrow { font-size: 0.85rem; }
}


