3154b47578
- HeroImage: на мобилке картинка позиционируется фоном за текстом, opacity 0.45 - Сильный mask-gradient к левому-нижнему углу, чтобы заголовок читался - Софт-вуаль поверх фона (background var(--bg) с убывающей прозрачностью) - Секция hero: min-h-[88vh] на мобиле, текст по центру по вертикали - В тёмной теме мобильная opacity ещё ниже (0.32) - Десктоп без изменений
140 lines
4.2 KiB
JavaScript
140 lines
4.2 KiB
JavaScript
'use client';
|
|
import { useEffect, useRef } from 'react';
|
|
|
|
/**
|
|
* Hero иллюстрация — фон секции. Поверх неё рендерится текст.
|
|
* Desktop: справа, fade к центру, parallax при скролле.
|
|
* Mobile: фон ВО ВСЮ секцию, текст поверх с дополнительным фейдом.
|
|
*/
|
|
export default function HeroImage() {
|
|
const ref = useRef(null);
|
|
|
|
useEffect(() => {
|
|
if (!ref.current) return;
|
|
let raf = 0;
|
|
const onScroll = () => {
|
|
cancelAnimationFrame(raf);
|
|
raf = requestAnimationFrame(() => {
|
|
const y = window.scrollY;
|
|
const offset = Math.min(60, y * 0.15);
|
|
if (ref.current) ref.current.style.transform = `translateY(-${offset}px)`;
|
|
});
|
|
};
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
return () => window.removeEventListener('scroll', onScroll);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="hero-image-wrap" aria-hidden="true">
|
|
<picture>
|
|
<source
|
|
srcSet="/uploads/hero-1920.webp 1920w, /uploads/hero-1280.webp 1280w, /uploads/hero-800.webp 800w"
|
|
sizes="(min-width: 768px) 70vw, 100vw"
|
|
type="image/webp"
|
|
/>
|
|
<img
|
|
ref={ref}
|
|
src="/uploads/hero-1280.webp"
|
|
alt=""
|
|
className="hero-image"
|
|
loading="eager"
|
|
fetchPriority="high"
|
|
decoding="async"
|
|
/>
|
|
</picture>
|
|
|
|
<style jsx>{`
|
|
.hero-image-wrap {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
overflow: hidden;
|
|
z-index: 0;
|
|
}
|
|
.hero-image {
|
|
position: absolute;
|
|
top: -40px;
|
|
right: -2%;
|
|
width: auto;
|
|
height: 115%;
|
|
max-width: none;
|
|
object-fit: contain;
|
|
object-position: right top;
|
|
opacity: 0.85;
|
|
will-change: transform;
|
|
-webkit-mask-image: linear-gradient(
|
|
to right,
|
|
transparent 0%,
|
|
rgba(0,0,0,0.4) 30%,
|
|
black 55%,
|
|
black 100%
|
|
);
|
|
mask-image: linear-gradient(
|
|
to right,
|
|
transparent 0%,
|
|
rgba(0,0,0,0.4) 30%,
|
|
black 55%,
|
|
black 100%
|
|
);
|
|
}
|
|
:global(.dark) .hero-image {
|
|
opacity: 0.55;
|
|
filter: brightness(0.85) saturate(0.9);
|
|
}
|
|
/* нижний fade-out к контенту */
|
|
.hero-image-wrap::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0; right: 0; bottom: 0;
|
|
height: 120px;
|
|
background: linear-gradient(to bottom, transparent, rgb(var(--bg)));
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* === MOBILE: картинка фоном, текст поверх === */
|
|
@media (max-width: 767px) {
|
|
.hero-image {
|
|
top: -20px;
|
|
right: -30%; /* выезжает за правый край — видна только часть */
|
|
height: 100%;
|
|
width: auto;
|
|
max-width: 160%;
|
|
opacity: 0.45; /* приглушаем, чтобы текст читался */
|
|
-webkit-mask-image: linear-gradient(
|
|
to bottom right,
|
|
transparent 0%,
|
|
rgba(0,0,0,0.15) 30%,
|
|
rgba(0,0,0,0.6) 60%,
|
|
black 100%
|
|
);
|
|
mask-image: linear-gradient(
|
|
to bottom right,
|
|
transparent 0%,
|
|
rgba(0,0,0,0.15) 30%,
|
|
rgba(0,0,0,0.6) 60%,
|
|
black 100%
|
|
);
|
|
}
|
|
:global(.dark) .hero-image {
|
|
opacity: 0.32;
|
|
}
|
|
/* софт-вуаль для контраста заголовка */
|
|
.hero-image-wrap::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(
|
|
to bottom,
|
|
rgb(var(--bg) / 0.4) 0%,
|
|
rgb(var(--bg) / 0.2) 40%,
|
|
rgb(var(--bg)) 100%
|
|
);
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
}
|