Ports legacy/hotel v7 into the React landing. - Copy: nav is Экономика / Для гостя / Интерьер / Сервис, and «Интерьер» now comes before «Сервис». The hero keeps one button and gets the 2 500 ₽ / 600 000 ₽ / 7,2 млн ₽ facts. New headings and leads for the interior, service and request blocks. Session lengths 10–15 and 30–60 min, area 12–36 m². - Calculator counts sessions and gains the ЭкзоКлиник benchmark and a «Получить консультацию» button. - Request form: company and email are optional. The server schema already treats them as optional. - v8 phone type scale (≤620px), a 380px `micro` step, and a `slim` (≤640px) variant. The max-width variants are now declared widest first, so the narrower one wins where two apply. - The market charts' SVGs stay inline, as in the mockup. The header «Звонок» button opens the «Перезвоним вам» dialog from the fitness landing (name, phone, consent). The server accepts form 'callback', names the deal «Обратный звонок — <имя>» and tags it «обратный звонок». The phone link inside the dialog still reports a call click. With Inter substituted into the mockup, the page text matches it line for line (except the fixed «2025–2026 гг.» typo), and every section height matches at 360–1440 px. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
98 lines
4.2 KiB
TypeScript
98 lines
4.2 KiB
TypeScript
import heroBg from '../assets/images/hero-bg.webp'
|
|
import { heroFacts } from '../data/content'
|
|
import { useReveal } from '../hooks/useReveal'
|
|
import { cx } from '../lib/cx'
|
|
import { ButtonLink } from './Button'
|
|
import { ArrowRightIcon } from './Icons'
|
|
import { Container } from './Layout'
|
|
|
|
export function Hero() {
|
|
const kicker = useReveal<HTMLDivElement>()
|
|
const title = useReveal<HTMLHeadingElement>()
|
|
const subtitle = useReveal<HTMLParagraphElement>()
|
|
const actions = useReveal<HTMLDivElement>()
|
|
const facts = useReveal<HTMLDivElement>()
|
|
|
|
return (
|
|
<section
|
|
id="top"
|
|
aria-labelledby="hero-title"
|
|
className="relative isolate flex min-h-[760px] items-end overflow-hidden bg-navy-deep pt-[122px] pb-[48px] text-white
|
|
sm:min-h-[820px] sm:pb-[60px] md:min-h-[790px] md:items-center md:pt-[150px] md:pb-[78px]"
|
|
>
|
|
<div
|
|
aria-hidden="true"
|
|
className="absolute inset-0 -z-3 scale-[1.02] bg-cover bg-[position:60%_center]"
|
|
style={{ backgroundImage: `url(${heroBg})` }}
|
|
/>
|
|
<div aria-hidden="true" className="hero-scrim absolute inset-0 -z-2" />
|
|
<div
|
|
aria-hidden="true"
|
|
className="pointer-events-none absolute -right-[220px] -bottom-[180px] -z-1 size-[480px] rounded-full bg-teal/[0.38] blur-[100px]"
|
|
/>
|
|
|
|
<Container>
|
|
<div className="w-[min(100%,950px)]">
|
|
<div
|
|
ref={kicker.ref}
|
|
className={cx(
|
|
'mb-[22px] inline-flex items-center gap-[10px] rounded-full border border-white/[0.22] bg-[#051E2F]/[0.36] px-[13px] py-[9px]',
|
|
'text-[12px] font-[780] tracking-[0.1em] text-white/[0.84] uppercase backdrop-blur-[14px]',
|
|
kicker.revealClass,
|
|
)}
|
|
>
|
|
<i aria-hidden="true" className="size-[8px] rounded-full bg-teal shadow-[0_0_0_6px_rgb(0_196_180/0.14)]" />
|
|
Готовый формат внутри отеля
|
|
</div>
|
|
|
|
<h1 ref={title.ref} id="hero-title" className={cx('delay-[80ms]', title.revealClass)}>
|
|
ЭкзоОтель: Пространство восстановления для гостей
|
|
</h1>
|
|
|
|
<p
|
|
ref={subtitle.ref}
|
|
className={cx(
|
|
'mb-[30px] max-w-[730px] text-[clamp(19px,2.2vw,27px)] leading-[1.42] text-white/[0.82] delay-[160ms] compact:text-[18px] compact:leading-[1.46]',
|
|
subtitle.revealClass,
|
|
)}
|
|
>
|
|
Кабинет восстановления, расслабления и дополнительной выручки внутри отеля
|
|
</p>
|
|
|
|
<div ref={actions.ref} className={cx('mb-[34px] flex flex-wrap gap-[12px] delay-[160ms]', actions.revealClass)}>
|
|
<ButtonLink href="#proposal">
|
|
Получить предложение
|
|
<ArrowRightIcon />
|
|
</ButtonLink>
|
|
</div>
|
|
|
|
<div
|
|
ref={facts.ref}
|
|
aria-label="Ключевая экономика сценария"
|
|
className={cx(
|
|
'grid max-w-[700px] grid-cols-3 gap-[10px] delay-[240ms] sm:gap-[12px] tiny:max-w-[330px] tiny:grid-cols-1',
|
|
facts.revealClass,
|
|
)}
|
|
>
|
|
{heroFacts.map((fact) => (
|
|
// Under 430px the fact turns into a value/label row. The value column
|
|
// grows with its content, so «600 000 ₽ / мес.» stays on one line.
|
|
<div
|
|
key={fact.value}
|
|
className="min-h-[82px] rounded-[18px] border border-white/[0.16] bg-[#051D2F]/[0.42] px-[16px] py-[15px] backdrop-blur-[16px]
|
|
tiny:grid tiny:min-h-0 tiny:grid-cols-[minmax(118px,auto)_1fr] tiny:items-center tiny:gap-[10px]
|
|
micro:grid-cols-[minmax(105px,auto)_1fr]"
|
|
>
|
|
<strong className="mb-[3px] block text-[clamp(19px,2.55vw,27px)] leading-[1.1] tracking-[-0.03em] whitespace-nowrap tiny:mb-0 tiny:text-[18px] micro:text-[17px]">
|
|
{fact.value}
|
|
</strong>
|
|
<span className="text-[12px] text-white/[0.62]">{fact.label}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</Container>
|
|
</section>
|
|
)
|
|
}
|