From 284e37fa94a6c415cc042ef50b5ea4ec1ff92c91 Mon Sep 17 00:00:00 2001 From: Web-serfer Date: Thu, 16 Apr 2026 01:15:06 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D1=8B=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/base/PageHero.astro | 33 ++-- frontend/src/components/home/Faq.astro | 66 ++++--- frontend/src/components/home/Reviews.astro | 58 +++--- frontend/src/components/home/Services.astro | 102 ++++++----- frontend/src/components/home/Steps.astro | 55 +++--- frontend/src/components/home/WhyUs.astro | 191 +++++++++++--------- frontend/src/pages/index1.astro | 42 +++++ 7 files changed, 320 insertions(+), 227 deletions(-) create mode 100644 frontend/src/pages/index1.astro diff --git a/frontend/src/components/base/PageHero.astro b/frontend/src/components/base/PageHero.astro index 066082b..623bcb7 100644 --- a/frontend/src/components/base/PageHero.astro +++ b/frontend/src/components/base/PageHero.astro @@ -50,6 +50,19 @@ const { const showImage = layout === 'with-image' && sideImage; --- + + +
{ const delay = parseInt((el as HTMLElement).dataset.delay || '0'); - // Используем requestAnimationFrame для лучшей производительности setTimeout(() => { requestAnimationFrame(() => { el.classList.add('is-visible'); @@ -220,10 +232,10 @@ const showImage = layout === 'with-image' && sideImage; max-width: 700px; } - /* --- АНИМАЦИИ ПРИ ЗАГРУЗКЕ --- */ + /* --- АНИМАЦИИ ПРИ ЗАГРУЗКЕ - ИСПРАВЛЕНО: opacity:1 по умолчанию --- */ .animate-load { - opacity: 0; - transform: translateY(30px); + opacity: 1; + 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); will-change: opacity, transform; @@ -236,8 +248,8 @@ const showImage = layout === 'with-image' && sideImage; /* Специальная анимация для изображения */ .hero-image-wrapper.animate-load { - opacity: 0; - transform: translateX(50px) scale(0.95); + opacity: 1; + transform: translateX(0) scale(1); transition: opacity 1s cubic-bezier(0.4, 0, 0.2, 1), transform 1s cubic-bezier(0.4, 0, 0.2, 1); } @@ -249,8 +261,8 @@ const showImage = layout === 'with-image' && sideImage; /* Анимация для бейджа опыта */ .experience-badge.animate-load { - opacity: 0; - transform: translateY(20px) scale(0.9); + opacity: 1; + transform: translateY(0) scale(1); transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1), transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); } @@ -281,7 +293,7 @@ const showImage = layout === 'with-image' && sideImage; flex-shrink: 0; } - /* Мерцающая точка - ИСПРАВЛЕНО: заменил box-shadow на opacity/transform */ + /* Мерцающая точка - оптимизированная анимация */ .status-dot { width: 10px; height: 10px; @@ -317,7 +329,6 @@ const showImage = layout === 'with-image' && sideImage; stroke-width: 2 !important; } - /* Оптимизированная анимация pulse - только opacity и transform */ @keyframes pulse-ring { 0% { transform: scale(1); @@ -472,7 +483,7 @@ const showImage = layout === 'with-image' && sideImage; } .hero-image-wrapper.animate-load { - transform: translateY(40px) scale(0.95); + transform: translateY(0) scale(1); } .hero-image-wrapper.animate-load.is-visible { diff --git a/frontend/src/components/home/Faq.astro b/frontend/src/components/home/Faq.astro index c7a4a0f..12418d6 100644 --- a/frontend/src/components/home/Faq.astro +++ b/frontend/src/components/home/Faq.astro @@ -37,6 +37,19 @@ const { ---
+ + +
@@ -180,18 +193,21 @@ const { line-height: 1.6; } - /* Анимации при скроллинге */ + /* + ИСПРАВЛЕНИЕ LCP: + opacity: 1 по умолчанию! + Если JS не загрузится - контент виден. + Если JS загрузится - инлайн-скрипт выше добавит opacity:0 и анимация сработает. + */ .animate-on-scroll { - opacity: 0; + opacity: 1; + transform: translateY(0); will-change: opacity, transform; - } - - [data-animation="fade-up"] { - transform: translateY(40px); transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1), transform 0.8s cubic-bezier(0.4, 0, 0.2, 1); } + /* Класс is-visible добавляется через JS при появлении в viewport */ .animate-on-scroll.is-visible { opacity: 1; transform: translateY(0); @@ -276,10 +292,6 @@ const { height: 100%; } - /* - КЛЮЧЕВОЕ ИЗМЕНЕНИЕ: ИСПОЛЬЗУЕМ GRID ВМЕСТО MAX-HEIGHT - Это полностью устраняет дерганье и делает анимацию плавной - */ .faq-answer { display: grid; grid-template-rows: 0fr; @@ -290,7 +302,6 @@ const { grid-template-rows: 1fr; } - /* Внутренний контейнер для предотвращения сжатия содержимого */ .answer-inner { overflow: hidden; } @@ -362,9 +373,9 @@ const { /* Уважаем 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; } .faq-item, @@ -389,8 +400,8 @@ const { // Intersection Observer для анимаций при скроллинге const observerOptions = { root: null, - rootMargin: '0px', - threshold: 0.15 + rootMargin: '50px', // Немного раньше начинаем анимацию + threshold: 0.1 }; const observer = new IntersectionObserver((entries) => { @@ -399,8 +410,11 @@ const { const el = entry.target as HTMLElement; const delay = parseInt(el.dataset.delay || '0'); + // Используем requestAnimationFrame для плавности setTimeout(() => { - el.classList.add('is-visible'); + requestAnimationFrame(() => { + el.classList.add('is-visible'); + }); }, delay); observer.unobserve(el); @@ -408,9 +422,18 @@ const { }); }, observerOptions); - document.querySelectorAll('.animate-on-scroll').forEach((el) => { - observer.observe(el); - }); + // Инициализация после загрузки DOM + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('.animate-on-scroll').forEach((el) => { + observer.observe(el); + }); + }); + } else { + document.querySelectorAll('.animate-on-scroll').forEach((el) => { + observer.observe(el); + }); + } // Аккордеон FAQ - улучшенная версия document.querySelectorAll('[data-faq-toggle]').forEach((button) => { @@ -419,7 +442,6 @@ const { const answer = faqItem?.querySelector('[data-faq-content]'); const isExpanded = button.getAttribute('aria-expanded') === 'true'; - // Если этот элемент уже открыт, просто закрываем его if (isExpanded) { faqItem?.classList.remove('active'); button.setAttribute('aria-expanded', 'false'); @@ -427,7 +449,6 @@ const { return; } - // Закрываем все другие открытые элементы document.querySelectorAll('.faq-item.active').forEach((item) => { if (item !== faqItem) { item.classList.remove('active'); @@ -438,7 +459,6 @@ const { } }); - // Открываем текущий faqItem?.classList.add('active'); button.setAttribute('aria-expanded', 'true'); answer?.classList.add('open'); diff --git a/frontend/src/components/home/Reviews.astro b/frontend/src/components/home/Reviews.astro index 7c87cee..b851425 100644 --- a/frontend/src/components/home/Reviews.astro +++ b/frontend/src/components/home/Reviews.astro @@ -2,15 +2,35 @@ import { reviewsData } from '@data/reviewsData'; import ReviewCard from '@components/reviews/ReviewCard.astro'; -// Берём 6 последних отзывов (сортируем по дате, берём последние) +// Берём 6 последних отзывов const latestReviews = [...reviewsData] .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) .slice(0, 6); -// Для эффекта бесконечности клонируем элементы (один набор в начало, один в конец) +// Для эффекта бесконечности клонируем элементы const displayReviews = [...latestReviews, ...latestReviews, ...latestReviews]; + +// Функция для получения инициала из имени +const getInitial = (name: string) => name.charAt(0).toUpperCase(); + +// Функция для получения цвета аватара (циклически из набора) +const colors = ['#eac26e', '#22c55e', '#3b82f6', '#ef4444', '#8b5cf6', '#f59e0b']; +const getColor = (index: number) => colors[index % colors.length]; --- + + +
@@ -40,11 +60,11 @@ const displayReviews = [...latestReviews, ...latestReviews, ...latestReviews]; car={review.car} text={review.text} rating={review.rating} - initial={review.initial} - color={review.color} date={review.date} votesCount={review.votesCount || 0} isHelpful={review.isHelpful || false} + initial={getInitial(review.name)} + color={getColor(index)} />
))} @@ -69,33 +89,24 @@ const displayReviews = [...latestReviews, ...latestReviews, ...latestReviews]; display: none; } - /* Карточка в слайдере — полностью сохраняет оригинальный дизайн ReviewCard */ .card-item { scroll-snap-align: center; display: flex; } - /* Центрирование карточки на мобильном */ #sliderContainer { scroll-snap-type: x mandatory; } - #sliderTrack { - scroll-snap-align: center; - } - - /* Убедимся, что карточка растягивается на всю ширину слайда */ .card-item :global(.review-card) { width: 100%; margin: 0; } - /* Уменьшаем имя для слайдера — влезает в одну строку */ .card-item :global(.author-name) { font-size: 0.9rem; } - /* Заголовок секции */ .section-header { margin-bottom: 4rem; text-align: center; @@ -127,14 +138,14 @@ const displayReviews = [...latestReviews, ...latestReviews, ...latestReviews]; width: 100%; } - /* Анимации при скроллинге */ + /* ИСПРАВЛЕНИЕ LCP: opacity: 1 по умолчанию */ .animate-on-scroll { - opacity: 0; + opacity: 1; + transform: translateY(0) scale(1); will-change: opacity, transform; } [data-animation="fade-up"] { - transform: translateY(30px); transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1), transform 0.8s cubic-bezier(0.4, 0, 0.2, 1); } @@ -144,7 +155,6 @@ const displayReviews = [...latestReviews, ...latestReviews, ...latestReviews]; } [data-animation="scale-up"] { - transform: translateY(40px) scale(0.95); transition: opacity 0.7s cubic-bezier(0.4, 0, 0.2, 1), transform 0.7s cubic-bezier(0.4, 0, 0.2, 1); } @@ -156,15 +166,14 @@ const displayReviews = [...latestReviews, ...latestReviews, ...latestReviews]; @media (prefers-reduced-motion: reduce) { .animate-on-scroll { - opacity: 1; - transform: none; - transition: none; + opacity: 1 !important; + transform: none !important; + transition: none !important; } } +
@@ -101,43 +113,6 @@ const {
- - +
@@ -59,19 +70,16 @@ const {
{steps.map((step: Step, index: number) => (
- {index > 0 && ( -
-
-
-
+
+
+
+
)} - +
-
-
{step.number}
@@ -82,7 +90,6 @@ const {
-

{step.title}

{step.description}

@@ -146,7 +153,6 @@ const { font-family: 'Inter', system-ui, -apple-system, sans-serif; } - /* Декоративные элементы фона */ .steps-section::before { content: ''; position: absolute; @@ -177,7 +183,6 @@ const { z-index: 2; } - /* Заголовок секции */ .section-header { margin-bottom: 4rem; text-align: center; @@ -226,14 +231,14 @@ const { line-height: 1.6; } - /* --- АНИМАЦИИ ПРИ СКРОЛЛИНГЕ --- */ + /* ИСПРАВЛЕНИЕ LCP: opacity: 1 по умолчанию! */ .animate-on-scroll { - opacity: 0; + opacity: 1; + transform: translateY(0); will-change: opacity, transform; } [data-animation="fade-up"] { - transform: translateY(40px); transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1), transform 0.8s cubic-bezier(0.4, 0, 0.2, 1); } @@ -243,7 +248,6 @@ const { transform: translateY(0); } - /* Контейнер шагов */ .steps-container { display: flex; justify-content: space-between; @@ -258,7 +262,6 @@ const { position: relative; } - /* Вертикальный соединитель для мобильной версии (скрыт по умолчанию) */ .mobile-connector { display: none; position: absolute; @@ -288,7 +291,6 @@ const { box-shadow: 0 0 10px var(--color-accent); } - /* Карточка шага */ .step-card { position: relative; background: rgba(255, 255, 255, 0.03); @@ -303,8 +305,6 @@ const { flex-direction: column; } - /* Убрана верхняя полоса внутри карточки */ - .step-card:hover { transform: translateY(-8px); background: rgba(255, 255, 255, 0.05); @@ -328,7 +328,6 @@ const { opacity: 1; } - /* Заголовок шага */ .step-header { display: flex; align-items: center; @@ -402,7 +401,6 @@ const { transform: scale(1.1); } - /* Текст шага */ .step-content { flex: 1; } @@ -422,7 +420,6 @@ const { margin: 0; } - /* Статистика */ .stats-bar { display: flex; justify-content: center; @@ -465,7 +462,6 @@ const { background: rgba(255, 255, 255, 0.1); } - /* CTA кнопка */ .steps-cta { text-align: center; } @@ -515,7 +511,6 @@ const { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); } - /* --- АДАПТИВНОСТЬ --- */ @media (max-width: 1024px) { .steps-container { flex-direction: column; @@ -531,7 +526,6 @@ const { margin-bottom: 0; } - /* Показываем вертикальные соединители */ .mobile-connector { display: block; } @@ -619,12 +613,11 @@ const { } } - /* Уважаем 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; } .step-card, @@ -651,7 +644,6 @@ const { const duration = 1500; const startTime = performance.now(); - // Easing функция (ease-out cubic) const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3); function update(currentTime: number) { @@ -679,11 +671,9 @@ const { setTimeout(() => { el.classList.add('is-visible'); - // Если это stats-bar, запускаем анимацию счётчиков if (el.id === 'stats-bar') { const counters = el.querySelectorAll('.stat-number'); counters.forEach((counter, index) => { - // Небольшая задержка между счётчиками для каскадного эффекта setTimeout(() => { animateCounter(counter as HTMLElement); }, index * 150); @@ -696,7 +686,6 @@ const { }); }, observerOptions); - // Наблюдаем за всеми анимируемыми элементами document.querySelectorAll('.animate-on-scroll').forEach((el) => { observer.observe(el); }); diff --git a/frontend/src/components/home/WhyUs.astro b/frontend/src/components/home/WhyUs.astro index 368f4f9..d6e5dc4 100644 --- a/frontend/src/components/home/WhyUs.astro +++ b/frontend/src/components/home/WhyUs.astro @@ -4,7 +4,7 @@ const sectionData = { subtitle: "Почему мы?", title: "Локальная экспертиза в Сургуте", floatingText: - "Знаем специфику судов Сургута и регламенты местной полиции изнутри.", + "Знаем специфику судов Сургута и регламенты местной полиции изнутри.", imageUrl: "/images/home/dogovor.avif", }; @@ -28,25 +28,42 @@ const features = [ ]; --- + + +
Юридическая практика

{sectionData.floatingText}

@@ -57,12 +74,12 @@ const features = [
{sectionData.subtitle}{sectionData.subtitle}

{sectionData.title}

@@ -71,17 +88,17 @@ const features = [
{ features.map((feature, index) => ( -
-
-
-

{feature.title}

-

{feature.desc}

+
+
+
+

{feature.title}

+

{feature.desc}

+
-
)) }
@@ -89,48 +106,16 @@ const features = [
- - + + \ No newline at end of file diff --git a/frontend/src/pages/index1.astro b/frontend/src/pages/index1.astro new file mode 100644 index 0000000..39a8f7d --- /dev/null +++ b/frontend/src/pages/index1.astro @@ -0,0 +1,42 @@ +--- +import Layout from '@layouts/Layout.astro'; +import PageHero from '@components/base/PageHero.astro'; +import Services from "@components/home/Services.astro"; +import Steps from "@components/home/Steps.astro"; +import WhyUs from "@components/home/WhyUs.astro"; +import Reviews from "@components/home/Reviews.astro"; +import Faq from "@components/home/Faq.astro"; +import { SITE_URL } from '@constants'; +--- + + + + + + + + + \ No newline at end of file