/* df.css — посадочная апарт-отеля «Дом Форм» (генератор tools/apart/build_df.py).

   20.08.2026, R-467: страница собрана С НУЛЯ по канону листа сверки
   premium/_f2-copy-proof.html. Прежние apart.css / build_apart.py НЕ наследуются —
   это был перекрашенный клон бренд-сайта, забракованный Алексом («до премиума как
   до Китая раком»). План: docs/plans/«Дом Форм — страница с нуля по канону листа
   сверки (20.08.2026).md».

   ── Девять правил канона (§2 плана), которым подчинён весь файл ──
   1. ОДНА колонка смысла: --w = min(1180px, 100% - 40px), всё в вертикальном потоке.
      ⛔ Никаких «текст слева / картинка справа»: ровно они дали дыры в пол-экрана.
   2. Ритм секции одинаковый: padding-block clamp(40,5vw,72) + волосяная линия сверху.
      Секции отбиваются ЛИНИЕЙ, а не пустотой в 200 px.
   3. Три роли текста, больше нет: кикер → дисплей → лид. Подзаголовок внутри —
      тихий капс 14 px наборным, НИКОГДА не второй крупный заголовок.
   4. Кадр во всю ширину колонки или лентой. Подпись 12 px, --fg-faint, короткая.
   5. Один акцент — золото --accent. Вторых цветов и заливок «на всякий случай» нет.
   6. Число — это дисплей: крупная цифра капителью + подпись капсом.
   7. Карточка минимальна: медиа, титул, мета, цена. Переключатель видов —
      единственный элемент управления.
   8. Плашка вместо кнопки для видео: постер, круглая кнопка, подпись по наведению.
   9. Факта без источника не бывает (правило копирайта, в CSS следа не оставляет).

   ── Почему словарь токенов продублирован, а не импортирован ──
   Страница живёт на комфорт-домене 72dom.life, а premium/assets/ отдаётся
   с premium.72dom.life — файл оттуда сюда не доедет. Слой скопирован ИМЕНАМИ И
   ЗНАЧЕНИЯМИ один в один с premium/assets/css/tokens.css; расхождение роняет гард
   (landing/server/check.py, секция [df]). Третий словарь заводить нельзя.
   Осознанно расходятся только --logo (путь к ассету у контуров разный, сверяется
   md5 файла) — список продублирован в гарде, менять в двух местах.

   ⚠ main.css эта страница НЕ подключает: комфорт-палитра спорит с брендовой
   (AGENTS.md §0), плюс её body{padding-bottom:76px} под мобильный sticky-CTA даёт
   76 px мёртвой зоны внизу. Свой минимальный ресет — ниже.

   ⚠ УРОК 20.08, из-за которого весь круг работы пропал даром: правило
   `.ap-body h1,h2,h3` (специфичность 0,1,1) молча перебивало `.ap-display` (0,1,0),
   и на всей странице стоял ОДИН CoFo. Здесь семейство заголовку назначает РОВНО
   ОДНО правило — .df-display — и ни один селектор с потомком шрифт не трогает.
   Гард проверяет не «класс в разметке», а getComputedStyle живой страницы. */

/* ═══ 1. РИТМ — общий слой, шкала кита Ventr (копия premium/assets/css/tokens.css) ═══ */
:root {
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-7: 32px;
  --s-8: 40px;
  --s-9: 48px;
  --s-10: 64px;
  --s-11: 80px;
  --s-12: 120px;

  --container: 1240px;
  --gutter: clamp(20px, 5vw, 100px);
  --section-y: clamp(64px, 9vw, 120px);

  --t-display: clamp(2.75rem, 7.5vw, 6rem);
  --t-h1: clamp(2rem, 5vw, 4rem);
  --t-h2: clamp(1.75rem, 3.6vw, 3rem);
  --t-h3: clamp(1.375rem, 2.4vw, 2rem);
  --t-h4: 1.5rem;
  --t-lead: 1.125rem;
  --t-body: 1rem;
  --t-small: 0.875rem;
  --t-caption: 0.75rem;

  --lh-tight: 1.05;
  --lh-head: 1.2;
  --lh-body: 1.5;
  --ls-tight: -0.02em;
  --ls-caps: 0.08em;

  --r-xs: 2px;
  --r-sm: 8px;
  --r-md: 16px;
  --r-lg: 24px;
  --r-xl: 32px;
  --r-full: 999px;
  --r-card: var(--r-md);
  --r-field: var(--r-lg);
  --r-chip: var(--r-full);

  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --dur-fast: 120ms;
  --dur: 240ms;
  --dur-slow: 480ms;

  --scrim:
    linear-gradient(90deg, rgb(0 0 0 / 0.62) 0%, rgb(0 0 0 / 0.28) 45%, rgb(0 0 0 / 0) 78%),
    linear-gradient(180deg, rgb(0 0 0 / 0.45) 0%, rgb(0 0 0 / 0.1) 40%, rgb(0 0 0 / 0.6) 100%);

  --focus-ring: 2px solid currentColor;
  --focus-offset: 3px;
}

/* ═══ 2. СЕМАНТИКА БРЕНДА — копия скоупа [data-brand="domform"] премиум-контура ═══ */
[data-brand="domform"] {
  --bg: #272b35;
  --bg-elev: #30343f;
  --fg: #ffffff;
  --fg-muted: rgb(255 255 255 / 0.66);
  --fg-faint: rgb(255 255 255 / 0.40);
  --line: rgb(255 255 255 / 0.14);
  --line-strong: rgb(255 255 255 / 0.28);
  --field-border: rgb(255 255 255 / 0.40);

  --accent: #e3c19c;
  --accent-fg: #272b35;
  --accent-loud: #e3c19c;
  --error: #ff9c8a;

  --secondary: #715d56;

  --paper: #ffffff;
  --paper-fg: #272b35;

  /* Путь к знаку у контуров свой — единственное осознанное расхождение (md5 файла
     сверяет гард). --logo-ratio у премиума есть, значение то же. */
  --logo: url("../img/logo-domform.svg");
  --logo-ratio: 7.63;

  /* CoFo Cinema 1909 разрешён отдельным решением Алекса 20.08 (R-462):
     fonts.json ok_for_prod=true, риск на стороне заказчика — их официальный сайт
     крутит тот же TRIAL. Гард проверяет не лицензию, а что шрифт РЕАЛЬНО применился. */
  --font-display: "CoFo Cinema 1909", Georgia, serif;
  --font-text: "Raleway", system-ui, sans-serif;
  --font-article: var(--font-text);
  --display-case: none;
  --display-weight: 400;
  --display-ls: 0.01em;
  --display-ls-wide: 0.12em;

  --r-btn: var(--r-full);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-fast: 0ms;
    --dur: 0ms;
    --dur-slow: 0ms;
  }
}

/* ═══ 3. ШРИФТЫ — свои файлы, без внешнего CDN ═══
   Google Fonts на этой странице НЕ подключаем: CoFo всё равно едет локально,
   а два источника шрифтов дают два лишних коннекта и второй FOUT. */
@font-face {
  font-family: "CoFo Cinema 1909";
  src: url("/assets/fonts/cofo-cinema-1909-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Raleway";
  src: url("/assets/fonts/raleway-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Raleway";
  src: url("/assets/fonts/raleway-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ═══ 4. КАНОН СТРАНИЦЫ ═══
   Свои значения канона держим на скоупе .df — в :root и [data-brand] их класть
   нельзя, там словарь обязан совпадать с премиум-контуром байт в байт. */
.df {
  /* Правило 1: одна колонка смысла. Ровно та же формула, что на листе сверки. */
  --w: min(1180px, 100% - 40px);
  /* Правило 2: один ритм у всех секций. */
  --sect-y: clamp(40px, 5vw, 72px);
  /* Кап высоты полноширинного кадра. Без него кадр 2400x1600 съедает экран
     целиком и страница вырастает на тысячи пикселей (бюджет длины — §4 плана). */
  --fig-max: min(52vh, 560px);
}

*, *::before, *::after { box-sizing: border-box; }

.df {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: var(--t-body)/1.65 var(--font-text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* height:auto обязателен и он же был дырой на 9 000 px. Атрибуты width/height у <img>
   стоят ради CLS, но height="1200" браузер применяет как presentational hint, и он
   ПЕРЕБИВАЕТ aspect-ratio: каждый кадр ленты рисовался высотой 1200 px вместо 233,
   мобильная страница вырастала до 28 793 px при бюджете 18 500. На скрине это
   выглядит как «просто крупные картинки» — ловится только замером высоты.
   :where() снимает специфичность, чтобы правило ни с кем не спорило. */
:where(.df) img { display: block; max-width: 100%; height: auto; }
/* ⚠ :where() обязателен. `.df a` — специфичность 0,1,1, она перебивает `.df-btn`
   (0,1,0), и залитая золотом кнопка молча набиралась белым по песку: контраст 1,70
   вместо 8,34 (поймано замером df_measure.mjs, глазами на скрине неотличимо).
   Это ровно та же ловушка, что убила сборку 20.08 (.ap-body h1,h2,h3 против
   .ap-display). :where() обнуляет вклад .df, и правило больше ни с кем не спорит. */
:where(.df) a { color: inherit; }

.df-wrap { width: var(--w); margin-inline: auto; }

/* Правило 2: секция = ритм + волосяная линия сверху. */
.df-sect {
  padding-block: var(--sect-y);
  border-top: 1px solid var(--line);
  /* Шапка липкая: без запаса переход по якорю прячет кикер и заголовок под ней. */
  scroll-margin-top: 62px;
}
.df-sect--flush { border-top: 0; }

/* ── Правило 3: ТРИ РОЛИ ТЕКСТА ──
   Кикер (11 px капс, разрядка, золото) → дисплей (CoFo, золото) → лид (наборный,
   приглушённый). Четвёртой роли нет. */
.df-kicker {
  margin: 0 0 10px;
  font-size: 11px;
  line-height: 1.4;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ЕДИНСТВЕННОЕ место, где заголовку назначается дисплейное семейство.
   Ни один селектор с потомком (.df h2 и подобные) шрифт не трогает — это и есть
   защита от специфичности, убившей сборку 20.08. */
.df-display {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(30px, 4vw, 54px);
  line-height: 1.05;
  letter-spacing: 0.10em;
  text-transform: var(--display-case);
  color: var(--accent);
}
.df-display--hero {
  font-size: clamp(44px, 8vw, 96px);
  letter-spacing: var(--display-ls-wide);
}

.df-lead {
  margin: 0 0 14px;
  max-width: 62ch;
  font-size: var(--t-lead);
  line-height: 1.55;
  color: var(--fg-muted);
}

/* ⚠ :where() и здесь обязателен, и это ТРЕТИЙ случай подряд в одном файле.
   `.df p` (0,1,1) перебивала `.df-more { margin: 18px 0 0 }` (0,1,0), и тумблер
   «показать все» вставал ВПЛОТНУЮ к нижнему ряду карточек — зазор ровно 0 px
   (замер: gapCardToLabel = 0 на 1440 и на 390). Правило: базовая типографика,
   висящая на корневом классе, ВСЕГДА в :where(), иначе она бьёт компоненты.
   Гард [df] это теперь и проверяет. */
:where(.df) p { margin: 0 0 14px; max-width: 70ch; }
.df-muted { color: var(--fg-muted); }
.df-faint { color: var(--fg-muted); font-size: var(--t-small); }

/* Подзаголовок внутри секции: тихий капс наборным. ⛔ Не второй крупный заголовок
   и ⛔ не дисплейное семейство — ровно от этого на экране появлялось два регистра. */
.df-sub {
  margin: 30px 0 10px;
  font-family: var(--font-text);
  font-weight: 500;
  font-size: 14px;
  line-height: 1.4;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg);
}
.df-sub:first-child { margin-top: 0; }

/* ── Правило 4: кадр во всю ширину колонки ── */
.df-fig { margin: 18px 0 0; }
.df-fig img {
  width: 100%;
  max-height: var(--fig-max);
  object-fit: cover;
  border-radius: var(--r-sm);
}
/* Подпись кадра. Канон листа сверки ставит --fg-faint, но у токена в паспорте
   написано «3,58:1 — только 18px+/декор»: на 12 px это провал WCAG AA (замер
   df_measure.mjs). Берём --fg-muted (7,06:1) — регистр тише основного текста,
   но читаемый. Осознанное расхождение с §2.4 плана. */
.df-fig figcaption,
.df-cap {
  margin-top: 8px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--fg-muted);
}

/* Лента кадров: одна ширина, скролл-снап на узком экране. */
.df-strip {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  margin: 18px 0 0;
}
.df-strip figure { margin: 0; }
.df-strip img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--r-sm);
}
.df-strip figcaption {
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--fg-muted);
}

/* ── Правило 6: число — это дисплей ── */
.df-stat { margin: 22px 0 0; }
.df-stat__num {
  display: block;
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(40px, 6vw, 72px);
  line-height: 1;
  letter-spacing: 0.04em;
  color: var(--accent);
}
.df-stat__label {
  margin: 6px 0 0;
  max-width: 34ch;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

/* ── Правило 8: плашка видео (постер + круглая кнопка, подпись по наведению) ── */
.df-video { margin: 22px 0 0; }
.df-video__btn {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  border-radius: var(--r-sm);
  overflow: hidden;
}
.df-video__btn img {
  width: 100%;
  max-height: var(--fig-max);
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease);
}
.df-video__btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.42));
  transition: background var(--dur) var(--ease);
}
.df-video__ui {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 14px;
}
.df-video__play {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.34);
  border: 1px solid var(--accent);
  display: grid;
  place-items: center;
  backdrop-filter: blur(4px);
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.df-video__play svg { width: 18px; height: 20px; margin-left: 3px; fill: var(--accent); }
/* Подложка под подписью обязательна: постер бывает почти белым (лобби), и золото
   на нём не читается. Меряем пиксели под строкой, а не токен фона. */
.df-video__label {
  padding: 7px 14px;
  border-radius: var(--r-full);
  background: rgb(0 0 0 / 0.62);
  backdrop-filter: blur(6px);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.df-video__btn:hover .df-video__label,
.df-video__btn:focus-visible .df-video__label { opacity: 1; transform: none; }
.df-video__btn:hover .df-video__play,
.df-video__btn:focus-visible .df-video__play { transform: scale(1.06); background: rgb(0 0 0 / 0.5); }
.df-video__btn:hover img { transform: scale(1.02); }
.df-video__btn:hover::after { background: linear-gradient(180deg, rgb(0 0 0 / 0.02), rgb(0 0 0 / 0.34)); }
.df-video__btn:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
@media (hover: none) { .df-video__label { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .df-video__btn img, .df-video__play, .df-video__label { transition: none; }
}

/* ── Кнопки. Один акцент: залитая золотом primary и контурная ghost. ── */
.df-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;
  padding: 0 26px;
  border: 1px solid var(--accent);
  border-radius: var(--r-btn);
  background: var(--accent);
  color: var(--accent-fg);
  font: 500 13px/1 var(--font-text);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: opacity var(--dur) var(--ease);
}
.df-btn:hover { opacity: 0.86; }
.df-btn:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
.df-btn--ghost { background: none; color: var(--accent); }
.df-btn--wide { width: 100%; }
.df-cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 22px; }

/* ═══ 5. ШАПКА ═══ */
.df-head {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgb(39 43 53 / 0.86);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.df-head__row {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-height: 62px;
}
/* ⛔ Знак бренда — НЕ ссылка (директива Алекса 20.08.2026). Раньше он вёл на «/»,
   то есть уводил премиум-посетителя на комфорт-витрину 72dom.life: чужой контур,
   чужие офферы, обратно человек не возвращается. Своей цели у клика нет — шапка
   липкая, «наверх» и так под рукой, — поэтому это просто знак, а не навигация. */
.df-logo { flex: none; }
.df-logo__mark {
  display: block;
  width: 122px;
  height: calc(122px / 7.63);
  background: var(--accent);
  -webkit-mask: var(--logo) no-repeat center / contain;
  mask: var(--logo) no-repeat center / contain;
}
.df-nav { display: none; margin-left: auto; gap: var(--s-6); }
.df-nav a {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-muted);
  text-decoration: none;
}
.df-nav a:hover { color: var(--accent); }
.df-head__phone {
  margin-left: auto;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--fg);
  text-decoration: none;
  white-space: nowrap;
}
.df-head__cta { min-height: 40px; padding: 0 18px; font-size: 11px; }
@media (min-width: 900px) {
  .df-nav { display: flex; }
  .df-head__phone { margin-left: 0; }
}
/* Единственная постоянная точка конверсии на телефоне — прятать её нельзя
   (урок [apart]: на 390 px нажимать было не на что до 58% глубины страницы). */
@media (max-width: 460px) {
  .df-head__phone { display: none; }
  .df-logo__mark { width: 96px; height: calc(96px / 7.63); }
}

/* ═══ 6. ОБЛОЖКА ═══
   Один титул. Прежняя сборка ставила два (клеймо капителью + SEO-строка капсом 64)
   и первый экран оставался без иерархии. */
.df-hero { position: relative; border-top: 0; }
.df-hero__media { position: relative; }
.df-hero__media img {
  width: 100%;
  height: min(84svh, 760px);
  object-fit: cover;
}
/* Скрим обложки. --scrim премиум-контура рассчитан на текст СЛЕВА и тёмный кадр;
   на мобиле фасад занимает весь кадр и подложка снизу обязана быть жёстче, иначе
   лид набирается белым по светлому зданию. Второй слой — локальный, под текстовым
   блоком. Меряем пиксели под строкой (df_measure.mjs), а не «на глаз». */
.df-hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgb(0 0 0 / 0) 38%, rgb(0 0 0 / 0.55) 72%, rgb(0 0 0 / 0.82) 100%),
    var(--scrim);
}
.df-hero__text {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: clamp(28px, 5vw, 64px);
  z-index: 2;
}
.df-hero__text .df-lead { max-width: 44ch; color: var(--fg); }
/* ⚠ На мобиле текст обложки НЕ лежит на кадре, а стоит под ним.
   Замер пикселей (df_measure.mjs, проверка «контраст текста обложки»): на 390 px
   фасад занимает весь кадр, и даже тройной скрим давал кикеру 2,55:1 вместо 4,5,
   а титулу 2,93 вместо 3. Гасить кадр до нужной черноты — значит убить сам кадр.
   Раскладка «кадр, под ним кикер → титул → лид» — это и есть регистр листа сверки,
   так что решение не компромисс, а канон. Цена — ~300 px длины при запасе в 4 000. */
@media (max-width: 640px) {
  .df-hero__media img { height: min(56svh, 430px); }
  .df-hero__media::after { display: none; }
  .df-hero__text {
    position: static;
    transform: none;
    padding-block: 26px 0;
  }
  .df-hero__text .df-lead { color: var(--fg-muted); }
}

/* ═══ 7. ЯКОРНАЯ СТРОКА ═══
   Нав пропадает ниже 900 px — на телефоне это единственное оглавление. */
.df-anchors {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 22px;
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}
.df-anchors a {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid rgb(227 193 156 / 0.35);
  padding-bottom: 3px;
}
.df-anchors a:hover { border-bottom-color: var(--accent); }

/* ═══ 8. СПЕКИ ПРОЕКТА ═══ */
/* Ячеек ровно 8. auto-fit раскладывал их 6+2 и оставлял пустую половину ряда,
   залитую цветом линии — читалось как недоделка. Сетка кратна восьми: 4×2 и 2×4. */
.df-specs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  margin: 22px 0 0;
  background: var(--line);
  border: 1px solid var(--line);
}
.df-specs__cell { background: var(--bg); padding: 14px 16px; }
@media (min-width: 760px) { .df-specs { grid-template-columns: repeat(4, 1fr); } }
.df-specs__k {
  margin: 0 0 4px;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.df-specs__v { margin: 0; font-size: 14px; line-height: 1.4; color: var(--fg); }

/* ═══ 9. КАТАЛОГ ═══
   Правило 7: карточка минимальна — медиа, титул, мета, цена. Переключатель видов —
   единственный элемент управления. Пилюли-признаки сняты осознанно: 117 одинаковых
   тегов 11-м кеглем в 39 карточках — это стена шрифта, а не каталог. */
.df-lots {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(252px, 1fr));
  margin: 22px 0 0;
}
.df-lot {
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  overflow: hidden;
}
/* Все четыре вида лежат на белой «бумаге» и вписываются целиком (contain).
   cover тут нельзя: 3D-вид почти квадратный, в рамке 4:3 он терял 20 % высоты,
   а вертикальный чертёж «План» (892×1390) обрезался до неузнаваемости.
   Картинку кладём абсолютом: в обычном потоке aspect-ratio УСТУПАЕТ контенту, если
   изображение выше рамки, и карточки разъезжаются по высоте (замер 20.08: разброс
   до 90 px). Рамка держит высоту, картинка вписывается внутрь. */
.df-lot__media {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--paper);
  overflow: hidden;
}
.df-lot__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 10px;
  transition: opacity var(--dur-fast) var(--ease);
}
.df-lot__body { padding: 12px 14px 14px; }
.df-lot__title { margin: 0 0 4px; font-size: 15px; font-weight: 500; line-height: 1.3; }
.df-lot__meta { margin: 0 0 8px; font-size: 12px; color: var(--fg-muted); }
.df-lot__price {
  margin: 0;
  font-size: 15px;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
/* Чипы обязаны помещаться в ОДНУ строку: при колонке уже ~255 px «Этаж»
   переносится и ломает высоту карточки (замер на 1280 и 390 — по одной строке). */
.df-sw { display: flex; gap: 6px; margin: 0 0 10px; flex-wrap: wrap; }
.df-sw__b {
  padding: 4px 9px;
  border-radius: var(--r-chip);
  border: 1px solid var(--line);
  background: none;
  color: var(--fg-muted);
  font: inherit;
  font-size: 11px;
  letter-spacing: 0.04em;
  white-space: nowrap;
  cursor: pointer;
}
.df-sw__b[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.df-sw__b:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

/* Тумблер «показать все» — без JS, на :checked. */
/* Зазор от сетки — по ритму блоков секции (22 px), а не 18: тумблер стоит
   под карточками с рамкой, и меньший отступ читается как касание. */
.df-more { margin: 22px 0 0; }
.df-more__box { position: absolute; opacity: 0; pointer-events: none; }
.df-more__label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 22px;
  border: 1px solid var(--accent);
  border-radius: var(--r-btn);
  color: var(--accent);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
}
.df-lot--rest { display: none; }
.df-more__box:checked ~ .df-lots .df-lot--rest { display: block; }
.df-more__box:checked ~ .df-more .df-more__label { display: none; }
.df-more__box:focus-visible ~ .df-more .df-more__label {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* ═══ 10. ВОПРОСЫ ═══ */
.df-faq { margin: 22px 0 0; border-top: 1px solid var(--line); }
.df-faq details { border-bottom: 1px solid var(--line); }
.df-faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 56px;
  padding: 14px 0;
  font-size: 15px;
  line-height: 1.4;
  cursor: pointer;
  list-style: none;
}
.df-faq summary::-webkit-details-marker { display: none; }
.df-faq summary::after {
  content: "+";
  flex: none;
  font-size: 20px;
  line-height: 1;
  color: var(--accent);
}
.df-faq details[open] summary::after { content: "–"; }
.df-faq p { margin: 0 0 16px; color: var(--fg-muted); }

/* ═══ 11. ФОРМА ═══ */
.df-form { margin: 22px 0 0; max-width: 460px; }
.df-form__field { margin: 0 0 12px; }
/* Класс, а не `.df-form input`: селектор с потомком, назначающий шрифт, запрещён
   гардом [df] целиком — именно такой формы правило (`.ap-body h1,h2,h3`) молча
   перебило `.ap-display` и обнулило дисплейный шрифт всей страницы 20.08.
   Исключений не заводим: одно исключение — и запрет перестаёт работать. */
.df-form__input {
  width: 100%;
  min-height: 52px;
  padding: 0 18px;
  border: 1px solid var(--field-border);
  border-radius: var(--r-field);
  background: none;
  color: var(--fg);
  font: var(--t-body)/1.4 var(--font-text);
}
.df-form__input::placeholder { color: var(--fg-muted); }
.df-form__input:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.df-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.df-agree {
  display: flex;
  gap: 10px;
  margin: 0 0 16px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--fg-muted);
}
.df-agree input { margin-top: 3px; accent-color: var(--accent); }
.df-agree a { color: var(--accent); }
.df-error { margin: 0 0 12px; color: var(--error); font-size: 13px; }
.df-ok { margin: 0; color: var(--accent); font-size: 15px; line-height: 1.6; }

/* ═══ 12. ПОДВАЛ ═══ */
.df-foot {
  padding: 34px 0 60px;
  border-top: 1px solid var(--line);
  color: var(--fg-muted);
  font-size: 12px;
  line-height: 1.6;
}
.df-foot__cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 18px 24px;
  margin: 0 0 26px;
  padding: 0;
  list-style: none;
}
.df-foot__cols a {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-muted);
  text-decoration: none;
}
.df-foot__cols a:hover { color: var(--accent); }
.df-foot__notes { margin: 0; padding: 0; list-style: none; }
.df-foot__notes li { margin: 0 0 8px; max-width: 92ch; }
.df-foot__notes a { color: var(--fg-muted); }

/* ═══ 13. ЛАЙТБОКС ═══ */
.df-box {
  width: min(1180px, 96vw);
  max-height: 92vh;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: #000;
  color: var(--fg);
}
.df-box::backdrop { background: rgb(0 0 0 / 0.8); }
.df-box img, .df-box video { width: 100%; max-height: 92vh; object-fit: contain; }
.df-box__close {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 3;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: rgb(0 0 0 / 0.5);
  color: var(--fg);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.df-box__close:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

/* ═══ 14. ПОЛОСА ТЕСТ-КОНТУРА ═══
   Обязана быть на тестовой странице и обязана отсутствовать на боевой. */
.df-testflag {
  padding: 8px 16px;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
}

.df-skip {
  position: absolute;
  left: -9999px;
}
.df-skip:focus {
  left: 12px;
  top: 12px;
  z-index: 100;
  padding: 10px 16px;
  background: var(--accent);
  color: var(--accent-fg);
}
