first commit

This commit is contained in:
Web-serfer 2026-03-30 20:21:41 +05:00
commit 4a589825c2
297 changed files with 33019 additions and 0 deletions

View file

@ -0,0 +1,155 @@
---
import { PRACTICE_START_YEAR } from "@constants/constants.ts";
import { getYearDeclension } from "@utils/stats.utils.ts";
interface Props {
backgroundImage?: string;
}
const { backgroundImage = "/images/hero/heroImg.avif" } = Astro.props;
const currentYear = new Date().getFullYear();
const yearsOfPractice = currentYear - PRACTICE_START_YEAR;
const yearDeclension = getYearDeclension(yearsOfPractice);
---
<section
class="group relative w-full min-h-screen flex items-center overflow-hidden bg-[var(--color-navy)]"
>
<!-- 1. ФОНОВОЕ ИЗОБРАЖЕНИЕ с эффектом проявления -->
<div class="absolute inset-0 z-0">
<img
src={backgroundImage}
alt="Адвокат Сургута"
width="1920"
height="1080"
class="w-full h-full object-cover object-center
opacity-40 mix-blend-overlay
transition-all duration-1000 ease-out
group-hover:opacity-100 group-hover:mix-blend-normal group-hover:scale-105"
/>
<!-- Градиенты (становятся светлее при наведении) -->
<div
class="absolute inset-0 bg-gradient-to-r from-[var(--color-navy)] via-[var(--color-navy)]/60 to-[var(--color-navy)]/30 transition-all duration-1000 group-hover:via-[var(--color-navy)]/40 group-hover:to-[var(--color-navy)]/20"
>
</div>
<div
class="absolute inset-0 bg-gradient-to-l from-[var(--color-navy)] via-[var(--color-navy)]/60 to-[var(--color-navy)]/30 transition-all duration-1000 group-hover:via-[var(--color-navy)]/40 group-hover:to-[var(--color-navy)]/20"
>
</div>
<div
class="absolute inset-0 bg-gradient-to-t from-[var(--color-navy)] via-transparent to-transparent opacity-60 transition-opacity duration-1000 group-hover:opacity-40"
>
</div>
</div>
<!-- Декоративный элемент (без мерцания, статичный) -->
<div
class="absolute bottom-1/4 left-1/4 w-64 h-64 bg-[var(--color-blue-primary)] opacity-10 rounded-full blur-3xl pointer-events-none transition-opacity duration-1000 group-hover:opacity-20"
>
</div>
<!-- 2. КОНТЕНТ -->
<div class="relative z-10 w-full px-4 md:container md:mx-auto md:px-4 md:max-w-7xl py-32">
<div class="max-w-3xl text-center lg:text-left">
<!-- Бейдж -->
<div
class="inline-flex items-center gap-3 px-4 py-2 bg-white/10 backdrop-blur-md border border-white/20 rounded-full mb-8"
>
<span class="w-2 h-2 bg-[var(--color-gold)] rounded-full"></span>
<span
class="text-[var(--color-gold)] text-[10px] md:text-xs font-bold uppercase tracking-[0.2em]"
>
Юридическая защита
</span>
</div>
<!-- Главный заголовок -->
<h1
class="text-white text-5xl md:text-6xl lg:text-7xl font-bold leading-[1.1] mb-8 uppercase tracking-tight drop-shadow-2xl"
>
Лучший адвокат в <br />
<span
class="text-transparent bg-clip-text bg-gradient-to-r from-[var(--color-gold)] to-[var(--color-gold-hover)] italic font-serif"
>Сургуте</span
>
</h1>
<!-- Описание -->
<p
class="text-gray-200 text-lg md:text-xl leading-relaxed mb-12 max-w-2xl border-l-0 md:border-l-4 border-[var(--color-gold)] pl-0 md:pl-6 transition-colors duration-700 group-hover:text-white text-center lg:text-left"
>
Высококвалифицированная правовая поддержка по самым сложным делам.
{yearsOfPractice}
{yearDeclension} безупречной репутации и сотни выигранных процессов в судах
ХМАО - Югры.
</p>
<!-- Кнопки -->
<div
class="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start items-center lg:items-start"
>
<a
href="#contact"
class="group/btn relative px-8 py-4 bg-gradient-to-r from-[var(--color-gold)] to-[var(--color-gold-hover)] text-white font-bold rounded-xl overflow-hidden shadow-lg shadow-[var(--color-gold)]/30 hover:shadow-xl hover:shadow-[var(--color-gold)]/40 transition-all duration-300 hover:-translate-y-0.5"
>
<span class="relative z-10 flex items-center gap-2">
Записаться на прием
<svg
class="w-5 h-5 transform group-hover/btn:translate-x-1 transition-transform"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
</svg>
</span>
</a>
<a
href="#services"
class="group/btn px-8 py-4 bg-white/10 backdrop-blur-md border border-white/30 text-white font-bold rounded-xl hover:bg-white/20 transition-all duration-300 hover:-translate-y-0.5"
>
<span class="flex items-center gap-2">
Направления практики
<svg
class="w-5 h-5 transform group-hover/btn:translate-x-1 transition-transform"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</span>
</a>
</div>
</div>
</div>
<!-- Индикатор прокрутки -->
<div
class="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 text-white/50 text-xs font-medium tracking-widest uppercase opacity-0 transition-opacity duration-1000 group-hover:opacity-100"
>
<span>Прокрутите</span>
<svg
class="w-4 h-4 animate-bounce"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
</svg>
</div>
</section>

View file

@ -0,0 +1,211 @@
---
const reviews = [
{
author: "Александр Волков",
role: "ООО «СтройГрупп»",
text: "Профессионал своего дела! Помог выиграть сложнейший арбитражный спор. Всегда на связи, всё четко и по делу.",
rating: 5,
},
{
author: "Мария Смирнова",
role: "Частное лицо",
text: "Обращалась по вопросу раздела имущества. Процесс был тяжелым, но благодаря поддержке адвоката удалось достичь мирного соглашения.",
rating: 5,
},
{
author: "Дмитрий Кузнецов",
role: "Предприниматель",
text: "Лучшая юридическая консультация, которую я когда-либо получал. Очень грамотно разобрали мою ситуацию по уголовному делу.",
rating: 5,
},
{
author: "Елена Васечкина",
role: "Гендиректор «Вектор»",
text: "Сотрудничаем на постоянной основе уже 3 года. Полное юридическое сопровождение бизнеса на высшем уровне.",
rating: 5,
},
];
---
<section id="reviews" class="relative py-24 bg-[var(--color-navy)] overflow-hidden">
<!-- Декоративный фон -->
<div class="absolute inset-0 pointer-events-none">
<div class="absolute top-0 left-1/4 w-96 h-96 bg-[var(--color-blue-primary)] opacity-10 rounded-full blur-3xl"></div>
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-[var(--color-gold)] opacity-10 rounded-full blur-3xl"></div>
</div>
<div class="w-full px-4 md:container md:mx-auto md:px-4 md:max-w-7xl relative z-10">
<!-- Заголовок -->
<div class="text-center mb-16">
<span class="inline-block px-4 py-2 bg-white/10 text-[var(--color-gold)] text-xs font-bold uppercase tracking-wider rounded-full mb-4">
Репутация
</span>
<h2 class="text-4xl md:text-5xl font-bold text-white">
Отзывы наших <span class="text-transparent bg-clip-text bg-gradient-to-r from-[var(--color-gold)] to-[var(--color-gold-hover)]">доверителей</span>
</h2>
</div>
<!-- Карусель -->
<div class="relative group" id="reviews-carousel">
<!-- Кнопки навигации -->
<button id="prev-btn" class="absolute -left-4 lg:-left-12 top-1/2 -translate-y-1/2 z-20 w-12 h-12 bg-white/10 backdrop-blur-md border border-white/20 rounded-full flex items-center justify-center text-white hover:bg-[var(--color-gold)] hover:border-[var(--color-gold)] transition-all duration-300 opacity-0 group-hover:opacity-100 cursor-pointer">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
<button id="next-btn" class="absolute -right-4 lg:-right-12 top-1/2 -translate-y-1/2 z-20 w-12 h-12 bg-white/10 backdrop-blur-md border border-white/20 rounded-full flex items-center justify-center text-white hover:bg-[var(--color-gold)] hover:border-[var(--color-gold)] transition-all duration-300 opacity-0 group-hover:opacity-100 cursor-pointer">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</button>
<!-- Трек -->
<div class="overflow-hidden">
<div class="flex gap-6" id="reviews-track">
{reviews.map((review) => (
<div class="flex-shrink-0 w-full md:w-[calc(50%-12px)] lg:w-[calc(33.333%-16px)]">
<div class="h-full bg-white/10 backdrop-blur-md border border-white/10 rounded-2xl p-8 hover:bg-white/15 hover:border-[var(--color-gold)]/30 transition-all duration-300">
<!-- Звезды -->
<div class="flex gap-1 mb-6">
{Array.from({ length: 5 }).map((_, i) => (
<svg class={`w-5 h-5 ${i < review.rating ? 'text-[var(--color-gold)]' : 'text-gray-600'}`} fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
</svg>
))}
</div>
<blockquote class="text-gray-300 text-lg leading-relaxed mb-8 italic">
"{review.text}"
</blockquote>
<div class="pt-6 border-t border-white/10">
<h4 class="text-white font-bold text-lg mb-1">{review.author}</h4>
<span class="text-[var(--color-gold)] text-sm font-medium">{review.role}</span>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
<style>
@keyframes scroll {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
.animate-scroll {
animation: scroll 30s linear infinite;
}
.animate-scroll:hover {
animation-play-state: paused;
}
</style>
<script>
const initReviewsCarousel = () => {
const track = document.getElementById('reviews-track');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
if (!track || !prevBtn || !nextBtn) return;
let currentIndex = 0;
let autoScrollInterval: ReturnType<typeof setInterval>;
const cards = track.querySelectorAll('.flex-shrink-0');
const totalCards = cards.length;
// Определяем количество видимых карточек
const getVisibleCards = () => {
if (window.innerWidth >= 1024) return 3;
if (window.innerWidth >= 768) return 2;
return 1;
};
// Обновление позиции трека
const updateTrackPosition = () => {
const cardWidth = cards[0]?.getBoundingClientRect().width || 0;
const gap = 24; // 24px gap между карточками
const visibleCards = getVisibleCards();
const maxIndex = totalCards - visibleCards;
// Ограничиваем индекс
if (currentIndex < 0) currentIndex = 0;
if (currentIndex > maxIndex) currentIndex = maxIndex;
const offset = -(currentIndex * (cardWidth + gap));
track.style.transform = `translateX(${offset}px)`;
track.style.transition = 'transform 0.5s ease-in-out';
};
// Автопрокрутка
const startAutoScroll = () => {
autoScrollInterval = setInterval(() => {
const visibleCards = getVisibleCards();
const maxIndex = totalCards - visibleCards;
if (currentIndex >= maxIndex) {
currentIndex = 0;
} else {
currentIndex++;
}
updateTrackPosition();
}, 3000);
};
const stopAutoScroll = () => {
clearInterval(autoScrollInterval);
};
// Обработчики кнопок
prevBtn.addEventListener('click', () => {
stopAutoScroll();
const visibleCards = getVisibleCards();
currentIndex = Math.max(0, currentIndex - visibleCards);
updateTrackPosition();
startAutoScroll();
});
nextBtn.addEventListener('click', () => {
stopAutoScroll();
const visibleCards = getVisibleCards();
const maxIndex = totalCards - visibleCards;
currentIndex = Math.min(maxIndex, currentIndex + visibleCards);
updateTrackPosition();
startAutoScroll();
});
// Дублируем карточки для бесконечной прокрутки (как в оригинале)
const cloneCards = () => {
cards.forEach(card => {
const clone = card.cloneNode(true);
clone.setAttribute('data-cloned', 'true');
track.appendChild(clone);
});
};
// Инициализация
cloneCards();
updateTrackPosition();
startAutoScroll();
// Пересчёт при изменении размера окна
window.addEventListener('resize', () => {
currentIndex = 0;
updateTrackPosition();
});
};
// Запуск после загрузки страницы
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initReviewsCarousel);
} else {
initReviewsCarousel();
}
// Для Astro View Transitions
document.addEventListener('astro:page-load', initReviewsCarousel);
</script>

View file

@ -0,0 +1,109 @@
---
const services = [
{
title: "Уголовные дела",
desc: "Эффективная защита на стадиях предварительного следствия и в суде по делам любой сложности.",
href: "/services/criminal",
icon: "scale"
},
{
title: "Гражданские дела",
desc: "Защита прав собственности, жилищные споры и взыскание задолженностей в судебном порядке.",
href: "/services/civil",
icon: "balance"
},
{
title: "Семейные дела",
desc: "Расторжение брака, раздел совместно нажитого имущества и определение места жительства детей.",
href: "/services/family",
icon: "users"
},
{
title: "Административные дела",
desc: "Профессиональное представительство интересов в административных спорах и ДТП.",
href: "/services/administrative",
icon: "shield"
},
{
title: "Арбитражные дела",
desc: "Защита интересов бизнеса в арбитражных судах всех инстанций.",
href: "/services/arbitration",
icon: "briefcase"
},
{
title: "Защита должников",
desc: "Защита от финансовых претензий банков и коллекторских организаций.",
href: "/services/debt-protection",
icon: "protect"
}
];
const iconPaths: Record<string, string> = {
scale: "M3 6l3 1m0 0l-3 9a5.002 5.002 0 006.001 0M6 7l3 9M6 7l6-2m6 2l3-1m-3 1l-3 9a5.002 5.002 0 006.001 0M18 7l3 9m-3-9l-6-2m0-2v2m0 16V5m0 16H9m3 0h3",
balance: "M12 2a10 10 0 100 20 10 10 0 000-20zm0 18a8 8 0 110-16 8 8 0 010 16zm-1-13h2v6h-2zm0 8h2v2h-2z",
users: "M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z",
shield: "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z",
briefcase: "M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z",
protect: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
};
---
<section id="services" class="relative py-24 bg-white overflow-hidden">
<!-- Декоративный фон -->
<div class="absolute inset-0 pointer-events-none">
<div class="absolute top-0 right-0 w-1/2 h-full bg-gradient-to-l from-gray-50 to-transparent"></div>
</div>
<div class="w-full px-4 md:container md:mx-auto md:px-4 md:max-w-7xl relative z-10">
<!-- Заголовок -->
<div class="text-center mb-16">
<span class="inline-block px-4 py-2 bg-[var(--color-gold)]/10 text-[var(--color-gold)] text-xs font-bold uppercase tracking-wider rounded-full mb-4">
Специализация
</span>
<h2 class="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
Направления <span class="text-[var(--color-blue-primary)]">практики</span>
</h2>
<div class="w-24 h-1.5 bg-gradient-to-r from-[var(--color-gold)] to-[var(--color-gold-hover)] mx-auto rounded-full"></div>
<p class="mt-6 text-gray-600 max-w-2xl mx-auto">
Специализируюсь на представлении интересов в самых сложных правовых спорах на территории Сургута и ХМАО-Югры
</p>
</div>
<!-- Сетка -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{services.map((item, index) => (
<a
href={item.href}
class="group relative bg-white/80 backdrop-blur-xl p-8 rounded-2xl shadow-lg border border-gray-100 hover:shadow-2xl hover:shadow-[var(--color-blue-primary)]/10 hover:-translate-y-2 hover:border-[var(--color-blue-primary)]/20 transition-all duration-500 overflow-hidden"
>
<!-- Декоративный градиент -->
<div class="absolute inset-0 bg-gradient-to-br from-[var(--color-blue-primary)]/5 via-transparent to-[var(--color-gold)]/5 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<!-- Иконка -->
<div class="relative w-16 h-16 bg-gradient-to-br from-[var(--color-blue-primary)] to-blue-600 rounded-2xl flex items-center justify-center mb-6 shadow-lg shadow-blue-500/30 group-hover:scale-110 group-hover:shadow-xl transition-all duration-300">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d={iconPaths[item.icon]}/>
</svg>
</div>
<!-- Контент -->
<h3 class="relative text-xl font-bold text-gray-900 mb-3 group-hover:text-[var(--color-blue-primary)] transition-colors">
{item.title}
</h3>
<p class="relative text-gray-600 leading-relaxed mb-6">
{item.desc}
</p>
<!-- Ссылка -->
<div class="relative flex items-center gap-2 text-[var(--color-gold)] font-bold text-sm uppercase tracking-wider group-hover:gap-3 transition-all">
<span>Подробнее</span>
<svg class="w-5 h-5 transform group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"/>
</svg>
</div>
</a>
))}
</div>
</div>
</section>

View file

@ -0,0 +1,105 @@
---
import { getDynamicStats, getYearDeclension, getCaseDeclension, getClientDeclension } from '@utils/stats.utils.ts';
const stats = getDynamicStats();
const statIcons = [
"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z", // Calendar
"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z", // Check circle
"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z", // Users
"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" // Clock
];
---
<section class="relative py-20 overflow-hidden bg-gray-50">
<!-- Декоративный фон -->
<div class="absolute inset-0 pointer-events-none">
<div class="absolute top-0 left-1/4 w-96 h-96 bg-[var(--color-blue-primary)] opacity-5 rounded-full blur-3xl"></div>
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-[var(--color-gold)] opacity-5 rounded-full blur-3xl"></div>
</div>
<div class="w-full px-4 md:container md:mx-auto md:px-4 md:max-w-7xl relative z-10">
<div class="grid grid-cols-2 lg:grid-cols-4 gap-6">
{stats.map((stat, index) => (
<div class="group relative bg-white/80 backdrop-blur-xl p-8 rounded-2xl shadow-lg border border-white/50 hover:shadow-2xl hover:shadow-[var(--color-blue-primary)]/10 hover:-translate-y-1 transition-all duration-500 text-center overflow-hidden">
<!-- Декоративный фон при hover -->
<div class="absolute inset-0 bg-gradient-to-br from-[var(--color-blue-primary)]/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<!-- Иконка -->
<div class="relative w-14 h-14 mx-auto mb-4 bg-[var(--color-blue-primary)]/10 rounded-xl flex items-center justify-center group-hover:bg-[var(--color-blue-primary)] transition-colors duration-300">
<svg class="w-7 h-7 text-[var(--color-blue-primary)] group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d={statIcons[index]}/>
</svg>
</div>
<!-- Цифра -->
<div class="flex items-baseline justify-center gap-1 mb-2">
<span
class="stat-counter text-4xl font-black text-gray-900 tracking-tight"
data-target={stat.number}
>
0
</span>
<span class="text-2xl font-bold text-[var(--color-blue-primary)]">{stat.suffix}</span>
</div>
<!-- Текст -->
<p class="text-xs font-bold text-gray-500 uppercase tracking-wider">
{stat.text.includes('Лет практики') ? `${stat.number} ${getYearDeclension(stat.number)} практики` :
stat.text.includes('Успешных дел') ? `${stat.number} ${stat.suffix} ${stat.text.toLowerCase()}` :
stat.text.includes('Клиентов') ? `${stat.number} довольных ${getClientDeclension(stat.number)}` :
stat.text.includes('На связи') ? 'в режиме 24/7' : stat.text}
</p>
</div>
))}
</div>
</div>
</section>
<script>
const animateStats = () => {
const counters = document.querySelectorAll('.stat-counter');
const speed = 2000;
const startAnimation = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const counter = entry.target as HTMLElement;
const target = +counter.getAttribute('data-target')!;
let startTime: number | null = null;
const step = (timestamp: number) => {
if (!startTime) startTime = timestamp;
const progress = timestamp - startTime;
const percentage = Math.min(progress / speed, 1);
const ease = 1 - Math.pow(1 - percentage, 3);
const current = Math.floor(ease * target);
counter.innerText = current.toString();
if (percentage < 1) {
window.requestAnimationFrame(step);
} else {
counter.innerText = target.toString();
}
};
window.requestAnimationFrame(step);
observer.unobserve(counter);
}
});
};
const observer = new IntersectionObserver(startAnimation, {
root: null,
rootMargin: '0px',
threshold: 0.5
});
counters.forEach(counter => observer.observe(counter));
};
animateStats();
document.addEventListener('astro:after-swap', animateStats);
</script>

View file

@ -0,0 +1,114 @@
---
import { Image } from 'astro:assets';
import { PRACTICE_START_YEAR } from '@constants/constants.ts';
import { getYearDeclension } from '@utils/stats.utils.ts';
import whyUsImage from '@assets/images/home/whyus/WhyUs.png';
const currentYear = new Date().getFullYear();
const yearsOfPractice = currentYear - PRACTICE_START_YEAR;
const yearDeclension = getYearDeclension(yearsOfPractice);
const features = [
{
title: "Абсолютная анонимность",
desc: "Строгое соблюдение адвокатской тайны. Любая информация остается полностью конфиденциальной.",
icon: "shield-check"
},
{
title: "Глубокая экспертиза",
desc: "Опыт включает сотни выигранных дел в различных инстанциях, от мировых судов до Верховного Суда РФ.",
icon: "academic-cap"
},
{
title: "Прозрачные условия",
desc: "Фиксированная стоимость услуг, прописанная в договоре. Никаких скрытых платежей.",
icon: "currency"
}
];
const iconPaths: Record<string, string> = {
"shield-check": "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z",
"academic-cap": "M12 14l9-5-9-5-9 5 9 5z M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z M12 14L4.5 8.25",
"currency": "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
};
---
<section class="relative py-24 bg-gray-50 overflow-hidden">
<!-- Декоративный фон -->
<div class="absolute inset-0 pointer-events-none">
<div class="absolute top-1/2 left-0 w-96 h-96 bg-[var(--color-gold)] opacity-5 rounded-full blur-3xl -translate-y-1/2"></div>
</div>
<div class="w-full px-4 md:container md:mx-auto md:px-4 md:max-w-7xl relative z-10">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center">
<!-- Левая колонка: Изображение -->
<div class="relative group">
<div class="absolute -inset-4 bg-gradient-to-br from-[var(--color-gold)]/20 to-[var(--color-blue-primary)]/20 rounded-3xl blur-2xl opacity-50 group-hover:opacity-70 transition-opacity"></div>
<div class="relative rounded-2xl overflow-hidden shadow-2xl">
<Image
src={whyUsImage}
alt="Адвокатский стол"
class="w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-700"
width="800"
height="600"
loading="lazy"
/>
<!-- Плавающая карточка -->
<div class="absolute bottom-8 right-8 bg-white/95 backdrop-blur-xl p-6 rounded-2xl shadow-2xl border border-white/50 max-w-[240px]">
<div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-[var(--color-gold)] to-[var(--color-gold-hover)] rounded-xl flex items-center justify-center shadow-lg flex-shrink-0">
<span class="text-2xl font-bold text-white">{yearsOfPractice}</span>
</div>
<div>
<span class="block text-xs font-bold text-gray-500 uppercase tracking-wider mb-1">Опыт работы</span>
<span class="text-sm text-gray-900 font-semibold leading-tight">{yearDeclension} безупречной практики</span>
</div>
</div>
</div>
</div>
</div>
<!-- Правая колонка: Контент -->
<div>
<div class="mb-12">
<span class="inline-block px-4 py-2 bg-[var(--color-blue-primary)]/10 text-[var(--color-blue-primary)] text-xs font-bold uppercase tracking-wider rounded-full mb-4">
Наше преимущество
</span>
<h2 class="text-4xl md:text-5xl font-bold text-gray-900 leading-tight">
Почему выбирают <span class="text-[var(--color-blue-primary)]">нас?</span>
</h2>
</div>
<div class="space-y-8">
{features.map((item, index) => (
<div class="flex gap-6 group">
<div class="relative flex-shrink-0">
<div class="w-14 h-14 bg-white rounded-2xl shadow-lg border border-gray-100 flex items-center justify-center group-hover:bg-[var(--color-blue-primary)] group-hover:border-[var(--color-blue-primary)] transition-all duration-300">
<svg class="w-6 h-6 text-[var(--color-blue-primary)] group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d={iconPaths[item.icon]}/>
</svg>
</div>
<!-- Линия соединительная -->
{index !== features.length - 1 && (
<div class="absolute top-14 left-1/2 w-px h-8 bg-gray-200 -translate-x-1/2"></div>
)}
</div>
<div class="pt-2">
<h3 class="text-xl font-bold text-gray-900 mb-2 group-hover:text-[var(--color-blue-primary)] transition-colors">
{item.title}
</h3>
<p class="text-gray-600 leading-relaxed">
{item.desc}
</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>