/* ==========================================================================
   Раздел "Отели" - страницы раздела на дизайн-системе сайта (tp-*).
   Задача 27: карточка отеля /hotels/<slug>-<id> (подключается после
   product-page.css, как у карточки экскурсии). Задача 28: страница города
   /hotels/<country>/<city>. Задача 29: каталог /hotels и страна
   /hotels/<country> (последний раздел файла). Спецификация перевода -
   sdd/tp-migration-spec.md, разделы B.1-B.4, C.2 и G (референсы владельца).

   Только токены tokens.css: цвета, отступы, радиусы, кегли. Свои значения,
   которых нет в спецификации перевода, - только у размеров: затемнение
   снимка под счетчиком галереи (G.2), прозрачность лида шапки на снимке
   (C.2), ширины колонок сетки карточки, высота места под карту города
   320/420 px (блок 7 - решение под контейнер карты, не число спецификации),
   ширина колонки таблицы погоды 25% (блок 9 - четыре колонки поровну на
   всю ширину, задача 31, см. докстринг правила), сетка 2x2 ровно на
   четырех фактах блока 8 (правка ревью, докстринг правила), отступ
   прокрутки карты над полосой цены (блок 12, задача 31), ширина подписи
   под фото поверх кадра на узком экране - без 8rem под счетчик (блок 3,
   правки ревью задачи 31). Квадрат оценки 36 px - тоже свое значение, но
   лежит в tokens.css (общая карточка отеля, C.2). Задача 35: мера текста
   блока 7 72ch - число ТЗ (п. 21.4; колонку меток 11rem задача 36 сняла);
   высота колонки списка блока 12 с 992 px - высота его карты (420 px); поля
   фильтров 44 px и чип 26 px - как у полей и мелких чипов tokens.css.
   Задача 36: строка H3 группы блока 8 1.2 (прежняя строка заголовков
   Bootstrap) - переменной --tp-hotel-group-lh, общей с поправкой зазора
   блока 7.
   Движение - только вне prefers-reduced-motion.
   Разметка карточки записана компактно (решение 11: вес не урезает
   содержание, но разметка без раздувания): где классов на элементах нет,
   оформление задают селекторы внутри одного класса блока.
   ========================================================================== */

/* Длинные названия отелей, номеров и объектов не распирают колонку -
   замена правила .tp-product, которое здесь не используется (A.3). Из
   значений dd - только пары блока 8: там текст партнера, и слово без
   пробелов длиннее колонки бывает; значения фактов (паспорт, сравнение)
   переносит правило tokens.css, адрес - по пробелам (ниже). */
.tp-hotel h1,
.tp-hotel h2,
.tp-hotel h3,
.tp-hotel p,
.tp-hotel li,
.tp-hotel-pairs dd {
    overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   Сетка карточки. Порядок в разметке - порядок ТЗ (шапка, галерея, блок 4,
   блоки 5-15); колонки делает сетка, а не перестановка. До 992 px одна
   колонка в том же порядке, с 992 px блок 4 справа на всю высоту сетки.
   -------------------------------------------------------------------------- */
.tp-hotel__layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "head" "gallery" "aside" "main";
}
.tp-hotel__head { grid-area: head; }
.tp-hotel__aside { grid-area: aside; margin-bottom: var(--tp-6); }
.tp-hotel__main { grid-area: main; min-width: 0; }

@media (min-width: 992px) {
    .tp-hotel__layout {
        grid-template-columns: minmax(0, 1fr) 340px;
        grid-template-rows: auto auto 1fr;
        grid-template-areas: "head aside" "gallery aside" "main aside";
        column-gap: var(--tp-6);
    }
    /* Правая колонка тянется на высоту всех трех строк: внутри нее
       карточка цены прилипает (tp-aside) до конца блока 15 и не наезжает
       на похожие отели - они под сеткой. */
    .tp-hotel__aside { margin-bottom: 0; }
}
@media (min-width: 1200px) {
    .tp-hotel__layout { grid-template-columns: minmax(0, 1fr) 380px; }
}
/* Блока 4 нет вовсе - одна колонка без пустой правой. Кнопка ухода
   "Посмотреть цены" есть у каждого отеля источника level.travel (задача
   31), поэтому так бывает только у отеля другого источника без цены
   (Hotel.go_url, правки ревью). */
.tp-hotel__layout--solo {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "head" "gallery" "main";
}

/* --------------------------------------------------------------------------
   Шапка (блок 2, G.1): чипы оценки, H1, лид, адрес, чипы заезда и выезда.
   -------------------------------------------------------------------------- */
.tp-hotel__head { margin-bottom: var(--tp-2); }
.tp-hotel__head h1 { padding-top: var(--tp-2); padding-bottom: 0; }
.tp-hotel__head .tp-chips {
    margin: 0 0 var(--tp-4);
    padding: 0;
    list-style: none;
}
/* Строка адреса - текстом в одну строку потока, иконка - в поле слева
   (перенесенная строка идет под текстом, а не под иконкой). Задача 33:
   строка начинается с района ("Сиркеджи, " перед адресом), и во flex-строке
   ссылка "Показать на карте" стояла отдельным элементом - после длинного
   адреса она уходила на свою строку, и сумма блока 4 опускалась ниже
   первого экрана 360x740 (замер приемки задачи 33). В потоке ссылка
   встает в конец последней строки адреса, если помещается, и не рвется.
   Зазор перед ней - пробел разметки, без своего отступа (правки ревью:
   отступ и пробел давали двойной зазор). */
.tp-hotel__addr {
    position: relative;
    margin: calc(-1 * var(--tp-2)) 0 var(--tp-4);
    padding-left: calc(1em + var(--tp-2));
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-sm);
}
.tp-hotel__addr > .bi {
    position: absolute;
    top: 0;
    left: 0;
    line-height: inherit;
}
.tp-hotel__addr a {
    color: var(--tp-link);
    white-space: nowrap;
}

/* Звезды категории (.tp-hotel-stars) и оценка (.tp-hotel-score с
   модификаторами) - в tokens.css с задачи 28: их показывает и карточка
   отеля в виджете на странице города экскурсий, где этот файл не
   грузится. */
/* Ширина иконки задана заранее: шрифт Bootstrap Icons грузится после
   первой отрисовки, и глиф другой ширины сдвигал чипы шапки и плашку
   галереи (CLS 0,0004 на 390 px). */
.tp-hotel__head .bi,
.tp-gallery__count .bi,
.tp-gallery__more .bi,
.tp-hotel-summary .bi {
    display: inline-block;
    width: 1em;
}

/* --------------------------------------------------------------------------
   Галерея (блок 3, G.2): сетка tp-gallery из product-page.css, зазор 4 px,
   скругление только у внешних углов (overflow у контейнера). Галерея и
   подпись "Источник фото" под ней (задача 31) - одна ячейка сетки
   карточки; подпись - кнопка tp-gallery__credit (products.css) с высотой
   24 px и отступом сверху, место под нее есть в серверной разметке, сдвига
   нет. Уже 768 px подпись лежит поверх кадра (ниже, правки ревью).
   -------------------------------------------------------------------------- */
.tp-hotel__gallery {
    grid-area: gallery;
    position: relative;
    min-width: 0;
    margin-bottom: var(--tp-5);
}
#hotel-gallery {
    gap: var(--tp-1);
}
/* С 992 до 1199 px колонка узкая, и 8:3 давало галерее 219 px высоты. */
@media (min-width: 992px) and (max-width: 1199.98px) {
    #hotel-gallery { aspect-ratio: 2 / 1; }
}
/* Два-четыре снимка: сетка без пустых ячеек. */
@media (min-width: 768px) {
    .tp-gallery--1 { grid-template-columns: 1fr; grid-template-rows: 1fr; }
    .tp-gallery--1 .tp-gallery__item:first-child { grid-row: auto; }
    .tp-gallery--2,
    .tp-gallery--3 { grid-template-columns: 2fr 1fr; }
    .tp-gallery--2 .tp-gallery__item { grid-row: span 2; }
    .tp-gallery--4 .tp-gallery__item:nth-child(4) { grid-column: span 2; }
}
/* Счетчик с 768 px: затемнение последней плитки и "Все N фото" по центру
   (перекрывает пилюлю tp-gallery__more страницы товара). */
#hotel-gallery .tp-gallery__more {
    inset: 0;
    justify-content: center;
    min-height: 0;
    padding: var(--tp-2);
    border-radius: 0;
    background: rgba(0, 0, 0, .45);
    color: var(--tp-surface);
    font-size: var(--tp-fs-md);
    font-weight: 600;
    box-shadow: none;
}
/* Счетчик до 768 px: плашка "N фото" в углу единственного кадра. */
.tp-gallery__count {
    position: absolute;
    right: var(--tp-3);
    bottom: var(--tp-3);
    display: inline-flex;
    align-items: center;
    gap: var(--tp-1);
    padding: var(--tp-1) var(--tp-3);
    border-radius: var(--tp-r-pill);
    background: rgba(22, 35, 43, .72);
    color: var(--tp-surface);
    font-size: var(--tp-fs-sm);
    font-weight: 500;
}
@media (max-width: 767.98px) {
    #hotel-gallery .tp-gallery__more { display: none; }
}
@media (min-width: 768px) {
    .tp-gallery__count { display: none; }
}
/* Уже 768 px (правки ревью задачи 31, решение владельца 2: верх суммы
   блока 4 на первом экране 390x844 с запасом) подпись "Источник фото" не
   добавляет строку под кадром (8 + 24 px), а лежит поверх нижнего левого
   угла единственного кадра плашкой, как счетчик "N фото" справа, - белым на
   том же затемнении. Ширина - не заходя под счетчик (с отступами он
   занимает до 8rem при двузначном числе снимков): на 320 px подпись
   переносится в две строки. Зазор ячейки галереи до блока 4 - 16 px. */
@media (max-width: 767.98px) {
    .tp-hotel__gallery { margin-bottom: var(--tp-4); }
    .tp-hotel__credit {
        position: absolute;
        left: var(--tp-3);
        bottom: var(--tp-3);
        max-width: calc(100% - 8rem);
    }
    .tp-hotel__credit .tp-gallery__credit {
        min-height: 0;
        margin-top: 0;
        padding: var(--tp-1) var(--tp-3);
        border-radius: var(--tp-r-pill);
        background: rgba(22, 35, 43, .72);
        color: var(--tp-surface);
        line-height: 1.5;
        text-align: left;
    }
}

/* --------------------------------------------------------------------------
   Блок 4: карточка бронирования в правой колонке.
   -------------------------------------------------------------------------- */
.tp-hotel__aside .hotel-go-form { margin-top: var(--tp-3); }
/* Кнопку проверки открывает скрипт (hidden до него, контракт задачи 25),
   и подпись "Не оферта" под ней съезжала вниз уже после отрисовки (CLS
   0,0008 на 1440 px). Место под кнопку занято сразу, сама она невидима и
   вне порядка Tab до скрипта; без JavaScript правило в noscript шапки
   страницы (hotel.html) убирает и место. Селектор - по классу: единственная
   скрытая кнопка колонки - кнопка проверки, а data-hotel-price в шапке
   страницы стоять не должен (первое вхождение - корень блока 4, D.1). */
.tp-hotel__aside .btn[hidden] {
    display: block !important;
    visibility: hidden;
}
.tp-hotel__aside [data-hotel-price-lines]:empty + .hotel-go-form,
.tp-hotel__aside .hotel-go-form:first-child { margin-top: 0; }
/* Задача 33 (п. 5.2, 5.3 ТЗ новых данных): под строкой суммы и профиля -
   серым "около N руб. за ночь", за ней чип "Мгновенное подтверждение"
   (иконка янтарным - цвет предупреждений и акцентов сайта, не меньше 5:1
   на фоне чипа). Оба -
   в обертке строк цены, ответ кнопки проверки снимает их вместе с ней.
   position: relative - точка отсчета скрытой подписи чипа (как у карточки
   номера, задача 26, C-01). Ширина иконки задана до шрифта, как у иконок
   первого экрана. */
.tp-hotel-night {
    margin: 0;
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-sm);
    font-variant-numeric: tabular-nums;
}
.tp-hotel-instant { margin: var(--tp-2) 0 0; }
.tp-hotel-instant .tp-chip { position: relative; }
.tp-hotel-instant .bi {
    display: inline-block;
    width: 1em;
    color: var(--tp-amber-ink);
}
/* Ответ кнопки проверки (задача 31, правка владельца 3). Разметку строит
   hotel/js/price_refresh.js классами tp-hotel-offers__*: мелкий
   подзаголовок с профилем ответа, строки номеров - название слева
   (переносится) и цена справа без переноса, моноширинными цифрами, 600;
   тонкая линия между строками, ритм по токенам. Самая низкая цена списка -
   цветом цены сайта (тот же зеленый, что у оценки и цен в выдаче).
   "Предложений нет", "Проверяем" и ошибка - плашкой, а не голым текстом.
   Первые пять номеров на виду, остальные - за кнопкой "Показать все
   номера: N", поэтому своя прокрутка у списка не нужна. */
[data-hotel-price-result]:not(:empty) { margin-top: var(--tp-3); }
.tp-hotel-offers__head {
    margin: 0 0 var(--tp-1);
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-xs);
}
.tp-hotel-offers__list {
    margin: 0;
    padding: 0;
    list-style: none;
}
.tp-hotel-offers__list > li {
    display: flex;
    align-items: baseline;
    gap: var(--tp-3);
    padding: var(--tp-2) 0;
}
.tp-hotel-offers__list > li + li { border-top: 1px solid var(--tp-border); }
.tp-hotel-offers__name {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--tp-ink);
}
.tp-hotel-offers__price {
    flex: none;
    margin-left: auto;
    color: var(--tp-ink);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}
.tp-hotel-offers__list > .is-best .tp-hotel-offers__price { color: var(--tp-price); }
.tp-hotel-offers__rest { color: var(--tp-ink-muted); }
.tp-hotel-offers__all { margin-top: var(--tp-2); }
.tp-hotel-offers__msg {
    margin: 0;
    padding: var(--tp-2) var(--tp-3);
    border-radius: var(--tp-r-sm);
    background: var(--tp-surface-tint);
    color: var(--tp-ink-muted);
}
.tp-hotel-offers__msg--error { color: var(--tp-amber-ink); }
/* Подпись "Цена по данным Level.Travel ... Не оферта. Сборы ..." отделена
   от ответа кнопки линией: без нее мелкий текст подписи сливался с
   последними строками номеров. */
.tp-hotel__aside [data-hotel-price-result]:not(:empty) + [data-hotel-price-note] {
    padding-top: var(--tp-3);
    border-top: 1px solid var(--tp-border);
}
/* Подпись - текстом в потоке (задача 33): ссылка "Что оплачивается на
   месте" встает в конец строки про сборы. Раскладка flex из
   product-page.css (подпись с иконкой у экскурсий) делала ссылку отдельной
   узкой колонкой справа от текста. */
.tp-hotel__aside [data-hotel-price-note] { display: block; }

/* Мобильная полоса цены (решение владельца 3): рисует
   hotel/js/price_refresh.js на основе tp-sticky страницы товара. Скрытая -
   вне порядка Tab и дерева доступности (visibility), с 992 px ее нет:
   там блок 4 прилипает сам. */
.tp-hotel-bar {
    visibility: hidden;
    transition: transform .2s ease, visibility 0s linear .2s;
}
.tp-hotel-bar.is-visible {
    visibility: visible;
    transition: transform .2s ease;
}
.tp-hotel-bar .btn { flex: none; }
@media (min-width: 992px) {
    .tp-hotel-bar { display: none; }
}
@media (prefers-reduced-motion: reduce) {
    .tp-hotel-bar,
    .tp-hotel-bar.is-visible { transition: none; }
}

/* --------------------------------------------------------------------------
   Блок 2в "Важная информация от отеля" (задача 33, п. 4 ТЗ новых данных):
   плашка во всю ширину основной колонки на токенах предупреждения сайта -
   фон и рамка Bootstrap warning subtle (bootstrap.min.css, общие для сайта),
   иконка и рамка между объявлениями - тем же цветом. Текст - основным
   цветом, срок - вторичным (4,98:1 на этом фоне, иконка янтарным - 5:1).
   Не липкая.
   -------------------------------------------------------------------------- */
.tp-hotel-notice {
    margin-bottom: var(--tp-6);
    padding: var(--tp-4);
    border: 1px solid var(--bs-warning-border-subtle);
    border-radius: var(--tp-r-lg);
    background: var(--bs-warning-bg-subtle);
    color: var(--tp-ink);
}
.tp-hotel-notice h2 {
    display: flex;
    align-items: baseline;
    gap: var(--tp-2);
    margin: 0 0 var(--tp-2);
    font-size: var(--tp-fs-lg);
    font-weight: 600;
}
.tp-hotel-notice h2 .bi {
    display: inline-block;
    flex: none;
    width: 1em;
    color: var(--tp-amber-ink);
}
.tp-hotel-notice h3 {
    margin: 0 0 var(--tp-1);
    font-size: var(--tp-fs-md);
    font-weight: 600;
}
.tp-hotel-notice p { margin: 0 0 var(--tp-2); }
.tp-hotel-notice > div > :last-child { margin-bottom: 0; }
.tp-hotel-notice > div + div {
    margin-top: var(--tp-3);
    padding-top: var(--tp-3);
    border-top: 1px solid var(--bs-warning-border-subtle);
}
.tp-hotel-notice__until {
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-sm);
}

/* --------------------------------------------------------------------------
   Сводка под галереей (блок 2а, G.3): до трех белых карточек одной высоты.
   На белом фоне страницы карточку отделяет волосяная граница токена.
   -------------------------------------------------------------------------- */
.tp-hotel-summary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--tp-3);
    margin-bottom: var(--tp-7);
}
.tp-hotel-summary > div {
    display: flex;
    flex-direction: column;
    gap: var(--tp-3);
    min-width: 0;
    padding: var(--tp-4);
    border-radius: var(--tp-r-lg);
    background: var(--tp-surface);
    box-shadow: var(--tp-ring), var(--tp-shadow-sm);
}
.tp-hotel-summary__title {
    margin: 0;
    color: var(--tp-ink);
    font-size: var(--tp-fs-md);
    font-weight: 600;
}
.tp-hotel-summary ul {
    display: grid;
    gap: var(--tp-2);
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--tp-fs-sm);
}
/* Строки удобств и объектов рядом; у списка оценок по категориям свои
   правила (.tp-hotel-cats, блок 14 ниже). */
.tp-hotel-summary ul:not(.tp-hotel-cats) > li {
    display: flex;
    align-items: baseline;
    gap: var(--tp-2);
}
/* Иконка удобства - отдельный элемент с aria-hidden (G.7): глиф из
   области частного использования шрифта в доступное имя пункта не идет. */
.tp-hotel-summary li > .bi { flex: none; color: var(--tp-ink-muted); }
.tp-hotel-summary ul:not(.tp-hotel-cats) > li > span {
    margin-left: auto;
    color: var(--tp-ink-muted);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.tp-hotel-summary > div > a {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    margin-top: auto;
    color: var(--tp-link);
    font-size: var(--tp-fs-sm);
}
/* "Читать отзывы" - сразу под оценкой и категориями, а не у низа карточки:
   иначе при высокой соседней карточке ссылку отрывало от оценки пустым
   полем (правка ревью M1). У удобств и объектов ссылки держатся низа -
   в ряду они стоят на одной линии. */
.tp-hotel-summary > div > .tp-hotel-summary__rating ~ a { margin-top: 0; }
/* Оценка крупно, рядом словесная оценка и число отзывов (и в сводке, и в
   шапке блока 14). */
.tp-hotel-summary__rating {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    column-gap: var(--tp-3);
    margin: 0;
}
.tp-hotel-summary__rating .tp-hotel-score { grid-row: span 2; }
.tp-hotel-summary__rating > span:not(.tp-hotel-score) {
    color: var(--tp-ink);
    font-weight: 600;
}
.tp-hotel-summary__rating > span:last-child:not(.tp-hotel-score) {
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-sm);
    font-weight: 400;
}

/* --------------------------------------------------------------------------
   Блок 5: паспорт. Адрес во всю ширину сетки фактов на всех ширинах (правка
   ревью I3: в половине ширины на 390 px он рвался по слогам). Правило
   четности tokens.css широкую ячейку не учитывает, поэтому у сетки
   паспорта растяжение ячеек задает представление классами (_facts_layout):
   --end2 - во весь ряд до 992 px (два факта в ряд), --end3 и --half3 - во
   весь ряд и в половину ряда с 992 px (три в ряд). Правила tokens.css для
   экскурсий не меняются: у них класса tp-hotel-facts нет.
   -------------------------------------------------------------------------- */
.tp-hotel .tp-hotel-facts > .tp-fact { grid-column: span 1; }
@media (min-width: 576px) {
    .tp-hotel .tp-hotel-facts > .tp-fact { grid-column: span 2; }
}
@media (max-width: 991.98px) {
    .tp-hotel .tp-hotel-facts > .tp-fact--end2 { grid-column: 1 / -1; }
}
@media (min-width: 992px) {
    .tp-hotel .tp-hotel-facts > .tp-fact--end3 { grid-column: 1 / -1; }
    .tp-hotel .tp-hotel-facts > .tp-fact--half3 { grid-column: span 3; }
}
.tp-hotel .tp-hotel-facts > .tp-fact--wide { grid-column: 1 / -1; }
/* Адрес переносится только по пробелам: без слогов (hyphens: auto у
   tokens.css до 576 px) и без разрыва слова. Самое длинное слово адреса в
   базе разработки - 26 знаков, в ширину ячейки на 320 px оно помещается. */
.tp-hotel .tp-fact--wide dd {
    hyphens: manual;
    overflow-wrap: normal;
}
/* Задача 33: та же сетка у блока 8а "Условия проживания" (п. 7 ТЗ новых
   данных). Оплата на месте - список строк партнера обычным весом внутри
   значения (строка на пункт, названия длинные), подпись под ним - мелким
   серым; ссылка "Проверить в реестре" паспорта (п. 6) - обычной ссылкой
   сайта после значения. */
.tp-hotel-facts dd ul {
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--tp-fs-md);
    font-weight: 400;
}
.tp-hotel-facts dd li + li { margin-top: var(--tp-1); }
.tp-hotel-facts dd .tp-fact-note { margin: var(--tp-2) 0 0; }
.tp-hotel-facts dd a {
    color: var(--tp-link);
    font-size: var(--tp-fs-sm);
    font-weight: 400;
    white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Блок 14а: текст эксперта и компонент свертки (задача 33, п. 13 ТЗ новых
   данных; разметка - hotel_fold.html; до задачи 35 - и блок 7). Число строк
   оценивает card_data.fold; модификатор говорит, до какой ширины свертка
   нужна: --sm - уже 768 px, --md - уже 1200 px, --all - везде. Все - CSS до
   первой отрисовки, без скрипта: сдвига макета нет.
   Правки ревью задачи 33:
   - срез - -webkit-line-clamp по восьмой строке текста: отступы абзацев
     строками не считаются, и под срезом нет пустого хвоста (срез высотой
     8lh кончался на отступе абзаца - пустота около двух строк на 390 px);
   - срез и кнопка - только в браузере с :has (@supports): раскрывает
     открытый details именно :has, и без него текст шел бы срезанным
     навсегда. Вне @supports текст целиком, кнопки нет;
   - tp-fold--fits ставит скрипт страницы (hotel.html), если текст на экране
     все же поместился: срез и кнопка снимаются.
   Открытый details (нативно, без скрипта) снимает срез; кнопка остается на
   месте с текстом "Свернуть". Абзацы отделены целой строкой (1lh; запас в
   em при строке 1.5 - style.css).
   -------------------------------------------------------------------------- */
.tp-hotel-text p {
    margin: 0 0 1.5em;
    margin: 0 0 1lh;
}
.tp-hotel-text p:last-child { margin-bottom: 0; }
.tp-fold { position: relative; }
.tp-fold__more { display: none; }
@supports selector(:has(*)) {
    .tp-fold > .tp-hotel-text {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 8;
        overflow: hidden;
    }
    .tp-fold > .tp-fold__more { display: block; }
    .tp-fold:has(> .tp-fold__more[open]) > .tp-hotel-text,
    .tp-fold--fits > .tp-hotel-text {
        display: block;
        -webkit-line-clamp: none;
        overflow: visible;
    }
    .tp-fold--fits > .tp-fold__more { display: none; }
    @media (min-width: 768px) {
        .tp-fold--sm > .tp-hotel-text { display: block; -webkit-line-clamp: none; overflow: visible; }
        .tp-fold--sm > .tp-fold__more { display: none; }
    }
    @media (min-width: 1200px) {
        .tp-fold--md > .tp-hotel-text { display: block; -webkit-line-clamp: none; overflow: visible; }
        .tp-fold--md > .tp-fold__more { display: none; }
    }
}
.tp-fold__more > summary {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    margin-top: var(--tp-2);
    color: var(--tp-link);
    cursor: pointer;
    list-style: none;
}
.tp-fold__more > summary::-webkit-details-marker { display: none; }
.tp-fold__more:not([open]) .tp-fold__close,
.tp-fold__more[open] .tp-fold__open { display: none; }

/* --------------------------------------------------------------------------
   Блок 7 "Об отеле" (задача 36, п. 22.1 ТЗ новых данных; разметка -
   hotel_about.html, разбор - card_data.about_rows и about_clean; до нее -
   задача 35, п. 21.4). Одна колонка, один левый край: каждая строка -
   абзац не шире 72ch, строка с меткой начинается меткой (span) серым, тем
   же кеглем, вес 600, с двоеточием. Колонки меток dl задачи 35 нет: она и
   свободные абзацы давали два левых края, длинная метка переносилась в
   узкой колонке. Ритм: между строками одной группы - один шаг шкалы
   (--tp-2), между группами (пустая строка поставщика, tp-hotel-gap) - два
   (--tp-4).
   Зазор под H2 - как у блока 8 (замер по коробке первой строки текста): у
   блока 8 первая строка - H3 группы (кегль --tp-hotel-group-fs, строка
   --tp-hotel-group-lh: 16 px и 1.2), у блока 7 - текст 15 px со строкой
   1.5. Коробка строки сидит в середине строки, поэтому у блока 7 она ниже
   на половину разницы высот строк без половины разницы кеглей (замер:
   коробка шрифта 16 px выше коробки 15 px около 1 px). margin-top
   контейнера снимает эту разницу и складывается с отступом
   tp-section__title (одна переменная секций остается); первое объявление
   - запас в em при строке 1.5 для браузера без единицы lh. Кегль и строка
   H3 группы - переменные, общие с правилом H3 блока 8 (правки ревью,
   мелкое 6): поменяется H3 - поправка пойдет за ним.
   Свертка - как у групп блока 8: строки за сверткой (tp-hotel-rest)
   скрыты, пока закрыт details "Читать полностью" под текстом (раскрыт -
   "Свернуть" на том же месте, фокус не теряется); скрывает CSS до первой
   отрисовки, раскрывает открытый details через :has - поэтому только
   внутри @supports, без :has все строки видны и кнопки нет; без JavaScript
   все строки видны и кнопки нет - правило в noscript шапки (hotel.html).
   position: relative - точка отсчета скрытой подписи кнопки.
   -------------------------------------------------------------------------- */
:root {
    --tp-hotel-group-fs: var(--tp-fs-md);
    --tp-hotel-group-lh: 1.2;
}
.tp-hotel-about {
    position: relative;
    margin-top: calc((var(--tp-hotel-group-lh) * var(--tp-hotel-group-fs) - 1.5em
                      - var(--tp-hotel-group-fs) + 1em) / 2);
    margin-top: calc((var(--tp-hotel-group-lh) * var(--tp-hotel-group-fs) - 1lh
                      - var(--tp-hotel-group-fs) + 1em) / 2);
}
.tp-hotel-about > p { max-width: 72ch; }
.tp-hotel-about > * { margin: 0; }
.tp-hotel-about > * + * { margin-top: var(--tp-2); }
.tp-hotel-about > .tp-hotel-gap { margin-top: var(--tp-4); }
.tp-hotel-about > p > span {
    color: var(--tp-ink-muted);
    font-weight: 600;
}
.tp-hotel-about > details { display: none; }
@supports selector(:has(*)) {
    .tp-hotel-about > details { display: block; }
    .tp-hotel-about:has(> details:not([open])) .tp-hotel-rest { display: none; }
}
.tp-hotel-about > details > summary {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    color: var(--tp-link);
    cursor: pointer;
    list-style: none;
}
.tp-hotel-about > details > summary::-webkit-details-marker { display: none; }
.tp-hotel-about > details:not([open]) .tp-hotel-less,
.tp-hotel-about > details[open] .tp-hotel-more { display: none; }

/* --------------------------------------------------------------------------
   Блок 8 "Удобства и услуги" (задача 33, п. 3.4 ТЗ новых данных): группы
   из features CSS-колонками - 1 до 768 px, 2 с 768, 3 с 1200 (основная
   колонка на 1200 px - 704 px, на 992-1199 - 564, там две); группа между
   колонками не рвется. У группы иконка и заголовок 16 px / 600, под ним
   строки 14 px, под строкой серым - подпись совпавшей пары content.facts.
   Ширина иконки задана до шрифта Bootstrap Icons. Ниже - оставшиеся пары.
   Задача 34 (раздел 20 ТЗ новых данных): строки партнера бывшего блока 9 -
   в тех же группах; метка оплаты - серым, мельче текста, в строку после
   него (tp-hotel-tag); группа длиннее шести строк - остальные строки
   (tp-hotel-rest) скрыты, пока закрыт details "Еще N" под списком (видом
   ссылки; раскрыт - "Свернуть", подпись на месте, фокус не теряется). Как
   у свертки блоков 7 и 14а: скрывает CSS до первой отрисовки, раскрывает
   открытый details через :has - поэтому только внутри @supports, без :has
   все строки видны и кнопки нет. Группа - точка отсчета скрытых подписей
   (.visually-hidden - position: absolute): без нее подпись уходит за край
   страницы на 320-390 px (задача 26, C-01). Длинное слово партнера
   ("конференц-зал/банкетный") переносится, а не распирает колонку.
   -------------------------------------------------------------------------- */
.tp-hotel-amenities {
    columns: 1;
    column-gap: var(--tp-5);
}
@media (min-width: 768px) {
    .tp-hotel-amenities { columns: 2; }
}
@media (min-width: 1200px) {
    .tp-hotel-amenities { columns: 3; }
}
.tp-hotel-amenities > div {
    break-inside: avoid;
    position: relative;
    padding-bottom: var(--tp-4);
}
.tp-hotel-amenities h3 {
    display: flex;
    align-items: baseline;
    gap: var(--tp-2);
    margin: 0 0 var(--tp-2);
    font-size: var(--tp-hotel-group-fs);
    font-weight: 600;
    line-height: var(--tp-hotel-group-lh);
}
.tp-hotel-amenities .bi,
.tp-hotel-airports .bi {
    display: inline-block;
    flex: none;
    width: 1em;
    color: var(--tp-ink-muted);
}
.tp-hotel-amenities ul {
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--tp-fs-sm);
}
.tp-hotel-amenities li { overflow-wrap: anywhere; }
.tp-hotel-amenities li + li { margin-top: var(--tp-1); }
.tp-hotel-amenities li > span:not([class]) {
    display: block;
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-xs);
}
.tp-hotel-amenities .tp-hotel-tag {
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-xs);
}
.tp-hotel-amenities details { display: none; }
@supports selector(:has(*)) {
    .tp-hotel-amenities details { display: block; }
    .tp-hotel-amenities > div:has(> details:not([open])) .tp-hotel-rest { display: none; }
}
.tp-hotel-amenities details > summary {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    margin-top: var(--tp-1);
    color: var(--tp-link);
    font-size: var(--tp-fs-sm);
    cursor: pointer;
    list-style: none;
}
.tp-hotel-amenities details > summary::-webkit-details-marker { display: none; }
.tp-hotel-amenities details:not([open]) .tp-hotel-less,
.tp-hotel-amenities details[open] .tp-hotel-more { display: none; }
.tp-hotel-amenities + .tp-hotel-pairs { margin-top: var(--tp-2); }

/* --------------------------------------------------------------------------
   Блок 8: оставшиеся пары. Подблоки территории блока 9
   (tp-hotel-sub, tp-hotel-lines, M-05) сняты задачей 34: блок 9 вошел в
   блок 8 группами.
   -------------------------------------------------------------------------- */
.tp-hotel-pairs {
    display: grid;
    gap: var(--tp-3) var(--tp-5);
    margin: 0;
}
@media (min-width: 768px) {
    .tp-hotel-pairs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.tp-hotel-pairs dt { color: var(--tp-ink); font-weight: 600; }
.tp-hotel-pairs dd {
    margin: 0;
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-sm);
}

/* --------------------------------------------------------------------------
   Блок 10: карточки номеров (решение владельца 5, G.6). Классов на
   элементах карточки нет - вес; оформление задают селекторы ниже.
   -------------------------------------------------------------------------- */
.tp-rooms {
    display: grid;
    gap: var(--tp-3);
    margin: 0;
    padding: 0;
    list-style: none;
}
@media (min-width: 768px) {
    .tp-rooms { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* position: relative - точка отсчета скрытой подписи .visually-hidden в
   summary "Описание" (position: absolute): без нее подпись уходит за край
   страницы на 320-390 px (задача 26, C-01). */
.tp-rooms > li {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--tp-2);
    min-width: 0;
    padding: var(--tp-4);
    border-radius: var(--tp-r-lg);
    background: var(--tp-surface);
    box-shadow: var(--tp-ring);
}
.tp-rooms h3 {
    margin: 0;
    font-size: var(--tp-fs-lg);
    font-weight: 600;
    line-height: 1.3;
}
/* Вместимость и площадь - чипы размера --sm. */
.tp-rooms > li > p {
    display: flex;
    flex-wrap: wrap;
    gap: var(--tp-2);
    margin: 0;
}
.tp-rooms > li > p > span {
    display: inline-flex;
    align-items: center;
    gap: var(--tp-1);
    min-height: 26px;
    padding: 0 var(--tp-2);
    border-radius: var(--tp-r-pill);
    background: var(--tp-surface-tint);
    font-size: var(--tp-fs-xs);
}
/* Иконки гостей по числу (не больше четырех): один элемент i с
   aria-hidden (G.6), глифы bi-person-fill рисует его псевдоэлемент - один
   тег вместо четырех. letter-spacing 1px - зазор между глифами, а не
   отступ блока: шкала токенов начинается с 4px (--tp-1), и с ним четыре
   фигурки растягиваются на ширину пятой. */
.tp-g1::before,
.tp-g2::before,
.tp-g3::before,
.tp-g4::before {
    color: var(--tp-ink-muted);
    font-family: bootstrap-icons;
    font-style: normal;
    font-weight: 400;
    letter-spacing: 1px;
}
.tp-g1::before { content: "\f4da"; }
.tp-g2::before { content: "\f4da\f4da"; }
.tp-g3::before { content: "\f4da\f4da\f4da"; }
.tp-g4::before { content: "\f4da\f4da\f4da\f4da"; }
/* Удобства строками с иконкой по коду группы: иконка - элемент i.bi с
   aria-hidden (G.6, G.7) в поле слева от текста; "и еще N" без иконки
   стоит по той же линии, что названия. */
.tp-rooms li ul {
    display: grid;
    gap: var(--tp-1);
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--tp-fs-sm);
}
.tp-rooms li li {
    position: relative;
    padding-left: 1.5em;
}
.tp-rooms li li > .bi {
    position: absolute;
    top: 0;
    left: 0;
    color: var(--tp-ink-muted);
}
.tp-rooms details { font-size: var(--tp-fs-sm); }
.tp-rooms details > summary {
    display: list-item;
    min-height: 24px;
    color: var(--tp-link);
    cursor: pointer;
}
.tp-rooms details p { margin: var(--tp-2) 0 0; }
/* "Все удобства номера: N" (задача 33, п. 10 ТЗ новых данных) - details
   видом ссылки, как "Описание" (правило выше); список всех удобств с
   иконками, с 768 px в две колонки. */
.tp-rooms .tp-room-all > ul { margin-top: var(--tp-2); }
@media (min-width: 768px) {
    .tp-rooms .tp-room-all > ul {
        display: block;
        columns: 2;
        column-gap: var(--tp-3);
    }
    .tp-rooms .tp-room-all li {
        break-inside: avoid;
        padding-bottom: var(--tp-1);
    }
}
/* "Показать все номера: N" - кнопка сайта; раскрыта - кнопки нет. */
.tp-rooms__all > summary {
    margin-top: var(--tp-3);
    list-style: none;
}
.tp-rooms__all > summary::-webkit-details-marker { display: none; }
.tp-rooms__all[open] > summary { display: none; }
.tp-rooms__all > .tp-rooms { margin-top: var(--tp-3); }

/* --------------------------------------------------------------------------
   Блок 12 "Расположение отеля ... и что рядом" (задача 35, п. 21.1 ТЗ новых
   данных, образец владельца; до нее - G.5 задачи 27 и подблоки задачи 33).
   Под H2 строкой - адрес, серым расстояние до центра и сайт отеля (части
   переносятся целиком). Панель - поверхность карточки (как карточка
   номера): поля фильтров в стиле полей сайта (tp-range__field tokens.css:
   44 px, рамка --tp-border-strong, фокус - рамка и контур ссылки), поле
   расстояния - текстовое с десятичной клавиатурой (крутилок нет), ошибка
   ввода - янтарной рамкой и подписью; ниже список и карта - уже 992 px карта над
   списком (order), список без своей прокрутки; с 992 px две колонки, список
   прокручивается внутри высотой карты (420 px); внизу "Показать еще" по
   центру. Строка объекта: чип категории (tp-chip мельче, фон
   --tp-surface-accent - заметен на белой панели), название 600, ниже серым
   иконка и расстояние; строки через тонкий разделитель. Название объекта с
   точкой на карте - кнопка (со скриптом, map.html), видом текста 600 с
   тонким подчеркиванием цвета рамки: нажимается, но не спорит со ссылками.
   Без JavaScript правило в noscript шапки (hotel.html) прячет поля, кнопку
   и карту, открывает все строки и снимает высоту колонки.
   -------------------------------------------------------------------------- */
.tp-hotel-where {
    display: flex;
    flex-wrap: wrap;
    gap: var(--tp-1) var(--tp-4);
    margin: 0 0 var(--tp-4);
}
.tp-hotel-where > .tp-hotel-where__center { color: var(--tp-ink-muted); }
.tp-hotel-where a { color: var(--tp-link); }
.tp-hotel-where .bi {
    display: inline-block;
    width: 1em;
    font-size: var(--tp-fs-xs);
}
.tp-hotel-nearby {
    position: relative;
    padding: var(--tp-4);
    border-radius: var(--tp-r-lg);
    background: var(--tp-surface);
    box-shadow: var(--tp-ring);
}
@media (min-width: 768px) {
    .tp-hotel-nearby { padding: var(--tp-5); }
}
.tp-hotel-nearby__filters {
    display: grid;
    gap: var(--tp-3) var(--tp-4);
    margin-bottom: var(--tp-4);
}
@media (min-width: 576px) {
    .tp-hotel-nearby__filters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.tp-hotel-nearby__filters label {
    display: block;
    margin-bottom: var(--tp-1);
    color: var(--tp-ink);
    font-size: var(--tp-fs-sm);
    font-weight: 600;
}
.tp-hotel-nearby__filters input {
    width: 100%;
    min-height: 44px;
    padding: 0 var(--tp-3);
    border: 1px solid var(--tp-border-strong);
    border-radius: var(--tp-r-sm);
    background: var(--tp-surface);
    color: var(--tp-ink);
    font-size: var(--tp-fs-md);
    -webkit-appearance: none;
    appearance: none;
}
.tp-hotel-nearby__filters [data-nearby-max] { font-variant-numeric: tabular-nums; }
.tp-hotel-nearby__filters input::placeholder {
    color: var(--tp-ink-muted);
    opacity: 1;
}
.tp-hotel-nearby__filters input:focus-visible {
    outline: 2px solid var(--tp-link);
    outline-offset: 1px;
    border-color: var(--tp-link);
}
/* Непонятный ввод в поле расстояния (правки ревью, мелкое 2): рамка и
   подпись янтарным текстом ошибок раздела (как ошибка ответа кнопки
   проверки цены, tp-hotel-offers__msg--error). */
.tp-hotel-nearby__filters input[aria-invalid="true"] { border-color: var(--tp-amber-ink); }
.tp-hotel-nearby__error {
    margin: var(--tp-1) 0 0;
    color: var(--tp-amber-ink);
    font-size: var(--tp-fs-sm);
}
.tp-hotel-nearby__body {
    display: grid;
    gap: var(--tp-4);
}
.tp-hotel-nearby__body--map > .tp-hotel-map { order: -1; }
@media (min-width: 992px) {
    .tp-hotel-nearby__body--map {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: var(--tp-5);
        align-items: start;
    }
    .tp-hotel-nearby__body--map > .tp-hotel-map { order: 0; }
    .tp-hotel-nearby__body--map > .tp-hotel-nearby__list {
        display: flex;
        flex-direction: column;
        max-height: 420px;
    }
    .tp-hotel-nearby__body--map .tp-hotel-objects {
        min-height: 0;
        overflow-y: auto;
    }
}
.tp-hotel-nearby__list { min-width: 0; }
.tp-hotel-nearby__list > h3 {
    margin: 0 0 var(--tp-1);
    font-size: var(--tp-fs-md);
    font-weight: 600;
}
.tp-hotel-objects {
    margin: 0;
    padding: 0;
    list-style: none;
}
.tp-hotel-objects > li {
    display: grid;
    justify-items: start;
    gap: var(--tp-1);
    padding: var(--tp-3) 0;
    border-bottom: 1px solid var(--tp-border);
}
.tp-hotel-objects .tp-chip {
    min-height: 26px;
    padding: 0 var(--tp-2);
    background: var(--tp-surface-accent);
    font-size: var(--tp-fs-xs);
}
.tp-hotel-objects [data-map-balloon-name] { font-weight: 600; }
.tp-hotel-objects > li > span:last-child {
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-sm);
    font-variant-numeric: tabular-nums;
}
.tp-hotel-objects [data-map-balloon] > button {
    padding: 0;
    border: 0;
    background: none;
    color: var(--tp-ink);
    font: inherit;
    font-weight: 600;
    text-align: left;
    text-decoration: underline;
    text-decoration-color: var(--tp-border-strong);
    text-underline-offset: .2em;
    cursor: pointer;
}
.tp-hotel-objects [data-map-balloon] > button:hover {
    color: var(--tp-link);
    text-decoration-color: currentColor;
}
.tp-hotel-objects [data-map-balloon] > button:focus-visible {
    outline: 2px solid var(--tp-link);
    outline-offset: 2px;
}
.tp-hotel-nearby__none { padding: var(--tp-3) 0; }
.tp-hotel-nearby__none p {
    margin: 0 0 var(--tp-2);
    color: var(--tp-ink-muted);
}
.tp-hotel-nearby__more {
    margin: var(--tp-4) 0 0;
    text-align: center;
}
/* "Показать еще" - сплошная кнопка (находка контролера по скриншотам):
   заливка выбранного пункта сортировки tokens.css (--tp-link, белый текст
   6.44:1); желтый акцент сайта остается у "Посмотреть цены". */
.tp-hotel-nearby__more .btn {
    --bs-btn-bg: var(--tp-link);
    --bs-btn-color: var(--tp-surface);
    --bs-btn-border-color: var(--tp-link);
    --bs-btn-hover-bg: var(--tp-ink);
    --bs-btn-hover-color: var(--tp-surface);
    --bs-btn-hover-border-color: var(--tp-ink);
    --bs-btn-active-bg: var(--tp-ink);
    --bs-btn-active-color: var(--tp-surface);
    --bs-btn-active-border-color: var(--tp-ink);
    min-width: 14rem;
    min-height: 44px;
    font-weight: 600;
}
.tp-hotel-nearby__more .btn:focus-visible {
    outline: 2px solid var(--tp-link);
    outline-offset: 2px;
}
/* Место под карту занято до загрузки API Яндекса: подгрузка ничего не
   сдвигает (CLS 0). */
.tp-hotel-map { min-width: 0; }
.tp-hotel-map #yMap {
    height: 280px;
    overflow: hidden;
    border-radius: var(--tp-r-lg);
    background: var(--tp-surface-tint);
}
@media (min-width: 992px) {
    .tp-hotel-map #yMap { height: 420px; }
}
/* Балун из списка прокручивает карту в видимую область (задача 31). Уже
   992 px внизу экрана стоит полоса цены (tp-hotel-bar, около 72 px), а
   балун маленькой карты Яндекс рисует панелью у ее нижнего края - отступ
   прокрутки держит карту над полосой. */
@media (max-width: 991.98px) {
    .tp-hotel-map #block-map { scroll-margin-bottom: calc(var(--tp-8) + var(--tp-4)); }
}
/* Аэропорты (задача 33, п. 12) - под панелью: H3 с иконкой (правило иконки
   - у блока 8 выше), строки - название и серым расстояние. */
.tp-hotel-airports {
    display: flex;
    align-items: baseline;
    gap: var(--tp-2);
    margin: 0 0 var(--tp-2);
    font-size: var(--tp-fs-lg);
    font-weight: 600;
}
.tp-hotel-nearby + .tp-hotel-airports { margin-top: var(--tp-5); }
.tp-hotel-near {
    margin: 0 0 var(--tp-3);
    padding: 0;
    list-style: none;
}
.tp-hotel-near li {
    padding: var(--tp-2) 0;
    border-bottom: 1px solid var(--tp-border);
}
.tp-hotel-near li > span:last-child {
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-sm);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.tp-hotel-nearby + .tp-fact-note { margin-top: var(--tp-3); }
/* Балун метки объекта (задача 31, правки ревью; с задачи 35 - без ссылок) -
   собирает maps.js из строки списка: название 600, под ним серым "541 м от
   отеля". Ширина не больше карты: на узкой карте Яндекс рисует балун
   панелью во всю ее ширину, длинное название переносится. */
.tp-map-balloon {
    max-width: 100%;
    color: var(--tp-ink);
    font-size: var(--tp-fs-sm);
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.tp-map-balloon p { margin: 0; }
.tp-map-balloon__title {
    font-size: var(--tp-fs-md);
    font-weight: 600;
}
.tp-map-balloon .tp-map-balloon__note {
    margin-top: var(--tp-1);
    color: var(--tp-ink-muted);
    font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   Блок 14: шапка оценки с категориями (G.4); оценка отзыва - выше,
   .tp-hotel-score--sm.
   -------------------------------------------------------------------------- */
/* Текст отзыва и ответ отеля - на всю ширину колонки (задача 31, правка
   владельца 2), без меры 68ch из tp-review в tokens.css: тот компонент
   общий и не меняется. */
.tp-hotel .tp-review p { max-width: none; }
/* Блок 14а "Мнение эксперта" (задача 33, п. 13 ТЗ новых данных): карточка
   на поверхности отзыва - белая, волосяная граница и радиус токенов, как
   карточка номера. Кружок с инициалами вместо аватара (аватар - на домене
   источника), рядом имя (600, правило tp-review__meta) и серым "эксперт
   Level.Travel" под ним; подзаголовок - h3 18 px / 600, как у блоков
   карточки. */
.tp-hotel-expert {
    padding: var(--tp-4);
    border-top: 0;
    border-radius: var(--tp-r-lg);
    background: var(--tp-surface);
    box-shadow: var(--tp-ring);
}
.tp-hotel-expert__head {
    display: flex;
    align-items: center;
    gap: var(--tp-3);
    margin-bottom: var(--tp-3);
}
.tp-hotel-expert__head .tp-review__meta {
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    min-width: 0;
    margin: 0;
}
.tp-hotel-expert__avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: var(--tp-7);
    height: var(--tp-7);
    border-radius: 50%;
    background: var(--tp-surface-accent);
    color: var(--tp-ink);
    font-weight: 600;
}
.tp-hotel-expert h3 {
    margin: 0 0 var(--tp-2);
    font-size: var(--tp-fs-lg);
    font-weight: 600;
}
.tp-hotel-rating {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--tp-4) var(--tp-6);
    margin: var(--tp-3) 0 var(--tp-2);
    padding: var(--tp-4);
    border-radius: var(--tp-r-lg);
    background: var(--tp-surface-tint);
}
/* Оценки по категориям (hotel_rating_cats.html) - одна разметка и одни
   правила для шапки блока 14 и карточки "Оценка гостей" сводки: строка -
   название, полоса, значение. */
.tp-hotel-cats {
    display: grid;
    gap: var(--tp-2) var(--tp-5);
    margin: 0;
    padding: 0;
    list-style: none;
}
.tp-hotel-cats li {
    display: grid;
    grid-template-columns: minmax(0, 7.5em) minmax(0, 1fr) 2.5em;
    align-items: center;
    gap: var(--tp-2);
    font-size: var(--tp-fs-sm);
}
/* Полоса: ширина заливки - переменная --v из разметки (данные). */
.tp-hotel-cats div {
    height: 8px;
    overflow: hidden;
    border-radius: var(--tp-r-pill);
    background: var(--tp-surface);
}
.tp-hotel-cats div::before {
    content: "";
    display: block;
    width: var(--v);
    height: 100%;
    background: var(--tp-price);
}
.tp-hotel-cats li > span:last-child {
    font-weight: 600;
    text-align: right;
    font-variant-numeric: tabular-nums;
}
/* В шапке блока 14 список рядом с оценкой, с 768 px - в две колонки. */
.tp-hotel-rating .tp-hotel-cats { flex: 1 1 320px; }
@media (min-width: 768px) {
    .tp-hotel-rating .tp-hotel-cats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* В сводке - компактно, одной колонкой под оценкой; карточка белая, и
   пустая часть полосы - цветом границы, а не белым. */
.tp-hotel-summary .tp-hotel-cats div { background: var(--tp-border); }

/* --------------------------------------------------------------------------
   Блок 15: шеврон аккордеона шрифтом Bootstrap Icons, без <i> в разметке
   (B.1). Пустой альтернативный текст после "/" убирает глиф из доступного
   имени вопроса: без него Chromium зачитывал символ области частного
   использования в конце каждого вопроса (найдено деревом доступности при
   приемке правок ревью). Первая строка content - для браузеров без
   альтернативного текста: вторую они отбрасывают целиком.
   -------------------------------------------------------------------------- */
.tp-faq__q::after {
    content: "\f282";
    content: "\f282" / "";
    flex: none;
    color: var(--tp-ink-muted);
    font-family: bootstrap-icons;
    transition: transform .2s;
}
.tp-faq__item[open] .tp-faq__q::after { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
    .tp-faq__q::after { transition: none; }
}

/* --------------------------------------------------------------------------
   Блок 17: узкие карточки экскурсий, две в ряд на телефоне.
   -------------------------------------------------------------------------- */
.tp-hotel-cross .product-card-media {
    display: block;
    height: 160px;
    background: var(--tp-surface-accent);
}
@media (min-width: 576px) {
    .tp-hotel-cross .product-card-media { height: 200px; }
}
@media (min-width: 992px) {
    .tp-hotel-cross .product-card-media { height: 277px; }
}
.tp-hotel-cross .card-body { padding: var(--tp-3); }

/* ==========================================================================
   Страницы выдачи раздела (задача 28 - город; страна и каталог - задача 29).
   Спецификация перевода, B.2 и C.2: по образцу города экскурсий - общий
   серый фон, шапка hero-section, полосы tp-band, компоненты листинга
   tokens.css. Карточка отеля в выдаче - в tokens.css (tp-hotel-card).
   ========================================================================== */

/* Шапка (hotel/_blocks/page_head.html): лид по центру мерой 62ch, на
   снимке - белым с прозрачностью, как у лида экскурсий. Размер и отступы
   H1 - hero-section (style.css, tokens.css). */
.tp-hotel-hero__lead {
    max-width: 62ch;
    margin: 0 auto;
    color: rgba(255, 255, 255, .88);
    font-size: var(--tp-fs-lg);
}
@media (max-width: 767.98px) {
    .tp-hotel-hero__lead { font-size: var(--tp-fs-md); }
}
/* Без снимка - темная шапка на токенах: самый светлый край (--tp-ink-muted)
   с белым - 5,23:1, AA и для лида. */
.tp-hotel-hero--noimage {
    background: linear-gradient(135deg, var(--tp-ink), var(--tp-ink-muted));
}
/* Светлый вариант (страна и каталог): белая полоса с линией снизу. Отступ
   до первой полосы задает сама полоса (tp-band, 24 / 32 px), нижнее поле
   hero-section (30 px) удвоило бы его. */
.tp-hotel-hero--plain {
    margin-bottom: 0;
    padding-block: var(--tp-6) var(--tp-5);
    border-bottom: 1px solid var(--tp-border);
    background: var(--tp-surface);
    color: var(--tp-ink);
    text-shadow: none;
}
.tp-hotel-hero--plain .tp-hotel-hero__lead { color: var(--tp-ink-muted); }

/* Панель выдачи (блок 4; задача 38 - фильтры, как у экскурсий): лента
   tp-sort одной строкой на любой ширине, не помещается - прокручивается
   внутри себя, страница по ширине не растет (tokens.css делает так только
   до 576 px, а с 576 до 767 px пять пунктов переносились бы на вторую
   строку). Панель прилипает к верху при прокрутке, как у экскурсий
   (board/js/filter.js). Отступ под панелью - у формы, а не у самой панели:
   распорка на месте прилипшей панели берет только ее высоту без полей, и
   отступ панели сдвигал бы выдачу на 16 px (CLS). */
.tp-hotel-filter { margin-bottom: var(--tp-4); }
.tp-hotel-panel .tp-sort {
    flex-wrap: nowrap;
    overflow-x: auto;
    min-width: 0;
    max-width: 100%;
}

/* Модальное окно фильтра (задача 38): компоненты - tokens.css (tp-filters,
   tp-check, tp-toggle, tp-tile, tp-range), здесь - только то, чего у
   экскурсий нет. Поле "Название отеля" - той же высоты и рамки, что поля
   цены (tp-range__field). */
.tp-hotel-filter__search {
    width: 100%;
    min-height: 44px;
    padding: 0 var(--tp-3);
    border: 1px solid var(--tp-border-strong);
    border-radius: var(--tp-r-sm);
    background: var(--tp-surface);
    color: var(--tp-ink);
    font-size: var(--tp-fs-md);
}
.tp-hotel-filter__search:focus-visible {
    outline: 2px solid var(--tp-link);
    outline-offset: 1px;
    border-color: var(--tp-link);
}
.tp-hotel-filter__search::placeholder { color: var(--tp-ink-muted); }

/* Подпись к названию "Название отеля" - label, а не legend: у одного поля
   нет группы. Отступ под ней - как у legend. */
label.tp-filters__legend { display: block; }

/* Пояснение под заголовком "Удобства": между удобствами И, а визуально
   группа неотличима от мультивыбора (14-filters-sorting, "Одно
   исключение"). */
.tp-hotel-filter__note {
    margin: calc(-1 * var(--tp-2)) 0 var(--tp-3);
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-sm);
    line-height: 1.35;
}

/* Звезда в чипе звездности - цветом звезд карточки (tp-hotel-stars);
   у отмеченного чипа (синий фон) - цветом текста. */
.tp-hotel-filter__star { color: var(--tp-cta); font-size: .8em; }
.tp-toggle:has(input:checked) .tp-hotel-filter__star { color: inherit; }

/* Подписи удобств не обрезаются многоточием, как у плиток транспорта
   экскурсий: "Круглосуточная стойка" в две колонки телефона переносится на
   вторую строку. */
.tp-hotel-filter .tp-tile__label {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    line-height: 1.25;
}

/* Страницы (tp-pager): на 320 px семь пунктов по 40 px с зазорами не
   входили в строку, и стрелка "вперед" уезжала на вторую. */
@media (max-width: 359.98px) {
    .tp-pager__list { gap: 0; }
}

/* Полосы раздела - без линии между соседними (задача 31, правка владельца
   4): полосы разделяет воздух (отступ tp-band сверху). Правило tokens.css
   с линией общее - его берут и страницы экскурсий, они не меняются; этот
   файл грузят только страницы раздела. */
.tp-band + .tp-band { border-top: 0; }

/* Последняя полоса страницы не прижимается к подвалу: у tp-band отступ
   снизу 8 px - он рассчитан на следующую полосу. */
main > section.tp-band:last-of-type { padding-bottom: var(--tp-7); }

/* "Показать еще" - ссылка с классом btn (контракт скрипта подгрузки):
   высота 48 px задана минимальной, текст по центру по вертикали. */
#hotels-load-more {
    display: inline-flex;
    align-items: center;
}
/* Задача 38 (filters_audit.mjs): у .btn без варианта Bootstrap тень фокуса
   не задана, и фокус с клавиатуры на кнопке не был виден. */
#hotels-load-more:focus-visible {
    outline: 2px solid var(--tp-link);
    outline-offset: 2px;
}

/* Карта города (блок 7): место под карту занято до загрузки API
   Яндекса - подгрузка ничего не сдвигает (CLS 0). */
.tp-hotel-citymap #yMap {
    height: 320px;
    overflow: hidden;
    border-radius: var(--tp-r-lg);
    background: var(--tp-surface-tint);
}
@media (min-width: 768px) {
    .tp-hotel-citymap #yMap { height: 420px; }
}

/* Факты города (блок 8): ровно на четырех фактах правило tokens.css для
   остатка ряда (nth-child(3n+1)) растягивает четвертый факт во всю ширину -
   три узкие ячейки сверху и одна широкая под ними смотрятся вразнобой.
   Сетка с 992 px становится 2x2 только для этого случая: :has() считает
   детей самого элемента. Правило живет в hotel.css, а не в tokens.css,
   поэтому экскурсии его не получают - у них тот же класс tp-facts-bento,
   но hotel.css на их страницах не подключен. */
@media (min-width: 992px) {
    .tp-facts-bento .tp-facts:has(> :nth-child(4):last-child) {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
    .tp-facts-bento .tp-facts:has(> :nth-child(4):last-child) > :last-child {
        grid-column: span 2;
    }
}

/* Погода (блок 9): таблица сайта tp-dirs, но ширины ее колонок - под
   таблицу направлений (там вторая колонка - счетчик в 2,75rem на
   телефоне, и "-12°" с полями в нее не входит). Здесь колонки по
   содержимому: на 320 px таблица входит в рамку без прокрутки, подписи
   чисел на телефоне переносятся. Задача 31, правка владельца 5: таблица на
   всю ширину контейнера (была мерой 68ch), четыре колонки поровну, числа
   вправо (tp-dirs__num). */
#pogoda .tp-dirs__table { table-layout: auto; }
#pogoda .tp-dirs__table th { width: 25%; }
@media (max-width: 575.98px) {
    #pogoda .tp-dirs__table > thead > tr > th { white-space: normal; }
}

/* ==========================================================================
   Каталог /hotels и страна /hotels/<country> (задача 29, спецификация
   перевода B.3, B.4, C.2): светлая шапка (--plain выше), полосы tp-band,
   плитки стран и городов на одной сетке.
   ========================================================================== */

/* Сетка плиток: 2 / 3 / 4 в ряд (с 768 и с 992 px), неполный ряд по
   центру. Ширина ячейки считается от числа колонок с учетом зазора, а не
   от содержимого - плитка страны и плитка города одной ширины на любой
   ширине экрана. Зазор 16 px - как у сетки городов экскурсий (g-3,
   excursion/_blocks/city/cities.html). */
.tp-hotel-tiles {
    --tp-hotel-cols: 2;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--tp-4);
    margin: 0;
    padding: 0;
    list-style: none;
}
.tp-hotel-tiles > li {
    flex: 0 0 calc((100% - (var(--tp-hotel-cols) - 1) * var(--tp-4)) / var(--tp-hotel-cols));
    min-width: 0;
}
@media (min-width: 768px) {
    .tp-hotel-tiles { --tp-hotel-cols: 3; }
}
@media (min-width: 992px) {
    .tp-hotel-tiles { --tp-hotel-cols: 4; }
}
.tp-hotel-tiles > li > a { display: block; }
/* Плитка города (city_card.html): карточка во всю ширину ячейки - правило
   a .scale из style.css делает ее inline-block по ширине снимка. Снимок
   4:3 при любом размере файла: место под него занято до загрузки (CLS
   каталога был 0,24 из-за снимков без размеров, C-02). */
.tp-hotel-tiles .card { display: block; }
.tp-hotel-tiles .card img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
}
/* Ширина глифа задана заранее: шрифт Bootstrap Icons грузится после
   первой отрисовки, и строка низа плитки не сдвигается. */
.tp-hotel-tiles .bi {
    display: inline-block;
    width: 1em;
}
/* Звезда средней звездности - в кегль строки, а не в размер стрелки
   (tokens.css дает иконкам низа плитки города 1,25rem). */
.tp-hotel-tiles .tp-city-card__foot span .bi { font-size: 1em; }
/* Правка ревью: контраст названия города на светлом снимке (Стамбул на
   светлом небе). Общий класс экскурсий card-img-background-top (style.css)
   к верху накладки светлеет до #ffffff44 - белое название там читается
   плохо; сам класс не трогаем (он общий для страниц экскурсий), для плиток
   раздела верх темнее своим правилом, плюс тень у названия сильнее. */
.tp-hotel-tiles .card-img-background-top {
    background: linear-gradient(0deg, #00000099 30%, #00000066 100%);
}
.tp-hotel-tiles .tp-city-card__head {
    text-shadow: 0 1px 4px rgba(0, 0, 0, .9);
}
/* Плитка страны - tp-country главной (tokens.css), в разделе 4:3: ряды
   стран и городов одной высоты. */
.tp-country--wide { aspect-ratio: 4 / 3; }
/* Без снимка: светлая плитка той же пропорции, текст темный - белый на
   светлом фоне и затемнение накладки без снимка не читались бы. */
.tp-hotel-tiles .tp-hotel-noimage {
    aspect-ratio: 4 / 3;
    background: var(--tp-surface-accent);
    color: var(--tp-ink);
}
.tp-hotel-noimage .card-img-overlay,
.tp-hotel-noimage .tp-country__label {
    background: none;
    text-shadow: none;
}
.tp-hotel-noimage .tp-country__count { color: var(--tp-ink-muted); }
/* M-09: увеличение снимка плитки города (style.css, .scale) - только вне
   reduce; у плитки страны правило reduce - при самом tp-country. */
@media (prefers-reduced-motion: reduce) {
    .tp-hotel-tiles .scale img { transition: none; }
    .tp-hotel-tiles .scale:hover img,
    .tp-hotel-tiles a:hover .scale img { transform: none; }
}

/* Алфавит страны (блок 5): буква - h3, города буквы колонками. */
.tp-hotel-abc h3 {
    margin: var(--tp-4) 0 var(--tp-2);
    font-size: var(--tp-fs-lg);
    font-weight: 600;
}
.tp-hotel-letters {
    columns: 2;
    column-gap: var(--tp-5);
    margin: 0;
    padding: 0;
    list-style: none;
}
.tp-hotel-letters li {
    break-inside: avoid;
    padding-bottom: var(--tp-1);
}
@media (min-width: 768px) {
    .tp-hotel-letters { columns: 3; }
}
@media (min-width: 992px) {
    .tp-hotel-letters { columns: 4; }
}

/* --------------------------------------------------------------------------
   Страница перехода к партнеру /hotels/order (задача 37, hotel/order.html):
   крутилка, заголовок, название отеля, пометка рекламы и форма "Перейти к
   ценам" без JavaScript - по центру, как у экскурсий (order/empty.html),
   но на токенах и без встроенных стилей. Все - серверная разметка, сдвига
   нет; ширина глифа пометки задана до загрузки шрифта иконок, как у
   плиток каталога.
   -------------------------------------------------------------------------- */
.tp-hotel-order {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--tp-3);
    padding-block: var(--tp-7);
    text-align: center;
}
.tp-hotel-order > * { margin: 0; }
.tp-hotel-order__spinner { color: var(--tp-price); }
.tp-hotel-order h1 {
    font-size: var(--tp-fs-2xl);
    font-weight: 600;
    overflow-wrap: anywhere;
}
.tp-hotel-order__name {
    color: var(--tp-ink-muted);
    font-size: var(--tp-fs-lg);
    overflow-wrap: anywhere;
}
.tp-hotel-order .bi {
    display: inline-block;
    width: 1em;
}
.tp-hotel-order__go {
    padding: var(--tp-3) var(--tp-5);
    border: 0;
    border-radius: var(--tp-r-pill);
    background: var(--tp-cta);
    color: var(--tp-cta-ink);
    font-weight: 600;
}
.tp-hotel-order__go:hover { background: var(--tp-cta-hover); }
.tp-hotel-order__go:focus-visible {
    outline: 3px solid var(--tp-link);
    outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
    .tp-hotel-order__spinner { animation: none; }
}
