Spaces:
Sleeping
Sleeping
| import React, { Suspense, useState, useEffect, useRef } from 'react'; | |
| import * as THREE from 'three'; | |
| import { Canvas } from '@react-three/fiber'; | |
| import { GenerativeSphere } from './components/GenerativeSphere'; | |
| import { AmbientBackground } from './components/AmbientBackground'; | |
| import { WorkSection } from './components/sections/WorkSection'; | |
| import { ResearchSection } from './components/sections/ResearchSection'; | |
| import { ServicesSection } from './components/sections/ServicesSection'; | |
| import { ContactSection } from './components/sections/ContactSection'; | |
| import { HeroOverlay } from './components/sections/HeroOverlay'; | |
| // Import Mobile Sections | |
| import { MobileWorkSection } from './components/sections/mobile/MobileWorkSection'; | |
| import { MobileResearchSection } from './components/sections/mobile/MobileResearchSection'; | |
| import { MobileServicesSection } from './components/sections/mobile/MobileServicesSection'; | |
| import { MobileContactSection } from './components/sections/mobile/MobileContactSection'; | |
| // --- Helper Hooks --- | |
| function useIsMobile() { | |
| const [isMobile, setIsMobile] = useState(false); | |
| useEffect(() => { | |
| const check = () => setIsMobile(window.innerWidth < 768); | |
| check(); | |
| window.addEventListener('resize', check); | |
| return () => window.removeEventListener('resize', check); | |
| }, []); | |
| return isMobile; | |
| } | |
| // --- UI Components --- | |
| // OPTIMIZED LOADER | |
| const LoaderOverlay = ({ visible, progress }: { visible: boolean; progress: number }) => { | |
| return ( | |
| <div | |
| className={`fixed inset-0 z-[100] flex flex-col items-center | |
| justify-end pb-32 /* MOBILE: Align Bottom with padding */ | |
| md:justify-center md:pb-0 /* DESKTOP: Align Center */ | |
| pointer-events-none transition-opacity duration-1000 ease-in-out | |
| ${visible ? 'opacity-100' : 'opacity-0'} | |
| `} | |
| > | |
| {/* Title: BOLD WHITE - NO BLINKING */} | |
| <h1 className="text-white font-bold text-2xl md:text-4xl tracking-[0.3em] uppercase antialiased select-none"> | |
| Team Triangle | |
| </h1> | |
| {/* Timer: Bottom Left */} | |
| <div className="absolute bottom-8 left-8 md:bottom-12 md:left-12 text-left"> | |
| <div className="flex flex-col items-start gap-1"> | |
| <span className="text-[10px] font-mono text-white/40 uppercase tracking-widest"> | |
| 2025 Team Triangle System | |
| </span> | |
| <span className="text-4xl md:text-5xl font-mono font-light text-white/80 tabular-nums"> | |
| {progress}% | |
| </span> | |
| </div> | |
| {/* Loading Bar Line */} | |
| <div className="w-32 h-[1px] bg-white/10 mt-2 relative overflow-hidden"> | |
| <div | |
| className="absolute top-0 left-0 h-full bg-red-500 transition-all duration-100 ease-out" | |
| style={{ width: `${progress}%` }} | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| const Navbar = ({ | |
| visible, | |
| isDimmed, | |
| shapeMode, | |
| toggleShape, | |
| isTtsPlaying, | |
| onSectionClick, | |
| activeSection | |
| }: { | |
| visible: boolean; | |
| isDimmed: boolean; | |
| shapeMode: 'sphere' | 'triangle' | 'explode'; | |
| toggleShape: () => void; | |
| isTtsPlaying: boolean; | |
| onSectionClick: (section: string) => void; | |
| activeSection: string | null; | |
| }) => { | |
| const isTriangle = shapeMode === 'triangle'; | |
| const [mobileMenuOpen, setMobileMenuOpen] = useState(false); | |
| useEffect(() => { | |
| if (activeSection) setMobileMenuOpen(false); | |
| }, [activeSection]); | |
| const hideClass = (isDimmed && !activeSection) ? 'opacity-0 -translate-y-4 pointer-events-none' : 'opacity-100 translate-y-0'; | |
| const bgClass = isDimmed ? 'opacity-0' : 'opacity-100'; | |
| const menuItems = ['work', 'research', 'services', 'contact']; | |
| return ( | |
| <> | |
| <nav | |
| className={`fixed top-4 left-0 w-full z-40 transition-all duration-1000 ease-out | |
| ${visible ? 'translate-y-0 opacity-100' : '-translate-y-4 opacity-0 pointer-events-none'} | |
| `} | |
| > | |
| <div className={`absolute inset-0 bg-black/5 backdrop-blur-sm border-b border-white/5 transition-all duration-500 ease-out ${bgClass}`} /> | |
| <div className="relative w-full max-w-[1400px] mx-auto px-6 py-5 md:px-12 flex justify-between items-center"> | |
| <div | |
| onClick={() => { if(activeSection) onSectionClick(''); setMobileMenuOpen(false); }} | |
| className={`flex items-center gap-3 cursor-pointer group transition-all duration-500 ease-out ${hideClass} z-50`} | |
| > | |
| <div className="relative w-6 h-6 flex items-center justify-center"> | |
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" className="text-white group-hover:text-red-500 transition-colors duration-300"> | |
| <path d="M12 4L4 20H20L12 4Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/> | |
| <circle cx="12" cy="13" r="1" fill="currentColor" className="opacity-0 group-hover:opacity-100 transition-opacity duration-300" /> | |
| </svg> | |
| </div> | |
| <span className="hidden sm:block text-white/90 font-light tracking-[0.15em] text-xs uppercase select-none group-hover:text-white transition-colors"> | |
| Team Triangle | |
| </span> | |
| </div> | |
| <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 hidden md:block"> | |
| <div className={`flex items-center justify-center transition-all duration-500 ease-out ${activeSection ? 'opacity-0 pointer-events-none translate-y-[-10px]' : hideClass}`}> | |
| <div className="flex gap-1 bg-white/5 backdrop-blur-md px-1.5 py-1.5 rounded-full border border-white/5 shadow-2xl shadow-black/20"> | |
| {menuItems.map((item) => ( | |
| <button | |
| key={item} | |
| onClick={() => onSectionClick(item)} | |
| className={`px-6 py-2 rounded-full text-[10px] font-mono tracking-widest uppercase transition-all duration-300 select-none | |
| ${activeSection === item ? 'bg-white text-black' : 'text-white/50 hover:text-white hover:bg-white/10'} | |
| `} | |
| > | |
| {item} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| <div className={`flex items-center gap-6 transition-all duration-500 z-50 ${activeSection ? 'opacity-0 pointer-events-none' : ''}`}> | |
| <button disabled={isTtsPlaying} onClick={toggleShape} className={`hidden md:block transition-colors duration-300 group ${isTtsPlaying ? 'text-white/20 cursor-not-allowed' : 'text-white/50 hover:text-red-400'} ${isTriangle ? 'text-red-400' : ''}`}> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="group-hover:animate-pulse"> | |
| <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon> | |
| <path d="M15.54 8.46a5 5 0 0 1 0 7.07" className={`transition-opacity duration-300 ${isTriangle ? 'opacity-100' : 'opacity-50 group-hover:opacity-100'}`} /> | |
| <path d="M19.07 4.93a10 10 0 0 1 0 14.14" className={`transition-all duration-500 ${isTriangle ? 'opacity-100 translate-x-0' : 'opacity-0 -translate-x-1'}`} /> | |
| </svg> | |
| </button> | |
| <button | |
| onClick={() => setMobileMenuOpen(!mobileMenuOpen)} | |
| className={`md:hidden flex items-center justify-center w-10 h-10 border border-white/10 rounded-full bg-black/20 backdrop-blur-md transition-all duration-300 ${mobileMenuOpen ? 'border-red-500/50 text-red-500' : 'text-white/70'}`} | |
| > | |
| {mobileMenuOpen ? ( | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"> | |
| <line x1="18" y1="6" x2="6" y2="18"></line> | |
| <line x1="6" y1="6" x2="18" y2="18"></line> | |
| </svg> | |
| ) : ( | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"> | |
| <rect x="3" y="3" width="7" height="7"></rect> | |
| <rect x="14" y="3" width="7" height="7"></rect> | |
| <rect x="14" y="14" width="7" height="7"></rect> | |
| <rect x="3" y="14" width="7" height="7"></rect> | |
| </svg> | |
| )} | |
| </button> | |
| </div> | |
| </div> | |
| </nav> | |
| <div className={`fixed inset-0 z-30 bg-black/95 backdrop-blur-xl flex flex-col items-center justify-center transition-all duration-500 md:hidden ${mobileMenuOpen ? 'opacity-100 visible' : 'opacity-0 invisible pointer-events-none'}`}> | |
| <div className="flex flex-col gap-6 w-full px-12"> | |
| {menuItems.map((item, i) => ( | |
| <button | |
| key={item} | |
| onClick={() => { onSectionClick(item); setMobileMenuOpen(false); }} | |
| className={`text-3xl font-light text-white uppercase tracking-widest text-left py-4 border-b border-white/10 transition-all duration-500 | |
| ${mobileMenuOpen ? 'translate-x-0 opacity-100' : '-translate-x-8 opacity-0'} | |
| `} | |
| style={{ transitionDelay: `${i * 100}ms` }} | |
| > | |
| <span className="text-xs text-red-500 font-mono mr-4">0{i + 1}</span> | |
| {item} | |
| </button> | |
| ))} | |
| <div className={`mt-8 pt-8 flex justify-between items-center transition-all duration-700 delay-300 ${mobileMenuOpen ? 'opacity-100' : 'opacity-0'}`}> | |
| <span className="text-[10px] font-mono text-white/30 uppercase">System Status: Online</span> | |
| <button onClick={toggleShape} className="text-white/50 text-xs font-mono uppercase border border-white/10 px-3 py-1 rounded-full"> | |
| Toggle Geo | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </> | |
| ); | |
| }; | |
| const TypingConsole = ({ visible }: { visible: boolean }) => { | |
| const [text, setText] = useState(''); | |
| const [isDeleting, setIsDeleting] = useState(false); | |
| const [loopNum, setLoopNum] = useState(0); | |
| const [typingSpeed, setTypingSpeed] = useState(150); | |
| const phrases = ["training model...", "deploying gen-ai pipeline...", "optimizing inference...", "allocating neural buffers..."]; | |
| useEffect(() => { | |
| let timer: any; | |
| const handleTyping = () => { | |
| const i = loopNum % phrases.length; | |
| const fullText = phrases[i]; | |
| setText(isDeleting ? fullText.substring(0, text.length - 1) : fullText.substring(0, text.length + 1)); | |
| let typeSpeed = 50 + Math.random() * 60; | |
| if (isDeleting) typeSpeed /= 2.5; | |
| if (!isDeleting && text === fullText) { typeSpeed = 2500; setIsDeleting(true); } | |
| else if (isDeleting && text === '') { setIsDeleting(false); setLoopNum(loopNum + 1); typeSpeed = 500; } | |
| setTypingSpeed(typeSpeed); | |
| }; | |
| if (visible) timer = setTimeout(handleTyping, typingSpeed); | |
| return () => clearTimeout(timer); | |
| }, [text, isDeleting, loopNum, phrases, typingSpeed, visible]); | |
| return ( | |
| <div | |
| className={`fixed z-40 p-4 rounded-sm bg-black/20 backdrop-blur-sm border border-white/5 font-mono text-xs text-white/70 shadow-lg select-none transition-all duration-1000 delay-700 ease-out hidden md:block | |
| ${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'} | |
| md:bottom-28 md:right-12 md:w-64 | |
| `} | |
| > | |
| <div className="flex flex-col gap-2"> | |
| <div className="flex justify-between items-center text-[8px] uppercase tracking-widest text-white/20 border-b border-white/5 pb-2 mb-1"><span>System Output</span><div className="flex gap-1"><span className="w-1 h-1 rounded-full bg-white/20"></span><span className="w-1 h-1 rounded-full bg-white/20"></span></div></div> | |
| <div className="min-h-[2em] flex items-center"><span className="text-red-500/60 mr-2 text-[10px]">➜</span><span className="text-white/80">{text}</span><span className="animate-pulse ml-0.5 inline-block w-1.5 h-3 bg-red-500/80 align-middle"></span></div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| // --- Reusable Scroll Sections --- | |
| const ContentTeaser = ({ title, sub, desc, btnText, onClick }: any) => { | |
| return ( | |
| <div className="border border-white/10 bg-black/40 backdrop-blur-sm p-8 md:p-12 hover:bg-white/5 transition-all duration-500 group flex flex-col justify-between h-full"> | |
| <div> | |
| <div className="flex items-center gap-3 mb-6"> | |
| <span className="text-xs font-mono uppercase tracking-widest text-white/40 border border-white/10 px-2 py-1 rounded-full">{sub}</span> | |
| </div> | |
| <h3 className="text-3xl md:text-4xl font-light text-white mb-4 group-hover:text-red-500 transition-colors">{title}</h3> | |
| <p className="text-white/60 text-sm md:text-base leading-relaxed mb-8">{desc}</p> | |
| </div> | |
| <button onClick={onClick} className="self-start flex items-center gap-3 text-xs font-mono uppercase tracking-widest text-white hover:text-red-400 transition-colors pb-1 border-b border-white/20 hover:border-red-400"> | |
| {btnText} <span className="group-hover:translate-x-1 transition-transform">→</span> | |
| </button> | |
| </div> | |
| ) | |
| } | |
| // --- Main App --- | |
| const App: React.FC = () => { | |
| const [showLoader, setShowLoader] = useState(true); | |
| const [loadProgress, setLoadProgress] = useState(0); | |
| const [showInterface, setShowInterface] = useState(false); | |
| const [isNavDimmed, setIsNavDimmed] = useState(false); | |
| const [isAudioMuted, setIsAudioMuted] = useState(true); | |
| const audioRef = useRef<HTMLAudioElement | null>(null); | |
| const audioInitializedRef = useRef(false); | |
| const [isTtsPlaying, setIsTtsPlaying] = useState(false); | |
| const ttsAudioRef = useRef<HTMLAudioElement | null>(null); | |
| const [activeSection, setActiveSection] = useState<string | null>(null); | |
| const [isTriangle, setIsTriangle] = useState(false); | |
| const scrollRef = useRef(0); | |
| const smoothScrollRef = useRef(0); | |
| const shiftRef = useRef(0); | |
| const lastScrollTop = useRef(0); | |
| const isMobile = useIsMobile(); | |
| const shapeMode = activeSection ? 'explode' : ((isTriangle || isTtsPlaying) ? 'triangle' : 'sphere'); | |
| useEffect(() => { | |
| try { | |
| const saved = window.localStorage.getItem('tt_audio_muted'); | |
| if (saved === '0') setIsAudioMuted(false); | |
| if (saved === '1') setIsAudioMuted(true); | |
| } catch { | |
| // ignore | |
| } | |
| }, []); | |
| useEffect(() => { | |
| try { | |
| window.localStorage.setItem('tt_audio_muted', isAudioMuted ? '1' : '0'); | |
| } catch { | |
| // ignore | |
| } | |
| }, [isAudioMuted]); | |
| useEffect(() => { | |
| return () => { | |
| if (audioRef.current) { | |
| audioRef.current.pause(); | |
| audioRef.current.src = ''; | |
| audioRef.current.load(); | |
| audioRef.current = null; | |
| } | |
| }; | |
| }, []); | |
| const ensureAudioInitialized = () => { | |
| if (audioInitializedRef.current) return; | |
| audioInitializedRef.current = true; | |
| const a = new Audio('/audio/theme.mp3'); | |
| a.loop = true; | |
| a.volume = 0.35; | |
| a.preload = 'auto'; | |
| audioRef.current = a; | |
| }; | |
| const applyAudioState = async (nextMuted: boolean) => { | |
| ensureAudioInitialized(); | |
| const a = audioRef.current; | |
| if (!a) return; | |
| if (nextMuted) { | |
| a.pause(); | |
| a.muted = true; | |
| return; | |
| } | |
| a.muted = false; | |
| try { | |
| await a.play(); | |
| } catch { | |
| // Autoplay can be blocked; user can click again. | |
| } | |
| }; | |
| const toggleAudioMuted = async () => { | |
| const next = !isAudioMuted; | |
| setIsAudioMuted(next); | |
| await applyAudioState(next); | |
| }; | |
| const stopTts = () => { | |
| setIsTtsPlaying(false); | |
| if (ttsAudioRef.current) { | |
| ttsAudioRef.current.pause(); | |
| ttsAudioRef.current.src = ''; | |
| ttsAudioRef.current.load(); | |
| ttsAudioRef.current = null; | |
| } | |
| }; | |
| const playHomeTts = async () => { | |
| if (activeSection) return; | |
| if (isTtsPlaying) return; | |
| const a = new Audio('/audio/tts.wav'); | |
| a.preload = 'auto'; | |
| a.volume = 1.0; | |
| ttsAudioRef.current = a; | |
| a.addEventListener('ended', stopTts, { once: true }); | |
| a.addEventListener('error', stopTts, { once: true }); | |
| setIsTtsPlaying(true); | |
| try { | |
| await a.play(); | |
| } catch { | |
| stopTts(); | |
| } | |
| }; | |
| const handleSectionClick = (section: string) => { | |
| setActiveSection(section === activeSection ? null : section); | |
| }; | |
| // --- FIXED LOADER LOGIC --- | |
| useEffect(() => { | |
| let interval: any; | |
| interval = setInterval(() => { | |
| setLoadProgress(prev => { | |
| // When close to 100, ensure we reach it | |
| if (prev >= 98) { | |
| clearInterval(interval); | |
| return 100; | |
| } | |
| // Random increment for organic feel | |
| const increment = Math.random() * 3; | |
| const next = prev + increment; | |
| // Cap at 100 | |
| return Math.min(next, 100); | |
| }); | |
| }, 25); | |
| return () => clearInterval(interval); | |
| }, []); | |
| // Handle Transition from Loader to Interface | |
| useEffect(() => { | |
| if (loadProgress >= 100) { | |
| // Small delay at 100% before fading out | |
| const timeout = setTimeout(() => { | |
| setShowLoader(false); | |
| // Delay interface appearance slightly to ensure smooth fade | |
| setTimeout(() => setShowInterface(true), 500); | |
| }, 500); | |
| return () => clearTimeout(timeout); | |
| } | |
| }, [loadProgress]); | |
| // Scroll Loop | |
| useEffect(() => { | |
| let frameId: number; | |
| const loop = () => { | |
| smoothScrollRef.current = THREE.MathUtils.lerp(smoothScrollRef.current, scrollRef.current, 0.08); | |
| frameId = requestAnimationFrame(loop); | |
| }; | |
| loop(); | |
| return () => cancelAnimationFrame(frameId); | |
| }, []); | |
| const handleScroll = (e: React.UIEvent<HTMLDivElement>) => { | |
| const s = e.currentTarget.scrollTop; | |
| scrollRef.current = s; | |
| if (s > lastScrollTop.current && s > 50) setIsNavDimmed(true); | |
| else if (s < lastScrollTop.current || s <= 50) setIsNavDimmed(false); | |
| lastScrollTop.current = s; | |
| }; | |
| return ( | |
| <div className="relative w-full h-full bg-[#050000] text-white overflow-hidden selection:bg-red-500/30"> | |
| <div className="absolute inset-0 z-0 pointer-events-none"> | |
| <Canvas dpr={[1, 2]} camera={{ position: [0, 0, 18], fov: 35, near: 0.1, far: 100 }} gl={{ antialias: true, alpha: false, powerPreference: "high-performance" }}> | |
| <Suspense fallback={null}> | |
| <AmbientBackground scrollRef={scrollRef} beatActive={isTtsPlaying} /> | |
| {/* Sphere is visible behind the loader */} | |
| <GenerativeSphere | |
| scrollRef={scrollRef} | |
| shiftRef={shiftRef} | |
| mode={shapeMode} | |
| isMobile={isMobile} | |
| /> | |
| </Suspense> | |
| </Canvas> | |
| </div> | |
| {/* | |
| Loader Overlay: Transparent BG, shows Sphere behind. | |
| Mobile: Bottom aligned (pb-32) | |
| Desktop: Center aligned (pb-0) | |
| */} | |
| <LoaderOverlay visible={showLoader} progress={Math.floor(loadProgress)} /> | |
| {/* | |
| Main Interface Container: | |
| Remains strictly hidden (opacity-0) until showInterface is true. | |
| */} | |
| <div className={`transition-opacity duration-1000 ${showInterface ? 'opacity-100' : 'opacity-0'}`}> | |
| <Navbar | |
| visible={true} | |
| isDimmed={isNavDimmed} | |
| shapeMode={shapeMode} | |
| toggleShape={playHomeTts} | |
| isTtsPlaying={isTtsPlaying} | |
| onSectionClick={handleSectionClick} | |
| activeSection={activeSection} | |
| /> | |
| <HeroOverlay | |
| visible={!activeSection} | |
| smoothScrollRef={smoothScrollRef} | |
| shiftRef={shiftRef} | |
| shapeMode={shapeMode} | |
| isMobile={isMobile} | |
| /> | |
| <TypingConsole visible={!activeSection} /> | |
| </div> | |
| {/* Active Section Modals */} | |
| {activeSection === 'work' && (isMobile ? <MobileWorkSection onClose={() => setActiveSection(null)} /> : <WorkSection onClose={() => setActiveSection(null)} />)} | |
| {activeSection === 'research' && (isMobile ? <MobileResearchSection onClose={() => setActiveSection(null)} /> : <ResearchSection onClose={() => setActiveSection(null)} />)} | |
| {activeSection === 'services' && (isMobile ? <MobileServicesSection onClose={() => setActiveSection(null)} /> : <ServicesSection onClose={() => setActiveSection(null)} />)} | |
| {activeSection === 'contact' && ( | |
| isMobile ? ( | |
| <MobileContactSection onClose={() => setActiveSection(null)} /> | |
| ) : ( | |
| <ContactSection onClose={() => setActiveSection(null)} isAudioMuted={isAudioMuted} onToggleAudioMuted={toggleAudioMuted} /> | |
| ) | |
| )} | |
| {/* Main Scroll Container */} | |
| <div | |
| className={`absolute inset-0 z-10 overflow-y-auto scroll-smooth overflow-x-hidden no-scrollbar | |
| ${activeSection || !showInterface ? 'pointer-events-none' : ''} | |
| ${showInterface ? 'opacity-100' : 'opacity-0'} transition-opacity duration-1000 | |
| `} | |
| onScroll={handleScroll} | |
| > | |
| {/* Spacer for Hero to be visible */} | |
| <div className="h-[100vh] w-full pointer-events-none"></div> | |
| {/* 1. Core Capabilities List */} | |
| <section className="relative z-10 w-full max-w-[1400px] mx-auto px-6 md:px-12 py-32 md:py-48"> | |
| <div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12 border-t border-white/20 pt-12"> | |
| <div className="group"> | |
| <span className="text-xs font-mono text-red-500 uppercase tracking-widest mb-4 block">[01]</span> | |
| <h2 className="text-4xl md:text-5xl font-light leading-tight text-white/90 group-hover:text-white transition-colors"> | |
| Machine Learning Systems | |
| </h2> | |
| </div> | |
| <div className="group"> | |
| <span className="text-xs font-mono text-blue-400 uppercase tracking-widest mb-4 block">[02]</span> | |
| <h2 className="text-4xl md:text-5xl font-light leading-tight text-white/90 group-hover:text-white transition-colors"> | |
| Generative AI Platforms | |
| </h2> | |
| </div> | |
| <div className="group"> | |
| <span className="text-xs font-mono text-emerald-500 uppercase tracking-widest mb-4 block">[03]</span> | |
| <h2 className="text-4xl md:text-5xl font-light leading-tight text-white/90 group-hover:text-white transition-colors"> | |
| Fullstack Product Delivery | |
| </h2> | |
| </div> | |
| </div> | |
| </section> | |
| {/* 2. Content Teasers */} | |
| <section className="relative z-10 w-full max-w-[1400px] mx-auto px-6 md:px-12 pb-32"> | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <ContentTeaser | |
| title="Selected Works" | |
| sub="Portfolio" | |
| desc="A curated index of deployed neural architectures and generative interfaces." | |
| btnText="Access Index" | |
| onClick={() => handleSectionClick('work')} | |
| /> | |
| <ContentTeaser | |
| title="R&D Lab" | |
| sub="Experiments" | |
| desc="Deep tech research. Turning whitepapers into executable code." | |
| btnText="View Protocols" | |
| onClick={() => handleSectionClick('research')} | |
| /> | |
| <ContentTeaser | |
| title="System Services" | |
| sub="Capabilities" | |
| desc="High-performance engineering for the next generation of the web." | |
| btnText="Initialize" | |
| onClick={() => handleSectionClick('services')} | |
| /> | |
| <ContentTeaser | |
| title="Direct Uplink" | |
| sub="Contact" | |
| desc="Open a secure channel to the founders. Transmit a packet, or tap a profile for analysis." | |
| btnText="Initiate" | |
| onClick={() => handleSectionClick('contact')} | |
| /> | |
| </div> | |
| </section> | |
| {/* 3. Massive Footer */} | |
| <footer className="relative z-10 w-full pt-32 pb-12 overflow-hidden flex flex-col items-center justify-center"> | |
| <div className="w-full border-t border-white/10 mb-12"></div> | |
| <div className="w-full px-2 md:px-6 text-center"> | |
| {/* Fixed: Reduced mobile size from 15vw to 10vw so "TEAM TRIANGLE" fits without clipping */} | |
| <h1 className="text-[10vw] md:text-[12vw] leading-none font-black text-white/10 tracking-tighter select-none whitespace-nowrap"> | |
| TEAM TRIANGLE | |
| </h1> | |
| </div> | |
| <div className="mt-12 flex flex-col md:flex-row gap-8 items-center justify-between w-full max-w-[1400px] px-12 text-[10px] font-mono uppercase text-white/30 tracking-widest"> | |
| <span>© 2025 Team Triangle</span> | |
| <button onClick={() => handleSectionClick('contact')} className="hover:text-white transition-colors border-b border-white/10 hover:border-white pb-0.5"> | |
| Establish Uplink | |
| </button> | |
| </div> | |
| </footer> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default App; |