astro_avtourist/frontend/src/components/home/Steps.astro

692 lines
No EOL
19 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
import Button from '@components/base/Button.astro';
interface Step {
number: number;
title: string;
description: string;
icon: string;
}
const {
sectionSubtitle = "ПРОСТОЙ ПРОЦЕСС",
sectionTitle = "Как мы работаем",
steps = [
{
number: 1,
title: "Бесплатная консультация",
description: "Позвоните или оставьте заявку. Разберём вашу ситуацию и оценим перспективы дела.",
icon: "📞"
},
{
number: 2,
title: "Анализ документов",
description: "Изучим протоколы, постановления и материалы дела. Найдём слабые места.",
icon: "📋"
},
{
number: 3,
title: "Разработка стратегии",
description: "Подберём оптимальную тактику защиты с учётом практики судов Сургута.",
icon: "🎯"
},
{
number: 4,
title: "Достижение результата",
description: "Представим ваши интересы в суде или ГИБДД. Добьёмся победы.",
icon: "🏆"
}
] as Step[]
} = Astro.props;
---
<!-- КРИТИЧНО: Инлайн-скрипт скрывает элементы ДО рендера, но только если 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;transform:translateY(40px)}';
document.head.appendChild(style);
})();
</script>
<section class="steps-section" id="how-we-work">
<div class="site-container">
<!-- Заголовок секции -->
<div class="section-header">
<div class="subtitle-wrapper animate-on-scroll" data-animation="fade-up">
<div class="subtitle-line"></div>
<span class="subtitle">{sectionSubtitle}</span>
<div class="subtitle-line"></div>
</div>
<h2 class="title animate-on-scroll" data-animation="fade-up" data-delay="100">{sectionTitle}</h2>
<p class="section-description animate-on-scroll" data-animation="fade-up" data-delay="150">
Мы создали прозрачную систему взаимодействия, чтобы вы всегда знали, на каком этапе находится ваше дело
</p>
</div>
<!-- Шаги -->
<div class="steps-container">
{steps.map((step: Step, index: number) => (
<div class="step-card-wrapper animate-on-scroll" data-animation="fade-up" data-delay={index * 100 + 200}>
{index > 0 && (
<div class="mobile-connector mobile-only">
<div class="mobile-connector-line"></div>
<div class="mobile-connector-dot"></div>
</div>
)}
<div class="step-card">
<div class="step-bg"></div>
<div class="step-header">
<div class="step-number">{step.number}</div>
<div class="step-icon-wrapper">
<div class="step-icon-glow"></div>
<div class="step-icon">
<span class="icon-emoji">{step.icon}</span>
</div>
</div>
</div>
<div class="step-content">
<h3 class="step-title">{step.title}</h3>
<p class="step-description">{step.description}</p>
</div>
</div>
</div>
))}
</div>
<!-- Статистика -->
<div class="stats-bar animate-on-scroll" data-animation="fade-up" data-delay="700" id="stats-bar">
<div class="stat-item">
<div class="stat-number" data-target="98" data-suffix="%">0%</div>
<div class="stat-label">успешных дел</div>
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<div class="stat-number" data-target="500" data-suffix="+">0+</div>
<div class="stat-label">довольных клиентов</div>
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<div class="stat-number" data-target="15" data-suffix=" лет">0 лет</div>
<div class="stat-label">опыта</div>
</div>
</div>
<!-- Кнопка CTA -->
<div class="steps-cta animate-on-scroll" data-animation="fade-up" data-delay="800">
<Button variant="gold" size="lg" id="consultation-btn" data-modal-target="consultation-modal">
<span class="cta-text">Начать консультацию бесплатно</span>
</Button>
</div>
</div>
</section>
<style>
:root {
--color-primary: #0a2540;
--color-primary-light: #1e3050;
--color-accent: #eac26e;
--color-accent-dark: #ce9f40;
--color-accent-glow: rgba(234, 194, 110, 0.3);
--color-light: #ffffff;
--color-gray: #94a3b8;
--color-gray-dark: #64748b;
--gradient-bg: linear-gradient(135deg, #0a2540 0%, #1e3050 100%);
--gradient-accent: linear-gradient(135deg, #eac26e 0%, #ce9f40 100%);
--shadow-sm: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
--shadow-glow: 0 0 40px rgba(234, 194, 110, 0.2);
}
.steps-section {
padding: 6rem 1.5rem;
background: var(--gradient-bg);
position: relative;
overflow: hidden;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
.steps-section::before {
content: '';
position: absolute;
top: -50%;
right: -20%;
width: 80%;
height: 150%;
background: radial-gradient(circle, rgba(234, 194, 110, 0.03) 0%, transparent 70%);
pointer-events: none;
border-radius: 50%;
}
.steps-section::after {
content: '';
position: absolute;
bottom: -30%;
left: -10%;
width: 60%;
height: 100%;
background: radial-gradient(circle, rgba(234, 194, 110, 0.02) 0%, transparent 70%);
pointer-events: none;
}
.site-container {
max-width: var(--site-max-width, 1400px);
margin: 0 auto;
position: relative;
z-index: 2;
}
.section-header {
margin-bottom: 4rem;
text-align: center;
}
.subtitle-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1rem;
}
.subtitle-line {
width: 40px;
height: 1px;
background: linear-gradient(90deg, transparent, var(--color-accent), transparent);
}
.subtitle {
color: var(--color-accent);
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 4px;
}
.title {
color: var(--color-light);
font-size: clamp(2rem, 4vw, 3.5rem);
font-weight: 800;
margin: 0 0 1rem 0;
line-height: 1.2;
letter-spacing: -0.02em;
background: linear-gradient(135deg, #ffffff 0%, #e2e8f0 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.section-description {
color: var(--color-gray);
font-size: 1.1rem;
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
}
/* ИСПРАВЛЕНИЕ LCP: opacity: 1 по умолчанию! */
.animate-on-scroll {
opacity: 1;
transform: translateY(0);
will-change: opacity, transform;
}
[data-animation="fade-up"] {
transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
.animate-on-scroll.is-visible {
opacity: 1;
transform: translateY(0);
}
.steps-container {
display: flex;
justify-content: space-between;
gap: 1.5rem;
margin-bottom: 4rem;
position: relative;
}
.step-card-wrapper {
flex: 1;
min-width: 0;
position: relative;
}
.mobile-connector {
display: none;
position: absolute;
top: -24px;
left: 50%;
transform: translateX(-50%);
height: 24px;
width: 2px;
z-index: 3;
}
.mobile-connector-line {
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(234, 194, 110, 0.2) 0%, var(--color-accent) 100%);
}
.mobile-connector-dot {
position: absolute;
bottom: -4px;
left: 50%;
transform: translateX(-50%);
width: 8px;
height: 8px;
background: var(--color-accent);
border-radius: 50%;
box-shadow: 0 0 10px var(--color-accent);
}
.step-card {
position: relative;
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(10px);
border-radius: 24px;
padding: 2rem 1.5rem;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(255, 255, 255, 0.05);
overflow: hidden;
height: 100%;
display: flex;
flex-direction: column;
}
.step-card:hover {
transform: translateY(-8px);
background: rgba(255, 255, 255, 0.05);
border-color: rgba(234, 194, 110, 0.2);
box-shadow: var(--shadow-glow);
}
.step-bg {
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(234, 194, 110, 0.05) 0%, transparent 70%);
opacity: 0;
transition: opacity 0.4s ease;
pointer-events: none;
}
.step-card:hover .step-bg {
opacity: 1;
}
.step-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1.5rem;
position: relative;
}
.step-number {
width: 48px;
height: 48px;
background: var(--gradient-accent);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: 900;
color: var(--color-primary);
box-shadow: var(--shadow-md);
position: relative;
z-index: 2;
flex-shrink: 0;
}
.step-icon-wrapper {
position: relative;
flex-shrink: 0;
}
.step-icon-glow {
position: absolute;
inset: -4px;
background: var(--gradient-accent);
border-radius: 16px;
opacity: 0;
filter: blur(8px);
transition: opacity 0.3s ease;
}
.step-card:hover .step-icon-glow {
opacity: 0.5;
}
.step-icon {
width: 56px;
height: 56px;
background: rgba(255, 255, 255, 0.1);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.step-card:hover .step-icon {
background: rgba(234, 194, 110, 0.15);
border-color: rgba(234, 194, 110, 0.3);
transform: scale(1.05);
}
.icon-emoji {
font-size: 1.75rem;
line-height: 1;
transition: transform 0.3s ease;
}
.step-card:hover .icon-emoji {
transform: scale(1.1);
}
.step-content {
flex: 1;
}
.step-title {
font-size: 1.25rem;
font-weight: 700;
color: var(--color-light);
margin: 0 0 0.75rem 0;
line-height: 1.3;
}
.step-description {
font-size: 0.9rem;
line-height: 1.6;
color: var(--color-gray);
margin: 0;
}
.stats-bar {
display: flex;
justify-content: center;
align-items: center;
gap: 3rem;
padding: 2rem;
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(10px);
border-radius: 60px;
border: 1px solid rgba(255, 255, 255, 0.05);
margin-bottom: 3rem;
flex-wrap: wrap;
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: 800;
background: var(--gradient-accent);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
line-height: 1;
margin-bottom: 0.5rem;
}
.stat-label {
font-size: 0.85rem;
color: var(--color-gray);
text-transform: uppercase;
letter-spacing: 1px;
}
.stat-divider {
width: 1px;
height: 40px;
background: rgba(255, 255, 255, 0.1);
}
.steps-cta {
text-align: center;
}
.steps-cta .btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding: 0.875rem 1.5rem;
font-size: 1.125rem;
font-weight: 700;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
text-transform: none;
letter-spacing: normal;
position: relative;
overflow: hidden;
border: none;
min-height: 3.25rem;
}
.steps-cta .btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s ease;
}
.steps-cta .btn:hover::before {
left: 100%;
}
.steps-cta .btn:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.steps-cta .btn:active:not(:disabled) {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
@media (max-width: 1024px) {
.steps-container {
flex-direction: column;
gap: 0;
}
.step-card-wrapper {
width: 100%;
margin-bottom: 1.5rem;
}
.step-card-wrapper:last-child {
margin-bottom: 0;
}
.mobile-connector {
display: block;
}
.stats-bar {
gap: 2rem;
padding: 1.5rem;
border-radius: 40px;
}
.stat-number {
font-size: 1.5rem;
}
}
@media (max-width: 768px) {
.steps-section {
padding: 4rem 1rem;
}
.section-header {
margin-bottom: 2rem;
}
.subtitle-wrapper {
gap: 0.5rem;
}
.subtitle-line {
width: 30px;
}
.section-description {
font-size: 1rem;
}
.step-card {
padding: 1.5rem;
}
.step-header {
margin-bottom: 1rem;
justify-content: center;
}
.step-number {
width: 40px;
height: 40px;
font-size: 1.25rem;
border-radius: 12px;
}
.step-icon {
width: 48px;
height: 48px;
border-radius: 12px;
margin-left: auto;
margin-right: auto;
}
.step-content {
text-align: center;
}
.icon-emoji {
font-size: 1.5rem;
}
.stats-bar {
flex-direction: column;
gap: 1.5rem;
border-radius: 30px;
}
.stat-divider {
width: 40px;
height: 1px;
}
.steps-cta .btn {
width: 100%;
justify-content: center;
padding: 0.875rem 1.5rem;
font-size: 1rem;
}
}
@media (prefers-reduced-motion: reduce) {
.animate-on-scroll {
opacity: 1 !important;
transform: none !important;
transition: none !important;
}
.step-card,
.steps-cta .btn,
.step-icon,
.icon-emoji {
transition: none;
}
}
</style>
<script>
// Intersection Observer для анимаций при скроллинге
const observerOptions = {
root: null,
rootMargin: '0px',
threshold: 0.15
};
// Анимация счётчиков
function animateCounter(el: HTMLElement) {
const target = parseInt(el.dataset.target || '0');
const suffix = el.dataset.suffix || '';
const duration = 1500;
const startTime = performance.now();
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);
function update(currentTime: number) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeOutCubic(progress);
const currentValue = Math.round(easedProgress * target);
el.textContent = `${currentValue}${suffix}`;
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const el = entry.target as HTMLElement;
const delay = parseInt(el.dataset.delay || '0');
setTimeout(() => {
el.classList.add('is-visible');
if (el.id === 'stats-bar') {
const counters = el.querySelectorAll('.stat-number');
counters.forEach((counter, index) => {
setTimeout(() => {
animateCounter(counter as HTMLElement);
}, index * 150);
});
}
}, delay);
observer.unobserve(el);
}
});
}, observerOptions);
document.querySelectorAll('.animate-on-scroll').forEach((el) => {
observer.observe(el);
});
</script>