Переписаны компоенты главной страницы

This commit is contained in:
Web-serfer 2026-04-16 01:15:06 +05:00
parent 2b087c02b7
commit 284e37fa94
7 changed files with 320 additions and 227 deletions

View file

@ -4,7 +4,7 @@ const sectionData = {
subtitle: "Почему мы?",
title: "Локальная экспертиза в Сургуте",
floatingText:
"Знаем специфику судов Сургута и регламенты местной полиции изнутри.",
"Знаем специфику судов Сургута и регламенты местной полиции изнутри.",
imageUrl:
"/images/home/dogovor.avif",
};
@ -28,25 +28,42 @@ const features = [
];
---
<!-- КРИТИЧНО: Инлайн-скрипт скрывает элементы ДО рендера, но только если JS работает -->
<script is:inline>
(function() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const style = document.createElement('style');
style.textContent = '.animate-on-scroll{opacity:0}' +
'[data-animation="fade-in"]{opacity:0}' +
'[data-animation="fade-up"]{opacity:0;transform:translateY(30px)}' +
'[data-animation="slide-up"]{opacity:0;transform:translateY(40px)}' +
'[data-animation="slide-right"]{opacity:0;transform:translateX(-40px)}' +
'[data-animation="scale-in"]{opacity:0;transform:scale(0.95)}' +
'[data-animation="stagger-item"]{opacity:0;transform:translateX(-30px)}';
document.head.appendChild(style);
})();
</script>
<section class="why-us-section" id="why-us">
<div class="site-container">
<!-- Левая колонка: Изображение -->
<div class="image-column">
<div class="image-wrapper animate-on-scroll" data-animation="scale-in">
<img
src={sectionData.imageUrl}
alt="Юридическая практика"
class="main-image"
loading="lazy"
width="440"
height="340"
src={sectionData.imageUrl}
alt="Юридическая практика"
class="main-image"
loading="lazy"
width="440"
height="340"
/>
<!-- Плавающая бежевая плашка -->
<div
class="floating-card animate-on-scroll"
data-animation="slide-up"
data-delay="300"
class="floating-card animate-on-scroll"
data-animation="slide-up"
data-delay="300"
>
<p>{sectionData.floatingText}</p>
</div>
@ -57,12 +74,12 @@ const features = [
<div class="content-column">
<div class="section-header">
<span class="subtitle animate-on-scroll" data-animation="fade-up"
>{sectionData.subtitle}</span
>{sectionData.subtitle}</span
>
<h2
class="title animate-on-scroll"
data-animation="slide-right"
data-delay="100"
class="title animate-on-scroll"
data-animation="slide-right"
data-delay="100"
>
{sectionData.title}
</h2>
@ -71,17 +88,17 @@ const features = [
<div class="features-list">
{
features.map((feature, index) => (
<div
class="feature-item animate-on-scroll"
data-animation="stagger-item"
data-delay={index * 150 + 200}
>
<div class="icon-box" set:html={feature.icon} />
<div class="feature-text">
<h3 class="feature-title">{feature.title}</h3>
<p class="feature-desc">{feature.desc}</p>
<div
class="feature-item animate-on-scroll"
data-animation="stagger-item"
data-delay={index * 150 + 200}
>
<div class="icon-box" set:html={feature.icon} />
<div class="feature-text">
<h3 class="feature-title">{feature.title}</h3>
<p class="feature-desc">{feature.desc}</p>
</div>
</div>
</div>
))
}
</div>
@ -89,48 +106,16 @@ const features = [
</div>
</section>
<script>
// Intersection Observer для анимаций при скроллинге
const observerOptions = {
root: null,
rootMargin: "0px",
threshold: 0.15,
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const delay = (entry.target as HTMLElement).dataset.delay || "0";
setTimeout(
() => {
entry.target.classList.add("is-visible");
},
parseInt(delay as string),
);
// Отключаем наблюдение после появления
observer.unobserve(entry.target);
}
});
}, observerOptions);
// Наблюдаем за всеми анимируемыми элементами
document.querySelectorAll(".animate-on-scroll").forEach((el) => {
observer.observe(el);
});
</script>
<style>
/* Общие стили секции */
.why-us-section {
padding: 6rem 2rem;
background-color: #f4f6f9;
font-family:
system-ui,
-apple-system,
sans-serif;
overflow: hidden; /* Предотвращаем горизонтальный скролл от анимаций */
system-ui,
-apple-system,
sans-serif;
overflow: hidden;
}
.site-container {
@ -142,9 +127,9 @@ const features = [
margin: 0 auto;
}
/* --- Базовые стили анимаций --- */
/* --- Базовые стили анимаций - ИСПРАВЛЕНО: opacity:1 по умолчанию --- */
.animate-on-scroll {
opacity: 0;
opacity: 1;
will-change: opacity, transform;
}
@ -155,42 +140,42 @@ const features = [
/* Fade Up */
[data-animation="fade-up"] {
transform: translateY(30px);
transform: translateY(0);
transition:
opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Slide Up (снизу вверх) */
[data-animation="slide-up"] {
transform: translateY(40px);
transform: translateY(0);
transition:
opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Slide Right (справа налево) */
[data-animation="slide-right"] {
transform: translateX(-40px);
transform: translateX(0);
transition:
opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Scale In (с увеличением) */
[data-animation="scale-in"] {
transform: scale(0.95);
transform: scale(1);
transition:
opacity 1s cubic-bezier(0.4, 0, 0.2, 1),
transform 1s cubic-bezier(0.4, 0, 0.2, 1);
opacity 1s cubic-bezier(0.4, 0, 0.2, 1),
transform 1s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Stagger Item (для списка) */
[data-animation="stagger-item"] {
transform: translateX(-30px);
transform: translateX(0);
transition:
opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Активное состояние анимаций */
@ -221,7 +206,7 @@ const features = [
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
/* Бежевая плавающая плашка с дополнительной анимацией тени */
/* Бежевая плавающая плашка */
.floating-card {
position: absolute;
bottom: -30px;
@ -275,7 +260,7 @@ const features = [
letter-spacing: -0.02em;
}
/* Список особенностей (Features) */
/* Список особенностей */
.features-list {
display: flex;
flex-direction: column;
@ -288,7 +273,6 @@ const features = [
align-items: flex-start;
}
/* Иконки с hover-эффектом */
.icon-box {
flex-shrink: 0;
width: 46px;
@ -302,8 +286,8 @@ const features = [
padding: 10px;
box-shadow: 0 4px 10px rgba(18, 33, 56, 0.2);
transition:
transform 0.3s ease,
box-shadow 0.3s ease;
transform 0.3s ease,
box-shadow 0.3s ease;
}
.feature-item:hover .icon-box {
@ -311,7 +295,6 @@ const features = [
box-shadow: 0 6px 15px rgba(18, 33, 56, 0.3);
}
/* Типографика текстов особенностей */
.feature-text {
flex-grow: 1;
padding-top: 0.2rem;
@ -360,7 +343,6 @@ const features = [
gap: 3rem;
}
/* Центрирование заголовка на мобильных */
.section-header {
text-align: center;
margin-bottom: 2.5rem;
@ -375,13 +357,12 @@ const features = [
font-size: clamp(1.5rem, 5vw, 2rem);
}
/* Мобильные анимации - упрощаем направления */
[data-animation="slide-right"] {
transform: translateY(30px);
transform: translateY(0);
}
[data-animation="stagger-item"] {
transform: translateY(20px);
transform: translateY(0);
}
.image-column {
@ -466,9 +447,39 @@ const features = [
/* Уважаем prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
.animate-on-scroll {
opacity: 1;
transform: none;
transition: none;
opacity: 1 !important;
transform: none !important;
transition: none !important;
}
}
</style>
<script>
// Intersection Observer для анимаций при скроллинге
const observerOptions = {
root: null,
rootMargin: "0px",
threshold: 0.15,
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const delay = (entry.target as HTMLElement).dataset.delay || "0";
setTimeout(
() => {
entry.target.classList.add("is-visible");
},
parseInt(delay as string),
);
observer.unobserve(entry.target);
}
});
}, observerOptions);
document.querySelectorAll(".animate-on-scroll").forEach((el) => {
observer.observe(el);
});
</script>