165 lines
6.6 KiB
TypeScript
165 lines
6.6 KiB
TypeScript
|
|
|
|||
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|||
|
|
import Hero from './components/Hero';
|
|||
|
|
import Infrastructure from './components/Infrastructure';
|
|||
|
|
import Science from './components/Science';
|
|||
|
|
import Social from './components/Social';
|
|||
|
|
import Leadership from './components/Leadership';
|
|||
|
|
import Metrics from './components/Metrics';
|
|||
|
|
import Scaling from './components/Scaling';
|
|||
|
|
import Team from './components/Team';
|
|||
|
|
import Timeline from './components/Timeline';
|
|||
|
|
import BootAnimation from './components/BootAnimation';
|
|||
|
|
import Modal from './components/Modal';
|
|||
|
|
import { ModalData } from './types';
|
|||
|
|
// import TechVisualizer from './components/TechVisualizer'; // Disabled per user request
|
|||
|
|
import { ArrowUp } from 'lucide-react';
|
|||
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|||
|
|
|
|||
|
|
const App: React.FC = () => {
|
|||
|
|
const [isBooting, setIsBooting] = useState(true);
|
|||
|
|
const [showScrollTop, setShowScrollTop] = useState(false);
|
|||
|
|
const [modalData, setModalData] = useState<ModalData | null>(null);
|
|||
|
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|||
|
|
|
|||
|
|
// Theme and Scroll Logic
|
|||
|
|
useEffect(() => {
|
|||
|
|
const container = scrollContainerRef.current;
|
|||
|
|
if (!container) return;
|
|||
|
|
|
|||
|
|
const handleScroll = () => {
|
|||
|
|
const scrollY = container.scrollTop;
|
|||
|
|
const windowHeight = window.innerHeight;
|
|||
|
|
|
|||
|
|
const leadershipSection = document.getElementById('leadership'); // Block 4
|
|||
|
|
const teamSection = document.getElementById('team');
|
|||
|
|
|
|||
|
|
// --- Theme Logic: Light mode starting from Leadership (Block 4) ---
|
|||
|
|
if (leadershipSection && teamSection) {
|
|||
|
|
// We use offsetTop relative to the container usually, but since sections are stacked
|
|||
|
|
// and container scrolls, standard offsetTop works if container is relative/static.
|
|||
|
|
const leadershipTrigger = leadershipSection.offsetTop - windowHeight * 0.6;
|
|||
|
|
const teamTrigger = teamSection.offsetTop - windowHeight * 0.8;
|
|||
|
|
|
|||
|
|
// Active Light Mode for Leadership, Metrics, Scaling. Back to Dark for Team.
|
|||
|
|
if (scrollY >= leadershipTrigger && scrollY < teamTrigger) {
|
|||
|
|
document.body.classList.add('theme-light');
|
|||
|
|
} else {
|
|||
|
|
document.body.classList.remove('theme-light');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (scrollY > 300) setShowScrollTop(true);
|
|||
|
|
else setShowScrollTop(false);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
container.addEventListener('scroll', handleScroll);
|
|||
|
|
handleScroll(); // Trigger once on load
|
|||
|
|
|
|||
|
|
return () => {
|
|||
|
|
container.removeEventListener('scroll', handleScroll);
|
|||
|
|
document.body.classList.remove('theme-light');
|
|||
|
|
};
|
|||
|
|
}, [isBooting]);
|
|||
|
|
|
|||
|
|
const scrollToTop = () => {
|
|||
|
|
if (scrollContainerRef.current) {
|
|||
|
|
scrollContainerRef.current.scrollTo({ top: 0, behavior: 'smooth' });
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleOpenModal = (data: ModalData) => {
|
|||
|
|
setModalData(data);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleCloseModal = () => {
|
|||
|
|
setModalData(null);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleContactClick = () => {
|
|||
|
|
handleOpenModal({
|
|||
|
|
title: 'Связь с разработчиком',
|
|||
|
|
type: 'form',
|
|||
|
|
theme: 'dark', // Keep header interaction dark themed typically
|
|||
|
|
content: {
|
|||
|
|
text: 'Оставьте свои контакты, и мы свяжемся с вами для обсуждения интеграции.'
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="h-screen w-full bg-theme-main overflow-hidden relative">
|
|||
|
|
<AnimatePresence>
|
|||
|
|
{isBooting && <BootAnimation onComplete={() => setIsBooting(false)} />}
|
|||
|
|
</AnimatePresence>
|
|||
|
|
|
|||
|
|
{!isBooting && (
|
|||
|
|
<div className="relative h-full w-full">
|
|||
|
|
{/* Grid Pattern Background */}
|
|||
|
|
<div className="absolute inset-0 pointer-events-none grid-pattern z-0" />
|
|||
|
|
|
|||
|
|
{/* Header */}
|
|||
|
|
<nav className="fixed top-0 left-0 right-0 z-[90] py-6 mix-blend-difference text-white pointer-events-none">
|
|||
|
|
<div className="max-w-7xl mx-auto px-6 flex justify-between items-center pointer-events-auto">
|
|||
|
|
<div className="font-mono text-sm font-bold tracking-widest flex items-center gap-3">
|
|||
|
|
{/* Logo Icon */}
|
|||
|
|
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-12 h-12">
|
|||
|
|
<circle cx="100" cy="100" r="70" stroke="#ff4b4b" strokeWidth="6" fill="none" strokeDasharray="15 85"/>
|
|||
|
|
<circle cx="100" cy="100" r="50" stroke="#ff4b4b" strokeWidth="6" fill="none" strokeDasharray="12 58" transform="rotate(30 100 100)"/>
|
|||
|
|
<circle cx="100" cy="100" r="30" stroke="#ff4b4b" strokeWidth="6" fill="none" strokeDasharray="8 32" transform="rotate(60 100 100)"/>
|
|||
|
|
</svg>
|
|||
|
|
iiEasy <span className="opacity-50"> // Будущее.Просто.</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<button
|
|||
|
|
onClick={handleContactClick}
|
|||
|
|
className="text-xs font-mono font-bold hover:text-[#20e3b2] transition-colors uppercase cursor-pointer"
|
|||
|
|
>
|
|||
|
|
[ Связь ]
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</nav>
|
|||
|
|
|
|||
|
|
<Timeline />
|
|||
|
|
|
|||
|
|
{/* Scroll Container */}
|
|||
|
|
<main
|
|||
|
|
ref={scrollContainerRef}
|
|||
|
|
className="h-full w-full overflow-y-scroll snap-y snap-proximity lg:snap-mandatory snap-container scroll-smooth relative z-10"
|
|||
|
|
>
|
|||
|
|
<Hero id="hero" onOpenModal={handleOpenModal} />
|
|||
|
|
<Infrastructure onOpenModal={handleOpenModal} />
|
|||
|
|
<Science onOpenModal={handleOpenModal} />
|
|||
|
|
<Social onOpenModal={handleOpenModal} />
|
|||
|
|
<Leadership onOpenModal={handleOpenModal} />
|
|||
|
|
<Metrics onOpenModal={handleOpenModal} />
|
|||
|
|
<Scaling onOpenModal={handleOpenModal} />
|
|||
|
|
<Team onOpenModal={handleOpenModal} />
|
|||
|
|
</main>
|
|||
|
|
|
|||
|
|
{/* Global Modal */}
|
|||
|
|
<AnimatePresence>
|
|||
|
|
{modalData && <Modal data={modalData} onClose={handleCloseModal} />}
|
|||
|
|
</AnimatePresence>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<AnimatePresence>
|
|||
|
|
{showScrollTop && !isBooting && !modalData && (
|
|||
|
|
<motion.button
|
|||
|
|
initial={{ opacity: 0, y: 20 }}
|
|||
|
|
animate={{ opacity: 1, y: 0 }}
|
|||
|
|
exit={{ opacity: 0, y: 20 }}
|
|||
|
|
onClick={scrollToTop}
|
|||
|
|
className="fixed bottom-8 right-8 z-50 p-4 bg-theme-main border border-theme text-theme-main hover:border-[#ff4b4b] transition-colors"
|
|||
|
|
>
|
|||
|
|
<ArrowUp className="w-5 h-5" />
|
|||
|
|
</motion.button>
|
|||
|
|
)}
|
|||
|
|
</AnimatePresence>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default App;
|