Files
zeropost-web/components/HeroImage.js
T
Alexey Pavlov 3154b47578 fix(mobile): hero — картинка как фон секции, текст поверх
- HeroImage: на мобилке картинка позиционируется фоном за текстом, opacity 0.45
- Сильный mask-gradient к левому-нижнему углу, чтобы заголовок читался
- Софт-вуаль поверх фона (background var(--bg) с убывающей прозрачностью)
- Секция hero: min-h-[88vh] на мобиле, текст по центру по вертикали
- В тёмной теме мобильная opacity ещё ниже (0.32)
- Десктоп без изменений
2026-05-31 10:00:25 +03:00

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>
);
}