Initial commit

This commit is contained in:
2026-02-10 16:22:14 +05:00
parent bc64b84640
commit 505f49fbd9
6720 changed files with 1357701 additions and 0 deletions

35
components/Benefits.tsx Normal file
View File

@@ -0,0 +1,35 @@
import React from 'react';
import { BENEFITS } from '../constants';
const Benefits: React.FC = () => {
return (
<div className="py-24 bg-white">
<div className="container mx-auto px-6">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
<div className="lg:col-span-4">
<span className="text-sm text-gray-500 uppercase tracking-widest mb-2 block">Выбирая нас</span>
<h2 className="text-4xl font-bold text-gray-900 mb-6">Вы получаете</h2>
<p className="text-gray-500 text-sm">Нас выбирают люди</p>
</div>
<div className="lg:col-span-8 grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-16">
{BENEFITS.map((item, index) => (
<div key={index} className="flex flex-col items-start gap-4">
<div className="w-12 h-12 rounded-full bg-brand-orange/20 flex items-center justify-center text-brand-orange">
<item.icon size={24} />
</div>
<p className="text-gray-700 leading-relaxed font-medium">
{item.description}
</p>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default Benefits;

135
components/Footer.tsx Normal file
View File

@@ -0,0 +1,135 @@
import React from 'react';
import { Send, MapPin, Mail, Phone } from 'lucide-react';
import { Link } from 'react-router-dom';
const Footer: React.FC = () => {
return (
<footer className="bg-[#1a0f0f] text-white py-20 rounded-t-[3rem] mt-auto" id="contacts">
<div className="container mx-auto px-6">
<div className="flex flex-col lg:flex-row gap-20">
{/* Contacts Section with Accent */}
<div className="lg:w-1/2">
<h2 className="text-4xl font-bold mb-8 text-brand-orange">Свяжитесь с нами</h2>
<p className="text-gray-300 text-lg mb-10 max-w-md">
Готовы ответить на ваши вопросы и предложить лучшие решения для вашего проекта
</p>
<div className="space-y-6 max-w-md">
<a
href="tel:83472927370"
className="flex items-start gap-4 p-6 bg-brand-orange/10 border border-brand-orange/30 rounded-2xl hover:bg-brand-orange hover:scale-105 transition-all duration-300 group"
>
<Phone size={28} className="text-brand-orange group-hover:text-white flex-shrink-0" />
<div>
<p className="text-xs text-gray-400 mb-1 group-hover:text-white/80">Телефон</p>
<p className="text-xl font-bold text-white">8 (347) 292 73 70</p>
<p className="text-sm text-gray-400 mt-1 group-hover:text-white/70">Звоните с 9:00 до 18:00</p>
</div>
</a>
<a
href="mailto:gw@geowektor.ru"
className="flex items-start gap-4 p-6 bg-brand-orange/10 border border-brand-orange/30 rounded-2xl hover:bg-brand-orange hover:scale-105 transition-all duration-300 group"
>
<Mail size={28} className="text-brand-orange group-hover:text-white flex-shrink-0" />
<div>
<p className="text-xs text-gray-400 mb-1 group-hover:text-white/80">Email</p>
<p className="text-xl font-bold text-white">gw@geowektor.ru</p>
<p className="text-sm text-gray-400 mt-1 group-hover:text-white/70">Ответим в течение часа</p>
</div>
</a>
<div className="flex items-start gap-4 p-6 bg-brand-orange/10 border border-brand-orange/30 rounded-2xl">
<MapPin size={28} className="text-brand-orange flex-shrink-0" />
<div>
<p className="text-xs text-gray-400 mb-1">Адрес</p>
<p className="text-lg font-bold text-white">450001, РБ, г. Уфа</p>
<p className="text-white/90">ул. Комсомольская 19/1</p>
</div>
</div>
</div>
</div>
{/* Links & Social Section */}
<div className="lg:w-1/2 grid grid-cols-1 md:grid-cols-3 gap-8">
{/* Основная навигация */}
<div>
<h4 className="font-bold mb-6 text-lg">Компания</h4>
<ul className="space-y-3 text-sm text-gray-400">
<li><Link to="/" className="hover:text-brand-orange transition-colors">Главная</Link></li>
<li><Link to="/about" className="hover:text-brand-orange transition-colors">О компании</Link></li>
<li><Link to="/projects" className="hover:text-brand-orange transition-colors">Проекты</Link></li>
<li><Link to="/fleet" className="hover:text-brand-orange transition-colors">Автопарк</Link></li>
<li><Link to="/certificates" className="hover:text-brand-orange transition-colors">Сертификаты</Link></li>
<li><Link to="/contacts" className="hover:text-brand-orange transition-colors">Контакты</Link></li>
</ul>
</div>
{/* Услуги */}
<div>
<h4 className="font-bold mb-6 text-lg">Услуги</h4>
<ul className="space-y-3 text-sm text-gray-400">
<li><Link to="/services" className="hover:text-brand-orange transition-colors">Все услуги</Link></li>
<li><Link to="/services/surveying" className="hover:text-brand-orange transition-colors">Инженерные изыскания</Link></li>
<li><Link to="/services/design" className="hover:text-brand-orange transition-colors">Проектирование</Link></li>
<li><Link to="/services/construction" className="hover:text-brand-orange transition-colors">Строительство</Link></li>
<li><Link to="/services/soil-survey" className="hover:text-brand-orange transition-colors">Обследование грунтов</Link></li>
<li><Link to="/services/building-survey" className="hover:text-brand-orange transition-colors">Обследование зданий</Link></li>
<li><Link to="/services/land-survey" className="hover:text-brand-orange transition-colors">Кадастровые работы</Link></li>
</ul>
</div>
{/* Лаборатории и соцсети */}
<div>
<h4 className="font-bold mb-6 text-lg">Лаборатории</h4>
<ul className="space-y-3 text-sm text-gray-400 mb-8">
<li><Link to="/laboratories/soil" className="hover:text-brand-orange transition-colors">Грунтовая лаборатория</Link></li>
<li><Link to="/laboratories/radiation" className="hover:text-brand-orange transition-colors">Радиационная лаборатория</Link></li>
</ul>
<h4 className="font-bold mb-4 text-lg">Мы в интернете</h4>
<div className="flex gap-4 mb-8">
<a
href="https://t.me/ooo_geo_wektor"
target="_blank"
rel="noopener noreferrer"
className="w-12 h-12 rounded-full bg-brand-orange flex items-center justify-center text-white cursor-pointer hover:bg-white hover:text-brand-orange transition-all transform hover:scale-110"
title="Telegram"
>
<Send size={22} />
</a>
<a
href="https://vk.com/geowektor_ru"
target="_blank"
rel="noopener noreferrer"
className="w-12 h-12 rounded-full bg-brand-orange flex items-center justify-center text-white cursor-pointer hover:bg-white hover:text-brand-orange transition-all transform hover:scale-110"
title="VK"
>
<div className="font-bold text-sm">Vk</div>
</a>
</div>
<div className="space-y-3 text-sm">
<Link
to="/privacy-policy"
className="text-gray-400 hover:text-brand-orange transition-colors block"
>
Политика конфиденциальности
</Link>
</div>
<div className="mt-8 text-xs text-gray-600">
©2025 ООО «ГеоВектор».<br />
Все права защищены.
</div>
</div>
</div>
</div>
</div>
</footer>
);
};
export default Footer;

63
components/Hero.tsx Normal file
View File

@@ -0,0 +1,63 @@
import React from 'react';
import { STATS } from '../constants';
import { Link } from 'react-router-dom';
const Hero: React.FC = () => {
return (
<div className="relative w-full min-h-screen bg-brand-dark text-white flex flex-col">
{/* Background Image Overlay */}
<div className="absolute inset-0 z-0">
<img
src="/media/images/hero/hero-main.png"
alt="Construction Site"
className="w-full h-full object-cover opacity-40"
loading="eager"
/>
<div className="absolute inset-0 bg-gradient-to-r from-brand-dark/90 via-brand-dark/50 to-brand-dark/70 animate-gradient" />
</div>
{/* Hero Content */}
<div className="relative z-10 container mx-auto px-6 flex-grow flex flex-col justify-center py-32 md:py-20">
<div className="max-w-5xl mx-auto text-center mt-10 md:mt-0">
<h1 className="text-4xl md:text-5xl lg:text-7xl font-bold leading-tight mb-8">
Инженерные изыскания,
<br />
<span className="text-brand-orange">проектирование</span> и
<br />
<span className="text-brand-orange">строительство</span>
</h1>
<p className="text-gray-300 text-lg md:text-xl mb-12 max-w-3xl mx-auto leading-relaxed">
ООО «ГеоВектор» профессиональные решения для вашего проекта от изысканий до сдачи объекта.
Современное оборудование, опытные специалисты и соблюдение всех норм и стандартов.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
<Link
to="/contacts"
className="inline-flex items-center justify-center bg-brand-orange text-white font-bold py-4 px-8 rounded-xl hover:bg-orange-600 transition-all duration-300 shadow-lg hover:shadow-xl hover:scale-105"
>
Рассчитать стоимость проекта
</Link>
<Link
to="/services"
className="inline-flex items-center justify-center bg-white/10 backdrop-blur-sm text-white font-bold py-4 px-8 rounded-xl hover:bg-white/20 transition-all duration-300 border-2 border-white/30"
>
Наши услуги
</Link>
</div>
</div>
{/* Stats */}
<div className="mt-24 grid grid-cols-1 md:grid-cols-3 gap-8 border-t border-white/10 pt-12 max-w-5xl mx-auto w-full">
{STATS.map((stat, idx) => (
<div key={idx} className="flex flex-col items-center text-center">
<span className="text-4xl md:text-5xl font-bold text-brand-orange mb-2">{stat.value}</span>
<p className="text-gray-400 text-sm md:text-base leading-relaxed">{stat.label}</p>
</div>
))}
</div>
</div>
</div>
);
};
export default Hero;

186
components/Laboratories.tsx Normal file
View File

@@ -0,0 +1,186 @@
import React from 'react';
import { Microscope, Activity, ArrowRight, CheckCircle2, Shield } from 'lucide-react';
import { Link } from 'react-router-dom';
const Laboratories: React.FC = () => {
return (
<section className="py-20 bg-gradient-to-b from-white to-gray-50">
<div className="container mx-auto px-6">
<div className="text-center mb-16">
<div className="inline-flex items-center justify-center w-16 h-16 bg-brand-orange/10 rounded-full mb-6">
<Microscope className="text-brand-orange" size={32} />
</div>
<h2 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
Наши лаборатории
</h2>
<p className="text-gray-600 text-lg max-w-2xl mx-auto">
Современное оборудование и квалифицированные специалисты для проведения
комплексных исследований
</p>
</div>
<div className="max-w-7xl mx-auto grid md:grid-cols-2 gap-8">
{/* Грунтовая лаборатория */}
<div className="group bg-white rounded-3xl overflow-hidden shadow-xl hover:shadow-2xl transition-all duration-300 border-2 border-transparent hover:border-brand-orange">
{/* Изображение с наложением */}
<div className="relative h-64 overflow-hidden">
<img
src="/media/images/services/soil-survey.png"
alt="Грунтовая лаборатория"
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent"></div>
{/* Иконка */}
<div className="absolute top-6 left-6">
<div className="w-16 h-16 bg-white/20 backdrop-blur-md rounded-xl flex items-center justify-center border-2 border-white/30">
<Microscope className="text-white" size={32} />
</div>
</div>
{/* Заголовок на изображении */}
<div className="absolute bottom-6 left-6 right-6">
<h3 className="text-2xl font-bold text-white mb-2">
Грунтовая лаборатория
</h3>
<p className="text-white/90 text-sm">
Исследования физических, механических и химических свойств
</p>
</div>
</div>
{/* Контент карточки */}
<div className="p-8">
<div className="space-y-4 mb-8">
<div className="flex items-start gap-3">
<CheckCircle2 className="text-brand-orange flex-shrink-0 mt-1" size={20} />
<div>
<p className="text-gray-700 font-medium">Физические свойства грунтов</p>
<p className="text-sm text-gray-500">Влажность, плотность, гранулометрический состав</p>
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircle2 className="text-brand-orange flex-shrink-0 mt-1" size={20} />
<div>
<p className="text-gray-700 font-medium">Механические характеристики</p>
<p className="text-sm text-gray-500">Прочность, деформируемость, сжимаемость</p>
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircle2 className="text-brand-orange flex-shrink-0 mt-1" size={20} />
<div>
<p className="text-gray-700 font-medium">Химический анализ</p>
<p className="text-sm text-gray-500">Агрессивность грунтов, анализ воды</p>
</div>
</div>
</div>
{/* Кнопка */}
<Link
to="/laboratories/soil"
className="flex items-center justify-center gap-2 w-full py-4 bg-gradient-to-r from-brand-orange to-orange-600 text-white font-bold rounded-xl hover:from-orange-600 hover:to-brand-orange transition-all duration-300 group/btn"
>
Подробнее о лаборатории
<ArrowRight className="group-hover/btn:translate-x-1 transition-transform" size={20} />
</Link>
</div>
</div>
{/* Радиационная лаборатория */}
<div className="group bg-white rounded-3xl overflow-hidden shadow-xl hover:shadow-2xl transition-all duration-300 border-2 border-transparent hover:border-brand-orange">
{/* Изображение с наложением */}
<div className="relative h-64 overflow-hidden">
<img
src="/media/images/services/engineering-surveys.png"
alt="Радиационная лаборатория"
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent"></div>
{/* Иконка */}
<div className="absolute top-6 left-6">
<div className="w-16 h-16 bg-white/20 backdrop-blur-md rounded-xl flex items-center justify-center border-2 border-white/30">
<Activity className="text-white" size={32} />
</div>
</div>
{/* Заголовок на изображении */}
<div className="absolute bottom-6 left-6 right-6">
<h3 className="text-2xl font-bold text-white mb-2">
Радиационная лаборатория
</h3>
<p className="text-white/90 text-sm">
Профессиональные исследования радиационной безопасности
</p>
</div>
</div>
{/* Контент карточки */}
<div className="p-8">
<div className="space-y-4 mb-8">
<div className="flex items-start gap-3">
<Shield className="text-brand-orange flex-shrink-0 mt-1" size={20} />
<div>
<p className="text-gray-700 font-medium">Радиационный контроль</p>
<p className="text-sm text-gray-500">Измерение уровня радона и гамма-излучения</p>
</div>
</div>
<div className="flex items-start gap-3">
<Shield className="text-brand-orange flex-shrink-0 mt-1" size={20} />
<div>
<p className="text-gray-700 font-medium">Аккредитованная лаборатория</p>
<p className="text-sm text-gray-500">Техническая компетентность и независимость</p>
</div>
</div>
<div className="flex items-start gap-3">
<Shield className="text-brand-orange flex-shrink-0 mt-1" size={20} />
<div>
<p className="text-gray-700 font-medium">Современное оборудование</p>
<p className="text-sm text-gray-500">КАМЕРА-01, ДКГ-02У, ДРБП-03</p>
</div>
</div>
</div>
{/* Кнопка */}
<Link
to="/laboratories/radiation"
className="flex items-center justify-center gap-2 w-full py-4 bg-gradient-to-r from-brand-orange to-orange-600 text-white font-bold rounded-xl hover:from-orange-600 hover:to-brand-orange transition-all duration-300 group/btn"
>
Подробнее о лаборатории
<ArrowRight className="group-hover/btn:translate-x-1 transition-transform" size={20} />
</Link>
</div>
</div>
</div>
{/* Дополнительная информация */}
<div className="mt-16 max-w-4xl mx-auto">
<div className="bg-gradient-to-br from-gray-900 to-gray-800 text-white rounded-2xl p-8 md:p-12 text-center">
<h3 className="text-2xl md:text-3xl font-bold mb-4">
Нужна консультация по лабораторным исследованиям?
</h3>
<p className="text-gray-300 mb-6 max-w-2xl mx-auto">
Наши специалисты помогут подобрать оптимальный комплекс исследований для вашего проекта
</p>
<Link
to="/contacts"
className="inline-flex items-center gap-2 px-8 py-4 bg-brand-orange text-white font-bold rounded-xl hover:bg-orange-600 transition-colors"
>
Связаться с нами
<ArrowRight size={20} />
</Link>
</div>
</div>
</div>
</section>
);
};
export default Laboratories;

25
components/Loading.tsx Normal file
View File

@@ -0,0 +1,25 @@
import React from 'react';
const Loading: React.FC = () => {
return (
<div className="fixed inset-0 bg-brand-dark flex items-center justify-center z-50">
<div className="text-center">
<img
src="/media/geo-logo.webp"
alt="ГеоВектор"
className="w-24 h-24 mx-auto mb-6 animate-pulse"
/>
<div className="flex items-center justify-center gap-2">
<div className="w-3 h-3 bg-brand-orange rounded-full animate-bounce"></div>
<div className="w-3 h-3 bg-brand-orange rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
<div className="w-3 h-3 bg-brand-orange rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
</div>
<p className="text-gray-400 mt-4 text-sm">Загрузка...</p>
</div>
</div>
);
};
export default Loading;

414
components/Navbar.tsx Normal file
View File

@@ -0,0 +1,414 @@
import React, { useState, useEffect } from 'react';
import { Phone, Menu, X, ChevronDown } from 'lucide-react';
import { Link, useLocation } from 'react-router-dom';
import { NAV_LINKS } from '../constants';
interface NavbarProps {
transparent?: boolean;
}
const Navbar: React.FC<NavbarProps> = ({ transparent = false }) => {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isMobileServicesMenuOpen, setIsMobileServicesMenuOpen] = useState(false);
const [isMobileLabMenuOpen, setIsMobileLabMenuOpen] = useState(false);
const [imgError, setImgError] = useState(false);
const location = useLocation();
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 50);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const isHome = location.pathname === '/';
// Use transparent background only on Home and when not scrolled, if requested
const isTransparent = transparent && isHome && !isScrolled;
return (
<nav
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
isTransparent ? 'bg-transparent py-6' : 'bg-brand-dark/95 backdrop-blur-md py-4 shadow-lg'
}`}
>
<div className="container mx-auto px-6 flex items-center justify-between text-white">
{/* Logo */}
<Link to="/" className="flex items-center gap-2 group">
{!imgError ? (
<img
src="/media/geo-logo.webp"
alt="ГеоВектор"
className="h-[64px] w-auto object-contain"
onError={() => setImgError(true)}
/>
) : (
<span className="text-xl font-bold tracking-tighter">
ГЕО<span className="text-brand-orange">ВЕКТОР</span>
</span>
)}
</Link>
{/* Desktop Menu */}
<ul className="hidden md:flex gap-8 text-sm font-medium items-center">
{/* Главная */}
<li>
<Link
to="/"
className={`transition-colors hover:text-brand-orange ${
location.pathname === '/' ? 'text-brand-orange' : 'text-gray-300'
}`}
>
Главная
</Link>
</li>
{/* О компании */}
<li>
<Link
to="/about"
className={`transition-colors hover:text-brand-orange ${
location.pathname === '/about' ? 'text-brand-orange' : 'text-gray-300'
}`}
>
О компании
</Link>
</li>
{/* Услуги с выпадающим меню */}
<li className="relative group">
<button
className={`flex items-center gap-1 transition-colors hover:text-brand-orange ${
location.pathname.startsWith('/services') ? 'text-brand-orange' : 'text-gray-300'
}`}
>
Услуги
<ChevronDown size={16} className={`transition-transform group-hover:rotate-180`} />
</button>
{/* Выпадающее меню */}
<div className="absolute top-full left-0 pt-2 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200">
<div className="w-72 bg-brand-dark border border-gray-700 rounded-lg shadow-xl overflow-hidden">
<Link
to="/services"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors font-semibold border-b border-gray-700"
>
Все услуги
</Link>
<Link
to="/services/surveying"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors"
>
Инженерные изыскания
</Link>
<Link
to="/services/design"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors"
>
Проектирование
</Link>
<Link
to="/services/construction"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors"
>
Строительство
</Link>
<Link
to="/services/soil-survey"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors"
>
Обследование грунтов
</Link>
<Link
to="/services/building-survey"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors"
>
Обследование здания
</Link>
<Link
to="/services/land-survey"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors"
>
Кадастровые работы
</Link>
<Link
to="/services/technical-tasks"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors border-t border-gray-700"
>
Образцы технических заданий
</Link>
</div>
</div>
</li>
{/* Проекты */}
<li>
<Link
to="/projects"
className={`transition-colors hover:text-brand-orange ${
location.pathname === '/projects' ? 'text-brand-orange' : 'text-gray-300'
}`}
>
Проекты
</Link>
</li>
{/* Автопарк */}
<li>
<Link
to="/fleet"
className={`transition-colors hover:text-brand-orange ${
location.pathname === '/fleet' ? 'text-brand-orange' : 'text-gray-300'
}`}
>
Автопарк
</Link>
</li>
{/* Сертификаты */}
<li>
<Link
to="/certificates"
className={`transition-colors hover:text-brand-orange ${
location.pathname === '/certificates' ? 'text-brand-orange' : 'text-gray-300'
}`}
>
Сертификаты
</Link>
</li>
{/* Лаборатории с выпадающим меню */}
<li className="relative group">
<button
className={`flex items-center gap-1 transition-colors hover:text-brand-orange ${
location.pathname.startsWith('/laboratories') ? 'text-brand-orange' : 'text-gray-300'
}`}
>
Лаборатории
<ChevronDown size={16} className={`transition-transform group-hover:rotate-180`} />
</button>
{/* Выпадающее меню */}
<div className="absolute top-full left-0 pt-2 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200">
<div className="w-64 bg-brand-dark border border-gray-700 rounded-lg shadow-xl overflow-hidden">
<Link
to="/laboratories/soil"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors"
>
Грунтовая лаборатория
</Link>
<Link
to="/laboratories/radiation"
className="block px-6 py-3 text-gray-300 hover:bg-brand-orange hover:text-white transition-colors"
>
Радиационная лаборатория
</Link>
</div>
</div>
</li>
{/* Контакты */}
<li>
<Link
to="/contacts"
className={`transition-colors hover:text-brand-orange ${
location.pathname === '/contacts' ? 'text-brand-orange' : 'text-gray-300'
}`}
>
Контакты
</Link>
</li>
</ul>
{/* CTA & Mobile Toggle */}
<div className="flex items-center gap-4">
<Link to="/contacts" className="hidden md:flex items-center gap-2 text-sm font-medium hover:text-brand-orange transition-colors">
<Phone className="w-4 h-4 text-brand-orange" />
<span>Связаться</span>
</Link>
<button
className="md:hidden text-white"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
{isMobileMenuOpen ? <X /> : <Menu />}
</button>
</div>
</div>
{/* Mobile Menu */}
{isMobileMenuOpen && (
<div className="absolute top-full left-0 w-full bg-brand-dark border-t border-gray-800 p-6 flex flex-col gap-6 md:hidden shadow-xl">
{/* Главная */}
<Link
to="/"
className="text-lg font-medium text-gray-300 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Главная
</Link>
{/* О компании */}
<Link
to="/about"
className="text-lg font-medium text-gray-300 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
О компании
</Link>
{/* Услуги в мобильном меню */}
<div>
<button
className="flex items-center justify-between w-full text-lg font-medium text-gray-300 hover:text-brand-orange"
onClick={() => setIsMobileServicesMenuOpen(!isMobileServicesMenuOpen)}
>
Услуги
<ChevronDown size={20} className={`transition-transform ${isMobileServicesMenuOpen ? 'rotate-180' : ''}`} />
</button>
{isMobileServicesMenuOpen && (
<div className="ml-4 mt-3 flex flex-col gap-3">
<Link
to="/services"
className="text-gray-400 hover:text-brand-orange font-semibold"
onClick={() => setIsMobileMenuOpen(false)}
>
Все услуги
</Link>
<Link
to="/services/surveying"
className="text-gray-400 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Инженерные изыскания
</Link>
<Link
to="/services/design"
className="text-gray-400 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Проектирование
</Link>
<Link
to="/services/construction"
className="text-gray-400 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Строительство
</Link>
<Link
to="/services/soil-survey"
className="text-gray-400 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Обследование грунтов
</Link>
<Link
to="/services/building-survey"
className="text-gray-400 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Обследование здания
</Link>
<Link
to="/services/land-survey"
className="text-gray-400 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Кадастровые работы
</Link>
<Link
to="/services/technical-tasks"
className="text-gray-400 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Образцы ТЗ
</Link>
</div>
)}
</div>
{/* Проекты */}
<Link
to="/projects"
className="text-lg font-medium text-gray-300 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Проекты
</Link>
{/* Автопарк */}
<Link
to="/fleet"
className="text-lg font-medium text-gray-300 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Автопарк
</Link>
{/* Сертификаты */}
<Link
to="/certificates"
className="text-lg font-medium text-gray-300 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Сертификаты
</Link>
{/* Лаборатории в мобильном меню */}
<div>
<button
className="flex items-center justify-between w-full text-lg font-medium text-gray-300 hover:text-brand-orange"
onClick={() => setIsMobileLabMenuOpen(!isMobileLabMenuOpen)}
>
Лаборатории
<ChevronDown size={20} className={`transition-transform ${isMobileLabMenuOpen ? 'rotate-180' : ''}`} />
</button>
{isMobileLabMenuOpen && (
<div className="ml-4 mt-3 flex flex-col gap-3">
<Link
to="/laboratories/soil"
className="text-gray-400 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Грунтовая лаборатория
</Link>
<Link
to="/laboratories/radiation"
className="text-gray-400 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Радиационная лаборатория
</Link>
</div>
)}
</div>
{/* Контакты */}
<Link
to="/contacts"
className="text-lg font-medium text-gray-300 hover:text-brand-orange"
onClick={() => setIsMobileMenuOpen(false)}
>
Контакты
</Link>
<Link
to="/contacts"
className="flex items-center gap-2 text-brand-orange font-bold mt-4"
onClick={() => setIsMobileMenuOpen(false)}
>
<Phone className="w-5 h-5" /> Связаться
</Link>
</div>
)}
</nav>
);
};
export default Navbar;

37
components/PageHeader.tsx Normal file
View File

@@ -0,0 +1,37 @@
import React from 'react';
interface PageHeaderProps {
title: string;
description?: string;
image?: string;
}
const PageHeader: React.FC<PageHeaderProps> = ({
title,
description,
image = "/media/images/headers/header-about.png"
}) => {
return (
<div className="relative w-full h-[400px] md:h-[500px] bg-brand-dark text-white flex flex-col justify-center items-center text-center overflow-hidden">
<div className="absolute inset-0 z-0">
<img
src={image}
alt={title}
className="w-full h-full object-cover opacity-30"
/>
<div className="absolute inset-0 bg-gradient-to-b from-brand-dark/80 to-brand-dark/40" />
</div>
<div className="relative z-10 container mx-auto px-6 mt-16">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6">{title}</h1>
{description && (
<p className="text-gray-300 text-lg max-w-2xl mx-auto leading-relaxed">
{description}
</p>
)}
</div>
</div>
);
};
export default PageHeader;

40
components/Process.tsx Normal file
View File

@@ -0,0 +1,40 @@
import React from 'react';
import { STEPS } from '../constants';
const Process: React.FC = () => {
return (
<div className="py-20 bg-brand-light">
<div className="container mx-auto px-6">
<h2 className="text-4xl font-bold text-gray-900 mb-16 text-center">Как мы работаем</h2>
<div className="relative max-w-6xl mx-auto">
{/* Линия связи между шагами (только на больших экранах) */}
<div className="hidden lg:block absolute top-9 left-0 right-0 h-1 bg-gradient-to-r from-brand-orange via-brand-orange/50 to-brand-orange" style={{ zIndex: 0 }} />
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 relative" style={{ zIndex: 1 }}>
{STEPS.map((step, idx) => (
<div key={idx} className="relative">
{/* Номер шага */}
<div className="absolute -top-4 -left-4 w-10 h-10 bg-brand-orange text-white rounded-full flex items-center justify-center font-bold text-lg shadow-lg z-10 border-4 border-brand-light">
{idx + 1}
</div>
{/* Карточка шага */}
<div className="bg-white rounded-2xl p-6 shadow-md hover:shadow-xl transition-all duration-300 hover:scale-105 h-full border-2 border-transparent hover:border-brand-orange">
<div className="w-14 h-14 rounded-full bg-brand-orange/20 flex items-center justify-center text-brand-orange mb-4">
<step.icon size={28} />
</div>
<p className="text-gray-700 leading-relaxed">
{step.description}
</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default Process;

103
components/Projects.tsx Normal file
View File

@@ -0,0 +1,103 @@
import React, { useState } from 'react';
import { groupProjectsByCategory } from '../constants';
import { MapPin, ChevronDown } from 'lucide-react';
import { Link } from 'react-router-dom';
const Projects: React.FC = () => {
const categories = groupProjectsByCategory().slice(0, 3); // Показываем только 3 категории
const [openCategories, setOpenCategories] = useState<{ [key: string]: boolean }>({
[categories[0]?.name]: true // Открываем первую категорию по умолчанию
});
const toggleCategory = (categoryName: string) => {
setOpenCategories(prev => ({
...prev,
[categoryName]: !prev[categoryName]
}));
};
return (
<div className="py-24 bg-white" id="projects">
<div className="container mx-auto px-6">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-16 gap-8">
<h2 className="text-4xl font-bold text-gray-900 max-w-xs leading-tight">
Наши недавние проекты
</h2>
<p className="text-gray-600 max-w-md text-sm leading-relaxed">
Наша команда всегда ответственно относится к проектам, которые вы нам доверили.
Спасибо, что вы рядом.
</p>
</div>
<div className="space-y-4 mb-8">
{categories.map((category) => (
<div key={category.name} className="border border-gray-200 rounded-xl overflow-hidden">
{/* Заголовок категории */}
<button
onClick={() => toggleCategory(category.name)}
className="w-full flex items-center justify-between p-6 bg-gray-50 hover:bg-gray-100 transition-colors"
>
<div className="flex items-center gap-4">
<div className="flex-shrink-0 w-12 h-12 bg-brand-orange text-white rounded-lg flex items-center justify-center font-bold text-lg">
{category.projects.length}
</div>
<h3 className="text-xl font-bold text-gray-900">{category.name}</h3>
</div>
<ChevronDown
size={24}
className={`text-brand-orange transition-transform duration-300 ${
openCategories[category.name] ? 'rotate-180' : ''
}`}
/>
</button>
{/* Список проектов (показываем только первые 3) */}
<div
className={`transition-all duration-300 ${
openCategories[category.name]
? 'max-h-[2000px] opacity-100'
: 'max-h-0 opacity-0 overflow-hidden'
}`}
>
<div className="p-4 space-y-3 bg-white">
{category.projects.slice(0, 3).map((project, index) => (
<div
key={project.id}
className="group cursor-pointer bg-gray-50 hover:bg-gray-100 rounded-lg p-5 transition-all duration-300 border border-gray-200 hover:border-brand-orange"
>
<div className="flex items-start gap-4">
<div className="flex-shrink-0 w-10 h-10 bg-white border-2 border-brand-orange text-brand-orange rounded-lg flex items-center justify-center font-bold text-sm">
{index + 1}
</div>
<div className="flex-1">
<h4 className="text-base font-bold text-gray-900 mb-2 group-hover:text-brand-orange transition-colors">
{project.title}
</h4>
<div className="flex items-start gap-2 text-gray-600 text-sm">
<MapPin size={14} className="mt-1 flex-shrink-0 text-brand-orange" />
<span className="leading-relaxed line-clamp-2">{project.description}</span>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
))}
</div>
<div className="text-center">
<Link
to="/projects"
className="inline-block px-8 py-3 bg-brand-orange text-white font-bold rounded-lg hover:bg-orange-600 transition-colors"
>
Смотреть все проекты
</Link>
</div>
</div>
</div>
);
};
export default Projects;

View File

@@ -0,0 +1,38 @@
import React, { useState, useEffect } from 'react';
import { ArrowUp } from 'lucide-react';
const ScrollToTop: React.FC = () => {
const [visible, setVisible] = useState(false);
useEffect(() => {
const onScroll = () => {
setVisible(window.scrollY > 300);
};
window.addEventListener('scroll', onScroll);
return () => window.removeEventListener('scroll', onScroll);
}, []);
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
if (!visible) return null;
return (
<button
onClick={scrollToTop}
className="fixed bottom-8 right-8 w-14 h-14 bg-brand-orange rounded-full shadow-2xl hover:bg-orange-600 hover:scale-110 transition-all z-40 flex items-center justify-center group"
aria-label="Наверх"
>
<ArrowUp className="text-white group-hover:translate-y-[-2px] transition-transform" size={24} />
</button>
);
};
export default ScrollToTop;

83
components/Services.tsx Normal file
View File

@@ -0,0 +1,83 @@
import React from 'react';
import { ArrowRight } from 'lucide-react';
import { Link } from 'react-router-dom';
import { SERVICES } from '../constants';
const Services: React.FC = () => {
// Exclude Technical Tasks and show first 3 actual services
const actualServices = SERVICES.filter(service => service.title !== 'Технические задания');
const displayedServices = actualServices.slice(0, 3);
// Helper function to get service detail page URL
const getServiceUrl = (title: string) => {
const urlMap: { [key: string]: string } = {
'Инженерные изыскания': '/services/surveying',
'Проектирование': '/services/design',
'Строительство': '/services/construction',
'Обследование грунтов': '/services/soil-survey',
'Обследование здания': '/services/building-survey',
'Землестроительный и Кадастровые работы': '/services/land-survey'
};
return urlMap[title] || null;
};
return (
<div className="py-20 bg-gray-50" id="services">
<div className="container mx-auto px-6">
<div className="flex justify-between items-end mb-12">
<h2 className="text-4xl font-bold text-gray-900">Услуги</h2>
<Link to="/services" className="flex items-center gap-2 text-sm font-medium hover:text-brand-orange transition-colors">
Показать все <ArrowRight size={16} />
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{displayedServices.map((service, idx) => {
const detailUrl = getServiceUrl(service.title);
return (
<div
key={idx}
className="group relative h-[500px] rounded-2xl overflow-hidden cursor-pointer shadow-lg"
>
<img
src={service.image}
alt={service.title}
className="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent" />
<div className="absolute bottom-0 left-0 p-8 w-full">
<h3 className="text-2xl font-bold text-white mb-3">{service.title}</h3>
<p className="text-gray-300 text-sm mb-6 line-clamp-3 opacity-90">
{service.description}
</p>
<div className="flex flex-col gap-3">
{detailUrl && (
<Link
to={detailUrl}
className="inline-flex items-center justify-center gap-2 bg-brand-orange text-white font-bold px-5 py-2.5 rounded-lg hover:bg-orange-600 transition-colors"
>
Подробнее <ArrowRight size={18} />
</Link>
)}
<Link
to="/contacts"
className="flex items-center gap-2 text-brand-orange text-sm font-medium group-hover:gap-4 transition-all"
>
Рассчитать стоимость <ArrowRight size={16} />
</Link>
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
);
};
export default Services;