html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* ===== Лендинг «Фазенда Горьковская» ===== */
html {
    scroll-behavior: smooth;
    /* Горизонтальная ось страницы заперта наглухо. Reveal-блоки в стартовом положении
       уезжают на 100vw вправо; от растягивания страницы их держит overflow-x: clip на
       секции, но clip поддерживается только с Safari 16 — в более ранних он игнорируется,
       и страница становилась шире экрана. Пока показ был одноразовым, это почти не
       проявлялось; с повторным показом блоки регулярно возвращаются в стартовое положение
       (например, когда раскрытие аккордеона меняет высоту страницы) — и страницу дёргало вбок.
       overflow-x на html пробрасывается на вьюпорт: отдельный скроллер не создаётся,
       вертикальная прокрутка, якоря и position: fixed работают как прежде. */
    overflow-x: hidden;

    /* Двойной тап больше не масштабирует страницу. Ловилось это на счётчиках гостей и услуг:
       два быстрых нажатия по «+» или «−» — обычное дело, а браузер понимает их как жест зума
       и дёргает весь экран. В остальных местах сайта пары быстрых тапов по одной точке просто
       не случается, поэтому там проблема и не проявлялась.
       manipulation оставляет прокрутку и пинч двумя пальцами, убирая только двойной тап.
       Свойство не наследуется, но ограничение предка распространяется на потомков, поэтому
       правила на html достаточно; элементы со своим touch-action (лента полноэкранного
       просмотра — pan-x, увеличенный кадр — none) продолжают работать как задумано. */
    touch-action: manipulation;
}

/* Карусель домов: горизонтальная прокрутка без видимого скроллбара */
.no-scrollbar {
    -ms-overflow-style: none;
    scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {
    display: none;
}

/* Градиентная обводка карточек домов: белая (#FFFFFF) в левом-верхнем углу,
   затухает к правому-нижнему и сливается с фоном секции.
   Рисуется псевдоэлементом + маской — это единственный способ дать
   градиентную рамку на скруглённом блоке (border-image не уважает border-radius). */
.card-edge {
    position: relative;
    --edge-r: 14px;            /* базовый радиус скругления карточки (rounded-[14px]) */
    --edge-bg: rgb(46 58 36);  /* цвет фона секции, в который затухает обводка (forest) */
    --edge-hi-pos: 0%;         /* позиция стопа белого блика на градиенте */
}
.card-edge::before {
    content: "";
    position: absolute;
    inset: -1.5px;                          /* кольцо выходит за край карточки → outside-обводка */
    border-radius: calc(var(--edge-r) + 1.5px); /* = скругление карточки + смещение, концентрично */
    padding: 1.5px;                         /* толщина обводки */
    /* Сплошное затухание по диагонали 225°: от полупрозрачного белого в правом-верхнем
       углу (0%) к цвету фона секции в левом-нижнем (100%). */
    background: linear-gradient(225deg, #ffffffa3 var(--edge-hi-pos), var(--edge-bg) 100%);
    /* Маска оставляет видимой только рамку (кольцо), вырезая внутреннюю область */
    -webkit-mask:
        linear-gradient(#fff 0 0) content-box,
        linear-gradient(#fff 0 0);
            mask:
        linear-gradient(#fff 0 0) content-box,
        linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    pointer-events: none;
}

/* ── Лендинг: плавное появление блоков по мере прокрутки (js/landing.js) ──
   Элементы с [data-reveal] стартуют невидимыми и всплывают, когда IntersectionObserver
   вешает .is-revealed. Скрытые стартовые состояния включаются только классом
   has-reveal на <html> (его ставит инлайн-скрипт в App.razor) — без JS контент виден сразу.
   Показ не одноразовый: уехавший из вида блок сбрасывается и появляется снова (landing.js). */
html.has-reveal [data-reveal] {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity .8s ease-out, transform .8s cubic-bezier(.22, .61, .36, 1);
    /* --rd — сдвиг старта блока. Нужен там, где несколько соседних блоков попадают
       в экран разом (низ футера, верх страницы дома): каждому свой сдвиг, и показ
       читается как последовательность, а не как одна вспышка. По умолчанию 0.
       Для блоков с каскадом (data-reveal="items") та же переменная сдвигает и детей —
       смысл один: «когда блок начинает появляться». */
    transition-delay: var(--rd, 0ms);
}
html.has-reveal [data-reveal="fade"] { transform: none; } /* контейнеры-обёртки: без сдвига */
/* Варианты "right"/"left": блок влетает из-за края экрана — длинный путь (100vw),
   кривая с сильным торможением в конце даёт ощущение инерции. Сам [data-reveal]
   здесь остаётся статичным (IntersectionObserver считает пересечение по
   трансформированному боксу — унесённый за экран элемент «не виден» и никогда
   не сработал бы), едет вложенный [data-reveal-slide]. Секции с такими блоками
   нужен overflow-x-clip, иначе стартовое положение растянет страницу вширь.
   Вариант "items" — контейнер только триггер: сам не движется, едут дочерние
   [data-reveal-item] (первый экран, где анимация задана порядком, а не блоком). */
html.has-reveal [data-reveal="right"],
html.has-reveal [data-reveal="left"],
html.has-reveal [data-reveal="items"] {
    opacity: 1;
    transform: none;
    transition: none;
}
html.has-reveal [data-reveal="right"] [data-reveal-slide],
html.has-reveal [data-reveal="left"] [data-reveal-slide] {
    opacity: 0;
    transition: opacity .55s ease-out, transform 1.1s cubic-bezier(.16, 1, .3, 1);
}
html.has-reveal [data-reveal="right"] [data-reveal-slide] { transform: translateX(100vw); }
html.has-reveal [data-reveal="left"] [data-reveal-slide] { transform: translateX(-100vw); }
html.has-reveal [data-reveal="right"].is-revealed [data-reveal-slide],
html.has-reveal [data-reveal="left"].is-revealed [data-reveal-slide] {
    opacity: 1;
    transform: none;
}
html.has-reveal [data-reveal].is-revealed {
    opacity: 1;
    transform: none;
}

/* Разделительная линия «прорастает» слева направо после появления блока. */
html.has-reveal [data-reveal] [data-reveal-rule] {
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform .9s cubic-bezier(.65, 0, .35, 1) .35s;
}
html.has-reveal [data-reveal].is-revealed [data-reveal-rule] { transform: scaleX(1); }

/* Каскад дочерних элементов: каждый следующий (--i из разметки) чуть позже
   предыдущего. По умолчанию всплытие снизу; вариант "right" — выезд справа
   (фото карусели).
   Тайминг настраивается на контейнере: --rd — пауза до первого элемента,
   --rs — шаг между соседями. Одинаковый --i у двух элементов = выходят вместе. */
html.has-reveal [data-reveal] [data-reveal-item] {
    opacity: 0;
    transform: translateY(22px);
    transition: opacity .6s ease-out, transform .6s cubic-bezier(.22, .61, .36, 1);
    transition-delay: calc(var(--rd, 150ms) + var(--i, 0) * var(--rs, 90ms));
}
html.has-reveal [data-reveal] [data-reveal-item="right"] { transform: translateX(56px); }
html.has-reveal [data-reveal] [data-reveal-item="left"] { transform: translateX(-56px); }
/* Вариант "scale" — проявление с лёгким увеличением (акцент на кнопке-CTA). */
html.has-reveal [data-reveal] [data-reveal-item="scale"] { transform: scale(.88); }
/* Вариант "fade" — каскад только прозрачностью, без сдвига. Обязателен для элементов
   внутри scroll-snap-контейнеров (фото карусели): transform на snap-элементах
   заставляет браузер пересчитывать snap-геометрию на каждом кадре → дёрганье. */
html.has-reveal [data-reveal] [data-reveal-item="fade"] { transform: none; }
html.has-reveal [data-reveal].is-revealed [data-reveal-item] {
    opacity: 1;
    transform: none;
}

/* Пользователь просил поменьше движения — показываем всё сразу и без анимаций. */
@media (prefers-reduced-motion: reduce) {
    html.has-reveal [data-reveal],
    html.has-reveal [data-reveal] [data-reveal-slide],
    html.has-reveal [data-reveal] [data-reveal-item],
    html.has-reveal [data-reveal] [data-reveal-rule] {
        opacity: 1;
        transform: none;
        transition: none;
    }
}

/* ── Меню-бургер: пункты выходят лесенкой ──
   Состояние берём прямо у чекбокса-переключателя (#site-nav-toggle) — панель меню его
   следующий сосед, поэтому JS не нужен, как и всему меню. Задержка растёт по --i из разметки.
   При закрытии правило с задержкой перестаёт действовать, и пункты гаснут разом вместе
   с панелью — закрытие должно быть мгновенным, лесенка нужна только на открытии. */
[data-nav-item] {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity .3s ease-out, transform .35s cubic-bezier(.22, .61, .36, 1);
}
#site-nav-toggle:checked ~ [data-nav-panel] [data-nav-item] {
    opacity: 1;
    transform: none;
    transition-delay: calc(120ms + var(--i, 0) * 70ms);
}
@media (prefers-reduced-motion: reduce) {
    [data-nav-item] { opacity: 1; transform: none; transition: none; }
    #site-nav-toggle:checked ~ [data-nav-panel] [data-nav-item] { transition-delay: 0s; }
}

/* ── Подсказка о свайпе: стрелки по фактическому положению ленты (js/swipe-hint.js) ──
   Скрипт вешает на подсказку can-left/can-right (куда ещё можно листать) и is-idle
   (лента помещается целиком — листать нечего). Стрелки гасим прозрачностью, из потока
   не убираем: иначе текст подсказки прыгал бы вбок при каждом свайпе.
   Пока скрипт не отработал (или JS выключен) — видны обе стрелки. */
[data-swipe-arrow] { transition: opacity .25s ease; }
[data-swipe-hint][data-swipe-bound] [data-swipe-arrow] { opacity: 0; }
[data-swipe-hint].can-left [data-swipe-arrow="left"],
[data-swipe-hint].can-right [data-swipe-arrow="right"] { opacity: 1; }
[data-swipe-hint].is-idle { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
    [data-swipe-arrow] { transition: none; }
}

/* ── Первый экран: «живой» кадр, когда видео не пускают ──
   Класс is-video-blocked ставит js/hero-video.js на секцию после отказа video.play()
   (энергосбережение iOS запрещает автозапуск). Проявляется запасной [data-hero-fallback]
   — тот же снимок, что и poster, — и очень медленно ходит зумом туда-обратно: экран не
   выглядит застывшей картинкой, но внимания на себя не тянет. Секция с overflow-hidden,
   поэтому увеличенный кадр не растянет страницу.
   Анимируем именно <img>, а НЕ <video>: на iOS трансформ у видео ломает его
   композиционный слой — вместо кадра остаётся белый прямоугольник.
   Как только видео пошло, скрипт снимает класс и кадр плавно гаснет. */
@keyframes hero-poster-drift {
    from { transform: scale(1.03); }
    to   { transform: scale(1.12); }
}
.is-video-blocked [data-hero-fallback] {
    opacity: 1;
    animation: hero-poster-drift 24s ease-in-out infinite alternate;
}
@media (prefers-reduced-motion: reduce) {
    .is-video-blocked [data-hero-fallback] { animation: none; }
}

/* ── Плашки-уведомления внизу экрана ──
   Обработка данных и пояснение про тестовые логотипы. Уезжают вниз при ответе
   (js/notices.js). Прозрачность плюс сдвиг: исчезновение читается как «убрали», а не
   как пропажа отрисовки. */
[data-notice] {
    transition: opacity .3s ease-out, transform .3s cubic-bezier(.22, .61, .36, 1);
}
[data-notice].is-accepted {
    opacity: 0;
    transform: translateY(14px);
    pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
    [data-notice] { transition: none; }
}

/* ── Логотипы первого экрана: тап убирает метку ──
   Две метки (бляшка у края и крупная по центру) при каждой загрузке меняются местами
   случайно. Тап убирает метку, и чем чаще её убирают, тем дольше она не возвращается:
   после первого раза вернётся на следующей перезагрузке, после второго пропустит одну,
   после третьего — две. Счёт у каждой метки свой и привязан к картинке, а не к месту
   (js/logos.js; решение о показе принимает инлайн-скрипт в App.razor до отрисовки).
   Убираем прозрачностью и лёгким уменьшением, а не display: none, — чтобы исчезновение
   читалось как действие, а не как сбой отрисовки. Место в потоке остаётся занятым, поэтому
   соседний текст не подпрыгивает. */
/* Метка «отдыхает» — не показываем вовсе. Здесь уже display: none: анимировать нечего,
   а место в потоке пусть освободится. */
html.logo-off-mark [data-logo="mark"],
html.logo-off-full [data-logo="full"] { display: none; }
[data-logo] {
    cursor: pointer;
    transition: opacity .3s ease-out, transform .3s cubic-bezier(.22, .61, .36, 1);
}
[data-logo].is-dismissed {
    opacity: 0;
    transform: scale(.88);
    pointer-events: none;                       /* убранную метку нельзя нажать повторно */
}
@media (prefers-reduced-motion: reduce) {
    [data-logo] { transition: none; }
}

/* ── Заглушка на время загрузки фото ──
   Пока кадр не пришёл, вместо пустого места — мягкий «бегущий блик» по тёплому серому.
   Рисуется ФОНОМ самого <img>: у непрозрачных снимков с object-fit: cover загруженная
   картинка перекрывает фон сама, поэтому подменять элементы не нужно.
   Класс снимает gallery.js по событию load — иначе анимация продолжала бы крутиться под
   каждым уже показанным кадром (на лендинге их несколько десятков). */
@keyframes img-ph-sheen {
    from { background-position: 150% 0; }
    to   { background-position: -50% 0; }
}
.img-ph {
    background-color: #DAD5C7;                  /* приглушённый кремовый — в палитре сайта */
    background-image: linear-gradient(90deg,
        rgba(255, 255, 255, 0) 0%,
        rgba(255, 255, 255, .55) 50%,
        rgba(255, 255, 255, 0) 100%);
    background-size: 60% 100%;
    background-repeat: no-repeat;
    animation: img-ph-sheen 1.6s ease-in-out infinite;
}
/* В полноэкранном просмотре фон чёрный, и светлая заглушка била бы по глазам;
   там же object-fit: contain — фон виден и по краям кадра, пока идёт загрузка. */
.gv-img.img-ph {
    background-color: #1c1c1c;
    background-image: linear-gradient(90deg,
        rgba(255, 255, 255, 0) 0%,
        rgba(255, 255, 255, .1) 50%,
        rgba(255, 255, 255, 0) 100%);
}
@media (prefers-reduced-motion: reduce) {
    .img-ph { animation: none; }
}

/* ── Философия: фокус кадров фото-ленты ──
   Полный разбор — docs/philosophy-carousel-focus.md: два режима и почему их два, история
   дрожи с тупиковыми попытками, связки величин (масштаб ↔ py ленты ↔ gap).
   Состояние до переработки, на случай отката — docs/philosophy-carousel-history.md.
   Масштаб каждого кадра — функция близости его центра к центру ленты (гауссова кривая,
   считает gallery.js → CSS-переменная --focus-scale): по центру максимум, дальше плавно
   меньше, самый близкий к фокусу — крупнее дальних. Скейлим вложенный <img>, а не сам
   snap-элемент [data-focus-item]: его границы не меняются → геометрия scroll-snap стабильна.
   transform-origin по центру + snap-center держат точку привязки неподвижной.
   transition здесь НЕТ намеренно (был .1s linear): масштаб жёстко привязан к позиции
   скролла, и любое сглаживание превращается в отставание — на резком свайпе кадр не
   поспевал за лентой и колебался вокруг нужного размера. Плавность даёт пересчёт каждый
   кадр (rAF-петля в gallery.js), а не переходы.
   will-change поднимает кадры на отдельный слой: масштаб меняется каждый кадр, и без
   этого браузер перерисовывает картинку целиком. */
/* isolation: isolate — лента образует собственный контекст наложения, и z-index кадров
   действует ТОЛЬКО внутри неё. Без этого слои кадров (gallery.js выдаёт до 142 = масштаб
   × 100) конкурировали со слоями всей страницы и перекрывали полноэкранный просмотр
   (.gv-overlay, z-index 100) — маленькая карусель просвечивала поверх большой. */
[data-photo-strip] { isolation: isolate; }
/* Свободное место по краям ленты — распорками, а не отступами контейнера. Отступ
   прокручиваемой области браузеры учитывают по-разному (особенно конечный), из-за чего
   правый край вёл себя не так, как левый: последний кадр уезжал дальше центра. Распорка —
   обычный флекс-элемент, она попадает в поток и в ширину прокрутки честно, поэтому оба
   конца получаются зеркальными.
   Ширина = половина ленты минус половина кадра минус зазор: gap ставится и между распоркой
   и первым кадром, иначе крайние кадры не дотянулись бы до центра ровно на его величину. */
[data-photo-strip]::before,
[data-photo-strip]::after {
    content: "";
    flex: 0 0 max(0px, calc(50% - min(24.5vw, 106px) - 4rem));
}
/* position: relative — чтобы работал z-index, который gallery.js выдаёт по масштабу
   (крупный кадр перекрывает соседей, они уходят под него). На геометрию scroll-snap
   не влияет: сам бокс остаётся на месте, едет только вложенный <img>. */
[data-focus-item] { position: relative; }
[data-focus-item] > img {
    /* Пробовал выкладывать кадр в максимальном размере и только уменьшать его (width 142%
       + scale/1.42) — идея была убрать перерастеризацию при увеличении. Откатано: дрожь не
       ушла, а любая ветка, где --focus-scale не проставлена, показывала кадр застывшим на
       70% ширины. Цена оказалась выше пользы. */
    transform: scale(var(--focus-scale, 1));
    /* Приглушение дальних кадров — прозрачностью САМОЙ картинки. Раньше поверх лежала
       вуаль цвета секции отдельным слоем: она была нужна, пока кадры перекрывались (сквозь
       полупрозрачное фото просвечивал бы сосед). Зазор с тех пор вырос до 64px, кадры не
       перекрываются даже в максимуме — и лишний слой стал лишней работой на каждом кадре.
       Тень — статичная, на самой картинке. Ключевое слово «статичная»: она запекается в
       слой и дальше просто масштабируется вместе с ним. Прежняя тень отдельным слоем
       меняла размытие по мере фокуса, и браузер перерисовывал её заново каждый кадр.
       Итог: на кадр прокрутки обновляется ОДИН слой на фотографию вместо трёх, и обе
       меняющиеся величины (transform, opacity) обрабатывает композитор. */
    opacity: calc(.62 + .38 * var(--focus-t, 1));
    box-shadow: 0 10px 24px rgba(51, 36, 28, .3);
    will-change: transform, opacity;
}

/* Диагностические режимы ленты (ставит gallery.js по параметру ?focus= в адресе):
   focus-off — эффекта нет вовсе: кадры одного размера, без приглушения. Так проверяется,
     дрожит ли прокрутка сама по себе, без нашего масштабирования;
   focus-js — браузерная анимация отключена принудительно, работает покадровый расчёт в
     скрипте. Нужно, чтобы на Android воспроизвести ровно тот путь, что работает на iOS:
     иначе Chrome возьмёт зум на себя и покажет заведомо ровную картинку.
   Удалить вместе с переключателями, когда причина найдётся. */
[data-photo-strip].focus-off > [data-focus-item] > img {
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
    will-change: auto;
}
/* ── Ступенчатый зум: запасной путь для браузеров без привязки анимации к прокрутке ──
   Увеличен один кадр — тот, что ближе всех к центру (класс is-focused ставит gallery.js).
   Размер меняется обычным переходом, то есть РОВНО ОДИН раз на смену кадра, а не 60 раз
   в секунду. Прежний непрерывный зум пересчитывался скриптом на каждом кадре прокрутки и
   мелко дрожал; проверка на одном устройстве (Android: браузерная анимация — гладко,
   покадровый расчёт — дрожь) показала, что виноват сам подход, а не браузер.
   Чем отличается на глаз: масштаб идёт за прокруткой не непрерывно, а «подхватывает»
   новый центральный кадр. Зато ровно везде. */
[data-photo-strip].focus-step > [data-focus-item] > img {
    animation: none !important;
    transform: translateY(12px) scale(.85);
    opacity: .62;
    transition: transform .35s cubic-bezier(.22, .61, .36, 1), opacity .3s ease-out;
}
[data-photo-strip].focus-step > [data-focus-item].is-focused > img {
    transform: translateY(0) scale(1.42);
    opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
    [data-photo-strip].focus-step > [data-focus-item] > img {
        transform: none;
        opacity: 1;
        transition: none;
    }
}

/* ── Тот же зум, но силами браузера (прокрутка как шкала анимации) ──
   Там, где это поддерживается, масштаб привязан к положению ленты напрямую и считается
   композитором — скрипт в этом не участвует вовсе (js/gallery.js сам отключает свою петлю,
   увидев поддержку).
   Зачем: JS-петля читает scrollLeft раз в кадр, а на инерционной прокрутке в iOS прокрутка
   живёт на отдельном потоке и значения приходят рывками. Отсюда мелкая дрожь масштаба,
   которую сглаживанием можно уменьшить, но не убрать — она в самом источнике данных.
   Здесь источник другой: браузер знает точное положение сам, промежуточных замеров нет.
   Ключевые кадры повторяют прежнюю гауссову кривую: 50% — кадр ровно по центру ленты. */
@supports (animation-timeline: view(inline)) {
    /* Флаг для gallery.js: «зум берёт на себя браузер». Скрипт читает его вычисленным
       стилем и не включает свой ступенчатый режим.
       Почему так, а не проверкой в скрипте: CSS.supports('animation-timeline','view(inline)')
       в ряде движков возвращает ложь на значение-функцию с аргументом — Android из-за этого
       зря уходил в ступенчатый режим, хотя плавный там работает. Здесь о поддержке сообщает
       тот же движок, который и применяет правило, разойтись они не могут. */
    [data-photo-strip] { --focus-css: 1; }

    @keyframes fz-focus {
        0% { transform: translateY(12.0px) scale(0.850); opacity: 0.620; }
        5% { transform: translateY(12.0px) scale(0.850); opacity: 0.620; }
        10% { transform: translateY(12.0px) scale(0.852); opacity: 0.621; }
        15% { transform: translateY(11.9px) scale(0.857); opacity: 0.625; }
        20% { transform: translateY(11.5px) scale(0.872); opacity: 0.635; }
        25% { transform: translateY(10.7px) scale(0.910); opacity: 0.660; }
        30% { transform: translateY(9.2px) scale(0.985); opacity: 0.710; }
        35% { transform: translateY(6.7px) scale(1.104); opacity: 0.789; }
        40% { transform: translateY(3.6px) scale(1.248); opacity: 0.885; }
        45% { transform: translateY(1.0px) scale(1.371); opacity: 0.967; }
        50% { transform: translateY(0.0px) scale(1.420); opacity: 1.000; }
        55% { transform: translateY(1.0px) scale(1.371); opacity: 0.967; }
        60% { transform: translateY(3.6px) scale(1.248); opacity: 0.885; }
        65% { transform: translateY(6.7px) scale(1.104); opacity: 0.789; }
        70% { transform: translateY(9.2px) scale(0.985); opacity: 0.710; }
        75% { transform: translateY(10.7px) scale(0.910); opacity: 0.660; }
        80% { transform: translateY(11.5px) scale(0.872); opacity: 0.635; }
        85% { transform: translateY(11.9px) scale(0.857); opacity: 0.625; }
        90% { transform: translateY(12.0px) scale(0.852); opacity: 0.621; }
        95% { transform: translateY(12.0px) scale(0.850); opacity: 0.620; }
        100% { transform: translateY(12.0px) scale(0.850); opacity: 0.620; }
    }
    [data-focus-item] > img {
        animation: fz-focus linear both;
        animation-timeline: view(inline);
    }
}
@media (prefers-reduced-motion: reduce) {
    /* Без движения масштаб не меняется — значит и вуаль с растущей тенью не нужны:
       оставляем ровную лёгкую тень и полностью открытые кадры. */
    [data-focus-item] > img {
        transform: none;
        opacity: 1;
        will-change: auto;
        box-shadow: 0 2px 8px rgba(51, 36, 28, .1);
    }
}

/* ── Бронирование: состояния выбора дат в календаре /booking ── */
/* Подсветку выбора держим в обычном CSS (а не в динамических Tailwind-классах),
   чтобы поведение не зависело от рантайм-сканера Tailwind-CDN. */
.bd-price { color: #33241C; }                  /* bark — цена тёмная, как и число */
.bd-day:hover .bd-price { color: inherit; }    /* на ховере цена следует за цветом дня */

/* Крестик занятого дня — значительно толще за счёт обводки глифа. */
.bd-cross { -webkit-text-stroke: 0.8px currentColor; }

/* Недоступные для выбора дни (после выбора заезда) — приглушаем. */
.bd-day.is-dim { opacity: .35; }

/* Тач-экраны: после тапа :hover «залипает» на ячейке, из-за чего дата, снятая
   повторным нажатием, оставалась кремовой (hover:bg-cream из разметки) до
   следующего касания. Без настоящего ховера ховер-подсветку дней отключаем. */
@media (hover: none) {
    .bd-day[data-date]:hover { background-color: #fff; }
}

/* Кнопка «Добавить» услуги: светлая (F5F4F0) по умолчанию, bark на наведении/нажатии. */
.bd-add { background: #F5F4F0; color: #33241C; }
.bd-add:hover, .bd-add:active { background: #33241C; color: #D4CBBD; }
/* Услуга добавлена — зелёная «Добавлено» (текст светлый, semibold); ховер чуть темнее. */
.bd-add.is-added { background: #2E3A24; color: #D4CBBD; font-weight: 600; }
.bd-add.is-added:hover { background: #283420; }

/* Подсказка «… — только для выезда»: появляется красной, коротко дрожит,
   затем плавно перетекает в обычный цвет (bark). */
@keyframes bd-hint {
    0%   { color: #dc2626; transform: translateX(0); }
    8%   { transform: translateX(-3px); }
    16%  { transform: translateX(3px); }
    24%  { transform: translateX(-2px); }
    32%  { transform: translateX(2px); }
    40%  { color: #dc2626; transform: translateX(0); }
    100% { color: #33241C; transform: translateX(0); }
}
.bd-shake { animation: bd-hint 1s ease-out both; }

/* ── Выбранный диапазон дат ──
   ВАРИАНТ 4 (сравниваем на глаз): крайние дни залиты целиком и скруглены полуовалом
   снаружи, но внешняя половина — тот же зелёный с прозрачностью, с плавным переходом
   к сплошному. Половина дня читается как затухание полосы, а не как вырез: силуэт
   остаётся цельным, цифра стоит по центру клетки и всегда на цветном фоне.
   Заезд: свободна первая половина суток → бледнеет влево. Выезд: наоборот.

   Другие опробованные варианты (для быстрого возврата):
   • ВАРИАНТ 3 — то же самое, но без затухания: у .is-start/.is-end/.is-range общий
     background: #2E3A24 !important, скругления те же.
   • ВАРИАНТ 2 — полоса от СЕРЕДИНЫ клетки заезда до СЕРЕДИНЫ клетки выезда: у крайних
     дней background: transparent, полосу рисует ::before (top/bottom 0; заезд —
     left: 50%; right: 0; border-radius: 999px 0 0 999px; выезд — зеркально), одиночный
     заезд — ::before с left/right: 15% и полным скруглением; содержимое крайних клеток
     сдвинуто внутрь: .is-start > * { transform: translateX(25%) }, .is-end — минус,
     .is-single > * { transform: none }, и всем им position: relative; z-index: 1.
   • ВАРИАНТ 1 — заливка целиком плюс белый вырез-полуовал по внешнему краю:
     ::before inset 0, background #FFFFFF, clip-path ellipse(26% 34% at 0% 50%) слева
     и ellipse(26% 34% at 100% 50%) справа.
   • ИСХОДНЫЙ — все углы прямые (border-radius: 0), половина дня — кремовый треугольник:
     ::before inset 0, background #E8E4D8, clip-path polygon(0 0, 30% 50%, 0 100%) слева
     и polygon(100% 0, 70% 50%, 100% 100%) справа. */
.bd-day.is-start,
.bd-day.is-end,
.bd-day.is-range {
    position: relative;
    background: #2E3A24 !important;            /* forest */
    color: #E8E4D8 !important;                 /* cream */
}
/* Середина диапазона — строго прямая, чтобы дни склеивались в сплошную полосу. */
.bd-day.is-range { border-radius: 0 !important; }

/* Затухание к внешнему краю — не по всей высоте клетки, а полуовалом из середины: сверху
   и снизу полоса остаётся сплошной, светлеет только средний пояс. Раньше это был линейный
   градиент во всю высоту (linear-gradient 90deg, rgba(46,58,36,.28) → #2E3A24 60%), и край
   выглядел срезанным целиком.
   Реализовано осветлением, а не прозрачностью: заливка клетки сплошная, а поверх лежит
   белое пятно с мягкими краями — так форма пятна не зависит от того, что под клеткой. */
.bd-day.is-start::before,
.bd-day.is-end::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;                     /* пятно не вылезает за скруглённый угол */
    pointer-events: none;
}
.bd-day.is-start::before {
    background: radial-gradient(ellipse 58% 100% at 0% 50%, rgba(255, 255, 255, .72), rgba(255, 255, 255, 0) 72%);
}
.bd-day.is-end::before {
    background: radial-gradient(ellipse 58% 100% at 100% 50%, rgba(255, 255, 255, .72), rgba(255, 255, 255, 0) 72%);
}
/* Цифра и цена — поверх осветления, иначе край пятна размывал бы текст. */
.bd-day.is-start > *,
.bd-day.is-end > * {
    position: relative;
    z-index: 1;
}
/* Концы: скругление только с внешней стороны, радиусом 0.5rem — чуть мягче прямого угла
   и чуть плотнее, чем rounded-xl (0.75rem) у клеток занятых и прошедших дней. Полоса
   выделения так читается как единый брусок, а не как цепочка скруглённых плашек. */
.bd-day.is-start { border-radius: .5rem 0 0 .5rem !important; }
.bd-day.is-end   { border-radius: 0 .5rem .5rem 0 !important; }
/* Выбран только заезд (выезда ещё нет) — одиночная клетка, скругляем со всех сторон. */
.bd-day.is-single { border-radius: .5rem !important; }

.bd-day.is-start .bd-price,
.bd-day.is-end .bd-price,
.bd-day.is-range .bd-price { color: #E8E4D8; }

/* Верхние фиксированные контролы (бургер навигации, крестик меню) — ниже чёлки/статус-бара.
   Работает вместе с viewport-fit=cover в meta viewport (иначе env() = 0). Заменяет top-4. */
.safe-top-4 { top: calc(1rem + env(safe-area-inset-top, 0px)); }
/* Верх контента вровень с фиксированными кружками (safe-top-4): тот же отступ и та же
   поправка на вырез экрана. Обычный pt-* этого не умеет — при ненулевом safe-area-inset
   кнопки уезжали бы вниз, а контент оставался на месте и залезал под них. */
.safe-pt-4 { padding-top: calc(1rem + env(safe-area-inset-top, 0px)); }

/* Левая кнопка-пара к бургеру (× на шаге дат, «назад» на шаге выбора дома) —
   две разные роли по ширине экрана.
   Телефон: карточка занимает почти весь экран, кнопка работает в паре с бургером —
   отсчитываем от краёв экрана, чтобы обе были на одной высоте и зеркально по краям.
   От sm: карточка центрирована и узкая, а бургер остаётся у края окна — кнопка,
   привязанная к экрану, оторвалась бы от карточки на пол-экрана. Поэтому возвращаем
   её в угол самой карточки/колонки (у них position: relative). */
.booking-nav-left {
    position: fixed;
    top: calc(1rem + env(safe-area-inset-top, 0px));
    left: 1rem;
}
@media (min-width: 640px) {
    .booking-nav-left { position: absolute; top: 1rem; left: 1rem; }
}

/* ── Полноэкранный просмотр фото «как сторис» (строит js/gallery.js) ──
   Разметка создаётся динамически, поэтому стили держим в обычном CSS —
   не зависим от рантайм-сканера Tailwind-CDN (см. календарь выше). */
.gv-lock { overflow: hidden; }                 /* страница под оверлеем не прокручивается */
/* Пока открыт полноэкранный просмотр, гасим зум фото-ленты под ним. Её всё равно не видно,
   а работа идёт: при листании в просмотре мы подматываем ленту на тот же кадр, и каждая
   такая подмотка пересчитывает привязанную к прокрутке анимацию. Композитор занимается
   невидимым, и на этом спотыкается анимация самого просмотра — на Android это читалось
   как мерцание при перелистывании (только у философии: больше ни у одной карусели
   scroll-анимаций нет).
   Пауза снимается при закрытии, и кадр сразу оказывается в нужном размере: анимация
   привязана к положению ленты, а не ко времени, догонять ей нечего. */
.gv-lock [data-photo-strip] > [data-focus-item] > img { animation-play-state: paused; }

.gv-overlay {
    position: fixed;
    inset: 0;
    z-index: 100;                              /* поверх бургера (z-40) и его меню (z-50) */
    background: #000;
    opacity: 0;
    /* Гашение фона согласовано с полётом кадра (.gv-spring, .34s той же кривой):
       раньше подложка исчезала за .2s, кадр ещё летел вниз — и страница проступала
       рывком. Одинаковые длительность и кривая дают единое плавное движение. */
    transition: opacity .34s cubic-bezier(.22, .61, .36, 1);
    outline: none;
    user-select: none;
    -webkit-user-select: none;
}
.gv-overlay.is-open { opacity: 1; }

.gv-body { position: absolute; inset: 0; }
/* Хром просмотра исчезает на время жеста закрытия (--gv-chrome ведёт gallery.js): его
   жёсткие кромки — верхняя полоса затемнения и плашка подписи — при движении тела вниз
   читались как границы окна. На возврате пружиной хром проявляется обратно, плавно. */
.gv-topfade, .gv-top, .gv-cap, .gv-nav { opacity: var(--gv-chrome, 1); }
.gv-body.gv-spring .gv-topfade,
.gv-body.gv-spring .gv-top,
.gv-body.gv-spring .gv-cap,
.gv-body.gv-spring .gv-nav { transition: opacity .34s ease-out; }
/* Пружина возврата и полёт при закрытии. Прозрачность едет вместе с трансформацией:
   уезжающий кадр должен и растворяться, иначе он «отваливается» вниз резким пятном. */
.gv-body.gv-spring { transition: transform .34s cubic-bezier(.22, .61, .36, 1), opacity .34s ease-out; }

.gv-strip {
    /* Тонкий зазор между кадрами: при свайпе видно, что листается именно лента, а соседний
       кадр не приклеен встык. Значение продублировано в gallery.js (GAP) — шаг прокрутки
       считается как ширина экрана плюс зазор, менять их нужно вместе. */
    gap: 2px;
    display: flex;
    height: 100%;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior: contain;
    touch-action: pan-x;                       /* горизонталь — нативный свайп; вертикаль — жест закрытия (JS) */
}
.gv-slide {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    flex-shrink: 0;
    scroll-snap-align: center;
    scroll-snap-stop: always;                  /* как в сторис: один свайп — один кадр */
    /* Увеличенный кадр (зум/пинч) шире слайда — держим его в своих границах, иначе он
       наезжает на соседние фото. Зум снимается и в JS при смене кадра (clearZoom), но
       клип нужен и на время самой анимации возврата. */
    overflow: hidden;
}
.gv-img {
    /* Скругление ведёт жест закрытия (--gv-round из gallery.js): по мере вытягивания вниз
       кадр скругляется и читается как убираемая карточка, а не как экран с прямыми углами.
       В покое переменной нет — углы прямые. */
    border-radius: var(--gv-round, 0px);
    width: 100%;
    height: 100%;
    object-fit: contain;
    -webkit-user-drag: none;
}
.gv-img.is-zoomed { cursor: grab; }             /* увеличенное фото можно тянуть (пан) */

/* Верхняя панель: прогресс-сегменты + счётчик + закрыть. Лёгкое затемнение
   сверху — чтобы контролы читались и на светлых кадрах. */
.gv-top {
    position: absolute;
    inset: 0 0 auto 0;
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: calc(.75rem + env(safe-area-inset-top, 0px)) .75rem .875rem;
    pointer-events: none;                      /* свайп сквозь панель; кликабелен только ✕ */
}
/* Затемнение под контролами — ОТДЕЛЬНЫМ слоем, а не фоном панели. Причина: фон панели
   ограничен её высотой (~46px), и двухточечный градиент внутри такой полосы обрывается
   заметной кромкой — при закрытии свайпом она читалась как чёрная полоса поперёк экрана.
   Здесь слой вдвое выше панели, затухание задано ступенями (линейная интерполяция альфы
   глазом воспринимается как обрыв — промежуточные стопы это лечат), а пик приглушён
   с 0.45 до 0.3: контролам этого хватает, а на светлых кадрах полоса почти не видна. */
.gv-topfade {
    position: absolute;
    inset: 0 0 auto 0;
    height: calc(7rem + env(safe-area-inset-top, 0px));
    background: linear-gradient(to bottom,
        rgba(0, 0, 0, .30) 0%,
        rgba(0, 0, 0, .23) 22%,
        rgba(0, 0, 0, .14) 45%,
        rgba(0, 0, 0, .07) 65%,
        rgba(0, 0, 0, .02) 83%,
        rgba(0, 0, 0, 0) 100%);
    pointer-events: none;
}
/* Тонкие прогресс-полоски как в сторис (Instagram/Telegram): дорожка приглушена,
   внутренний .gv-seg-fill плавно заполняется по scaleX (значение ведёт gallery.js). */
.gv-segs { display: flex; flex: 1; gap: .25rem; }
.gv-seg {
    height: 2px;
    flex: 1;
    border-radius: 9999px;
    background: rgba(255, 255, 255, .35);
    overflow: hidden;
}
.gv-seg-fill {
    display: block;
    height: 100%;
    width: 100%;
    background: #fff;
    transform: scaleX(0);
    transform-origin: left center;
}
.gv-close {
    pointer-events: auto;
    flex-shrink: 0;
    margin-left: auto;                         /* при одиночном фото прижат вправо */
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 9999px;
    background: rgba(0, 0, 0, .45);
    color: #E8E4D8;
    font-size: 1.05rem;
    transition: background .15s;
}
.gv-close:hover { background: rgba(0, 0, 0, .7); }

/* Стрелки prev/next — только десктоп (как в карусели); на тач листаем свайпом. */
.gv-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: none;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 9999px;
    background: rgba(0, 0, 0, .4);
    color: #E8E4D8;
    font-size: 1.25rem;
    backdrop-filter: blur(4px);
    transition: background .15s;
}
.gv-nav:hover { background: rgba(0, 0, 0, .65); }
.gv-nav-prev { left: .75rem; }
.gv-nav-next { right: .75rem; }
@media (min-width: 640px) { .gv-nav { display: flex; } }

/* Подпись кадра: та же плашка, что в карусели «Подробнее» (градиентный хвост +
   ровный тёмный фон), кламп до 2 строк, длинные разворачиваются тапом. */
.gv-cap {
    position: absolute;
    inset: auto 0 0 0;
    color: #E8E4D8;
    font-size: .95rem;
    line-height: 1.375;
}
.gv-cap-fade { height: 3rem; background: linear-gradient(to top, rgba(0, 0, 0, .75), transparent); }
.gv-cap-panel { background: rgba(0, 0, 0, .75); padding: 0 1.25rem calc(1rem + env(safe-area-inset-bottom, 0px)); }
.gv-cap-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}
.gv-cap.is-expandable { cursor: pointer; }
.gv-cap.is-expanded .gv-cap-text {
    display: block;
    max-height: 45vh;                          /* совсем длинный текст скроллится внутри плашки */
    overflow-y: auto;
    touch-action: pan-y;                       /* прокрутка текста, а не жест закрытия */
}
.gv-cap-more { display: none; margin-top: .375rem; justify-content: center; color: rgba(232, 228, 216, .8); }
.gv-cap.is-expandable .gv-cap-more { display: flex; }
.gv-cap-more svg { width: 1rem; height: 1rem; transition: transform .2s; }
.gv-cap.is-expanded .gv-cap-more svg { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
    .gv-overlay, .gv-body.gv-spring, .gv-cap-more svg { transition: none; }
}

/* Аккордеоны на нативном <details>: убираем стандартный маркер-треугольник */
details > summary {
    list-style: none;
}
details > summary::-webkit-details-marker {
    display: none;
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}