Files
gov/App.tsx

165 lines
6.6 KiB
TypeScript
Raw Permalink Normal View History

2026-02-04 00:04:31 +05:00
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;