/* Модуль подбора по зонам тела. Палитра сайта, тёмная тема «Ритуала». */
.zselection-controls{padding:16px 0;display:flex;flex-wrap:wrap;align-items:center;gap:12px}
.zselection-controls p{flex-basis:100%;margin:0}
.zselection-help{font-size:13px;opacity:.75}
.zselection-controls button:disabled{opacity:.4;cursor:default}
@media(min-width:861px){.zselection-controls [data-show-programs]{display:none}}
.zmobile-action{display:none}
@media(max-width:860px){
  .zmobile-action:not([hidden]){display:block;position:fixed;z-index:45;left:12px;right:12px;
    bottom:calc(var(--action-bar-h, env(safe-area-inset-bottom, 0px)) + 12px);
    padding:10px 12px 12px;background:#f2ead9;color:#29251e;
    border:1px solid #b59a65;box-shadow:0 6px 28px rgba(0,0,0,.22)}
  .zmobile-action p{margin:0 0 8px;font:12px/1.4 sans-serif;text-align:center}
  .zmobile-action .zbtn{width:100%;min-height:48px;font-size:12px;background:#705923;color:#fff;
    border:1px solid #705923;cursor:pointer}
  body.zmobile-action-visible .contact-widget{visibility:hidden;pointer-events:none}
}
@font-face{
  font-family:'Garamond';
  src:url('../media/fonts/Garamond.woff2') format('woff2');
  font-weight:400;font-style:normal;font-display:swap;
}
:root{
  /* ── ДВИЖЕНИЕ ────────────────────────────────────────────────────────
     Салон продаёт замедление, и интерфейс обязан двигаться так же.
     До этого длительности выбирались на глаз: пятнадцать разных
     значений от 150 мс до секунды. Короткие - это скорость офисной
     программы, где важна отзывчивость; здесь она читается как рывок.

     Шкала времени. Ступени различимы на глаз (каждая примерно в полтора
     раза длиннее предыдущей) - иначе разница между ними не читается,
     и шкала вырождается в один средний темп.
       dur-1  смена состояния на месте: цвет, рамка, прозрачность
       dur-2  заметное перемещение: карточка, стрелка, подъём
       dur-3  крупное: панель, раскрытие, смена сцены
       dur-4  сценическое: завеса, наплыв кадра

     Кривые. Их две, и они отвечают на разные вопросы.
       ease       - ответ на действие человека. Трогается сразу, иначе
                    интерфейс кажется зависшим, и долго оседает.
       ease-breath - собственное движение сайта, которое никто не
                    запускал: подсказки, свечение, пульс. Симметричная:
                    выходит из покоя и возвращается в покой, без начала
                    и конца, как вдох и выдох.

     breath - период дыхательного цикла. 5.6 с - это темп спокойного
     дыхания взрослого человека (10-11 циклов в минуту). Совпадение
     не украшение: ритм, который человек узнаёт телом, успокаивает,
     а вдвое более быстрый - наоборот, подгоняет. */
  --dur-1:320ms; --dur-2:520ms; --dur-3:820ms; --dur-4:1200ms;
  --ease:cubic-bezier(.22,.61,.36,1);
  --ease-breath:cubic-bezier(.45,0,.35,1);
  --breath:5.6s;

  --cream:#f1e9d9;
  --navy:#171d2d;
  --navy-2:#3c4562;
  --deep:#0f1219;
  --gold:#d2b06a;
  --text-muted:#a9b0c2;
  --text-dim:#8f96a8;
  --ink:#20180a;
  --backdrop:rgba(9,11,17,.82);
  --disp:'Garamond',Georgia,'Times New Roman',serif;
  --ui:'Montserrat',Arial,sans-serif;
}
*{box-sizing:border-box}
/* Стиль страницы-стенда не должен перекрашивать body страницы, куда модуль
   встроен. Раньше это правило делало всю главную тёмной даже после нажатия
   «Кремовая тема». */
body.module-page{margin:0;background:var(--deep);color:var(--cream);font-family:var(--ui);font-size:15px;line-height:1.55}

#spa-zones{max-width:1180px;margin:0 auto;padding:64px 24px 80px;background:var(--deep);color:var(--cream)}

/* Встроенный модуль следует температуре страницы. Стенд module/index.html
   без data-temp остаётся тёмным, как и раньше. */
[data-temp="cream"] #spa-zones,
[data-temp="cream"] #program-dialog,
[data-temp="cream"] #lead-dialog{
  --deep:#f1e9d9;
  --cream:#171d2d;
  --navy:#e7dcc5;
  --navy-2:#3c4562;
  --gold:#755920;
  --text-muted:#3c4562;
  --text-dim:#545c75;
  --ink:#f1e9d9;
}
.zh h2{font-family:var(--disp);font-weight:400;font-size:clamp(28px,4vw,44px);line-height:1.1;margin:0 0 14px;text-wrap:balance}
.zh p{margin:0 0 40px;color:var(--text-muted);max-width:52ch}
.zbody{display:grid;grid-template-columns:minmax(280px,420px) 1fr;gap:48px;align-items:start}
.zfigure{position:sticky;top:24px}

.ztoggles{display:flex;gap:10px;flex-wrap:nowrap;margin-bottom:20px;overflow-x:auto;scrollbar-width:none}
.ztoggles::-webkit-scrollbar{display:none}
.zgroup{display:flex;border:1px solid rgba(210,176,106,.35);border-radius:0;overflow:hidden}
.zgroup button{position:relative;appearance:none;background:transparent;border:0;color:var(--text-dim);font:inherit;font-size:13px;padding:7px 13px;cursor:pointer;transition:color var(--dur-2) var(--ease);display:flex;align-items:center;gap:7px;white-space:nowrap}
/* Золотая черта под выбранным полом и видом. Раньше это была внутренняя
   тень, и она появлялась рывком: переход был объявлен, но тень из «нет»
   в «есть» браузер интерполирует не везде. Отдельная черта, которая
   раскрывается от центра масштабом, дышит одинаково во всех браузерах
   и не заставляет перерисовывать кнопку на каждом кадре. */
.zgroup button::before{content:'';position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--gold);
  opacity:0;transform:scaleX(.2);transition:opacity var(--dur-2) var(--ease),transform var(--dur-3) var(--ease)}
.zgroup button[aria-pressed="true"]::before{opacity:1;transform:none}
/* Полужирное начертание не анимируется: шрифт подключён отдельными
   файлами 400 и 600, и между ними нет промежуточных толщин. Смена
   font-weight на самой кнопке давала сразу два рывка - буквы
   перескакивали, а кнопка становилась шире и толкала соседей в ленте.
   Поэтому надпись лежит двумя слоями в одной ячейке сетки: обычный
   и полужирный (из data-label) сменяют друг друга прозрачностью,
   а ширину ячейки всегда задаёт более широкий полужирный - раскладке
   нечего менять. У полужирной копии пустой альтернативный текст,
   чтобы экранный диктор не читал подпись дважды. */
.zswap{display:inline-grid;justify-items:center}
.zswap>span,.zswap::after{grid-area:1/1;transition:opacity var(--dur-2) var(--ease)}
.zswap::after{content:attr(data-label);content:attr(data-label) / "";font-weight:600;opacity:0}
[aria-pressed="true"] .zswap>span{opacity:0}
[aria-pressed="true"] .zswap::after{opacity:1}
.zsex-icon{display:block;width:52px;height:52px;object-fit:contain;transition:opacity var(--dur-2) var(--ease),transform var(--dur-2) var(--ease)}
.zsex-icon--cream{display:none}
[data-temp="cream"] .zsex-icon--dark{display:none}
[data-temp="cream"] .zsex-icon--cream{display:block}
.zgroup button:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
.zgroup button[aria-pressed="true"]{background:transparent;color:var(--gold)}
.zgroup button[aria-pressed="false"] .zsex-icon{opacity:.56;transform:scale(.9)}

/* Силуэт — один WebP на пол-и-вид (карта нажатий 600×900 задаёт соотношение
   сторон контейнера). Поверх него — по два физических слоя свечения на
   роль (выбранная зона / зона под курсором): mask-image не анимируется,
   поэтому переход между зонами сделан кроссфейдом двух слоёв одной роли
   (module.js, setRoleZone), а не сменой маски на одном слое. Свечение не
   нарисовано в файле: слой заливается золотым градиентом, обрезается
   маской зоны (mask-image, по альфа-каналу PNG в zone-masks/) и
   накладывается режимом screen — поэтому светится, а не закрашивает.
   Маски зон — 400×600, тот же аспект 2:3, что и у карты нажатий, поэтому
   mask-size:contain выравнивает их без перекоса. */
/* ── свет и силуэт по температурам ──────────────────────────────────────
   Режим наложения здесь не украшение, а арифметика. screen считает
   1-(1-фон)(1-свет): на кремовом фоне яркость уже 0.82, и что ни подложи,
   выйдет почти тот же кремовый - свечения на светлом фоне он дать НЕ МОЖЕТ.
   Светлому фону нужен multiply, и «свет» там читается не как осветление,
   а как насыщенное золотое пятно. Соответственно переворачивается и
   градиент: под screen ярче центр, под multiply белый край означает
   «ничего не делать», а насыщенный центр - максимум свечения.

   Силуэт нарисован золотом под тёмный фон. На кремовом то же золото даёт
   контраст 2.4:1 и выцветает, поэтому там он притемняется и досыщается -
   получается старое золото, а не блёклая линия. Значения объявлены для
   ОБЕИХ температур: назначенное один раз на две, оно обязано провалиться
   на одной. */
#spa-zones{
  --zglow-image:radial-gradient(circle at 50% 45%,
    #ffd89b 0%, var(--gold) 45%, #a8763a 100%);
  --zglow-blend:screen;
  --zsil-filter:none;
}
[data-temp="cream"] #spa-zones{
  --zglow-image:radial-gradient(circle at 50% 45%,
    #b8862f 0%, #d9b676 42%, #fbf7ef 100%);
  --zglow-blend:multiply;
  --zsil-filter:brightness(.58) saturate(1.35);
}

[data-silhouette]{position:relative;width:100%;aspect-ratio:600/900;overflow:hidden}
/* Силуэтов два слоя, и смена пола или вида - это их кроссфейд, а не
   подмена картинки в одном <img>: подмена гасила фигуру до пустого места
   на время загрузки и потом проявляла новую рывком. Новый кадр сначала
   грузится невидимым, прежний всё это время стоит на месте; затем они
   меняются прозрачностью, а небольшой встречный масштаб (входящий
   чуть меньше, уходящий чуть больше) читается как спокойный вдох, а не
   как щелчок слайда (module.js, showSilhouette). */
.zsilhouette{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;
  z-index:1;display:block;pointer-events:none;opacity:0;transform:scale(.985);filter:var(--zsil-filter,none);
  transition:opacity var(--dur-3) var(--ease),filter var(--dur-3) var(--ease),transform var(--dur-3) var(--ease)}
.zsilhouette.is-current{opacity:1;transform:none}
.zsilhouette.is-leaving{transform:scale(1.015)}
.zsilhouette.is-broken{opacity:0}

/* Первое действие должно быть понятно без инструкции. Две спокойные точки
   показывают, что нажимается именно тело, а подпись отвечает на вопрос
   «что делать». После первого выбора подсказка мягко исчезает и больше не
   конкурирует с выбранной зоной. */
.zfigure-guide{position:absolute;z-index:5;inset:0;pointer-events:none;
  transition:opacity var(--dur-3) var(--ease),visibility 0s var(--dur-3)}
.zfigure-guide__pulse{position:absolute;width:24px;height:24px;border:2px solid var(--gold);
  border-radius:50%;background:rgba(210,176,106,.14);
  animation:zfigure-point 2.8s var(--ease-breath) infinite}
.zfigure-guide__pulse::before{content:'';position:absolute;inset:-3px;border:2px solid var(--gold);
  border-radius:50%;animation:zfigure-ring 2.8s ease-out infinite;animation-delay:inherit}
.zfigure-guide__pulse::after{content:'';position:absolute;inset:5px;border-radius:50%;background:var(--gold);
  box-shadow:0 0 9px rgba(255,217,135,.95),0 0 22px rgba(210,176,106,.8)}
.zfigure-guide__pulse--upper{left:62%;top:26%}
.zfigure-guide__pulse--lower{left:38%;top:66%;animation-delay:-1.4s}
.zfigure-guide__label{position:absolute;left:50%;bottom:8%;transform:translateX(-50%);
  min-width:max-content;padding:11px 15px;border:1px solid rgba(210,176,106,.52);
  background:color-mix(in srgb,var(--deep) 84%,transparent);color:var(--cream);
  font:500 11px/1.2 var(--ui);letter-spacing:.06em;text-transform:uppercase;
  box-shadow:0 12px 36px rgba(0,0,0,.22)}
.zfigure-guide__label b{color:var(--gold);font-weight:600}
#spa-zones.has-zone .zfigure-guide{opacity:0;visibility:hidden;transition-delay:0s,var(--dur-3)}
#spa-zones.is-full-selected .zsilhouette.is-current{transform:scale(1.012);
  filter:var(--zsil-filter,none) drop-shadow(0 0 24px rgba(210,176,106,.62))}
@keyframes zfigure-point{
  0%,100%{transform:scale(.94);opacity:.85;box-shadow:0 0 12px rgba(210,176,106,.45)}
  50%{transform:scale(1.14);opacity:1;box-shadow:0 0 24px rgba(210,176,106,.8)}
}
@keyframes zfigure-ring{
  0%{transform:scale(1);opacity:.85}
  75%,100%{transform:scale(2.3);opacity:0}
}

/* pointer-events:none обязателен на силуэте и на всех слоях свечения:
   попадание по зоне решает не браузер по DOM-элементу под курсором, а JS
   по пикселю карты нажатий (hitmap/) — слои не должны мешать этому. */
.zglow{
  position:absolute;z-index:2;inset:0;pointer-events:none;
  background:var(--zglow-image);
  -webkit-mask-size:contain;mask-size:contain;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  mix-blend-mode:var(--zglow-blend);
  opacity:0;
  filter:blur(24px); /* состояние покоя — утверждённое значение; усиленное
                         размытие 44px включается только на первом появлении
                         роли «с нуля», см. .zglow-appear ниже */
}
/* Выбранная зона и зона под курсором — разные роли (у каждой свои два
   слоя): будь слой один на роль, уход курсора после наведения погасил бы
   и уже выбранную зону. Непрозрачность свечения утверждена как 1 — цель
   роли .zglow-selected, у наведения она вдвое слабее, чтобы отличаться
   от выбора на глаз. */
.zglow-selected{--zglow-op:1}
.zglow-hover{--zglow-op:.55}

/* Три варианта появления — вставлены все, выбирает класс на #spa-zones
   (корень модуля). По умолчанию glow-a. Переключить на другой вариант —
   поменять этот класс на glow-b или glow-c, больше ничего трогать не надо.
   Они управляют только ПЕРВЫМ появлением роли «с нуля» — слой получает
   класс .zglow-appear только в этот момент (module.js, setRoleZone).
   Переход между уже показанными зонами — отдельная, короткая логика ниже
   (.zglow-cross-in/.zglow-cross-out): она одна и та же во всех трёх
   вариантах, потому что варианты про характер появления света с нуля,
   а не про то, как один свет сменяется другим. */

/* А — «проявление»: свет нарастает и наводится на резкость, 950 мс.
   Размытие в покое 24px, в начале появления 44px — утверждённые значения. */
.glow-a .zglow.zglow-appear,.glow-b .zglow.zglow-appear{
  filter:blur(44px);transform:scale(1.06);
  transition:opacity var(--dur-3) var(--ease),
             filter var(--dur-3) var(--ease),
             transform var(--dur-3) var(--ease);
}
.glow-a .zglow.zglow-appear.is-visible,.glow-b .zglow.zglow-appear.is-visible{
  opacity:var(--zglow-op);filter:blur(24px);transform:scale(1);
}

/* Б — то же плюс медленное дыхание после проявления, цикл 5 с. */
.glow-b .zglow.zglow-appear.is-visible{animation:zglow-breathe var(--breath) var(--ease-breath) 950ms infinite}
@keyframes zglow-breathe{
  0%,100%{opacity:var(--zglow-op);filter:blur(24px)}
  50%{opacity:calc(var(--zglow-op) * .66);filter:blur(30px)}
}

/* В — «разлив»: свет наливается сверху вниз волной, 1200 мс. */
@property --zwave{syntax:'<percentage>';inherits:false;initial-value:0%}
.glow-c .zglow.zglow-appear{
  filter:blur(24px);--zwave:0%;
  -webkit-mask-composite:source-in;
  transition:opacity var(--dur-2) var(--ease), --zwave var(--dur-4) cubic-bezier(.33,.68,.35,1);
}
.glow-c .zglow.zglow-appear::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(to bottom,#000 0%,#000 var(--zwave),transparent calc(var(--zwave) + 24%),transparent 100%);
}
.glow-c .zglow.zglow-appear.is-visible{opacity:var(--zglow-op);--zwave:100%}

/* ── Переход между уже показанными зонами — кроссфейд двух слоёв ───────
   mask-image не анимируется: вместо смены маски на одном слое свободный
   слой пары проявляет новую зону, а прежний слой одновременно гаснет
   (module.js, setRoleZone). Один и тот же для всех трёх вариантов
   появления — короче первого появления (человек уже видит свет, ждать
   почти секунду на каждое движение мыши не нужно) и без повторной
   анимации размытия/масштаба: оба слоя уже в состоянии покоя (24px,
   без scale). Клиент 15.09 попросил плавности везде, поэтому переход
   растянут до dur-3 на проявление и dur-2 на затухание: прежние 520/320
   мс на смене зоны читались как щелчок. Проявление нового слоя НАРОЧНО
   дольше затухания прежнего: слои складываются режимом screen, и если
   бы уходящий слой гас медленнее входящего, в момент пересечения их
   яркость суммировалась бы и была видна вспышка. */
.zglow.zglow-cross-in{transition:opacity var(--dur-3) var(--ease)}
.zglow.zglow-cross-in.is-visible{opacity:var(--zglow-op)}
.zglow.zglow-cross-out{transition:opacity var(--dur-2) var(--ease);opacity:0}


.zerr{margin:10px 0 0;color:var(--gold);font-size:13px}

/* Клавиатурный дубль силуэта: картинка не кликается по зонам с Tab/Enter,
   поэтому под ней — ряд обычных кнопок с теми же зонами текущего вида. */
.zrail{display:grid;grid-template-columns:minmax(0,1fr);gap:8px;align-items:center}
.zrail.has-overflow{grid-template-columns:44px minmax(0,1fr) 44px}
.zzone-rail{margin-top:14px}
.zzone-strip{display:flex;gap:8px;min-width:0;overflow-x:hidden;
  scroll-behavior:smooth;scrollbar-width:none;padding:4px 0}
.zzone-strip::-webkit-scrollbar{display:none}
.zrail-arrow{display:none;width:44px;height:44px;border:1px solid rgba(210,176,106,.55);background:transparent;
  color:var(--gold);font:22px/1 var(--ui);cursor:pointer;transition:opacity var(--dur-2) var(--ease),background var(--dur-2) var(--ease)}
.zrail.has-overflow .zrail-arrow{display:block}
.zrail-arrow:disabled{opacity:.22;cursor:default}.zrail-arrow:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.zzones{display:flex;flex-wrap:nowrap;gap:8px;flex:none}
.zzone-btn{appearance:none;cursor:pointer;background:transparent;border:1px solid rgba(210,176,106,.35);
  color:var(--text-dim);font:inherit;font-size:12.5px;padding:8px 12px;white-space:nowrap;scroll-snap-align:start;
  transition:color var(--dur-2) var(--ease),background-color var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease)}
.zzone-btn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.zzone-btn[aria-pressed="true"]{background:var(--gold);color:var(--ink);border-color:var(--gold)}

@media (hover:hover){
  .zgroup button:hover{color:var(--cream)}
  .zzone-btn:hover{color:var(--cream);border-color:rgba(210,176,106,.6)}
  .zwhole:hover{background:rgba(210,176,106,.1)}
}

/* «Всё тело» — категория, а не место на теле. Раньше кнопка стояла последней
   в горизонтальной ленте и была невидима до прокрутки; теперь это явное
   действие непосредственно под фигурой. */
.zwhole{display:grid;grid-template-columns:auto 1fr;align-items:center;column-gap:12px;width:100%;
  margin:0 0 12px;appearance:none;cursor:pointer;text-align:left;background:transparent;
  border:1px solid rgba(210,176,106,.45);color:var(--cream);font:inherit;font-size:13px;
  padding:13px 15px;transition:color var(--dur-2) var(--ease),background-color var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease)}
.zwhole>span:first-child{grid-row:1/3;font:400 28px/1 var(--disp);color:var(--gold);transition:color var(--dur-2) var(--ease)}
.zwhole>span:nth-child(2){font-weight:600;letter-spacing:.03em}
.zwhole small{grid-column:2;justify-items:start;color:var(--text-dim);font-size:10px;letter-spacing:.08em;text-transform:uppercase;
  transition:color var(--dur-2) var(--ease)}
.zwhole:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.zwhole[aria-pressed="true"]{background:var(--gold);color:var(--ink)}
.zwhole[aria-pressed="true"]>span:first-child,.zwhole[aria-pressed="true"] small{color:var(--ink)}

.zhint{font-family:var(--disp);font-size:26px;font-weight:400;margin:0 0 22px;color:var(--cream)}
.zpanel-shell{position:sticky;top:118px;min-width:0;max-height:calc(100vh - 142px)}
/* min-height держит место под список с самого начала. До первого выбора
   в панели одна строка, после - высота экрана, и на высоких мониторах
   (1920x1200) колонка программ оказывалась выше колонки с фигурой: вся
   страница ниже модуля уезжала на 264 px в момент выбора. На телефоне
   панель - шторка своей высоты, там min-height сбрасывается. */
.zpanel{position:relative;min-height:calc(100vh - 142px);max-height:calc(100vh - 142px);overflow-y:auto;cursor:default;
  overscroll-behavior-y:contain;scrollbar-width:thin;scrollbar-color:rgba(210,176,106,.55) transparent;
  padding-right:14px;scroll-behavior:smooth}
.zscroll-state{position:sticky;top:58px;z-index:4;margin:-8px 0 12px;padding:6px 8px;
  background:var(--deep);color:var(--gold);font:500 11px/1.4 var(--ui);letter-spacing:.05em;text-transform:uppercase}
.zpanel::-webkit-scrollbar{width:6px}
.zpanel::-webkit-scrollbar-track{background:transparent}
.zpanel::-webkit-scrollbar-thumb{background:rgba(210,176,106,.55)}
.zpanel .zhint{position:sticky;top:0;z-index:3;padding:4px 0 16px;margin-bottom:6px;
  background:linear-gradient(var(--deep) 75%,transparent)}
.zpanel .zhint::after{content:attr(data-count);display:block;margin-top:8px;font:500 11px/1.4 var(--ui);
  letter-spacing:.06em;text-transform:uppercase;color:var(--gold)}
.zdrawer-grip,.zdrawer-close,.zdrawer-backdrop{display:none}
.zlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}
.zcard{border:1px solid rgba(241,233,217,.16);padding:20px 22px;background:var(--navy);
  user-select:text;-webkit-user-select:text}
.zcard-media{-webkit-user-drag:none}
/* Картинка идёт от края до края карточки: ширина больше ста процентов
   ровно на два отступа, отрицательные поля вытягивают её наружу.
   max-width:none обязателен - общее правило страницы img{max-width:100%}
   обрезало бы ширину обратно до ста процентов, а отрицательные поля
   остались бы, и картинка съезжала бы влево, не доставая до правого края.
   На отдельной странице модуля этого не видно: там общих стилей нет,
   дефект появляется только когда модуль встроен в главную. */
.zcard-media{display:block;width:calc(100% + 44px);max-width:none;
  height:auto;aspect-ratio:16/7;object-fit:cover;margin:-20px -22px 18px;
  -webkit-user-drag:none;user-select:none;pointer-events:none}
.zcard h3{font-family:var(--disp);font-weight:400;font-size:23px;margin:0 0 8px;font-variant-numeric:lining-nums tabular-nums}
/* Метки времени и раздела. Раньше это был бледный текст в две колонки:
   он не читался как метка и терялся между заголовком и составом.
   Теперь это чипы - обведённые, с тёплым фоном, время выделено золотом
   как главное, раздел приглушён как второстепенное. */
.zmeta{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}
.zmeta span{display:inline-flex;align-items:center;gap:6px;
  font:600 11px/1 var(--ui);letter-spacing:.08em;text-transform:uppercase;
  padding:7px 11px;color:var(--text-muted);
  border:1px solid rgba(241,233,217,.14);
  background:rgba(241,233,217,.04);
  font-variant-numeric:lining-nums tabular-nums}
.zmeta span:first-child{color:var(--gold);
  border-color:rgba(210,176,106,.42);background:rgba(210,176,106,.08)}
.zsummary{margin:0 0 16px;color:var(--text-muted);font-size:13px;line-height:1.6}
.zsteps{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px;padding:0;list-style:none}
/* Состав программы. Это главное, что человек читает в карточке: из чего
   складывается ритуал. Раньше это была бледная обводка без фона - строки
   сливались друг с другом и с воздухом карточки, и список читался как
   серая масса.

   Теперь у каждого шага свой фон и точка-маркер: взгляд цепляется
   за начало строки и легко считает, сколько всего этапов. Цвет текста
   поднят с приглушённого до основного - это содержание, а не примечание. */
.zsteps li{display:inline-flex;align-items:center;gap:8px;
  font-size:12.5px;line-height:1.25;color:var(--cream);
  border:1px solid rgba(210,176,106,.3);
  background:rgba(210,176,106,.07);
  padding:7px 12px 7px 10px}
.zsteps li::before{content:'';width:5px;height:5px;flex:0 0 auto;
  background:var(--gold);transform:rotate(45deg)}
@media (hover:hover){
  .zsteps li{transition:border-color var(--dur-1) var(--ease),background var(--dur-1) var(--ease)}
  .zcard:hover .zsteps li{border-color:rgba(210,176,106,.45);
    background:rgba(210,176,106,.1)}
}
.zfoot{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap}
.zprice{font-family:var(--disp);font-size:24px;font-variant-numeric:lining-nums tabular-nums}
.zbtn{appearance:none;border:0;background:var(--gold);color:var(--ink);font:inherit;font-size:12.5px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;padding:12px 22px;cursor:pointer}
.zbtn:focus-visible{outline:2px solid var(--cream);outline-offset:2px}
.zbtn.ghost{background:transparent;border:1px solid rgba(241,233,217,.4);color:var(--cream)}

dialog{border:1px solid rgba(210,176,106,.4);background:var(--navy);color:var(--cream);max-width:560px;width:calc(100% - 32px);padding:32px}
dialog::backdrop{background:var(--backdrop)}
dialog h3{font-family:var(--disp);font-weight:400;font-size:30px;margin:0 0 10px;font-variant-numeric:lining-nums tabular-nums}
.zclose{position:absolute;top:10px;right:14px;background:none;border:0;color:var(--text-dim);font-size:26px;line-height:1;cursor:pointer}
.zclose:focus-visible{outline:2px solid var(--gold)}
.zlead-sub{color:var(--text-muted);font-size:14px;margin:0 0 20px}
#lead-form label{display:block;margin-bottom:12px;font-size:13px;color:var(--text-muted)}
#lead-form input[type="text"],#lead-form input[type="tel"],#lead-form input:not([type]){display:block;width:100%;margin-top:6px;background:transparent;border:1px solid var(--navy-2);color:var(--cream);font:inherit;padding:11px 13px}
#lead-form fieldset{border:0;padding:0;margin:0 0 20px}
/* Согласия обязательны юридически (ПДн и оферта), как в основной форме
   parts/forms.html: тот же текст, те же ссылки, флажки required. Диалог
   тёмный при любой температуре, поэтому текст кремовый, ссылки золотые. */
.zaccept{display:flex;flex-direction:column;gap:8px;margin:0 0 18px}
.zaccept label{display:block;padding-left:1.7em;text-indent:-1.7em;color:var(--cream);font-size:13px;line-height:1.5}
.zaccept input[type=checkbox]{margin-right:.5em;vertical-align:baseline;accent-color:var(--gold);width:1.05em;height:1.05em}
.zaccept a{color:var(--gold);text-decoration:underline;text-decoration-color:var(--gold);text-underline-offset:.15em}
#lead-form legend{font-size:13px;color:var(--text-muted);margin-bottom:8px}
#lead-form fieldset label{display:inline-flex;align-items:center;gap:7px;margin:0 16px 8px 0;color:var(--cream);font-size:14px}
.zsent{color:var(--gold);font-size:14px}

/* ── Окна программы и записи ──────────────────────────────────────────
   showModal() и close() показывают и убирают окно мгновенно. Появление
   сделано анимацией на [open]: она запускается каждый раз, когда окно
   из скрытого становится видимым, и не требует скрипта. Закрытие
   анимацией на [open] не сделать - атрибут снимается сразу, - поэтому
   скрипт сначала вешает .is-closing, ждёт dur-2 и только потом
   закрывает окно (module.js, closeDialogSmoothly).

   Переход из окна программы в окно записи - отдельный случай. Если
   закрыть первое и открыть второе, затемнение за ними мигнёт: одно
   погаснет, другое проявится с нуля. Поэтому окно записи открывается
   поверх с прозрачным затемнением (.is-stacked), окно программы гаснет
   без своего затемнения (.is-handover), а в момент, когда оно
   закрывается, затемнение переходит к окну записи в том же кадре. */
#program-dialog[open],#lead-dialog[open]{animation:zdlg-in var(--dur-3) var(--ease)}
#program-dialog[open]::backdrop,#lead-dialog[open]::backdrop{animation:zfade-in var(--dur-3) var(--ease)}
#program-dialog.is-closing,#lead-dialog.is-closing{animation:zdlg-out var(--dur-2) var(--ease) forwards}
#program-dialog.is-closing:not(.is-handover)::backdrop,#lead-dialog.is-closing::backdrop{animation:zfade-out var(--dur-2) var(--ease) forwards}
#program-dialog.is-handover::backdrop{animation:none}
#lead-dialog.is-stacked::backdrop{animation:none;background:transparent}
/* Сообщение об отправке сменяет форму. Высоту окна скрипт держит
   прежней, иначе окно по центру экрана схлопнулось бы рывком. */
.zsent:not([hidden]){animation:zfade-in var(--dur-3) var(--ease)}
@keyframes zdlg-in{from{opacity:0;transform:translateY(14px)}}
@keyframes zdlg-out{to{opacity:0;transform:translateY(8px)}}
@keyframes zfade-in{from{opacity:0}}
@keyframes zfade-out{to{opacity:0}}

[data-temp="cream"] .zcard{border-color:var(--line)}
[data-temp="cream"] .zbtn.ghost{border-color:#755920}

@media (max-width:860px){
  #spa-zones{padding-bottom:180px}
  .zbody{grid-template-columns:1fr;gap:32px}
  .zfigure{position:static}
  [data-silhouette]{width:min(78vw,290px);max-width:none;margin:0 auto 0 0}
  .ztoggles{justify-content:flex-start}
  .zgroup button{padding:6px 10px}.zgroup:not(:first-child) button{padding:10px 12px}
  .zsex-icon{width:48px;height:48px}
  .zzone-rail{margin-right:-24px;padding-right:8px}
  .zpanel-shell{position:static;max-height:none}
  .zpanel{display:block;position:fixed;z-index:202;left:0;right:0;bottom:0;top:auto;
    width:100%;height:min(78dvh,720px);max-height:none;padding:10px 16px 24px;
    overflow-y:auto;overscroll-behavior-y:contain;-webkit-overflow-scrolling:touch;
    background:var(--deep);border-top:1px solid rgba(210,176,106,.55);
    min-height:0;
    box-shadow:0 -24px 70px rgba(0,0,0,.5);transform:translateY(105%);visibility:hidden;pointer-events:none;
    transition:transform var(--dur-3) var(--ease),visibility 0s var(--dur-3);scroll-snap-type:none}
  .zpanel.is-open{transform:translateY(0);visibility:visible;pointer-events:auto;transition-delay:0s}
  .zdrawer-grip{display:block;position:sticky;z-index:5;top:0;width:58px;height:5px;margin:0 auto 12px;
    background:rgba(210,176,106,.6);touch-action:none;cursor:grab}
  .zdrawer-close{display:block;position:absolute;z-index:6;right:14px;top:16px;border:0;background:transparent;
    color:var(--cream);font:400 30px/1 var(--disp);cursor:pointer}
  .zdrawer-backdrop{display:block;position:fixed;z-index:201;inset:0;border:0;background:rgba(5,7,10,.68);
    opacity:0;pointer-events:none;transition:opacity var(--dur-3) var(--ease)}
  #spa-zones.drawer-open .zdrawer-backdrop{opacity:1;pointer-events:auto}
  body.zdrawer-open{overflow:hidden}
  .zpanel .zhint::after{max-width:28ch}
  .zpanel .zhint{top:-10px;padding:8px 42px 14px 0;background:linear-gradient(var(--deep) 82%,transparent)}
  .zlist{gap:10px}
  .zcard{display:grid;grid-template-columns:108px minmax(0,1fr);gap:7px 12px;padding:0 12px 12px 0;min-height:176px}
  .zcard-media{grid-column:1;grid-row:1/7;align-self:stretch;width:108px;height:100%;min-height:176px;
    margin:0;object-fit:cover;filter:brightness(.72)}
  .zcard h3,.zcard .zmeta,.zcard .zsummary,.zcard .zsteps,.zcard .zfoot{grid-column:2;margin-left:0;margin-right:0}
  .zcard h3{font-size:19px;margin-top:12px;margin-bottom:0}.zmeta{margin-bottom:0;gap:9px}
  .zsummary{font-size:11.5px;line-height:1.45;margin-bottom:0;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
  .zsteps{margin-bottom:0;gap:5px}.zsteps li{font-size:10px;padding:4px 6px}.zsteps li:nth-child(n+3){display:none}
  .zfoot{gap:8px;margin-top:auto}.zprice{font-size:20px}.zbtn{font-size:9px;padding:9px 10px}
  /* Переход объявлен на самом силуэте, а не только в .zone-confirming:
     объявленный внутри класса, он пропадал вместе с классом, и фигура
     возвращалась из увеличения рывком, как раз когда выезжала шторка. */
  [data-silhouette]{transition:transform var(--dur-3) var(--ease),filter var(--dur-3) var(--ease)}
  #spa-zones.zone-confirming [data-silhouette]{transform:scale(1.018);filter:drop-shadow(0 0 22px rgba(210,176,106,.18))}
}
