Skip to content

Commit 987b42b

Browse files
feat: redesign landing page to integrate Founder's Manifesto, stats dashboard, and synergistic branding
1 parent bc2a350 commit 987b42b

3 files changed

Lines changed: 1696 additions & 461 deletions

File tree

app.js

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
document.addEventListener('DOMContentLoaded', () => {
2+
// 1. Mobile Menu Toggle
3+
const navToggle = document.getElementById('nav-toggle');
4+
const navLinks = document.getElementById('nav-links');
5+
const navbar = document.getElementById('navbar');
6+
7+
if (navToggle && navLinks) {
8+
navToggle.addEventListener('click', () => {
9+
navLinks.classList.toggle('mobile-active');
10+
navbar.classList.toggle('mobile-active');
11+
const icon = navToggle.querySelector('i');
12+
if (icon) {
13+
icon.classList.toggle('fa-bars');
14+
icon.classList.toggle('fa-xmark');
15+
}
16+
});
17+
18+
// Close mobile menu when clicking a link
19+
document.querySelectorAll('.nav-link').forEach(link => {
20+
link.addEventListener('click', () => {
21+
navLinks.classList.remove('mobile-active');
22+
navbar.classList.remove('mobile-active');
23+
const icon = navToggle.querySelector('i');
24+
if (icon) {
25+
icon.classList.add('fa-bars');
26+
icon.classList.remove('fa-xmark');
27+
}
28+
});
29+
});
30+
}
31+
32+
// 2. Navbar Scroll Effect & Scroll Spy
33+
window.addEventListener('scroll', () => {
34+
if (window.scrollY > 50) {
35+
navbar.classList.add('scrolled');
36+
} else {
37+
navbar.classList.remove('scrolled');
38+
}
39+
highlightNavLink();
40+
});
41+
42+
const sections = document.querySelectorAll('section, header');
43+
const navItems = document.querySelectorAll('.nav-link');
44+
45+
function highlightNavLink() {
46+
let scrollPosition = window.scrollY + 100;
47+
48+
sections.forEach(section => {
49+
const top = section.offsetTop;
50+
const height = section.offsetHeight;
51+
const id = section.getAttribute('id');
52+
53+
if (scrollPosition >= top && scrollPosition < top + height) {
54+
navItems.forEach(item => {
55+
item.classList.remove('active');
56+
if (item.getAttribute('href') === `#${id}`) {
57+
item.classList.add('active');
58+
}
59+
});
60+
}
61+
});
62+
}
63+
64+
// 3. Scroll Reveal Animations (Intersection Observer)
65+
const revealElements = document.querySelectorAll('.reveal');
66+
const revealObserver = new IntersectionObserver((entries, observer) => {
67+
entries.forEach(entry => {
68+
if (entry.isIntersecting) {
69+
entry.target.classList.add('active');
70+
observer.unobserve(entry.target); // Animates once
71+
}
72+
});
73+
}, {
74+
threshold: 0.15,
75+
rootMargin: '0px 0px -50px 0px'
76+
});
77+
78+
revealElements.forEach(element => {
79+
revealObserver.observe(element);
80+
});
81+
82+
// 4. Statistics Counter Animation
83+
const statNumbers = document.querySelectorAll('.stat-number');
84+
const animateCounters = (element) => {
85+
const target = parseInt(element.getAttribute('data-target'), 10);
86+
const suffix = element.textContent.includes('%') ? '%' : (element.textContent.includes('x') ? 'x' : '');
87+
let current = 0;
88+
const duration = 2000; // 2 seconds
89+
const stepTime = Math.max(Math.floor(duration / target), 15);
90+
91+
const timer = setInterval(() => {
92+
current += 1;
93+
element.textContent = current + suffix;
94+
if (current >= target) {
95+
element.textContent = target + suffix;
96+
clearInterval(timer);
97+
}
98+
}, stepTime);
99+
};
100+
101+
const statsSection = document.getElementById('stats');
102+
if (statsSection) {
103+
const statsObserver = new IntersectionObserver((entries, observer) => {
104+
entries.forEach(entry => {
105+
if (entry.isIntersecting) {
106+
statNumbers.forEach(num => animateCounters(num));
107+
observer.unobserve(entry.target);
108+
}
109+
});
110+
}, { threshold: 0.3 });
111+
112+
statsObserver.observe(statsSection);
113+
}
114+
115+
// 5. Dynamic Collaboration Form Submission
116+
const colForm = document.getElementById('collaboration-form');
117+
if (colForm) {
118+
colForm.addEventListener('submit', (e) => {
119+
e.preventDefault();
120+
121+
const name = document.getElementById('name').value;
122+
const email = document.getElementById('email').value;
123+
const field = document.getElementById('field').value;
124+
125+
// Dynamic card replacement for a premium feedback loop
126+
const card = colForm.closest('.contact-form-card');
127+
if (card) {
128+
card.style.opacity = '0';
129+
card.style.transform = 'translateY(10px)';
130+
131+
setTimeout(() => {
132+
card.innerHTML = `
133+
<div style="text-align: center; padding: 2rem 0;">
134+
<div style="width: 80px; height: 80px; background: rgba(16, 185, 129, 0.1); border: 2px solid var(--primary); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 2rem; color: var(--primary); font-size: 2.5rem; animation: pulse-glow 2s infinite;">
135+
<i class="fa-solid fa-check"></i>
136+
</div>
137+
<h3 style="font-size: 1.8rem; margin-bottom: 1rem; color: var(--text-primary);">Đăng Ký Thành Công!</h3>
138+
<p style="color: var(--text-secondary); margin-bottom: 1.5rem; font-size: 1rem; line-height: 1.6;">
139+
Chào <strong>${name}</strong>, chúng tôi đã nhận được thông tin đăng ký đồng hành trong lĩnh vực <strong>${field}</strong>.
140+
</p>
141+
<p style="color: var(--text-muted); font-size: 0.9rem; margin-bottom: 2rem;">
142+
Hệ thống đã lưu trữ email <strong>${email}</strong>. CEO Thiên Phong và đội ngũ quản lý CRF sẽ liên hệ trực tiếp với bạn qua email trong vòng 24-48 giờ làm việc.
143+
</p>
144+
<button id="btn-back-form" class="btn btn-secondary">Quay lại</button>
145+
</div>
146+
`;
147+
card.style.opacity = '1';
148+
card.style.transform = 'translateY(0)';
149+
150+
// Allow re-submitting if needed
151+
const backBtn = document.getElementById('btn-back-form');
152+
if (backBtn) {
153+
backBtn.addEventListener('click', () => {
154+
window.location.reload();
155+
});
156+
}
157+
}, 300);
158+
}
159+
});
160+
}
161+
});
162+
163+
// Extra style animation definition
164+
const styleSheet = document.createElement("style");
165+
styleSheet.textContent = `
166+
@keyframes pulse-glow {
167+
0% {
168+
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
169+
}
170+
70% {
171+
box-shadow: 0 0 0 15px rgba(16, 185, 129, 0);
172+
}
173+
100% {
174+
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
175+
}
176+
}
177+
`;
178+
document.head.appendChild(styleSheet);

0 commit comments

Comments
 (0)