МЕРА-лэндинг: меню на узком экране + спейсер под липкую панель
Навигация на узком экране была не «спрятана», а потеряна: «Точность», «Проверьте себя», «Статьи» и кнопка «Проверить квартиру» отдавали нулевой getBoundingClientRect (замер на проде, 800px), замены им не было ни в шапке, ни рядом. При высоте страницы 11 316px это значит, что до раздела можно было добраться только прокруткой. Довод прежнего докстринга (липкая шапка на 375px занимала 29% экрана, потому что четыре пункта переносились) оставлен в силе: пункты вернулись НЕ в строку шапки, а в родной `<details>`. Закрытое меню — одна кнопка, высота липкой шапки прежняя (замерено до и после: 53px), открытая панель лежит поверх страницы (position: absolute) и шапку не раздвигает. Клиентского острова не завелось — шапка осталась серверной. Пункты описаны одним списком NAV_ITEMS и рендерятся в оба места, чтобы пункт не мог снова появиться в одном и потеряться в другом. Спейсер под липкой нижней панелью был на 2,5px короче самой панели с 1000px, где у неё растёт паддинг и появляется строка текста (замер на проде: панель 70,5px против спейсера 68px) — нижние пиксели подвала уходили под панель ровно там, где спейсер и заводили. Второе значение стоит в том же @media-блоке, что и паддинг, который его меняет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
189ae63e53
commit
5d3a045e86
2 changed files with 193 additions and 27 deletions
|
|
@ -17,17 +17,41 @@
|
|||
* которой выпадашку когда-то читали, теперь стоит текстом в герое (плашка
|
||||
* «полное покрытие / данных меньше», HeroV3), то есть видна без клика.
|
||||
*
|
||||
* НА УЗКОМ ЭКРАНЕ ШАПКА — ОДНА СТРОКА: лого и «Для бизнеса». Навигация по
|
||||
* секциям и CTA скрыты в CSS (`landing-v3.module.css`, блок шапки) и
|
||||
* возвращаются с той ширины, на которой шапка макета замерена как
|
||||
* НА УЗКОМ ЭКРАНЕ ШАПКА — ОДНА СТРОКА: лого, «Для бизнеса» и кнопка меню.
|
||||
* Развёрнутый ряд навигации и CTA скрыты в CSS (`landing-v3.module.css`, блок
|
||||
* шапки) и возвращаются с той ширины, на которой шапка макета замерена как
|
||||
* однострочная, — а не с общего для файла шага в 720px, где она всё ещё
|
||||
* переносится. На 375px переносилось всё, и липкая шапка занимала 29%
|
||||
* экрана — эти проценты отъедались при любой прокрутке. Куда делись пункты: «Точность»
|
||||
* и «Проверьте себя» — секции этой же страницы, до них доезжают прокруткой
|
||||
* (ради чего на мобильном и скроллят); «Статьи» и «МЕРА для бизнеса» стоят
|
||||
* ссылками в подвале (`FooterV3`); «Продажа под ключ» и на десктопе не
|
||||
* ссылка, а заглушка. CTA не потерян: снизу висит `StickyCtaV3` — на экране
|
||||
* остаётся ОДИН призыв вместо двух одинаковых.
|
||||
* экрана — эти проценты отъедались при любой прокрутке.
|
||||
*
|
||||
* ПОЧЕМУ ПОЯВИЛОСЬ МЕНЮ (31.08.2026). Прежняя редакция этого докстринга
|
||||
* объясняла, куда делись пункты: «Точность» и «Проверьте себя» — секции этой
|
||||
* же страницы, до них доезжают прокруткой; «Статьи» и «МЕРА для бизнеса»
|
||||
* стоят ссылками в подвале (`FooterV3`). Довод про высоту шапки верен и
|
||||
* оставлен в силе — а вот замена не проверялась замером: страница на 800px
|
||||
* имеет высоту 11 316px, то есть «доехать прокруткой» до `#accuracy` и
|
||||
* `#game` значит листать половину лэндинга, а до подвала — весь. Навигации на
|
||||
* узком экране не было вовсе (все четыре пункта и кнопка отдавали
|
||||
* `getBoundingClientRect` нулевого размера). Поэтому пункты возвращены, но НЕ
|
||||
* в строку шапки: `<details>` — родная разворачивалка браузера, без
|
||||
* JavaScript и без клиентского острова. Закрытое меню — одна кнопка, то есть
|
||||
* высота липкой шапки прежняя (замер, ради которого её и сужали, сохранён);
|
||||
* открытая панель лежит ПОВЕРХ страницы (`position: absolute`), а не
|
||||
* раздвигает шапку. CTA не потерян и на закрытой шапке: снизу висит
|
||||
* `StickyCtaV3` — на экране по-прежнему ОДИН призыв вместо двух одинаковых,
|
||||
* второй виден только когда человек сам открыл меню.
|
||||
*
|
||||
* ПОТОЛОК РОДНОГО `<details>`: после клика по пункту меню остаётся открытым —
|
||||
* закрыть его без JavaScript браузер не умеет, а клик по якорю страницу не
|
||||
* перезагружает. Человек видит, куда уехал, и закрывает той же кнопкой.
|
||||
* Апгрейд, если этого окажется мало: клиентский остров с onClick на пунктах
|
||||
* (`setOpen(false)`) — но это первый клиентский компонент в шапке, поэтому
|
||||
* заводить его стоит по жалобе, а не заранее.
|
||||
*
|
||||
* ПУНКТЫ ОПИСАНЫ ОДИН РАЗ (`NAV_ITEMS`) и рендерятся дважды: в ряду для
|
||||
* широкого экрана и в панели меню. Список в коде, а не две копии разметки, —
|
||||
* чтобы пункт не мог появиться в одном месте и потеряться в другом (ровно
|
||||
* этим и был дефект: на широком экране навигация есть, на узком её нет).
|
||||
*
|
||||
* НЕАКТИВНЫЕ ПУНКТЫ (29.08.2026). Осталась одна «Продажа под ключ» —
|
||||
* партнёрской формы нет ни в коде, ни у владельца, и пункт остаётся видимым,
|
||||
|
|
@ -53,6 +77,47 @@ import { B2B_URL } from "../../content";
|
|||
import styles from "../../landing-v3.module.css";
|
||||
import { PublicLink } from "../PublicLink";
|
||||
|
||||
/**
|
||||
* Пункты навигации — один список на оба места вывода (ряд шапки и панель
|
||||
* меню). `href: null` — пункт-заглушка: раздела нет ни в коде, ни у владельца,
|
||||
* поэтому он видим, но некликабелен (см. блок «НЕАКТИВНЫЕ ПУНКТЫ» выше).
|
||||
* `route` — ключ `PUBLIC_ROUTES`: короткие адреса публичного домена
|
||||
* выразимы только через `PublicLink`, next/link подставил бы basePath.
|
||||
*/
|
||||
const NAV_ITEMS = [
|
||||
{ label: "Точность", href: "#accuracy" },
|
||||
{ label: "Проверьте себя", href: "#game" },
|
||||
{ label: "Продажа под ключ", href: null },
|
||||
{ label: "Статьи", route: "articles" },
|
||||
] as const;
|
||||
|
||||
function renderNavItem(item: (typeof NAV_ITEMS)[number]) {
|
||||
if ("route" in item) {
|
||||
return (
|
||||
<PublicLink key={item.label} to={item.route} className={styles.hdrNavLink}>
|
||||
{item.label}
|
||||
</PublicLink>
|
||||
);
|
||||
}
|
||||
if (item.href === null) {
|
||||
return (
|
||||
<span
|
||||
key={item.label}
|
||||
className={styles.hdrNavDisabled}
|
||||
aria-disabled="true"
|
||||
title="Раздел скоро откроется"
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a key={item.label} href={item.href} className={styles.hdrNavLink}>
|
||||
{item.label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function HeaderV3() {
|
||||
return (
|
||||
<header className={styles.hdrRoot}>
|
||||
|
|
@ -68,22 +133,7 @@ export function HeaderV3() {
|
|||
</div>
|
||||
|
||||
<nav className={styles.hdrNav} aria-label="Основная навигация">
|
||||
<a href="#accuracy" className={styles.hdrNavLink}>
|
||||
Точность
|
||||
</a>
|
||||
<a href="#game" className={styles.hdrNavLink}>
|
||||
Проверьте себя
|
||||
</a>
|
||||
<span
|
||||
className={styles.hdrNavDisabled}
|
||||
aria-disabled="true"
|
||||
title="Раздел скоро откроется"
|
||||
>
|
||||
Продажа под ключ
|
||||
</span>
|
||||
<PublicLink to="articles" className={styles.hdrNavLink}>
|
||||
Статьи
|
||||
</PublicLink>
|
||||
{NAV_ITEMS.map(renderNavItem)}
|
||||
</nav>
|
||||
|
||||
<div className={styles.hdrRight}>
|
||||
|
|
@ -93,6 +143,23 @@ export function HeaderV3() {
|
|||
<a href="#check" className={styles.hdrCta}>
|
||||
Проверить квартиру
|
||||
</a>
|
||||
<details className={styles.hdrMenu}>
|
||||
<summary className={styles.hdrMenuBtn}>
|
||||
<span className={styles.hdrMenuIcon} aria-hidden="true" />
|
||||
Меню
|
||||
</summary>
|
||||
<div className={styles.hdrMenuPanel}>
|
||||
<nav
|
||||
className={styles.hdrMenuNav}
|
||||
aria-label="Навигация по разделам"
|
||||
>
|
||||
{NAV_ITEMS.map(renderNavItem)}
|
||||
</nav>
|
||||
<a href="#check" className={styles.hdrMenuCta}>
|
||||
Проверить квартиру
|
||||
</a>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@
|
|||
а не режим работы). Навигация и CTA возвращаются в @media-блоке в конце
|
||||
секции. */
|
||||
.hdrInner {
|
||||
/* `position: relative` — база для панели меню (`.hdrMenuPanel`): открытая
|
||||
панель лежит ПОВЕРХ страницы и не увеличивает высоту липкой шапки. */
|
||||
position: relative;
|
||||
max-width: 1280px;
|
||||
margin-inline: auto;
|
||||
padding: 8px clamp(20px, 5vw, 56px);
|
||||
|
|
@ -156,6 +159,7 @@
|
|||
}
|
||||
|
||||
.hdrCta,
|
||||
.hdrMenuCta,
|
||||
.barBtn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -183,10 +187,87 @@
|
|||
}
|
||||
|
||||
.hdrCta:hover,
|
||||
.hdrMenuCta:hover,
|
||||
.barBtn:hover {
|
||||
filter: brightness(0.92);
|
||||
}
|
||||
|
||||
/* Меню узкого экрана (HeaderV3). Заменяет собой скрытые здесь `.hdrNav` и
|
||||
`.hdrCta`: до этого на узком экране навигации не было ВООБЩЕ — ни ряда, ни
|
||||
меню, — и «Точность»/«Проверьте себя»/«Статьи» отдавали нулевой
|
||||
getBoundingClientRect. Разворачивалка — родной `<details>`: без JavaScript,
|
||||
то есть шапка остаётся серверной.
|
||||
|
||||
Закрытое меню — одна кнопка, поэтому довод, ради которого ряд навигации
|
||||
отсюда убирали (липкая шапка в 29% экрана на 375px), остаётся соблюдён:
|
||||
высота строки прежняя. Открытая панель позиционируется абсолютно — она
|
||||
лежит ПОВЕРХ страницы, а не раздвигает шапку. */
|
||||
.hdrMenu {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hdrMenuBtn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
/* Паддинг по вертикали — 6px, а не 8: с ним кнопка укладывается в ту же
|
||||
высоту строки, что и логотип, и липкая шапка на 800px остаётся 53px —
|
||||
ровно как до появления меню (замер до/после). */
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--b2c-line);
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
color: var(--b2c-ink);
|
||||
cursor: pointer;
|
||||
/* Родной маркер-треугольник убран, рядом стоит своя иконка: `list-style`
|
||||
закрывает Firefox, `::-webkit-details-marker` — Safari. */
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hdrMenuBtn::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Три полосы гамбургера одним элементом: сам элемент — средняя полоса, две
|
||||
крайние нарисованы тенями. Дешевле трёх <span> в разметке. */
|
||||
.hdrMenuIcon {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
box-shadow:
|
||||
0 -5px 0 currentColor,
|
||||
0 5px 0 currentColor;
|
||||
}
|
||||
|
||||
.hdrMenuPanel {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px clamp(20px, 5vw, 56px) 20px;
|
||||
/* Фон непрозрачный (не color-mix, как у самой шапки): под панелью едет
|
||||
контент страницы, и полупрозрачный слой делал бы пункты нечитаемыми. */
|
||||
background: var(--b2c-page-bg);
|
||||
border-bottom: 1px solid var(--b2c-line);
|
||||
}
|
||||
|
||||
.hdrMenuNav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hdrMenuCta {
|
||||
justify-content: center;
|
||||
padding: 12px 20px;
|
||||
font-size: 14.5px;
|
||||
}
|
||||
|
||||
/* Шапка макета целиком — с той ширины, на которой она РЕАЛЬНО влезает в одну
|
||||
строку (замерено: 1000px — лого, четыре пункта, «Для бизнеса» и кнопка;
|
||||
на 720px то же самое переносится в две строки и даёт 109px липкого хрома,
|
||||
|
|
@ -205,6 +286,11 @@
|
|||
.hdrCta {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* Меню не нужно: с этой ширины все его пункты стоят в строке шапки. */
|
||||
.hdrMenu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
|
|
@ -363,8 +449,15 @@
|
|||
/* Компенсация fixed-позиционирования `.barRoot` (тот вырезан из потока
|
||||
документа): без спейсера панель легла бы поверх последних пикселей
|
||||
страницы при докрутке до конца (реквизиты в подвале, последний вопрос
|
||||
FAQ). Высота одна на все ширины: панель везде в одну строку — на узком
|
||||
экране за счёт того, что текст скрыт (см. `.barText`), а не переносится. */
|
||||
FAQ).
|
||||
|
||||
ВЫСОТА НЕ ОДНА НА ВСЕ ШИРИНЫ (31.08.2026). Здесь стояло 68px «на все
|
||||
ширины», и на узком экране это верно (замер на проде, 800px: панель
|
||||
66,5px). Но с 1000px у панели растёт вертикальный паддинг и появляется
|
||||
строка текста — замерено 70,5px, то есть спейсер был на 2,5px КОРОЧЕ
|
||||
панели, и нижние пиксели подвала уходили под неё ровно там, где спейсер и
|
||||
заводили. Второе значение стоит в том же @media-блоке, что и паддинг,
|
||||
который его меняет, — чтобы правки не разъезжались. */
|
||||
.barSpacer {
|
||||
height: 68px;
|
||||
}
|
||||
|
|
@ -416,6 +509,12 @@
|
|||
padding-block: 12px;
|
||||
}
|
||||
|
||||
/* Ровно та высота, которую панель занимает с этими паддингами (замер на
|
||||
проде: 70,5px) плюс запас на округление подпикселей. */
|
||||
.barSpacer {
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.barText {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue