-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApp.tsx
More file actions
127 lines (112 loc) · 3.85 KB
/
Copy pathApp.tsx
File metadata and controls
127 lines (112 loc) · 3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
* App — Root Component
* =====================
* Assembles all portfolio sections in their display order.
* Controls scroll-based nav highlighting and scroll-triggered
* reveal animations.
*
* Section order (designed for PM recruiter scan flow):
* Hero → Proof of Impact → Case Studies → Experience
* → Skills → Education → Blog → Contact CTA
*
* "Show, don't tell" — projects come before work history
* so recruiters see proof-of-work first.
*/
import React, { useState, useEffect, useRef } from 'react';
import {
SCROLL_SPY_TOP_OFFSET,
SCROLL_SPY_VISIBILITY_RATIO,
INTERSECTION_THRESHOLD,
INTERSECTION_ROOT_MARGIN,
} from './config';
import Navbar from './components/Navbar';
import Hero from './components/Hero';
import ProofOfImpact from './components/ProofOfImpact';
import Experience from './components/Experience';
import Projects from './components/Projects';
import Skills from './components/Skills';
import Blog from './components/Blog';
import Education from './components/Education';
import NowNext from './components/NowNext';
import Footer from './components/Footer';
import ContactCTA from './components/ContactCTA';
import ErrorBoundary from './components/ErrorBoundary';
import ChatWidget from './components/ChatWidget';
const App: React.FC = () => {
const [activeSection, setActiveSection] = useState('home');
const mainRef = useRef<HTMLElement>(null);
/**
* Scroll spy — detects which section is currently visible
* and highlights the corresponding nav item.
* Order matches the actual DOM layout (top to bottom).
*/
useEffect(() => {
const handleScroll = () => {
const sections = ['home', 'about', 'projects', 'experience', 'skills', 'education', 'blog'];
for (const section of sections) {
const element = document.getElementById(section);
if (element) {
const rect = element.getBoundingClientRect();
if (
rect.top >= SCROLL_SPY_TOP_OFFSET &&
rect.top <= window.innerHeight / SCROLL_SPY_VISIBILITY_RATIO
) {
setActiveSection(section);
break;
}
}
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
/**
* Intersection Observer — triggers fade-in-up animation
* when each section scrolls into view (fires once per section).
*/
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-fade-in-up');
observer.unobserve(entry.target);
}
});
},
{ threshold: INTERSECTION_THRESHOLD, rootMargin: INTERSECTION_ROOT_MARGIN }
);
const sections = mainRef.current?.querySelectorAll('section:not(#home)');
sections?.forEach((section) => {
(section as HTMLElement).style.opacity = '0';
observer.observe(section);
});
return () => observer.disconnect();
}, []);
return (
<ErrorBoundary>
<div className="min-h-screen bg-[#fafafa] relative pb-28 md:pb-20">
<main id="main-content" ref={mainRef} className="max-w-4xl mx-auto px-6 sm:px-8">
{/* ---- Above the fold ---- */}
<Hero />
<ProofOfImpact />
{/* ---- Proof of work (projects first = "show, don't tell") ---- */}
<Projects />
<NowNext />
{/* ---- Traditional credentials ---- */}
<Experience />
<Skills />
<Education />
{/* ---- Writing & Conversion ---- */}
<Blog />
<ContactCTA />
</main>
<Footer />
{/* Floating UI elements */}
<Navbar activeSection={activeSection} />
<ChatWidget />
</div>
</ErrorBoundary>
);
};
export default App;