Files
gov/App.tsx

165 lines
6.6 KiB
TypeScript
Executable File
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 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;