/* ============================================================
   DEVELOPER — публичный сайт
   Требует tokens.css (подключается первым).
   ============================================================ */

.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--s-5);
}

/* ============================================================
   Появление снизу
   Скрываем только при .js на <html> — без JavaScript всё видно сразу,
   иначе страница осталась бы пустой.
   ============================================================ */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 0.7s var(--ease),
    transform 0.7s var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}

.js [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

/* Первый экран: сначала тексты сверху вниз, знак — последним.
   Задержки в CSS, а не в атрибутах: инлайновые стили запрещены политикой CSP. */
.js .hero .eyebrow    { --reveal-delay: 0ms; }
.js .hero h1          { --reveal-delay: 90ms; }
.js .hero-lead        { --reveal-delay: 180ms; }
.js .hero-stats       { --reveal-delay: 270ms; }
.js .hero-mark        { --reveal-delay: 480ms; }

/* Знак выезжает чуть заметнее и дольше — он приходит последним и должен
   читаться как отдельный акцент, а не как ещё один абзац */
.js .hero-mark[data-reveal] {
  transform: translateY(34px) scale(0.97);
  transition-duration: 0.9s;
}
.js .hero-mark[data-reveal].is-visible {
  transform: none;
}

/* Волна слева направо внутри ряда сетки: задержка по позиции в четвёрке,
   поэтому она не растёт бесконечно на 39 карточках портфолио. */
.js .portfolio-grid > [data-reveal]:nth-child(4n+1) { --reveal-delay: 0ms; }
.js .portfolio-grid > [data-reveal]:nth-child(4n+2) { --reveal-delay: 70ms; }
.js .portfolio-grid > [data-reveal]:nth-child(4n+3) { --reveal-delay: 140ms; }
.js .portfolio-grid > [data-reveal]:nth-child(4n+4) { --reveal-delay: 210ms; }

.js .triple > [data-reveal]:nth-child(3n+1) { --reveal-delay: 0ms; }
.js .triple > [data-reveal]:nth-child(3n+2) { --reveal-delay: 80ms; }
.js .triple > [data-reveal]:nth-child(3n+3) { --reveal-delay: 160ms; }

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Аварийный показ без анимации — ставится из JS, если документ так и остался
   скрытым и переходы заморожены браузером */
.js.reveal-instant [data-reveal] {
  opacity: 1;
  transform: none;
  transition: none;
}

/* ============================================================
   Шапка: надпись слева, логотип по центру, темы справа
   ============================================================ */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--header-bg);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid var(--border);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  min-height: var(--header-h);
}

/* Логотип и надпись — одной группой слева */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  min-width: 0;
}

/* Подложки нет ни в одной теме: у файла настоящая прозрачность,
   чёрный фон вырезан по градиентной маске при сборке ассета. */
.brand-mark {
  height: 30px;
  width: auto;
  flex: none;
}

.wordmark {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text);
  white-space: nowrap;
}

.header-actions { flex: none; }

/* Переключатель темы */
.theme-switcher {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 3px;
}

.theme-switcher-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.theme-switcher-btn svg { width: 15px; height: 15px; }
.theme-switcher-btn:hover { color: var(--text); }
.theme-switcher-btn.active {
  background: var(--accent-fill);
  color: var(--accent-ink);
  box-shadow: 0 2px 10px var(--accent-soft);
}

@media (max-width: 620px) {
  .container { padding: 0 var(--s-4); }
  .brand { gap: var(--s-2); }
  .brand-mark { height: 24px; }
  .wordmark { font-size: 0.8125rem; letter-spacing: 0.1em; }
  .theme-switcher-btn { width: 26px; height: 26px; }
  .theme-switcher-btn svg { width: 14px; height: 14px; }
}

/* 360px — ходовая ширина Android, надпись там должна оставаться.
   Скрываем только на совсем узких экранах. */
@media (max-width: 320px) {
  .wordmark { display: none; }
}

/* ============================================================
   Кнопки
   ============================================================ */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 14px var(--s-6);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid transparent;
  border-radius: var(--r-btn);
  cursor: pointer;
  white-space: nowrap;
  /* Блик обрезается по скруглению кнопки */
  overflow: hidden;
  transition: box-shadow var(--t-fast), background var(--t-fast),
              border-color var(--t-fast), color var(--t-fast);
}

/* Блик — единственная реакция кнопки на наведение: полоса света проходит
   слева направо. Ни подъёма, ни смены заливки: одно движение читается как
   отклик, три сразу — как суета. Заливка ровная, без градиента. */
.btn::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(100deg,
      transparent 38%, var(--btn-shine) 50%, transparent 62%);
  transform: translateX(-100%);
  pointer-events: none;
}
/* Блик пробегает и при наведении, и когда курсор уходит — оба раза слева
   направо. Класс на уход ставит скрипт: у CSS состояния «мышь только что
   ушла» нет. Кадры и объяснение двух имён — в tokens.css */
.btn:hover::after       { animation: btn-shine 0.62s ease-out; }
.btn.is-leaving::after  { animation: btn-shine-out 0.62s ease-out; }

.btn-primary {
  background: var(--accent-fill);
  color: var(--accent-ink);
  box-shadow: var(--shadow-accent);
}

.btn-ghost {
  background: var(--bg-2);
  border-color: var(--border-strong);
  color: var(--text);
  box-shadow: var(--edge);
}
.btn-ghost:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.btn svg { width: 16px; height: 16px; flex: none; }

/* ============================================================
   Hero
   ============================================================ */

.hero {
  position: relative;
  /* svh, а не vh: на мобильных vh считается по развёрнутому окну, и первый
     экран уезжал под адресную строку. Заголовок шапки вычитаем — она липкая. */
  min-height: calc(100svh - var(--header-h));
  display: grid;
  grid-template-rows: 1fr auto;
  padding: clamp(var(--s-6), 5vw, var(--s-8)) 0 clamp(var(--s-5), 4vw, var(--s-7));
  overflow: hidden;
}

/* Основной ряд по центру свободного места, метрики прижаты к низу экрана */
.hero > .hero-layout { align-self: center; }
.hero > .hero-foot   { align-self: end; padding-top: clamp(var(--s-6), 5vw, var(--s-8)); }

@supports not (height: 100svh) {
  .hero { min-height: calc(100vh - var(--header-h)); }
}

.hero::before {
  content: "";
  position: absolute;
  top: -30%;
  left: 50%;
  width: min(1100px, 130vw);
  aspect-ratio: 1;
  transform: translateX(-50%);
  background: radial-gradient(circle, var(--ember) 0%, transparent 62%);
  pointer-events: none;
}

.hero-inner { position: relative; z-index: 1; max-width: 860px; }

/* Знак в первом экране: логотип с настоящей прозрачностью, свечение и блик
   рисуются в CSS — поэтому они живут в токенах темы, а не запечены в файл. */
.hero-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-7);
  align-items: center;
}
@media (min-width: 1040px) {
  .hero-layout { grid-template-columns: minmax(0, 1fr) minmax(0, 0.95fr); }
}

/* Виден на всех ширинах: на узком экране это единственное изображение
   первого экрана, прятать его — оставить hero голым текстом. */
.hero-mark {
  position: relative;
  z-index: 1;
  justify-self: center;
  width: 100%;
  max-width: 420px;
  aspect-ratio: 1502 / 825;
}
@media (min-width: 1040px) {
  .hero-mark { max-width: 560px; }
}

/* Тление за знаком — пульсирует, а не мигает: длинный цикл и малый размах */
.hero-mark::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 118%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, var(--ember) 0%, transparent 62%);
  animation: mark-ember 6s ease-in-out infinite;
  pointer-events: none;
}

.hero-mark-img {
  position: relative;
  width: 100%;
  height: auto;
  /* Свечение по контуру — тот же жар, что внутри самого знака */
  filter: drop-shadow(0 10px 28px rgba(0, 0, 0, 0.55))
          drop-shadow(0 0 22px var(--accent-soft));
  animation: mark-float 7s ease-in-out infinite;
  will-change: transform;
}

/* Блик по металлу: полоса едет поперёк, обрезанная по форме логотипа.
   Маска берётся из того же файла — альфа у него настоящая. */
.hero-mark-sheen {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  -webkit-mask: url('../img/logo.webp') center / contain no-repeat;
  mask: url('../img/logo.webp') center / contain no-repeat;
  animation: mark-float 7s ease-in-out infinite;
}

.hero-mark-sheen i {
  position: absolute;
  top: -30%;
  left: 0;
  width: 26%;
  height: 160%;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.06) 30%,
    rgba(255, 255, 255, 0.42) 50%,
    rgba(255, 255, 255, 0.06) 70%,
    transparent
  );
  transform: translateX(-160%) skewX(-18deg);
  /* 5.5s из 7s — пауза: блик должен читаться как редкий отсвет, а не как бегущая строка */
  animation: mark-sheen 7s cubic-bezier(0.5, 0, 0.3, 1) infinite;
}

@keyframes mark-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-10px) rotate(-0.6deg); }
}

@keyframes mark-ember {
  0%, 100% { opacity: 0.75; transform: translate(-50%, -50%) scale(1); }
  50%      { opacity: 1;    transform: translate(-50%, -50%) scale(1.07); }
}

@keyframes mark-sheen {
  0%       { transform: translateX(-160%) skewX(-18deg); }
  22%      { transform: translateX(520%)  skewX(-18deg); }
  100%     { transform: translateX(520%)  skewX(-18deg); }
}

/* Без анимации знак остаётся на месте — не дёргается и не исчезает */
@media (prefers-reduced-motion: reduce) {
  .hero-mark::before,
  .hero-mark-img,
  .hero-mark-sheen { animation: none; }
  .hero-mark-sheen i { display: none; }
}

/* ---------- Текстовая часть первого экрана ---------- */

.hero h1 {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: 800;
  line-height: 0.98;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin: 0 0 var(--s-5);
}
.hero h1 span { display: block; }
.hero h1 .metal { --metal-line: 0.98em; }
.hero h1 .accent { color: var(--accent); }

.hero-lead {
  font-size: clamp(0.875rem, 1.05vw, 1rem);
  line-height: 1.65;
  color: var(--text-muted);
  max-width: 58ch;
  margin: 0;
}

/* Полоса метрик — только реальные числа из данных */
.hero-stats {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  gap: clamp(var(--s-5), 5vw, var(--s-8));
  padding-top: var(--s-5);
  border-top: 1px solid var(--border);
}

.hero-stat-label {
  display: block;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: var(--s-1);
}
.hero-stat-value {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2.2vw, 1.75rem);
  font-weight: 800;
  letter-spacing: -0.01em;
  /* Не чистый --text: на тяжёлом Montserrat он давал пересвет */
  color: var(--text-soft);
}

@media (max-width: 560px) {
  /* Две колонки, а не auto-fit: втиснутые три ломали подписи переносом
     («В ПОРТФО/ЛИО», «от 5 000 / ₽»). Две дают каждой метрике целую строку. */
  .hero-stats {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--s-5) var(--s-4);
  }
}

/* На телефоне в 100svh нужно уложить заголовок в три строки, лид в пять,
   знак и две строки метрик. Без сжатия последняя метрика уходила за край
   экрана на ~20px — первый экран переставал быть «на всю высоту». */
@media (max-width: 620px) {
  .hero {
    padding: var(--s-5) 0 var(--s-4);
    gap: var(--s-4);
  }
  .hero-layout { gap: var(--s-5); }
  .hero-mark { max-width: 300px; }
  .hero > .hero-foot { padding-top: var(--s-4); }
  .hero-stats { padding-top: var(--s-4); }
}

/* ============================================================
   Секции
   ============================================================ */

.section { padding: clamp(var(--s-7), 7vw, var(--s-9)) 0; }
.section + .section { padding-top: 0; }

.section-head { margin-bottom: var(--s-6); }

/* Отдельный класс вместо style="margin-bottom:0" — инлайновые стили
   запрещены политикой CSP (style-src 'self') и молча не применялись. */
.section-head-flush { margin-bottom: 0; }

.section-head h2 {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin: 0;
  --metal-line: 1.1em;
}

/* Крупный знак на фоне секции — правый верхний угол, почти невидимый.
   overflow: hidden обрезает вынос за край, а содержимое поднимается на
   отдельный слой, чтобы текст лежал поверх, а не под знаком. */
#services {
  position: relative;
  overflow: hidden;
}

#services > .container {
  position: relative;
  z-index: 1;
}

/* Знак висит на контейнере, а не на секции: секция во всю ширину экрана, и
   привязать левый край знака к началу второй колонки в её процентах нельзя.
   z-index: -1 уводит его под содержимое — абсолютно позиционированный
   псевдоэлемент иначе рисуется поверх статичного текста. */
#services > .container::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: -10%;
  /* Начало второй колонки: паддинг контейнера 24px + ширина колонки + зазор.
     Для трёх равных колонок с зазором 16px это ровно 33.333% + 13.33px. */
  left: calc(33.333% + 13.33px);
  width: min(1200px, 78%);
  aspect-ratio: 1502 / 825;
  background: url('../img/logo.webp') left center / contain no-repeat;
  /* 0.035, а не 0.05: у знака яркие металлические грани, и на почти чёрном
     фоне пяти процентов хватало, чтобы он читался как полноценная картинка */
  opacity: 0.035;
  pointer-events: none;
}

/* Две колонки — вторая начинается с середины */
@media (max-width: 1099px) {
  #services > .container::before { left: calc(50% + 8px); }
}

/* На узких экранах знак только мешал бы тексту — прячем */
@media (max-width: 760px) {
  #services > .container::before { display: none; }
}

/* ---------- Три колонки: цены, пакет, условия ---------- */

.triple {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-4);
  align-items: stretch;
}
@media (min-width: 760px) {
  .triple { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1100px) {
  .triple { grid-template-columns: repeat(3, 1fr); }
}

.panel {
  display: flex;
  flex-direction: column;
  padding: clamp(var(--s-5), 2.4vw, var(--s-6));
  border-radius: var(--r-lg);
  box-shadow: var(--edge);

  /* Обводка-градиент. Обычный border одним цветом не умеет разогреваться к
     углу, поэтому рамка рисуется фоном по border-box, а заливка — по
     padding-box; сам border делается прозрачным и служит только полосой,
     в которой видно нижний слой. Порядок слоёв: верхний — тление, под ним
     ровная заливка, ниже всех — рамка. */
  border: 1px solid transparent;
  --edge-angle: 315deg;
  background:
    var(--panel-glow) padding-box,
    linear-gradient(var(--bg-2), var(--bg-2)) padding-box,
    linear-gradient(var(--edge-angle), var(--edge-stops)) border-box;

  /* Анимация висит всегда, но на паузе. Так при уходе курсора обводка
     замирает там, где есть, а не прыгает рывком в исходный угол. */
  animation: panel-edge-spin 2.5s linear infinite;
  animation-play-state: paused;
}

.panel:hover,
.panel:focus-within {
  animation-play-state: running;
}

/* Угол анимируется только как зарегистрированное свойство: без @property
   браузер считает его строкой и интерполировать не может. Где не поддержано —
   обводка остаётся статичной, значение берётся из объявления в .panel. */
@keyframes panel-edge-spin {
  to { --edge-angle: 675deg; }
}

@media (prefers-reduced-motion: reduce) {
  .panel { animation: none; }
}

.panel h3 {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0 0 var(--s-5);
  --metal-line: 1.25em;
}

.panel-note {
  /* auto прижимает примечание к низу: колонка цен короче двух соседних,
     и без этого оно повисало в середине пустого места */
  margin: auto 0 0;
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
  font-size: var(--fs-label);
  line-height: 1.55;
  color: var(--text-dim);
}

/* Прайс: название слева, цена справа, волосяная линия между строками */
.price-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Сетка, а не флекс: пояснению нужна вся ширина строки, иначе цена справа
   отъедает место и текст рвётся на лишние строки. */
.price-list li {
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--border);
}

/* Сама сетка живёт на строке, а не на <li>: когда у услуги есть описание,
   строка становится кнопкой, и разметка внутри должна остаться прежней */
.price-row {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "name  value"
    "note  note";
  align-items: baseline;
  gap: 0 var(--s-4);
  width: 100%;
  text-align: left;
}

.price-row-btn {
  padding: 0;
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}
.price-row-btn:hover .price-name { color: var(--accent); }
.price-row-btn:hover .price-more { opacity: 1; }
.price-row-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--r-sm);
}

/* Значок «подробнее» приглушён и проявляется при наведении: он подсказка,
   а не элемент управления сам по себе */
.price-more {
  width: 14px;
  height: 14px;
  margin-left: 6px;
  vertical-align: -2px;
  color: var(--accent);
  opacity: 0.5;
  transition: opacity var(--t-fast);
}

.price-name  { grid-area: name; }
.price-value { grid-area: value; }
.price-note  { grid-area: note; }
.price-list li:first-child { padding-top: 0; }
.price-list li:last-child  { border-bottom: 0; padding-bottom: 0; }

.price-name {
  font-size: var(--fs-sm);
  color: var(--text);
}

/* Пояснение простыми словами — что это за сайт */
.price-note {
  display: block;
  margin-top: 3px;
  font-size: var(--fs-label);
  line-height: 1.45;
  color: var(--text-dim);
}

.price-value {
  flex: none;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}

/* Разграничение ответственности под условиями: мельче основного текста,
   но контраст оставлен читаемым — прятать такое нечестно */
.conditions-note {
  margin: var(--s-5) 0 0;
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--text-dim);
}

/* Чеклист базового пакета */
.package-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-3);
}
.package-list li {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: var(--s-3);
  align-items: start;
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.package-list li::before {
  content: "";
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  margin-top: 1px;
}

/* Галочка — отдельным слоем через маску, чтобы цвет брался из --accent
   и менялся вместе с темой, а не был зашит в data-URI. */
.package-list li {
  position: relative;
}
.package-list li::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 6px;
  width: 12px;
  height: 12px;
  background: var(--accent);
  -webkit-mask: var(--check) center / contain no-repeat;
  mask: var(--check) center / contain no-repeat;
}

/* Условия — нумерованные блоки с волосяными разделителями.
   Разделитель тот же, что в прайсе: отступ внутри пункта + нижняя граница,
   поэтому линия идёт от края до края панели, а не только под текстом. */
.conditions { display: grid; }
.condition {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-4);
  align-items: start;
  /* Больше, чем 12px в прайсе: там строки однострочные, здесь абзацы в
     несколько строк, и на 12px они слипались бы с разделителем. */
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--border);
}
.condition:first-child { padding-top: 0; }
.condition:last-child  { border-bottom: 0; padding-bottom: 0; }
.condition-num {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 800;
  line-height: 1;
  color: var(--border-strong);
  font-variant-numeric: tabular-nums;
  /* Выравнивание по верху букв, а не по верху боксов. Замерено на канве:
     у цифры (Montserrat 800 24px, lh 1) краска начинается в 2.5px от верха
     бокса, у текста (Inter 14px, lh 1.6) — в 5.7px. Разница 3.2px, на неё
     цифру и опускаем. При смене кегля цифры или --fs-sm замер повторить. */
  margin-top: 3.2px;
}
.condition p {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--text-muted);
}

/* ============================================================
   Онлайн-калькулятор
   ============================================================ */

.calc {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-4);
  align-items: start;
}
@media (min-width: 900px) {
  .calc { grid-template-columns: 1.15fr 0.85fr; }
}

.calc-group {
  margin: 0;
  padding: 0;
  border: 0;
}

/* Границы у fieldset нет намеренно: legend разрывает её, и справа от подписи
   торчал обрубок линии. Группы разделяет воздух, а не черта.
   Сверху отступ заметно больше, чем под подписью, — так заголовок явно
   принадлежит следующему списку, а не отрывается от него. */
.calc-group + .calc-group {
  margin-top: var(--s-6);
}

/* Акцентом и с чертой, как eyebrow: приглушённым серым две группы сливались
   в один сплошной список, и было неясно, где заканчивается выбор типа сайта. */
.calc-legend {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: 0;
  margin-bottom: var(--s-3);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}
.calc-legend::before {
  content: "";
  width: 22px;
  height: 2px;
  background: var(--accent);
  flex: none;
}

.calc-choices { display: grid; }

/* Строка выбора: маркер, название, цена справа */
.calc-choice {
  display: grid;
  grid-template-columns: 20px 1fr auto;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.calc-choice:last-child { border-bottom: 0; padding-bottom: 0; }
.calc-choice:first-child { padding-top: 0; }

.calc-choice input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.calc-choice-box {
  width: 20px;
  height: 20px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--bg-3);
  transition: border-color var(--t-fast), background var(--t-fast);
  position: relative;
}

/* Галочка и точка рисуются маской — цвет берётся из --accent и следует теме */
.calc-choice-box::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--t-fast);
  background: var(--accent-ink);
  -webkit-mask: var(--check) center / 11px 11px no-repeat;
  mask: var(--check) center / 11px 11px no-repeat;
}
.calc-choice input:checked + .calc-choice-box {
  background: var(--accent);
  border-color: var(--accent);
}
.calc-choice input:checked + .calc-choice-box::after { opacity: 1; }

.calc-choice input:focus-visible + .calc-choice-box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.calc-choice-text { min-width: 0; }
.calc-choice-name {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text);
}
.calc-choice-note {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-label);
  line-height: 1.4;
  color: var(--text-dim);
}

.calc-choice-price {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}

.calc-reset {
  align-self: flex-start;
  margin-top: var(--s-6);
  padding: 9px 18px;
  font: inherit;
  font-size: var(--fs-xs);
  color: var(--text-dim);
  background: transparent;
  border: 1px solid var(--border);
  /* --r-btn, а не --r-sm: это кнопка, и она должна скругляться вместе
     с остальными по выбранному пресету */
  border-radius: var(--r-btn);
  cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.calc-reset:hover { color: var(--text); border-color: var(--border-strong); }

/* ---------- Выпадающий список типа сайта ---------- */

.calc-select { position: relative; }

.calc-select-trigger {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  width: 100%;
  padding: var(--s-4);
  font: inherit;
  text-align: left;
  color: var(--text);
  background: var(--bg-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.calc-select-trigger:hover { border-color: var(--accent-line); }
.calc-select-trigger[aria-expanded="true"] {
  border-color: var(--accent);
  background: var(--bg-2);
}

.calc-select-current { flex: 1; min-width: 0; }

.calc-select-name {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
}
.calc-select-note {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-label);
  line-height: 1.4;
  color: var(--text-dim);
}

.calc-select-price {
  flex: none;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}

.calc-select-chevron {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--text-dim);
  transition: transform var(--t-base), color var(--t-fast);
}
.calc-select-chevron svg { width: 16px; height: 16px; }
.calc-select-trigger[aria-expanded="true"] .calc-select-chevron {
  transform: rotate(180deg);
  color: var(--accent);
}

/* Список поверх содержимого: абсолютное позиционирование, чтобы соседние
   блоки не съезжали при открытии */
.calc-select-list {
  position: absolute;
  z-index: 30;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  /* Потолок нужен для длинных списков, но обычные 5–7 типов должны помещаться
     целиком. Считать надо по двухстрочному пояснению: как только описание
     переносится, строка вырастает вдвое, и прежних 440px хватало только
     на однострочные — полоса прокрутки появлялась без всякой нужды. */
  max-height: min(76vh, 640px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--s-2);
  background: var(--bg-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}

.calc-select-option { display: block; }
.calc-select-option input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.calc-select-face {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background var(--t-fast);
}
.calc-select-option:hover .calc-select-face { background: var(--bg-3); }
.calc-select-option input:checked + .calc-select-face { background: var(--accent-soft); }
.calc-select-option input:focus-visible + .calc-select-face {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.calc-select-text { flex: 1; min-width: 0; }

.calc-select-check {
  flex: none;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  color: var(--accent);
  opacity: 0;
  transition: opacity var(--t-fast);
}
.calc-select-check svg { width: 14px; height: 14px; }
.calc-select-option input:checked + .calc-select-face .calc-select-check { opacity: 1; }

@media (max-width: 480px) {
  .calc-select-trigger { flex-wrap: wrap; gap: var(--s-2) var(--s-3); }
  .calc-select-current { flex: 1 1 100%; order: 1; }
  .calc-select-price   { order: 2; }
  .calc-select-chevron { order: 3; margin-left: auto; }
}

/* Горизонтальный переключатель для взаимоисключающих опций.
   Первый сегмент «Нет» обязателен: у группы radio иначе нельзя снять выбор. */
.calc-seg {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-btn);
}

.calc-seg-item { display: flex; min-width: 0; }
.calc-seg-item input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.calc-seg-face {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  padding: 9px var(--s-4);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-muted);
  /* Прозрачная рамка в покое: выбранный сегмент получает её видимой, и без
     этой заготовки все сегменты подпрыгивали бы на 2px при выборе */
  border: 1px solid transparent;
  border-radius: var(--r-btn);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.calc-seg-item:hover .calc-seg-face { color: var(--text); }

.calc-seg-price {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--accent);
  transition: color var(--t-fast);
}

/* Выбранный сегмент — светло-серая обводка на прозрачном, без заливки
   и без свечения. Цена остаётся акцентной: на прозрачном фоне ей больше
   ничто не мешает, а раньше она гасилась до белого поверх заливки. */
.calc-seg-item input:checked + .calc-seg-face {
  background: transparent;
  border-color: var(--text-dim);
  color: var(--text);
}
.calc-seg-item input:focus-visible + .calc-seg-face {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- Карточки: вид группы для длинных названий и пояснений ----------
   У названия своя строка, пояснение под ним, цена справа. Высота группы
   растёт по одной строке на вариант и не зависит от длины названия — лента
   в тех же условиях переносится и раздувает блок вдвое. */
.calc-cards {
  display: grid;
  gap: 4px;
  padding: 4px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

.calc-card {
  display: grid;
  grid-template-columns: 18px 1fr auto;
  align-items: start;
  gap: var(--s-3);
  padding: 10px var(--s-4);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.calc-card input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.calc-card:hover { background: var(--bg-2); }

/* Кружок выбора рисуется рамкой, а не элементом формы: у нативной радиокнопки
   в разных браузерах свой размер и своя обводка */
.calc-card-mark {
  width: 16px;
  height: 16px;
  margin-top: 2px;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.calc-card input:checked + .calc-card-mark {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 4px var(--accent);
}

.calc-card-text { min-width: 0; }
.calc-card-name {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-muted);
  transition: color var(--t-fast);
}
.calc-card-note {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-label);
  line-height: 1.45;
  color: var(--text-dim);
}
.calc-card-price {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-xs);
  color: var(--accent);
  white-space: nowrap;
}

.calc-card:has(input:checked) { border-color: var(--text-dim); }
.calc-card:has(input:checked) .calc-card-name { color: var(--text); }
.calc-card:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

.calc-seg-note {
  margin: var(--s-3) 0 0;
  font-size: var(--fs-label);
  line-height: 1.45;
  color: var(--text-dim);
}

/* На узком экране сегменты становятся столбиком во всю ширину */
@media (max-width: 560px) {
  /* В столбик полукруглая рамка контейнера выглядит раздутой — она рассчитана
     на горизонтальную ленту. Берём обычный радиус блока. */
  .calc-seg {
    flex-direction: column;
    border-radius: var(--r-lg);
  }
  .calc-seg-item { width: 100%; }
  .calc-seg-face { width: 100%; justify-content: space-between; }
}

/* ---------- Смета ---------- */

/* Прокрутка к смете останавливается ниже шапки: она закреплена сверху
   и без этого отступа накрывает заголовок «Итоговая стоимость» */
.calc-total { scroll-margin-top: calc(var(--header-h) + var(--s-4)); }

@media (min-width: 900px) {
  /* Смета едет за прокруткой: список слева длиннее, и без этого итог
     уезжал бы из поля зрения на середине выбора */
  .calc-total {
    position: sticky;
    top: calc(var(--header-h) + var(--s-4));
  }
}

.calc-lines {
  list-style: none;
  margin: 0;
  padding: 0;
}

.calc-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.calc-line:first-child { padding-top: 0; }
/* У последней строки черты нет: ниже своя черта у итога, и вдвоём они
   читались как двойная линия */
.calc-line:last-child { border-bottom: 0; }
.calc-line.is-muted { color: var(--text-dim); }

/* Одна строка на позицию: приписка с группой не должна растить высоту сметы.
   Лишнее обрезается по краю — и обрезается именно приписка, потому что она
   стоит последней. */
.calc-line-name {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.calc-line-group {
  font-style: normal;
  font-size: var(--fs-xs);
  color: var(--text-dim);
}
.calc-line-value {
  flex: none;
  font-family: var(--font-display);
  font-weight: 700;
  white-space: nowrap;
}

.calc-sum {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 2px dashed var(--border-strong);
}

/* Подписи итогов — обычным регистром. Разрядка в 0.14em с прописными хороша
   для коротких надзаголовков секций, но «ПРОДЛЕНИЕ, КАЖДЫЙ ГОД» на всю ширину
   панели рассыпается на отдельные буквы и читается по слогам */
.calc-sum-label {
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-muted);
}

/* Подпись главного итога — единственная, которой прописные идут: слово одно
   и короткое, разрядка его не рассыпает. Ежегодным подписям в две-три строки
   тот же приём читать мешает */
.calc-sum-main .calc-sum-label {
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text);
}

.calc-sum-value {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--accent);
  white-space: nowrap;
}

/* Срок идёт сразу под итогом, а черты под ним нет: между ним и ежегодными
   платежами теперь стоит кнопка, и она делит панель лучше любой линии. */
.calc-term {
  margin: var(--s-2) 0 0;
  font-size: var(--fs-label);
  color: var(--text-dim);
}

/* Ежегодные платежи — на собственной подложке. В общем столбце они читались
   продолжением сметы, то есть частью разового платежа. Отдельный блок сразу
   говорит: это про другое время. */
.calc-renew-box {
  margin-top: var(--s-4);
  padding: var(--s-4);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

/* Внутри подложки строки идут вплотную: разделять их ещё и отступами
   с чертой — значит рисовать таблицу внутри таблицы */
.calc-renew-box .calc-sum-annual {
  margin: 0;
  padding: 0;
  border: 0;
}
.calc-renew-box .calc-sum-annual + .calc-sum-annual { margin-top: var(--s-2); }

/* Тише итога и тише строк сметы: и подпись, и сумма одного кегля и одного
   цвета. Разным цветом сумма перетягивала внимание на себя, а решение
   о покупке принимают не по ней. */
.calc-renew-box .calc-sum-label,
.calc-renew-box .calc-sum-value {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.calc-renew-box .calc-sum-value { font-weight: 700; }

.calc-renew-note {
  margin: var(--s-3) 0 0;
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--text-dim);
}

/* Правовая оговорка — последняя строка сметы. Тише примечания над ней:
   выбирать по ней нечего, читают её один раз и по необходимости.
   Отделена чертой, чтобы не читалась продолжением продающего текста. */
.calc-offer-note {
  margin: var(--s-3) 0 0;
  padding-top: var(--s-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-label);
  line-height: 1.5;
  /* Без приглушения прозрачностью: на подложке сметы 12px при opacity 0.85
     дают контраст 4.32 — ниже порога 4.5. Мелкий правовой текст обязан
     читаться, иначе оговорка не работает и как оговорка. */
  color: var(--text-dim);
}

/* ---------- Бегущий итог на телефоне ----------
   На широком экране смета липкая и всегда перед глазами; на узком до неё
   полтора экрана прокрутки, и связь «поставил галочку — увидел сумму»
   рвётся. Полоса показывает тот же итог, пока настоящая смета не на экране,
   и переносит к ней по нажатию.

   Сверху, под шапкой, и в её же цвете — продолжение шапки, а не отдельная
   плашка. Снизу пробовали: там Safari на iPhone красит свою нижнюю панель
   по цвету у кромки экрана, и она становилась акцентной. */
.calc-bar {
  position: fixed;
  top: var(--header-h);
  left: 0;
  right: 0;
  z-index: 90;                 /* ниже шапки (100) и окон, выше содержимого */
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  width: 100%;
  padding: 10px var(--s-5);
  font-family: var(--font-body);
  text-align: left;
  background: var(--header-bg);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
  border: 0;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-100%);
  transition: opacity var(--t-fast), transform var(--t-fast), visibility var(--t-fast);
}
.calc-bar.is-shown {
  opacity: 1;
  visibility: visible;
  transform: none;
}
.calc-bar:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.calc-bar-label {
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text);
}
.calc-bar-value {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 800;
  color: var(--accent);
  white-space: nowrap;
}

/* На широком экране полоса не нужна: смета и так липкая */
@media (min-width: 900px) {
  .calc-bar { display: none; }
}

/* ============================================================
   Заявка из калькулятора
   ============================================================ */

.calc-order-btn {
  width: 100%;
  margin-top: var(--s-5);
}

/* Примечание идёт последним, и черта над ним не нужна: выше уже стоит
   подложка с ежегодными платежами, а раньше эта линия прилипала к кнопке */
.calc-total .panel-note {
  margin-top: var(--s-4);
  padding-top: 0;
  border-top: 0;
}

/* Окно — подложка во весь экран, и прокручивается именно она. Карточка
   внутри живёт обычным блоком: она целиком уезжает вверх вместе с кнопками,
   полоса прокрутки одна и у края экрана. Прежняя схема закрепляла шапку
   и низ, а прокручивала середину — получалась узкая полоса внутри окна,
   а на телефоне закреплённый низ ещё и наезжал на поля. */
.order-modal,
.svc-modal {
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  inset: 0;
  margin: 0;
  padding: var(--s-5) var(--s-4);
  color: var(--text);
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  overflow-y: auto;
  /* Прокрутка не уходит на страницу под окном, когда карточка домотана
     до края */
  overscroll-behavior: contain;
}
.order-modal[open],
.svc-modal[open] { display: flex; }

/* margin: auto, а не align-items: center. Центрирование флексом обрезает
   верх карточки, когда она выше экрана, — до него становится не домотать. */
.modal-card {
  width: min(560px, 100%);
  margin: auto;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}

.order-modal::backdrop,
.svc-modal::backdrop {
  background: var(--scrim);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

.order-form { display: flex; flex-direction: column; }

/* Ловушка для ботов: убрана из потока и от экранных читалок, но остаётся
   в DOM — иначе роботу нечего было бы заполнить */
.order-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.order-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5);
  border-bottom: 1px solid var(--border);
}
.order-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.order-close {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: none;
  color: var(--text-dim);
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast);
}
.order-close svg { width: 18px; height: 18px; }
.order-close:hover { color: var(--text); background: var(--bg-3); }

.order-body { padding: var(--s-5); }

.order-note {
  margin: 0 0 var(--s-4);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--text-muted);
}

/* Состав расчёта, с которым пришёл посетитель */
/* Состав заказа: тонированный блок со строками, а не одна склеенная фраза */
.order-summary {
  margin: 0 0 var(--s-5);
  padding: var(--s-4);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-sm);
}

.order-summary-title {
  margin: 0 0 var(--s-3);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

.order-summary-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.order-summary-list li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  padding: 5px 0;
  font-size: var(--fs-label);
  line-height: 1.45;
  color: var(--text-muted);
}
/* Одна строка на позицию: с припиской о группе названия стали длиннее,
   и без этого список в окне рос по высоте */
.order-summary-list li > span {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.order-summary-list b {
  flex: none;
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
}

.order-summary-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin: var(--s-3) 0 0;
  padding-top: var(--s-3);
  border-top: 1px solid var(--accent-line);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.order-summary-total b {
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--accent);
  text-transform: none;
}

/* Обязательное поле и ошибка под ним */
.order-req { color: var(--accent); }

.order-field-error {
  margin: 6px 0 0;
  font-size: var(--fs-label);
  line-height: 1.4;
  color: var(--danger);
}
.order-field input.is-invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-soft);
}

.order-field { margin-bottom: var(--s-4); }
.order-field label {
  display: block;
  margin-bottom: 6px;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted);
}
.order-field input,
.order-field textarea {
  width: 100%;
  padding: 11px var(--s-3);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  color: var(--text);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.order-field textarea { resize: vertical; min-height: 84px; line-height: 1.55; }
.order-field input::placeholder,
.order-field textarea::placeholder { color: var(--text-dim); }
.order-field input:focus,
.order-field textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.order-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-4);
}
@media (max-width: 480px) {
  .order-row { grid-template-columns: 1fr; }
}

.order-consent {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: var(--s-3);
  align-items: start;
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--text-muted);
  cursor: pointer;
}
.order-consent input { position: absolute; opacity: 0; width: 0; height: 0; }
.order-consent a { color: var(--accent); text-decoration: underline; }

.order-consent-box {
  position: relative;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  background: var(--bg-3);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  transition: background var(--t-fast), border-color var(--t-fast);
}
.order-consent-box::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  background: var(--accent-ink);
  -webkit-mask: var(--check) center / 11px 11px no-repeat;
  mask: var(--check) center / 11px 11px no-repeat;
  transition: opacity var(--t-fast);
}
.order-consent input:checked + .order-consent-box {
  background: var(--accent);
  border-color: var(--accent);
}
.order-consent input:checked + .order-consent-box::after { opacity: 1; }
.order-consent input:focus-visible + .order-consent-box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.order-error {
  margin: var(--s-4) 0 0;
  padding: 10px var(--s-3);
  font-size: var(--fs-label);
  line-height: 1.45;
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid var(--danger);
  border-radius: var(--r-sm);
}

.order-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-top: 1px solid var(--border);
}

.order-done {
  display: grid;
  justify-items: center;
  gap: var(--s-4);
  padding: var(--s-8) var(--s-5);
  text-align: center;
}
.order-done p { margin: 0; font-size: var(--fs-sm); color: var(--text-muted); max-width: 40ch; }
.order-done-mark {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  color: var(--accent-ink);
  background: var(--accent-fill);
  border-radius: 50%;
  box-shadow: var(--shadow-accent);
}
.order-done-mark svg { width: 26px; height: 26px; }

/* ============================================================
   Документ (политика конфиденциальности)
   ============================================================ */

.doc-page { padding-top: clamp(var(--s-6), 5vw, var(--s-8)); }
.doc { max-width: 820px; }

.doc-title {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin: 0 0 var(--s-3);
  --metal-line: 1.1em;
}

.doc-meta {
  margin: 0 0 var(--s-6);
  font-size: var(--fs-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.doc-warning {
  margin: 0 0 var(--s-6);
  padding: var(--s-4);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid var(--danger);
  border-radius: var(--r-sm);
}

.doc-body { font-size: var(--fs-sm); line-height: 1.7; color: var(--text-muted); }
.doc-body h2 {
  margin: var(--s-7) 0 var(--s-3);
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 800;
  line-height: 1.3;
  color: var(--text);
  /* Якорь из оглавления не должен уезжать под липкую шапку */
  scroll-margin-top: calc(var(--header-h) + var(--s-5));
}
.doc-body h2:first-child { margin-top: 0; }
.doc-body p  { margin: 0 0 var(--s-4); }
.doc-body ul { margin: 0 0 var(--s-4); padding-left: var(--s-5); }
.doc-body li { margin-bottom: 6px; }
.doc-body a  { color: var(--accent); text-decoration: underline; }
.doc-body strong { color: var(--text); }

/* Оглавление: нумерация — из счётчика, чтобы номер в списке не мог
   разъехаться с номером заголовка при добавлении раздела */
.doc-toc {
  margin: 0 0 var(--s-7);
  padding: var(--s-5);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.doc-toc-title {
  margin: 0 0 var(--s-3);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.doc-toc ol {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: toc;
  columns: 2;
  column-gap: var(--s-6);
  font-size: var(--fs-sm);
}
.doc-toc li {
  counter-increment: toc;
  margin-bottom: 6px;
  break-inside: avoid;
}
/* Ширины хватает и на двузначный номер: при 1.6em «10.» упиралось
   в текст пункта без пробела */
.doc-toc li::before {
  content: counter(toc) ".";
  display: inline-block;
  min-width: 2.2em;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.doc-toc a {
  color: var(--text-muted);
  text-decoration: none;
  transition: color var(--t-fast);
}
.doc-toc a:hover { color: var(--accent); text-decoration: underline; }
@media (max-width: 560px) {
  .doc-toc ol { columns: 1; }
}

.doc-terms { margin: 0 0 var(--s-4); }
.doc-terms dt {
  margin-top: var(--s-3);
  font-weight: 600;
  color: var(--text);
}
.doc-terms dt:first-child { margin-top: 0; }
.doc-terms dd { margin: 2px 0 0; }

.doc-table {
  width: 100%;
  margin: 0 0 var(--s-4);
  border-collapse: collapse;
  text-align: left;
}
.doc-table th,
.doc-table td {
  padding: 10px var(--s-3) 10px 0;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
}
.doc-table th {
  width: 38%;
  font-weight: 600;
  color: var(--text-dim);
}
.doc-table td { color: var(--text); }
.doc-table tr:last-child th,
.doc-table tr:last-child td { border-bottom: 0; }
@media (max-width: 480px) {
  /* На узком экране колонка реквизита сжимала значение до двух букв
     в строке — переводим таблицу в вертикальные пары */
  .doc-table th, .doc-table td { display: block; width: auto; }
  .doc-table th { padding-bottom: 0; border-bottom: 0; }
  .doc-table td { padding-top: 2px; }
}

.doc-contacts { list-style: none; padding-left: 0; }
.doc-contacts li { margin-bottom: 6px; color: var(--text); }

.doc-back { margin-top: var(--s-7); }

/* ============================================================
   Окно с описанием услуги
   ============================================================ */

.svc-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5);
  border-bottom: 1px solid var(--border);
}
.svc-modal-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 800;
  line-height: 1.2;
}
/* Подпись — обычным регистром: это целая фраза, а капсом она читается
   как крик и занимает две строки вместо одной */
.svc-modal-sub {
  margin: 6px 0 0;
  font-size: var(--fs-label);
  line-height: 1.45;
  color: var(--text-dim);
}

.svc-modal-body {
  padding: var(--s-5);
  font-size: var(--fs-sm);
  line-height: 1.7;
  color: var(--text-muted);
}
.svc-modal-body p { margin: 0 0 var(--s-4); }
.svc-modal-body p:last-child { margin-bottom: 0; }

.svc-modal-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  border-top: 1px solid var(--border);
}
.svc-modal-price {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}

@media (max-width: 480px) {
  .svc-modal-foot { flex-direction: column; align-items: stretch; }
  .svc-modal-foot .btn { width: 100%; }
}

/* Под открытым окном страница стоит на месте. Отступ равен ширине исчезнувшей
   полосы прокрутки — без него содержимое прыгало бы вбок на её ширину;
   элементам с position: fixed он же добавляется вручную, они от padding
   на body не сдвигаются. */
/* Запирает прокрутку body, а не html, и это принципиально. Значение overflow
   у body доходит до окна просмотра, когда у html оно visible, — прокрутка
   пользователем так же заперта, полоса так же исчезает. Зато html остаётся
   прокручиваемым контейнером, и position: sticky продолжает работать.

   С hidden на html sticky ломается: шапка и панель сметы уезжают на свои
   места в потоке. Под окном этого не видно, а при закрытии, пока блокировка
   ещё держится, страница на долю секунды остаётся без шапки. */
html.modal-open body { overflow: hidden; }
html.modal-open body { padding-right: var(--scroll-lock-gap, 0px); }
html.modal-open .cookie-banner { margin-right: var(--scroll-lock-gap, 0px); }


/* ---------- Появление и закрытие окон ---------- */

/* Появление и исчезновение сделаны разными механизмами намеренно.
   Вход — обычная анимация: она запускается сама, как только окно попадает
   на экран, и не зависит от поддержки @starting-style.
   Выход — переход, потому что анимировать нечего: окно закрывается, и его
   надо ещё чем-то удержать на экране. Это делают allow-discrete у display
   (иначе окно пропадает мгновенно) и у overlay (иначе оно выпадает
   из верхнего слоя, обгоняя собственное затухание).
   Браузер без allow-discrete закроет окно сразу — вход при этом работает. */

@keyframes modal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
}
@keyframes modal-backdrop-in {
  from { opacity: 0; }
}

/* Гаснет вся подложка целиком, а всплывает только карточка: двигать
   само окно нельзя — оно теперь контейнер прокрутки, и transform на нём
   утащил бы вместе с карточкой и полосу прокрутки. */
.svc-modal,
.order-modal {
  opacity: 0;
  transition:
    opacity 0.18s ease,
    overlay 0.18s allow-discrete,
    display 0.18s allow-discrete;
}
.svc-modal[open],
.order-modal[open] { opacity: 1; }
.svc-modal[open] .modal-card,
.order-modal[open] .modal-card {
  animation: modal-in 0.24s cubic-bezier(0.22, 0.75, 0.2, 1);
}

.svc-modal::backdrop,
.order-modal::backdrop {
  opacity: 0;
  transition:
    opacity 0.18s ease,
    overlay 0.18s allow-discrete,
    display 0.18s allow-discrete;
}
.svc-modal[open]::backdrop,
.order-modal[open]::backdrop {
  opacity: 1;
  animation: modal-backdrop-in 0.24s ease;
}

@media (prefers-reduced-motion: reduce) {
  .svc-modal,
  .order-modal,
  .svc-modal::backdrop,
  .order-modal::backdrop,
  .modal-card {
    transition: none;
    animation: none;
  }
}

/* ============================================================
   Портфолио
   ============================================================ */

.portfolio-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-5);
}

/* Лента вкладок собрана так же, как переключатели в калькуляторе:
   утопленная дорожка, внутри — кнопки без собственных рамок. */
.portfolio-filters {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-btn);
}

/* Заливка активной вкладки вынесена в отдельный слой и переезжает между
   кнопками. Красить фон самой кнопки нельзя: background не анимируется
   между позициями, и переход выглядел бы мгновенной перекраской. */
.filter-thumb {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--seg-w, 0);
  height: var(--seg-h, 0);
  transform: translate(var(--seg-x, 0), var(--seg-y, 0));
  background: transparent;
  border: 1px solid var(--text-dim);
  border-radius: var(--r-btn);
  opacity: 0;
  pointer-events: none;
}
/* Появление без переезда: до первого измерения плашка стоит в нуле,
   и анимировать её оттуда — значит показать рывок через всю ленту */
.filter-thumb.ready { opacity: 1; }
.filter-thumb.ready.animate {
  transition:
    transform 0.34s cubic-bezier(0.22, 0.75, 0.2, 1),
    width     0.34s cubic-bezier(0.22, 0.75, 0.2, 1),
    height    0.34s cubic-bezier(0.22, 0.75, 0.2, 1);
}

.filter-btn {
  position: relative;
  z-index: 1;
  padding: 9px var(--s-4);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  background: none;
  /* Ровно та же заготовка, что у сегментов калькулятора: плашка активной
     вкладки обводит кнопку по её внешней кромке, и без прозрачной рамки
     обводка в режиме без JS сдвигала бы ленту */
  border: 1px solid transparent;
  border-radius: var(--r-btn);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--t-fast);
}
.filter-btn:hover { color: var(--text); }
.filter-btn.active { color: var(--text); }
.filter-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Без JS плашка не появится, поэтому активная вкладка обводится сама */
html:not(.js) .filter-btn.active { border-color: var(--text-dim); }

/* Отключённая анимация в системе — переезд без перехода, состояние то же */
@media (prefers-reduced-motion: reduce) {
  .filter-thumb.ready.animate { transition: none; }
}

/* Лента переносится задолго до телефона: шесть типов сайта требуют около
   900px, а названия задаёт владелец — угадывать точку перелома медиазапросом
   бессмысленно. Класс ставит скрипт, когда лента фактически стала в два ряда.

   Полукруглая рамка рассчитана на одну строку: у блока в два ряда она
   выглядит раздутой капсулой. Кнопки в перенесённой ленте растягиваются
   по ширине ряда — иначе последний ряд обрывается рваным краем. */
.portfolio-filters.is-wrapped {
  border-radius: var(--r-lg);
  justify-content: center;
}
.portfolio-filters.is-wrapped .filter-btn {
  flex: 1 1 auto;
  text-align: center;
}

@media (max-width: 560px) {
  /* Запасной случай: без JS класс не появится, а на телефоне лента
     переносится всегда */
  .portfolio-filters { border-radius: var(--r-lg); }
}

.portfolio-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-3);
}
@media (min-width: 620px)  { .portfolio-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 900px)  { .portfolio-grid { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 1160px) { .portfolio-grid { grid-template-columns: repeat(var(--pf-cols), 1fr); gap: var(--s-4); } }

.portfolio-card {
  border-radius: var(--r-md);
  /* Перспектива на карточке, а не на сетке: у общей перспективы точка схода
     одна на всю строку, и крайние карточки разворачивались бы косо. */
  perspective: 1000px;
}
.portfolio-card.hidden { display: none; }

.portfolio-link { display: block; }

.portfolio-flip {
  position: relative;
  aspect-ratio: 16 / 10;
  transform-style: preserve-3d;
  transition: transform 0.55s var(--ease);
}

.portfolio-face {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  background: var(--bg-inset);
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

.portfolio-face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* Подложка поверх скриншота: в тёмной теме тёмная, в светлой светлая.
   Без неё подписи тонули в пёстрых скриншотах. */
.portfolio-scrim {
  position: absolute;
  inset: 0;
  background: var(--card-scrim);
  pointer-events: none;
}

.portfolio-back {
  transform: rotateY(180deg);
  display: grid;
  place-items: center;
}

@media (hover: hover) and (pointer: fine) {
  .portfolio-card:hover .portfolio-flip,
  .portfolio-link:focus-visible .portfolio-flip {
    transform: rotateY(180deg);
  }
}

/* Круг со стрелкой на изнанке — единственная подпись там */
.portfolio-arrow {
  position: relative;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  color: var(--accent-ink);
  background: var(--accent-fill);
  box-shadow: var(--shadow-accent);
}
.portfolio-arrow svg { width: 20px; height: 20px; }

.portfolio-img-placeholder {
  display: grid;
  place-items: center;
  height: 100%;
  font-size: var(--fs-label);
  color: var(--text-dim);
}

/* ---------- Подписи на лицевой стороне ---------- */

.portfolio-meta {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: var(--s-3);
}

.portfolio-domain {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text);
}
/* min-width:0 обязателен: без него флекс-элемент не сжимается ниже ширины
   текста, длинный домен выдавливал точку за край карточки вместо многоточия. */
.portfolio-domain-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.portfolio-type {
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Метка активности — теперь перед доменом, а не углом поверх картинки */
.status-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
}
.status-dot.is-active   { background: var(--ok); }
.status-dot.is-inactive { background: var(--danger); }

.price-badge {
  position: absolute;
  top: var(--s-2);
  right: var(--s-2);
  padding: 3px 8px;
  font-size: var(--fs-label);
  font-weight: 600;
  color: #fff;
  background: var(--overlay);
  border-radius: var(--r-pill);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

/* Без анимации карточка не крутится — изнанка просто проявляется */
@media (prefers-reduced-motion: reduce) {
  .portfolio-flip { transition: none; }
  .portfolio-card:hover .portfolio-flip { transform: none; }
  .portfolio-back {
    transform: none;
    opacity: 0;
    transition: none;
    -webkit-backface-visibility: visible;
    backface-visibility: visible;
  }
  .portfolio-card:hover .portfolio-back { opacity: 1; }
}

.empty-message {
  grid-column: 1 / -1;
  padding: var(--s-8) var(--s-5);
  text-align: center;
  color: var(--text-dim);
}

/* ============================================================
   Страница 404
   ============================================================ */

.error-page {
  display: grid;
  place-items: center;
  min-height: calc(100svh - var(--header-h));
  padding: var(--s-7) 0;
  text-align: center;
}
@supports not (height: 100svh) {
  .error-page { min-height: calc(100vh - var(--header-h)); }
}

.error-inner {
  position: relative;
  max-width: 640px;
}

/* Тление за цифрой — тот же приём, что в первом экране */
.error-inner::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 30%;
  width: min(700px, 120%);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, var(--ember) 0%, transparent 62%);
  pointer-events: none;
  z-index: -1;
}

.error-page .eyebrow { justify-content: center; }

.error-code {
  font-family: var(--font-display);
  font-size: clamp(5rem, 18vw, 11rem);
  font-weight: 800;
  line-height: 0.9;
  letter-spacing: -0.04em;
  margin: 0 0 var(--s-4);
  --metal-line: 0.9em;
}

.error-title {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin: 0 0 var(--s-4);
  --metal-line: 1.1em;
}

.error-lead {
  font-size: var(--fs-sm);
  line-height: 1.65;
  color: var(--text-muted);
  margin: 0 auto var(--s-6);
  max-width: 46ch;
}

.error-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  justify-content: center;
}
@media (max-width: 480px) {
  .error-actions { flex-direction: column; }
  .error-actions .btn { width: 100%; }
}

/* ============================================================
   Футер
   ============================================================ */

/* Своего отступа сверху у футера нет намеренно: перед ним всегда стоит
   секция, и её нижний padding уже отбивает содержимое от черты. Со своим
   margin отступы складывались, и между последней работой портфолио и футером
   зияло 192px пустоты. */
.site-footer {
  padding: var(--s-6) 0;
  border-top: 1px solid var(--border);
  background: var(--bg-1);
}
.site-footer p {
  margin: 0 0 var(--s-2);
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--text-dim);
  max-width: 90ch;
}
.site-footer p:last-child { margin-bottom: 0; }

.footer-links { margin-top: var(--s-3); }
.footer-links a {
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.footer-links a:hover { color: var(--accent); }

/* ============================================================
   Cookie-баннер
   ============================================================ */

/* Справа, а не слева: слева в первом экране стоит главная кнопка,
   и баннер её перекрывал. */
.cookie-banner {
  position: fixed;
  bottom: var(--s-5);
  right: var(--s-5);
  max-width: 380px;
  padding: var(--s-5);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg), var(--edge);
  z-index: 1500;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  transform: translateY(calc(100% + var(--s-6)));
  transition: transform var(--t-slow);
}
.cookie-banner.show { transform: translateY(0); }
.cookie-banner p {
  margin: 0;
  font-size: var(--fs-xs);
  line-height: 1.55;
  color: var(--text-muted);
}
.cookie-banner .btn { align-self: flex-start; padding: 10px var(--s-5); }

@media (max-width: 520px) {
  .cookie-banner { left: var(--s-3); right: var(--s-3); bottom: var(--s-3); max-width: none; }
}

/* На узком экране первый экран и надзаголовки секций — по центру.
   В одну колонку левый край перестаёт быть осью: текст висит у края,
   а знак и метрики стоят посередине, и страница выглядит расшатанной.

   Блок стоит в самом конце файла намеренно: базовые правила секций
   объявлены ниже по тексту, и при равной силе побеждает последнее.
   Стоя выше, эти правила молча проигрывали — .portfolio-head со своим
   align-items: flex-end прижимал заголовок вправо. */
@media (max-width: 720px) {
  .hero-inner,
  .section-head {
    text-align: center;
  }
  /* Лид ограничен по ширине — без auto он остаётся у левого края
     и не встаёт под заголовок */
  .hero-lead { margin-inline: auto; }
  /* Метрики: содержимое ячеек по центру и сам ряд по центру. Одного
     text-align мало — ряд у́же контейнера и без justify-content жмётся влево */
  .hero-stats {
    text-align: center;
    justify-content: center;
  }
  /* Заголовок портфолио — flex-элемент рядом с лентой вкладок, и своё
     выравнивание центрует его только внутри собственной ширины.
     В столбик с центрированием встают оба */
  .portfolio-head {
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
  /* Футер тоже по центру: он последний в этой же одноколоночной ленте */
  .site-footer { text-align: center; }
  .site-footer p { margin-inline: auto; }
}
