From e3af7d4a18a100424201266d5e440c3e05e406ce Mon Sep 17 00:00:00 2001 From: Rivka Weiss Date: Sun, 25 Jan 2026 15:59:48 +0200 Subject: [PATCH 1/8] refactor(client): remove deprecated home page components Remove AboutSection, DemoSection, and FeatureCard components that are no longer used in HomePage. These have been replaced with newer components in the home page redesign. Co-Authored-By: Claude Sonnet 4.5 --- client/src/components/home/AboutSection.jsx | 126 -------- .../components/home/AboutSection.module.css | 211 ------------- client/src/components/home/DemoSection.jsx | 75 ----- .../components/home/DemoSection.module.css | 287 ------------------ client/src/components/home/FeatureCard.jsx | 100 ------ .../components/home/FeatureCard.module.css | 280 ----------------- 6 files changed, 1079 deletions(-) delete mode 100644 client/src/components/home/AboutSection.jsx delete mode 100644 client/src/components/home/AboutSection.module.css delete mode 100644 client/src/components/home/DemoSection.jsx delete mode 100644 client/src/components/home/DemoSection.module.css delete mode 100644 client/src/components/home/FeatureCard.jsx delete mode 100644 client/src/components/home/FeatureCard.module.css diff --git a/client/src/components/home/AboutSection.jsx b/client/src/components/home/AboutSection.jsx deleted file mode 100644 index 77fe50b..0000000 --- a/client/src/components/home/AboutSection.jsx +++ /dev/null @@ -1,126 +0,0 @@ -import { useRef, useEffect } from 'react'; -import anime from 'animejs'; -import FeatureCard from './FeatureCard'; -import styles from './AboutSection.module.css'; - -export default function AboutSection() { - const sectionRef = useRef(null); - const titleRef = useRef(null); - const introRef = useRef(null); - const cardsRef = useRef(null); - const hasAnimated = useRef(false); - - useEffect(() => { - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting && !hasAnimated.current) { - hasAnimated.current = true; - - // Animate title - anime({ - targets: titleRef.current, - translateY: [40, 0], - opacity: [0, 1], - duration: 800, - easing: 'easeOutExpo', - }); - - // Animate intro text - anime({ - targets: introRef.current, - translateY: [30, 0], - opacity: [0, 1], - duration: 800, - delay: 200, - easing: 'easeOutExpo', - }); - - // Cards will animate via CSS with stagger - anime({ - targets: cardsRef.current, - opacity: [0, 1], - duration: 600, - delay: 400, - easing: 'easeOutExpo', - }); - - observer.unobserve(entry.target); - } - }); - }, - { threshold: 0.2 } - ); - - if (sectionRef.current) { - observer.observe(sectionRef.current); - } - - return () => observer.disconnect(); - }, []); - - const features = [ - { - icon: '🔐', - title: 'Password Security', - description: 'Learn to create strong passwords and understand common vulnerabilities through interactive challenges.', - }, - { - icon: '🛡️', - title: 'Privacy Protection', - description: 'Master data protection techniques and learn how to keep your personal information safe online.', - }, - { - icon: '🎮', - title: 'Gesture Controls', - description: 'Navigate through challenges using hand gestures powered by MediaPipe technology.', - }, - { - icon: '🤖', - title: 'AI Companions', - description: 'Collect unique AI-generated robot companions to help you on your cybersecurity journey.', - }, - { - icon: '🏆', - title: 'Earn Rewards', - description: 'Gain points, coins, badges, and unlock new areas as you master security concepts.', - }, - { - icon: '🌐', - title: 'Immersive 3D World', - description: 'Explore cyberpunk environments built with Three.js in a visually stunning game world.', - }, - ]; - - return ( -
- {/* Forest Silhouette Background */} -
-
- -
-

- What is SafeForest? -

- -

- SafeForest is a digital forest sanctuary where cybersecurity education - meets immersive gameplay. Transform complex security concepts into engaging challenges - while exploring a world where technology and nature harmoniously blend. -

- -
- {features.map((feature, index) => ( - - ))} -
-
-
- ); -} diff --git a/client/src/components/home/AboutSection.module.css b/client/src/components/home/AboutSection.module.css deleted file mode 100644 index 7003885..0000000 --- a/client/src/components/home/AboutSection.module.css +++ /dev/null @@ -1,211 +0,0 @@ -/* ===== ABOUT SECTION - DIGITAL FOREST SANCTUARY ===== */ -/* Cyber-Forest Sanctuary Theme */ - -.aboutSection { - position: relative; - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - padding: 120px 24px; - background: var(--neo-bg); - overflow: hidden; -} - -/* Forest Silhouette Background */ -.forestBackground { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 300px; - background: linear-gradient( - to top, - rgba(0, 255, 159, 0.05) 0%, - transparent 100% - ); - opacity: 0.3; - z-index: 0; -} - -.forestBackground::before { - content: ''; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 150px; - background: repeating-linear-gradient( - 90deg, - transparent, - transparent 40px, - rgba(0, 255, 159, 0.1) 40px, - rgba(0, 255, 159, 0.1) 42px - ); - clip-path: polygon( - 0% 100%, - 5% 60%, - 8% 100%, - 12% 50%, - 15% 100%, - 20% 40%, - 23% 100%, - 28% 55%, - 32% 100%, - 36% 45%, - 40% 100%, - 44% 60%, - 48% 100%, - 52% 50%, - 56% 100%, - 60% 40%, - 64% 100%, - 68% 55%, - 72% 100%, - 76% 45%, - 80% 100%, - 84% 60%, - 88% 100%, - 92% 50%, - 95% 100%, - 100% 100% - ); -} - -/* Circuit Pattern Overlay */ -.circuitPattern { - position: absolute; - inset: 0; - background-image: - linear-gradient(var(--neo-border) 1px, transparent 1px), - linear-gradient(90deg, var(--neo-border) 1px, transparent 1px); - background-size: 50px 50px; - opacity: 0.03; - z-index: 0; - animation: circuitMove 20s linear infinite; -} - -@keyframes circuitMove { - from { - background-position: 0 0, 0 0; - } - to { - background-position: 50px 50px, 50px 50px; - } -} - -/* Container */ -.container { - position: relative; - max-width: 1300px; - width: 100%; - margin: 0 auto; - z-index: 1; -} - -/* Section Title */ -.sectionTitle { - font-family: 'Orbitron', sans-serif; - font-size: clamp(42px, 6vw, 72px); - font-weight: 900; - color: var(--neo-text); - text-align: center; - margin: 0 0 32px; - letter-spacing: -1px; - text-transform: uppercase; - text-shadow: 0 4px 30px rgba(0, 0, 0, 0.3); -} - -.titleHighlight { - background: linear-gradient( - 135deg, - var(--forest-emerald), - var(--portal-cyan), - var(--portal-purple) - ); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - filter: drop-shadow(0 0 30px rgba(0, 255, 159, 0.5)); - font-weight: 900; -} - -/* Intro Text */ -.intro { - font-family: 'Fredoka', sans-serif; - font-size: clamp(16px, 2.2vw, 20px); - font-weight: 400; - color: var(--neo-text-dim); - text-align: center; - max-width: 900px; - margin: 0 auto 80px; - line-height: 1.9; - letter-spacing: 0.2px; -} - -.intro strong { - color: var(--neo-text); - font-weight: 600; - background: linear-gradient(135deg, var(--forest-emerald), var(--portal-cyan)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -/* Features Grid */ -.featuresGrid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 32px; - margin-top: 80px; - opacity: 0; -} - -/* Accessibility: Reduced Motion */ -@media (prefers-reduced-motion: reduce) { - .circuitPattern { - animation: none; - } -} - -/* Responsive Design */ -@media (max-width: 1024px) { - .featuresGrid { - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 24px; - } -} - -@media (max-width: 768px) { - .aboutSection { - padding: 100px 20px; - } - - .intro { - margin-bottom: 60px; - } - - .featuresGrid { - grid-template-columns: 1fr; - gap: 20px; - margin-top: 60px; - } - - .forestBackground { - height: 200px; - } - - .forestBackground::before { - height: 100px; - } -} - -@media (max-width: 480px) { - .aboutSection { - padding: 80px 16px; - } - - .circuitPattern { - display: none; - } -} diff --git a/client/src/components/home/DemoSection.jsx b/client/src/components/home/DemoSection.jsx deleted file mode 100644 index 53ffe1a..0000000 --- a/client/src/components/home/DemoSection.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useRef, useEffect } from 'react'; -import anime from 'animejs'; -import styles from './DemoSection.module.css'; - -export default function DemoSection() { - const sectionRef = useRef(null); - const videoRef = useRef(null); - const titleRef = useRef(null); - const hasAnimated = useRef(false); - - useEffect(() => { - // Intersection Observer for scroll animation - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting && !hasAnimated.current) { - hasAnimated.current = true; - - anime({ - targets: titleRef.current, - translateY: [40, 0], - opacity: [0, 1], - duration: 800, - easing: 'easeOutExpo', - }); - - anime({ - targets: videoRef.current, - translateY: [60, 0], - opacity: [0, 1], - duration: 1000, - delay: 200, - easing: 'easeOutExpo', - }); - - observer.unobserve(entry.target); - } - }); - }, - { threshold: 0.2 } - ); - - if (sectionRef.current) { - observer.observe(sectionRef.current); - } - - return () => observer.disconnect(); - }, []); - - return ( -
-
-

- See It In Action -

- -
- {/* Video placeholder - replace with actual video later */} -
-
- -
-

Demo Video Coming Soon

-
-
- -

- Watch how players navigate through cyberpunk environments, solve security - challenges using hand gestures, and earn rewards for mastering digital - safety skills. -

-
-
- ); -} diff --git a/client/src/components/home/DemoSection.module.css b/client/src/components/home/DemoSection.module.css deleted file mode 100644 index 093384f..0000000 --- a/client/src/components/home/DemoSection.module.css +++ /dev/null @@ -1,287 +0,0 @@ -/* ===== DEMO SECTION - TERMINAL/MONITOR FRAME ===== */ -/* Cyber-Forest Sanctuary Theme */ - -.demoSection { - position: relative; - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - padding: 100px 24px; - background: var(--neo-bg); - overflow: hidden; -} - -/* Data Stream Background */ -.demoSection::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 100%; - background: repeating-linear-gradient( - 0deg, - transparent, - transparent 2px, - var(--neo-border) 2px, - var(--neo-border) 3px - ); - opacity: 0.02; - animation: dataStream 20s linear infinite; - z-index: 0; -} - -@keyframes dataStreamMove { - from { - background-position: 0 0; - } - to { - background-position: 0 100px; - } -} - -.container { - position: relative; - max-width: 1200px; - width: 100%; - margin: 0 auto; - z-index: 1; -} - -.sectionTitle { - font-family: 'Orbitron', sans-serif; - font-size: clamp(42px, 6vw, 68px); - font-weight: 900; - color: var(--neo-text); - text-align: center; - margin: 0 0 60px; - background: linear-gradient(135deg, var(--portal-cyan), var(--portal-purple)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - letter-spacing: -0.8px; - text-transform: uppercase; - filter: drop-shadow(0 0 30px rgba(0, 242, 255, 0.3)); -} - -/* Terminal/Monitor Frame */ -.videoWrapper { - position: relative; - width: 100%; - max-width: 950px; - margin: 0 auto 48px; - border-radius: 24px; - overflow: visible; -} - -.videoWrapper::before { - content: ''; - position: absolute; - inset: -4px; - border-radius: 24px; - background: linear-gradient( - 135deg, - var(--portal-cyan), - var(--portal-purple), - var(--forest-emerald) - ); - background-size: 300% 300%; - animation: borderFlow 6s ease infinite; - z-index: -1; - filter: blur(8px); -} - -/* Terminal Header Bar */ -.videoWrapper::after { - content: '● ● ●'; - position: absolute; - top: -40px; - left: 20px; - font-size: 16px; - color: var(--neo-text-dim); - letter-spacing: 8px; - font-family: 'Orbitron', sans-serif; -} - -.videoPlaceholder { - position: relative; - width: 100%; - aspect-ratio: 16 / 9; - background: var(--glass-bg); - backdrop-filter: blur(var(--glass-blur)); - -webkit-backdrop-filter: blur(var(--glass-blur)); - border: 2px solid var(--neo-border); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 24px; - border-radius: 20px; - overflow: hidden; - box-shadow: - 0 0 40px rgba(0, 242, 255, 0.2), - 0 20px 60px rgba(0, 0, 0, 0.3), - inset 0 0 40px rgba(0, 242, 255, 0.05); -} - -/* Scanline Effect */ -.videoPlaceholder::before { - content: ''; - position: absolute; - inset: 0; - background: repeating-linear-gradient( - 0deg, - rgba(0, 0, 0, 0.05), - rgba(0, 0, 0, 0.05) 2px, - transparent 2px, - transparent 4px - ); - pointer-events: none; - z-index: 2; -} - -/* Terminal Grid Background */ -.videoPlaceholder::after { - content: ''; - position: absolute; - inset: 0; - background-image: - linear-gradient(var(--neo-border) 1px, transparent 1px), - linear-gradient(90deg, var(--neo-border) 1px, transparent 1px); - background-size: 40px 40px; - opacity: 0.05; - pointer-events: none; -} - -.playButton { - position: relative; - width: 90px; - height: 90px; - border-radius: 50%; - background: linear-gradient(135deg, var(--portal-cyan), var(--portal-purple)); - display: grid; - place-items: center; - cursor: pointer; - transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); - z-index: 3; - box-shadow: - 0 0 40px rgba(0, 242, 255, 0.5), - 0 0 60px rgba(112, 0, 255, 0.4); -} - -.playButton::before { - content: ''; - position: absolute; - inset: -6px; - border-radius: 50%; - background: linear-gradient(135deg, var(--portal-cyan), var(--portal-purple)); - z-index: -1; - animation: portalPulse 3s ease-in-out infinite; - opacity: 0.6; -} - -.playButton:hover { - transform: scale(1.15); - box-shadow: - 0 0 60px rgba(0, 242, 255, 0.7), - 0 0 100px rgba(112, 0, 255, 0.6); -} - -.playIcon { - font-size: 36px; - color: white; - margin-left: 6px; - filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3)); -} - -.placeholderText { - font-family: 'Orbitron', sans-serif; - font-size: 24px; - font-weight: 700; - color: var(--neo-text); - margin: 0; - letter-spacing: 0.5px; - text-transform: uppercase; - position: relative; - z-index: 3; - text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); -} - -.description { - font-family: 'Fredoka', sans-serif; - font-size: clamp(15px, 2vw, 19px); - font-weight: 400; - color: var(--neo-text-dim); - text-align: center; - max-width: 800px; - margin: 0 auto; - line-height: 1.9; - letter-spacing: 0.2px; -} - -/* Accessibility: Reduced Motion */ -@media (prefers-reduced-motion: reduce) { - .demoSection::before, - .videoWrapper::before, - .playButton::before { - animation: none !important; - } - - .playButton:hover { - transform: scale(1.08); - } -} - -/* Responsive Design */ -@media (max-width: 768px) { - .demoSection { - padding: 80px 20px; - } - - .sectionTitle { - margin-bottom: 48px; - } - - .videoWrapper::after { - top: -35px; - left: 16px; - font-size: 14px; - } - - .playButton { - width: 70px; - height: 70px; - } - - .playIcon { - font-size: 28px; - } - - .placeholderText { - font-size: 20px; - } -} - -@media (max-width: 480px) { - .demoSection { - padding: 60px 16px; - } - - .playButton { - width: 60px; - height: 60px; - } - - .playIcon { - font-size: 24px; - } - - .placeholderText { - font-size: 18px; - } - - .videoWrapper::after { - display: none; - } -} diff --git a/client/src/components/home/FeatureCard.jsx b/client/src/components/home/FeatureCard.jsx deleted file mode 100644 index fdfa51b..0000000 --- a/client/src/components/home/FeatureCard.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useRef, useEffect } from 'react'; -import styles from './FeatureCard.module.css'; - -export default function FeatureCard({ icon, title, description, index }) { - const cardRef = useRef(null); - - useEffect(() => { - const card = cardRef.current; - if (!card) return; - - const handleMouseMove = (e) => { - const rect = card.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - - card.style.setProperty('--mouse-x', `${x}px`); - card.style.setProperty('--mouse-y', `${y}px`); - }; - - card.addEventListener('mousemove', handleMouseMove); - return () => card.removeEventListener('mousemove', handleMouseMove); - }, []); - - // SVG Icon mapping - const getSvgIcon = () => { - switch (title) { - case 'Password Security': - return ( - - - - - ); - case 'Privacy Protection': - return ( - - - - ); - case 'Gesture Controls': - return ( - - - - - - - ); - case 'AI Companions': - return ( - - - - - - - - ); - case 'Earn Rewards': - return ( - - - - - ); - case 'Immersive 3D World': - return ( - - - - - - ); - default: - return {icon}; - } - }; - - return ( -
-
- -
-
-
-
-
-
- -
- {getSvgIcon()} -
- -

{title}

-

{description}

- -
-
- ); -} diff --git a/client/src/components/home/FeatureCard.module.css b/client/src/components/home/FeatureCard.module.css deleted file mode 100644 index 7edbd6d..0000000 --- a/client/src/components/home/FeatureCard.module.css +++ /dev/null @@ -1,280 +0,0 @@ -/* ===== HOLOGRAPHIC TERMINAL FEATURE CARD ===== */ -/* Cyber-Forest Sanctuary Theme */ - -.card { - position: relative; - padding: 36px 28px; - border-radius: 20px; - background: var(--glass-bg); - backdrop-filter: blur(var(--glass-blur)); - -webkit-backdrop-filter: blur(var(--glass-blur)); - border: 1px solid; - border-image: linear-gradient( - 135deg, - var(--portal-cyan), - var(--portal-purple), - transparent - ) 1; - transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); - overflow: hidden; - cursor: pointer; - --mouse-x: 50%; - --mouse-y: 50%; -} - -/* Animated Border Gradient */ -.card::before { - content: ''; - position: absolute; - inset: 0; - border-radius: 20px; - padding: 2px; - background: linear-gradient( - 135deg, - var(--portal-cyan), - var(--portal-purple), - var(--forest-emerald), - var(--portal-cyan) - ); - background-size: 300% 300%; - animation: borderFlow 4s ease infinite; - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - opacity: 0; - transition: opacity 0.4s; -} - -.card:hover::before { - opacity: 1; -} - -/* Scanning Line Animation */ -.scanLine { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 2px; - background: linear-gradient( - 90deg, - transparent, - var(--portal-cyan), - transparent - ); - box-shadow: 0 0 10px var(--portal-cyan); - transform: translateY(-100%); - opacity: 0; - transition: opacity 0.3s; -} - -.card:hover .scanLine { - animation: scan 2s linear infinite; - opacity: 1; -} - -/* Corner Brackets */ -.cornerBrackets div { - position: absolute; - width: 20px; - height: 20px; - border: 2px solid var(--security-shield); - opacity: 0; - transition: opacity 0.4s, transform 0.4s; -} - -.cornerTL { - top: 8px; - left: 8px; - border-right: none; - border-bottom: none; - transform: translate(-4px, -4px); -} - -.cornerTR { - top: 8px; - right: 8px; - border-left: none; - border-bottom: none; - transform: translate(4px, -4px); -} - -.cornerBL { - bottom: 8px; - left: 8px; - border-right: none; - border-top: none; - transform: translate(-4px, 4px); -} - -.cornerBR { - bottom: 8px; - right: 8px; - border-left: none; - border-top: none; - transform: translate(4px, 4px); -} - -.card:hover .cornerBrackets div { - opacity: 0.6; - transform: translate(0, 0); -} - -/* Icon Wrapper */ -.iconWrapper { - width: 64px; - height: 64px; - margin: 0 auto 20px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 16px; - background: linear-gradient(135deg, var(--portal-cyan), var(--portal-purple)); - padding: 16px; - transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); - position: relative; - z-index: 1; -} - -.iconWrapper svg { - width: 100%; - height: 100%; - color: white; - filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.3)); - transition: transform 0.4s; -} - -.card:hover .iconWrapper { - transform: translateY(-8px) scale(1.1); - box-shadow: - 0 0 30px rgba(0, 242, 255, 0.5), - 0 20px 40px rgba(0, 0, 0, 0.3); -} - -.card:hover .iconWrapper svg { - transform: rotate(5deg); -} - -/* Title */ -.title { - font-family: 'Orbitron', sans-serif; - font-size: 22px; - font-weight: 700; - color: var(--neo-text); - margin: 0 0 16px; - letter-spacing: 0.5px; - text-align: center; - text-transform: uppercase; - position: relative; - z-index: 1; - transition: color 0.3s; -} - -.card:hover .title { - background: linear-gradient(135deg, var(--portal-cyan), var(--forest-emerald)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -/* Description */ -.description { - font-family: 'Fredoka', sans-serif; - font-size: 15px; - font-weight: 400; - color: var(--neo-text-dim); - line-height: 1.7; - margin: 0; - text-align: center; - position: relative; - z-index: 1; -} - -/* Interactive Spotlight Effect */ -.spotlight { - position: absolute; - inset: 0; - background: radial-gradient( - 600px circle at var(--mouse-x) var(--mouse-y), - rgba(0, 242, 255, 0.1), - transparent 40% - ); - opacity: 0; - transition: opacity 0.3s; - pointer-events: none; -} - -.card:hover .spotlight { - opacity: 1; -} - -/* Hover Animation */ -.card:hover { - transform: translateY(-12px) scale(1.02); - box-shadow: - 0 0 40px rgba(0, 242, 255, 0.3), - 0 0 80px rgba(112, 0, 255, 0.2), - 0 30px 60px rgba(0, 0, 0, 0.3); -} - -/* Staggered Entrance Animation */ -@keyframes cardEntrance { - from { - opacity: 0; - transform: translateY(60px) scale(0.95); - } - to { - opacity: 1; - transform: translateY(0) scale(1); - } -} - -.card { - animation: cardEntrance 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) backwards; - animation-delay: calc(var(--index) * 0.1s); -} - -/* Accessibility: Reduced Motion */ -@media (prefers-reduced-motion: reduce) { - .card, - .scanLine, - .card::before, - .iconWrapper, - .iconWrapper svg { - animation: none !important; - transition-duration: 0.1s; - } - - .card:hover { - transform: translateY(-6px); - } - - .card:hover .iconWrapper { - transform: translateY(-4px) scale(1.05); - } - - .card:hover .iconWrapper svg { - transform: none; - } -} - -/* Responsive Design */ -@media (max-width: 768px) { - .card { - padding: 28px 24px; - } - - .iconWrapper { - width: 56px; - height: 56px; - margin-bottom: 16px; - } - - .title { - font-size: 20px; - } - - .description { - font-size: 14px; - } -} From 092e64af88eb74d92726ba77a4688431c0c559d5 Mon Sep 17 00:00:00 2001 From: Rivka Weiss Date: Sun, 25 Jan 2026 16:00:18 +0200 Subject: [PATCH 2/8] feat(client): add features showcase and learning modules components Add FeaturesShowcase component to display key product features with modern card-based UI. Add LearningModulesGrid with ModuleCard components to present educational content modules. Co-Authored-By: Claude Sonnet 4.5 --- .../src/components/home/FeaturesShowcase.jsx | 91 ++++++++ .../home/FeaturesShowcase.module.css | 220 ++++++++++++++++++ .../components/home/LearningModulesGrid.jsx | 93 ++++++++ .../home/LearningModulesGrid.module.css | 93 ++++++++ client/src/components/home/ModuleCard.jsx | 65 ++++++ .../src/components/home/ModuleCard.module.css | 202 ++++++++++++++++ 6 files changed, 764 insertions(+) create mode 100644 client/src/components/home/FeaturesShowcase.jsx create mode 100644 client/src/components/home/FeaturesShowcase.module.css create mode 100644 client/src/components/home/LearningModulesGrid.jsx create mode 100644 client/src/components/home/LearningModulesGrid.module.css create mode 100644 client/src/components/home/ModuleCard.jsx create mode 100644 client/src/components/home/ModuleCard.module.css diff --git a/client/src/components/home/FeaturesShowcase.jsx b/client/src/components/home/FeaturesShowcase.jsx new file mode 100644 index 0000000..d5b4baf --- /dev/null +++ b/client/src/components/home/FeaturesShowcase.jsx @@ -0,0 +1,91 @@ +import { useRef, useEffect, useState } from 'react'; +import styles from './FeaturesShowcase.module.css'; + +export default function FeaturesShowcase() { + const sectionRef = useRef(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.1 } + ); + + if (sectionRef.current) { + observer.observe(sectionRef.current); + } + + return () => observer.disconnect(); + }, []); + + const features = [ + { + title: 'Password Security', + description: 'Learn to create unbreakable passwords through interactive challenges. Test password strength in real-time and discover common vulnerabilities.', + icon: '🔐', + color: 'var(--portal-cyan)', + }, + { + title: 'Privacy Protection', + description: 'Master data privacy concepts with hands-on scenarios. Understand tracking, cookies, and how to protect your digital footprint.', + icon: '🛡️', + color: 'var(--forest-emerald)', + }, + { + title: 'Gesture Controls', + description: 'Navigate through the cyber-forest using intuitive hand gestures powered by MediaPipe AI. Experience cutting-edge interaction technology.', + icon: '👋', + color: 'var(--portal-purple)', + }, + { + title: 'AI Companions', + description: 'Generate unique AI-powered companion dolls that guide you through challenges. Each companion is personalized to your learning journey.', + icon: '🤖', + color: 'var(--portal-pink)', + }, + ]; + + return ( +
+
+
+

Why SafeForest?

+

+ Immersive learning meets cutting-edge technology in a gamified cybersecurity adventure +

+
+ +
+ {features.map((feature, index) => ( +
+
+
+ {feature.icon} +
+
+ +
+

{feature.title}

+

{feature.description}

+
+
+ ))} +
+
+
+ ); +} diff --git a/client/src/components/home/FeaturesShowcase.module.css b/client/src/components/home/FeaturesShowcase.module.css new file mode 100644 index 0000000..8ca273a --- /dev/null +++ b/client/src/components/home/FeaturesShowcase.module.css @@ -0,0 +1,220 @@ +.featuresShowcase { + position: relative; + padding: var(--space-3xl) var(--space-md); + background: var(--section-bg-alt); + overflow: hidden; +} + +.container { + max-width: var(--container-xl); + margin: 0 auto; +} + +.header { + text-align: center; + margin-bottom: var(--space-3xl); + opacity: 0; + transform: translateY(30px); + transition: all 0.8s ease; +} + +.featuresShowcase.visible .header { + opacity: 1; + transform: translateY(0); +} + +.heading { + font-family: 'Orbitron', sans-serif; + font-size: clamp(36px, 5vw, 56px); + font-weight: 900; + color: var(--neo-text); + margin: 0 0 var(--space-md); + letter-spacing: -1px; + text-transform: uppercase; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.subheading { + font-family: 'Fredoka', sans-serif; + font-size: clamp(16px, 2vw, 20px); + font-weight: 500; + color: var(--neo-text-dim); + max-width: 700px; + margin: 0 auto; + line-height: 1.6; +} + +.featuresGrid { + display: flex; + flex-direction: column; + gap: var(--space-3xl); +} + +.feature { + display: flex; + gap: var(--space-2xl); + align-items: center; + opacity: 0; + transform: translateX(-50px); + transition: all 0.8s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.featuresShowcase.visible .feature { + opacity: 1; + transform: translateX(0); +} + +.feature.reverse { + flex-direction: row-reverse; + transform: translateX(50px); +} + +.featuresShowcase.visible .feature.reverse { + transform: translateX(0); +} + +.featureVisual { + flex: 1 1 400px; + display: flex; + justify-content: center; + align-items: center; + min-height: 300px; +} + +.iconWrapper { + position: relative; + width: 200px; + height: 200px; + display: flex; + align-items: center; + justify-content: center; + background: var(--glass-bg); + border: 2px solid var(--glass-border); + border-radius: 24px; + backdrop-filter: blur(var(--glass-blur)); + -webkit-backdrop-filter: blur(var(--glass-blur)); + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + box-shadow: var(--glass-shadow); +} + +.iconWrapper::before { + content: ''; + position: absolute; + inset: -2px; + border-radius: 24px; + padding: 2px; + background: linear-gradient(135deg, var(--feature-color), transparent); + -webkit-mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + opacity: 0; + transition: opacity 0.4s ease; +} + +.feature:hover .iconWrapper::before { + opacity: 1; +} + +.featureIcon { + font-size: 80px; + filter: drop-shadow(0 0 20px var(--feature-color)); + transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.feature:hover .iconWrapper { + transform: translateY(-8px) scale(1.05); + box-shadow: + 0 20px 60px rgba(0, 242, 255, 0.3), + 0 20px 60px rgba(112, 0, 255, 0.2); +} + +.feature:hover .featureIcon { + transform: scale(1.1) rotate(5deg); +} + +.featureContent { + flex: 1 1 400px; +} + +.featureTitle { + font-family: 'Orbitron', sans-serif; + font-size: clamp(24px, 3vw, 32px); + font-weight: 800; + color: var(--neo-text); + margin: 0 0 var(--space-md); + letter-spacing: -0.5px; +} + +.featureDescription { + font-family: 'Fredoka', sans-serif; + font-size: clamp(15px, 1.5vw, 18px); + font-weight: 400; + color: var(--neo-text-dim); + line-height: 1.8; + margin: 0; +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .feature, + .feature.reverse { + flex-direction: column; + text-align: center; + gap: var(--space-xl); + } + + .featureVisual { + min-height: auto; + } + + .iconWrapper { + width: 160px; + height: 160px; + } + + .featureIcon { + font-size: 64px; + } +} + +@media (max-width: 768px) { + .featuresShowcase { + padding: var(--space-2xl) var(--space-md); + } + + .header { + margin-bottom: var(--space-2xl); + } + + .featuresGrid { + gap: var(--space-2xl); + } + + .iconWrapper { + width: 140px; + height: 140px; + } + + .featureIcon { + font-size: 56px; + } +} + +@media (prefers-reduced-motion: reduce) { + .feature { + transition: opacity 0.3s ease; + } + + .feature:hover .iconWrapper { + transform: scale(1.02); + } + + .feature:hover .featureIcon { + transform: scale(1.05); + } +} diff --git a/client/src/components/home/LearningModulesGrid.jsx b/client/src/components/home/LearningModulesGrid.jsx new file mode 100644 index 0000000..614705f --- /dev/null +++ b/client/src/components/home/LearningModulesGrid.jsx @@ -0,0 +1,93 @@ +import { useRef, useEffect, useState } from 'react'; +import ModuleCard from './ModuleCard'; +import styles from './LearningModulesGrid.module.css'; + +export default function LearningModulesGrid() { + const sectionRef = useRef(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.1 } + ); + + if (sectionRef.current) { + observer.observe(sectionRef.current); + } + + return () => observer.disconnect(); + }, []); + + const modules = [ + { + id: 1, + title: 'Password Room', + level: 'beginner', + icon: '🔐', + gradient: 'linear-gradient(135deg, #00f2ff, #0099cc)', + rating: 5, + players: 1200, + }, + { + id: 2, + title: 'Privacy Lab', + level: 'intermediate', + icon: '🛡️', + gradient: 'linear-gradient(135deg, #00ff9f, #00cc7f)', + rating: 5, + players: 850, + }, + { + id: 3, + title: 'Threat Arena', + level: 'advanced', + icon: '⚔️', + gradient: 'linear-gradient(135deg, #ff006e, #cc0056)', + rating: 4, + players: 620, + }, + { + id: 4, + title: 'Encryption Workshop', + level: 'intermediate', + icon: '🔑', + gradient: 'linear-gradient(135deg, #7000ff, #5500cc)', + rating: 5, + players: 940, + }, + ]; + + return ( +
+
+
+

Learning Paths

+

+ Choose your adventure through cybersecurity fundamentals +

+
+ +
+ {modules.map((module, index) => ( +
+ +
+ ))} +
+
+
+ ); +} diff --git a/client/src/components/home/LearningModulesGrid.module.css b/client/src/components/home/LearningModulesGrid.module.css new file mode 100644 index 0000000..c1ab4c1 --- /dev/null +++ b/client/src/components/home/LearningModulesGrid.module.css @@ -0,0 +1,93 @@ +.learningModules { + position: relative; + padding: var(--space-3xl) var(--space-md); + background: var(--neo-bg); + overflow: hidden; +} + +.container { + max-width: var(--container-xl); + margin: 0 auto; +} + +.header { + text-align: center; + margin-bottom: var(--space-2xl); + opacity: 0; + transform: translateY(30px); + transition: all 0.8s ease; +} + +.learningModules.visible .header { + opacity: 1; + transform: translateY(0); +} + +.heading { + font-family: 'Orbitron', sans-serif; + font-size: clamp(36px, 5vw, 56px); + font-weight: 900; + color: var(--neo-text); + margin: 0 0 var(--space-md); + letter-spacing: -1px; + text-transform: uppercase; + background: linear-gradient(135deg, var(--portal-cyan), var(--portal-purple), var(--forest-emerald)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.subheading { + font-family: 'Fredoka', sans-serif; + font-size: clamp(16px, 2vw, 20px); + font-weight: 500; + color: var(--neo-text-dim); + max-width: 700px; + margin: 0 auto; + line-height: 1.6; +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-lg); +} + +.gridItem { + opacity: 0; + transform: translateY(30px); + transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.learningModules.visible .gridItem { + opacity: 1; + transform: translateY(0); +} + +/* Responsive Design */ +@media (max-width: 768px) { + .learningModules { + padding: var(--space-2xl) var(--space-md); + } + + .header { + margin-bottom: var(--space-xl); + } + + .grid { + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: var(--space-md); + } +} + +@media (max-width: 480px) { + .grid { + grid-template-columns: 1fr; + } +} + +@media (prefers-reduced-motion: reduce) { + .gridItem { + transition: opacity 0.3s ease; + } +} diff --git a/client/src/components/home/ModuleCard.jsx b/client/src/components/home/ModuleCard.jsx new file mode 100644 index 0000000..f28d34e --- /dev/null +++ b/client/src/components/home/ModuleCard.jsx @@ -0,0 +1,65 @@ +import { useNavigate } from 'react-router-dom'; +import styles from './ModuleCard.module.css'; + +export default function ModuleCard({ module }) { + const navigate = useNavigate(); + + const getBadgeColor = (level) => { + switch (level) { + case 'beginner': + return 'var(--badge-beginner)'; + case 'intermediate': + return 'var(--badge-intermediate)'; + case 'advanced': + return 'var(--badge-advanced)'; + default: + return 'var(--badge-beginner)'; + } + }; + + const handleStartModule = () => { + navigate('/login'); + }; + + return ( +
+
+ {module.level} +
+ +
+
+ {module.icon} +
+
+ +
+

{module.title}

+ +
+
+ {[...Array(5)].map((_, i) => ( + + ★ + + ))} +
+ +
+ 👥 + {module.players}+ +
+
+ + +
+
+ ); +} diff --git a/client/src/components/home/ModuleCard.module.css b/client/src/components/home/ModuleCard.module.css new file mode 100644 index 0000000..d8935ff --- /dev/null +++ b/client/src/components/home/ModuleCard.module.css @@ -0,0 +1,202 @@ +.moduleCard { + position: relative; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 16px; + overflow: hidden; + backdrop-filter: blur(var(--glass-blur)); + -webkit-backdrop-filter: blur(var(--glass-blur)); + box-shadow: var(--glass-shadow); + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + cursor: pointer; +} + +.moduleCard:hover { + transform: translateY(-8px); + box-shadow: + 0 20px 60px rgba(0, 242, 255, 0.3), + 0 20px 60px rgba(112, 0, 255, 0.2); + border-color: var(--neo-cyan); +} + +.badge { + position: absolute; + top: 16px; + right: 16px; + padding: 6px 12px; + border-radius: 20px; + background: var(--badge-color); + color: white; + font-family: 'Orbitron', sans-serif; + font-size: 11px; + font-weight: 700; + letter-spacing: 1px; + text-transform: uppercase; + z-index: 10; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.imageWrapper { + position: relative; + width: 100%; + aspect-ratio: 16 / 9; + overflow: hidden; +} + +.imageGradient { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.4s ease; +} + +.moduleCard:hover .imageGradient { + transform: scale(1.1); +} + +.moduleIcon { + font-size: 64px; + filter: drop-shadow(0 0 20px rgba(0, 0, 0, 0.5)); +} + +.content { + padding: var(--space-md); +} + +.title { + font-family: 'Orbitron', sans-serif; + font-size: 20px; + font-weight: 800; + color: var(--neo-text); + margin: 0 0 var(--space-md); + letter-spacing: -0.5px; +} + +.meta { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-md); +} + +.rating { + display: flex; + gap: 2px; +} + +.star { + color: var(--star-gold); + font-size: 16px; + transition: opacity 0.3s ease; +} + +.players { + display: flex; + align-items: center; + gap: 6px; + font-family: 'Fredoka', sans-serif; + font-size: 14px; + font-weight: 600; + color: var(--neo-text-dim); +} + +.playersIcon { + font-size: 16px; +} + +.playersCount { + opacity: 0.9; +} + +.ctaButton { + width: 100%; + padding: 12px 24px; + border-radius: 8px; + border: 2px solid transparent; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + color: white; + font-family: 'Orbitron', sans-serif; + font-size: 14px; + font-weight: 700; + letter-spacing: 0.5px; + text-transform: uppercase; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.ctaButton::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: width 0.5s, height 0.5s; +} + +.ctaButton:hover::before { + width: 300px; + height: 300px; +} + +.ctaButton:hover { + transform: translateY(-2px); + box-shadow: + 0 8px 24px rgba(0, 242, 255, 0.4), + 0 8px 24px rgba(112, 0, 255, 0.3); +} + +.arrow { + font-size: 18px; + transition: transform 0.3s ease; + position: relative; + z-index: 1; +} + +.ctaButton:hover .arrow { + transform: translateX(4px); +} + +@media (max-width: 480px) { + .content { + padding: var(--space-sm); + } + + .title { + font-size: 18px; + } + + .ctaButton { + font-size: 13px; + padding: 10px 20px; + } +} + +@media (prefers-reduced-motion: reduce) { + .moduleCard:hover { + transform: translateY(-4px); + } + + .moduleCard:hover .imageGradient { + transform: scale(1.05); + } + + .ctaButton:hover { + transform: none; + } + + .ctaButton:hover .arrow { + transform: translateX(2px); + } +} From 29e778a25bac1aed26e3f1b76070932ee3711956 Mon Sep 17 00:00:00 2001 From: Rivka Weiss Date: Sun, 25 Jan 2026 16:00:59 +0200 Subject: [PATCH 3/8] feat(client): add testimonials and social proof components Add TestimonialsSection with TestimonialCard components for user reviews. Add TrustBadges to display credibility indicators. Add StatsCounter component for animated statistics display to build user confidence. Co-Authored-By: Claude Sonnet 4.5 --- client/src/components/home/StatsCounter.jsx | 79 +++++++++ .../components/home/StatsCounter.module.css | 159 ++++++++++++++++++ .../src/components/home/TestimonialCard.jsx | 27 +++ .../home/TestimonialCard.module.css | 116 +++++++++++++ .../components/home/TestimonialsSection.jsx | 74 ++++++++ .../home/TestimonialsSection.module.css | 109 ++++++++++++ client/src/components/home/TrustBadges.jsx | 57 +++++++ .../components/home/TrustBadges.module.css | 152 +++++++++++++++++ 8 files changed, 773 insertions(+) create mode 100644 client/src/components/home/StatsCounter.jsx create mode 100644 client/src/components/home/StatsCounter.module.css create mode 100644 client/src/components/home/TestimonialCard.jsx create mode 100644 client/src/components/home/TestimonialCard.module.css create mode 100644 client/src/components/home/TestimonialsSection.jsx create mode 100644 client/src/components/home/TestimonialsSection.module.css create mode 100644 client/src/components/home/TrustBadges.jsx create mode 100644 client/src/components/home/TrustBadges.module.css diff --git a/client/src/components/home/StatsCounter.jsx b/client/src/components/home/StatsCounter.jsx new file mode 100644 index 0000000..355ce32 --- /dev/null +++ b/client/src/components/home/StatsCounter.jsx @@ -0,0 +1,79 @@ +import { useRef, useEffect, useState } from 'react'; +import anime from 'animejs'; +import styles from './StatsCounter.module.css'; + +export default function StatsCounter() { + const sectionRef = useRef(null); + const [hasAnimated, setHasAnimated] = useState(false); + const statsRefs = useRef([]); + + const stats = [ + { label: 'Active Players', value: 5000, suffix: '+', icon: '👥' }, + { label: 'Challenges Completed', value: 2500, suffix: '+', icon: '🎯' }, + { label: 'Learning Modules', value: 15, suffix: '', icon: '📚' }, + { label: 'Badges Earned', value: 10000, suffix: '+', icon: '🏆' }, + ]; + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting && !hasAnimated) { + setHasAnimated(true); + + // Animate each stat counter + statsRefs.current.forEach((ref, index) => { + if (ref) { + const stat = stats[index]; + const obj = { value: 0 }; + + anime({ + targets: obj, + value: stat.value, + duration: 2000, + delay: index * 100, + easing: 'easeOutExpo', + round: 1, + update: function() { + ref.textContent = obj.value.toLocaleString(); + } + }); + } + }); + + observer.disconnect(); + } + }, + { threshold: 0.3 } + ); + + if (sectionRef.current) { + observer.observe(sectionRef.current); + } + + return () => observer.disconnect(); + }, [hasAnimated]); + + return ( +
+
+
+ {stats.map((stat, index) => ( +
+
{stat.icon}
+
+ (statsRefs.current[index] = el)} + className={styles.number} + > + 0 + + {stat.suffix} +
+
{stat.label}
+
+ ))} +
+
+
+ ); +} diff --git a/client/src/components/home/StatsCounter.module.css b/client/src/components/home/StatsCounter.module.css new file mode 100644 index 0000000..2fe4132 --- /dev/null +++ b/client/src/components/home/StatsCounter.module.css @@ -0,0 +1,159 @@ +.statsCounter { + position: relative; + padding: var(--space-3xl) var(--space-md); + background: var(--section-bg-alt); + overflow: hidden; +} + +.statsCounter::before { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 80%; + height: 1px; + background: linear-gradient( + 90deg, + transparent, + var(--neo-cyan), + var(--neo-purple), + transparent + ); + opacity: 0.5; +} + +.container { + max-width: var(--container-xl); + margin: 0 auto; +} + +.statsGrid { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--space-xl); +} + +.statCard { + flex: 1 1 200px; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-sm); + padding: var(--space-lg); + text-align: center; + position: relative; +} + +.statIcon { + font-size: 48px; + filter: drop-shadow(0 0 20px currentColor); + opacity: 0.9; + animation: float 3s ease-in-out infinite; +} + +.statCard:nth-child(1) .statIcon { + animation-delay: 0s; + color: var(--portal-cyan); +} + +.statCard:nth-child(2) .statIcon { + animation-delay: 0.5s; + color: var(--forest-emerald); +} + +.statCard:nth-child(3) .statIcon { + animation-delay: 1s; + color: var(--portal-purple); +} + +.statCard:nth-child(4) .statIcon { + animation-delay: 1.5s; + color: var(--star-gold); +} + +.statValue { + display: flex; + align-items: baseline; + gap: 4px; +} + +.number { + font-family: 'Orbitron', sans-serif; + font-size: clamp(40px, 6vw, 64px); + font-weight: 900; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + letter-spacing: -2px; + line-height: 1; +} + +.suffix { + font-family: 'Orbitron', sans-serif; + font-size: clamp(24px, 3vw, 32px); + font-weight: 700; + color: var(--neo-cyan); + opacity: 0.8; +} + +.statLabel { + font-family: 'Fredoka', sans-serif; + font-size: clamp(14px, 1.5vw, 18px); + font-weight: 600; + color: var(--neo-text-dim); + letter-spacing: 0.5px; + text-transform: uppercase; +} + +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .statsCounter { + padding: var(--space-2xl) var(--space-md); + } + + .statsGrid { + gap: var(--space-lg); + } + + .statCard { + flex: 1 1 150px; + padding: var(--space-md); + } + + .statIcon { + font-size: 40px; + } +} + +@media (max-width: 480px) { + .statsGrid { + gap: var(--space-md); + } + + .statCard { + flex: 1 1 120px; + padding: var(--space-sm); + } + + .statIcon { + font-size: 32px; + } +} + +@media (prefers-reduced-motion: reduce) { + .statIcon { + animation: none !important; + } +} diff --git a/client/src/components/home/TestimonialCard.jsx b/client/src/components/home/TestimonialCard.jsx new file mode 100644 index 0000000..f4a6968 --- /dev/null +++ b/client/src/components/home/TestimonialCard.jsx @@ -0,0 +1,27 @@ +import styles from './TestimonialCard.module.css'; + +export default function TestimonialCard({ testimonial }) { + return ( +
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ +
+ "{testimonial.quote}" +
+ +
+
+
{testimonial.avatar}
+
+
+
{testimonial.name}
+
{testimonial.title}
+
+
+
+ ); +} diff --git a/client/src/components/home/TestimonialCard.module.css b/client/src/components/home/TestimonialCard.module.css new file mode 100644 index 0000000..f3dcaa3 --- /dev/null +++ b/client/src/components/home/TestimonialCard.module.css @@ -0,0 +1,116 @@ +.testimonialCard { + position: relative; + padding: var(--space-lg); + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 16px; + backdrop-filter: blur(var(--glass-blur)); + -webkit-backdrop-filter: blur(var(--glass-blur)); + box-shadow: var(--glass-shadow); + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + display: flex; + flex-direction: column; + gap: var(--space-md); + min-height: 280px; +} + +.testimonialCard:hover { + transform: translateY(-6px); + box-shadow: + 0 20px 60px rgba(0, 242, 255, 0.25), + 0 20px 60px rgba(112, 0, 255, 0.15); + border-color: var(--neo-cyan); +} + +.rating { + display: flex; + gap: 4px; +} + +.star { + color: var(--star-gold); + font-size: 18px; + filter: drop-shadow(0 0 8px var(--star-gold)); +} + +.quote { + font-family: 'Fredoka', sans-serif; + font-size: clamp(15px, 1.5vw, 17px); + font-weight: 500; + color: var(--neo-text); + line-height: 1.7; + margin: 0; + flex: 1; + font-style: italic; +} + +.author { + display: flex; + align-items: center; + gap: var(--space-md); + padding-top: var(--space-sm); + border-top: 1px solid var(--neo-border); +} + +.avatarWrapper { + position: relative; + padding: 3px; + border-radius: 50%; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); +} + +.avatar { + width: 48px; + height: 48px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + background: var(--neo-bg); + border: 2px solid var(--neo-bg); +} + +.authorInfo { + flex: 1; +} + +.authorName { + font-family: 'Orbitron', sans-serif; + font-size: 15px; + font-weight: 700; + color: var(--neo-text); + margin-bottom: 2px; +} + +.authorTitle { + font-family: 'Fredoka', sans-serif; + font-size: 13px; + font-weight: 500; + color: var(--neo-text-dim); +} + +@media (max-width: 480px) { + .testimonialCard { + padding: var(--space-md); + min-height: 260px; + } + + .quote { + font-size: 15px; + } + + .authorName { + font-size: 14px; + } + + .authorTitle { + font-size: 12px; + } +} + +@media (prefers-reduced-motion: reduce) { + .testimonialCard:hover { + transform: translateY(-3px); + } +} diff --git a/client/src/components/home/TestimonialsSection.jsx b/client/src/components/home/TestimonialsSection.jsx new file mode 100644 index 0000000..f7870a2 --- /dev/null +++ b/client/src/components/home/TestimonialsSection.jsx @@ -0,0 +1,74 @@ +import { useRef, useEffect, useState } from 'react'; +import TestimonialCard from './TestimonialCard'; +import styles from './TestimonialsSection.module.css'; + +export default function TestimonialsSection() { + const sectionRef = useRef(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.1 } + ); + + if (sectionRef.current) { + observer.observe(sectionRef.current); + } + + return () => observer.disconnect(); + }, []); + + const testimonials = [ + { + quote: "SafeForest made learning cybersecurity actually fun! The gesture controls are incredible and the 3D environment keeps me engaged. I've learned more here in a week than months of traditional courses.", + avatar: '👨‍💻', + name: 'Alex Chen', + title: 'Computer Science Student', + }, + { + quote: "As a teacher, I'm always looking for innovative ways to engage students. SafeForest's gamified approach to security education is brilliant. My students are actually excited about learning password safety!", + avatar: '👩‍🏫', + name: 'Sarah Martinez', + title: 'High School Teacher', + }, + { + quote: "The AI companion feature is genius! Having a personalized guide through the challenges makes the learning experience feel unique. Plus, the forest-cyberpunk aesthetic is stunning.", + avatar: '👨‍🎓', + name: 'David Kim', + title: 'Cybersecurity Enthusiast', + }, + ]; + + return ( +
+
+
+

What Learners Say

+

+ Join thousands discovering cybersecurity through immersive gameplay +

+
+ +
+ {testimonials.map((testimonial, index) => ( +
+ +
+ ))} +
+
+
+ ); +} diff --git a/client/src/components/home/TestimonialsSection.module.css b/client/src/components/home/TestimonialsSection.module.css new file mode 100644 index 0000000..c934179 --- /dev/null +++ b/client/src/components/home/TestimonialsSection.module.css @@ -0,0 +1,109 @@ +.testimonialsSection { + position: relative; + padding: var(--space-3xl) var(--space-md); + background: var(--neo-bg); + overflow: hidden; +} + +.container { + max-width: var(--container-xl); + margin: 0 auto; +} + +.header { + text-align: center; + margin-bottom: var(--space-2xl); + opacity: 0; + transform: translateY(30px); + transition: all 0.8s ease; +} + +.testimonialsSection.visible .header { + opacity: 1; + transform: translateY(0); +} + +.heading { + font-family: 'Orbitron', sans-serif; + font-size: clamp(36px, 5vw, 56px); + font-weight: 900; + color: var(--neo-text); + margin: 0 0 var(--space-md); + letter-spacing: -1px; + text-transform: uppercase; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.subheading { + font-family: 'Fredoka', sans-serif; + font-size: clamp(16px, 2vw, 20px); + font-weight: 500; + color: var(--neo-text-dim); + max-width: 700px; + margin: 0 auto; + line-height: 1.6; +} + +.testimonialsGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: var(--space-lg); +} + +.gridItem { + opacity: 0; + transform: translateY(30px); + transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.testimonialsSection.visible .gridItem { + opacity: 1; + transform: translateY(0); +} + +/* Mobile: Horizontal Scroll */ +@media (max-width: 768px) { + .testimonialsSection { + padding: var(--space-2xl) var(--space-md); + } + + .header { + margin-bottom: var(--space-xl); + } + + .testimonialsGrid { + display: flex; + overflow-x: auto; + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + gap: var(--space-md); + padding: var(--space-sm) 0; + } + + .testimonialsGrid::-webkit-scrollbar { + display: none; + } + + .gridItem { + flex-shrink: 0; + scroll-snap-align: center; + width: 85vw; + max-width: 400px; + } +} + +@media (max-width: 480px) { + .gridItem { + width: 90vw; + } +} + +@media (prefers-reduced-motion: reduce) { + .gridItem { + transition: opacity 0.3s ease; + } +} diff --git a/client/src/components/home/TrustBadges.jsx b/client/src/components/home/TrustBadges.jsx new file mode 100644 index 0000000..f3d98bf --- /dev/null +++ b/client/src/components/home/TrustBadges.jsx @@ -0,0 +1,57 @@ +import { useRef, useEffect, useState } from 'react'; +import styles from './TrustBadges.module.css'; + +export default function TrustBadges() { + const containerRef = useRef(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + observer.disconnect(); + } + }, + { threshold: 0.2 } + ); + + if (containerRef.current) { + observer.observe(containerRef.current); + } + + return () => observer.disconnect(); + }, []); + + const technologies = [ + { name: 'Three.js', icon: '🎮' }, + { name: 'MediaPipe', icon: '👋' }, + { name: 'React', icon: '⚛️' }, + { name: 'Google OAuth', icon: '🔐' }, + { name: 'MySQL', icon: '🗄️' }, + { name: 'OpenAI', icon: '🤖' }, + ]; + + return ( +
+
+

Built with industry-leading technologies

+
+ {technologies.map((tech, index) => ( +
+ {tech.icon} + {tech.name} +
+ ))} +
+
+
+ ); +} diff --git a/client/src/components/home/TrustBadges.module.css b/client/src/components/home/TrustBadges.module.css new file mode 100644 index 0000000..82cfefb --- /dev/null +++ b/client/src/components/home/TrustBadges.module.css @@ -0,0 +1,152 @@ +.trustBadges { + position: relative; + padding: var(--space-xl) var(--space-md); + background: var(--neo-bg); + overflow: hidden; +} + +.container { + max-width: var(--container-xl); + margin: 0 auto; + text-align: center; +} + +.label { + font-family: 'Orbitron', sans-serif; + font-size: 13px; + font-weight: 600; + letter-spacing: 2px; + text-transform: uppercase; + color: var(--neo-text-dim); + margin: 0 0 var(--space-lg); + opacity: 0; + transform: translateY(20px); + transition: all 0.6s ease; +} + +.trustBadges.visible .label { + opacity: 1; + transform: translateY(0); +} + +.logoGrid { + display: flex; + justify-content: center; + align-items: center; + gap: var(--space-lg); + flex-wrap: wrap; +} + +.logoCard { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-xs); + padding: var(--space-md); + border-radius: 12px; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + backdrop-filter: blur(var(--glass-blur)); + -webkit-backdrop-filter: blur(var(--glass-blur)); + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + cursor: pointer; + min-width: 120px; + opacity: 0; + transform: translateY(30px); +} + +.trustBadges.visible .logoCard { + opacity: 1; + transform: translateY(0); + animation: fadeInUp 0.6s ease forwards; +} + +.logoIcon { + font-size: 40px; + filter: grayscale(100%); + opacity: 0.6; + transition: all 0.4s ease; +} + +.logoName { + font-family: 'Orbitron', sans-serif; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.5px; + color: var(--neo-text-dim); + transition: color 0.4s ease; +} + +.logoCard:hover { + transform: translateY(-8px); + box-shadow: + 0 12px 40px rgba(0, 242, 255, 0.2), + 0 12px 40px rgba(112, 0, 255, 0.15); + border-color: var(--neo-cyan); +} + +.logoCard:hover .logoIcon { + filter: grayscale(0%); + opacity: 1; + transform: scale(1.1); +} + +.logoCard:hover .logoName { + color: var(--neo-cyan); +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Mobile: Horizontal Scroll */ +@media (max-width: 768px) { + .logoGrid { + flex-wrap: nowrap; + overflow-x: auto; + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding: var(--space-sm) 0; + justify-content: flex-start; + } + + .logoGrid::-webkit-scrollbar { + display: none; + } + + .logoCard { + flex-shrink: 0; + scroll-snap-align: center; + min-width: 100px; + } + + .logoIcon { + font-size: 32px; + } + + .logoName { + font-size: 11px; + } +} + +@media (prefers-reduced-motion: reduce) { + .logoCard { + animation: none !important; + } + + .logoCard:hover { + transform: translateY(-4px); + } + + .logoCard:hover .logoIcon { + transform: scale(1.05); + } +} From bef8ca162f0b6fc20fcf3b1808f516d993be8039 Mon Sep 17 00:00:00 2001 From: Rivka Weiss Date: Sun, 25 Jan 2026 16:01:19 +0200 Subject: [PATCH 4/8] feat(client): add final call-to-action component Add FinalCTA component for end-of-page conversion section with compelling action buttons to drive user engagement. Co-Authored-By: Claude Sonnet 4.5 --- client/src/components/home/FinalCTA.jsx | 84 +++++ .../src/components/home/FinalCTA.module.css | 335 ++++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 client/src/components/home/FinalCTA.jsx create mode 100644 client/src/components/home/FinalCTA.module.css diff --git a/client/src/components/home/FinalCTA.jsx b/client/src/components/home/FinalCTA.jsx new file mode 100644 index 0000000..cd0b5be --- /dev/null +++ b/client/src/components/home/FinalCTA.jsx @@ -0,0 +1,84 @@ +import { useNavigate } from 'react-router-dom'; +import anime from 'animejs'; +import { useRef } from 'react'; +import styles from './FinalCTA.module.css'; + +export default function FinalCTA() { + const navigate = useNavigate(); + const primaryBtnRef = useRef(null); + const secondaryBtnRef = useRef(null); + + const handlePrimaryClick = () => { + anime({ + targets: primaryBtnRef.current, + scale: [1, 0.95, 1.05, 1], + duration: 400, + easing: 'easeInOutQuad', + complete: () => navigate('/login'), + }); + }; + + const handleSecondaryClick = () => { + anime({ + targets: secondaryBtnRef.current, + scale: [1, 0.95, 1.05, 1], + duration: 400, + easing: 'easeInOutQuad', + complete: () => { + // Scroll to demo section or show demo modal + const demoSection = document.getElementById('demo'); + if (demoSection) { + demoSection.scrollIntoView({ behavior: 'smooth' }); + } + }, + }); + }; + + return ( +
+
+ +
+
EXPLORE A NEW DIMENSION
+ +

+ Ready to unlock your{' '} + cybersecurity potential + {' '}and protect what matters most? +

+ +

+ Join thousands of learners mastering digital security through immersive gameplay. + Your journey to becoming a cyber-guardian starts here. +

+ +
+ + + +
+
+ + {/* Floating particles */} +
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+
+ ); +} diff --git a/client/src/components/home/FinalCTA.module.css b/client/src/components/home/FinalCTA.module.css new file mode 100644 index 0000000..0f4f43b --- /dev/null +++ b/client/src/components/home/FinalCTA.module.css @@ -0,0 +1,335 @@ +.finalCTA { + position: relative; + padding: var(--space-3xl) var(--space-md); + min-height: 600px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + background: var(--neo-bg); +} + +.gradientBg { + position: absolute; + inset: 0; + background: linear-gradient( + 135deg, + rgba(0, 242, 255, 0.15) 0%, + rgba(112, 0, 255, 0.15) 50%, + rgba(0, 255, 159, 0.15) 100% + ); + opacity: 0.8; +} + +.gradientBg::before { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient( + ellipse at center, + transparent 0%, + var(--neo-bg) 70% + ); +} + +.content { + position: relative; + z-index: 10; + text-align: center; + max-width: 900px; + padding: 0 var(--space-md); +} + +.eyebrow { + font-family: 'Orbitron', sans-serif; + font-size: 14px; + font-weight: 700; + letter-spacing: 3px; + text-transform: uppercase; + color: var(--portal-cyan); + margin-bottom: var(--space-md); + opacity: 0.9; + text-shadow: 0 0 20px rgba(0, 242, 255, 0.5); + animation: fadeInDown 0.8s ease; +} + +.headline { + font-family: 'Orbitron', sans-serif; + font-size: clamp(32px, 5vw, 56px); + font-weight: 900; + color: var(--neo-text); + margin: 0 0 var(--space-lg); + letter-spacing: -1px; + line-height: 1.2; + animation: fadeInUp 0.8s ease 0.2s backwards; +} + +.highlight { + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.description { + font-family: 'Fredoka', sans-serif; + font-size: clamp(16px, 2vw, 20px); + font-weight: 500; + color: var(--neo-text-dim); + line-height: 1.7; + margin: 0 auto var(--space-2xl); + max-width: 700px; + animation: fadeInUp 0.8s ease 0.4s backwards; +} + +.ctaButtons { + display: flex; + justify-content: center; + gap: var(--space-md); + flex-wrap: wrap; + animation: fadeInUp 0.8s ease 0.6s backwards; +} + +.primaryButton { + position: relative; + display: inline-flex; + align-items: center; + gap: 12px; + padding: 0 48px; + height: 70px; + border-radius: 999px; + border: none; + background: linear-gradient(135deg, var(--portal-cyan), var(--portal-purple)); + color: white; + font-family: 'Orbitron', sans-serif; + font-size: 18px; + font-weight: 800; + cursor: pointer; + overflow: hidden; + letter-spacing: 0.5px; + text-transform: uppercase; + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + box-shadow: + 0 0 50px rgba(0, 242, 255, 0.5), + 0 0 70px rgba(112, 0, 255, 0.4), + 0 20px 40px rgba(0, 0, 0, 0.3); +} + +.primaryButton::before { + content: ''; + position: absolute; + inset: -4px; + border-radius: 999px; + background: linear-gradient(135deg, var(--portal-cyan), var(--portal-purple), var(--portal-pink)); + z-index: -1; + animation: glow 2s ease-in-out infinite; + opacity: 0.8; +} + +.primaryButton::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.primaryButton:hover::after { + width: 400px; + height: 400px; +} + +.primaryButton:hover { + transform: translateY(-6px) scale(1.05); + box-shadow: + 0 0 70px rgba(0, 242, 255, 0.7), + 0 0 100px rgba(112, 0, 255, 0.6), + 0 30px 60px rgba(0, 0, 0, 0.4); +} + +.arrow { + font-size: 24px; + transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + position: relative; + z-index: 1; +} + +.primaryButton:hover .arrow { + transform: translateX(8px); +} + +.secondaryButton { + position: relative; + display: inline-flex; + align-items: center; + gap: 10px; + padding: 0 40px; + height: 70px; + border-radius: 999px; + border: 2px solid var(--neo-cyan); + background: transparent; + color: var(--neo-text); + font-family: 'Orbitron', sans-serif; + font-size: 18px; + font-weight: 700; + cursor: pointer; + letter-spacing: 0.5px; + text-transform: uppercase; + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +.secondaryButton:hover { + transform: translateY(-6px); + background: rgba(0, 242, 255, 0.1); + border-color: var(--neo-purple); + box-shadow: + 0 0 40px rgba(0, 242, 255, 0.4), + 0 20px 40px rgba(0, 0, 0, 0.2); +} + +.playIcon { + font-size: 14px; + transition: transform 0.3s ease; +} + +.secondaryButton:hover .playIcon { + transform: translateX(4px); +} + +.particles { + position: absolute; + inset: 0; + z-index: 1; + pointer-events: none; + overflow: hidden; +} + +.particle { + position: absolute; + width: 4px; + height: 4px; + background: var(--portal-cyan); + border-radius: 50%; + box-shadow: 0 0 10px currentColor; + animation: particleFloat 15s linear infinite; + opacity: 0; +} + +.particle:nth-child(1) { left: 10%; animation-delay: 0s; } +.particle:nth-child(2) { left: 20%; animation-delay: 2s; background: var(--forest-emerald); } +.particle:nth-child(3) { left: 35%; animation-delay: 4s; } +.particle:nth-child(4) { left: 50%; animation-delay: 1s; background: var(--portal-purple); } +.particle:nth-child(5) { left: 65%; animation-delay: 5s; } +.particle:nth-child(6) { left: 75%; animation-delay: 3s; background: var(--forest-emerald); } +.particle:nth-child(7) { left: 85%; animation-delay: 6s; } +.particle:nth-child(8) { left: 95%; animation-delay: 1.5s; background: var(--portal-purple); } + +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes glow { + 0%, 100% { + opacity: 0.6; + } + 50% { + opacity: 1; + } +} + +@keyframes particleFloat { + 0% { + transform: translateY(100vh) translateX(0) rotate(0deg); + opacity: 0; + } + 10% { + opacity: 0.8; + } + 90% { + opacity: 0.8; + } + 100% { + transform: translateY(-100vh) translateX(50px) rotate(360deg); + opacity: 0; + } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .finalCTA { + padding: var(--space-2xl) var(--space-md); + min-height: 500px; + } + + .ctaButtons { + flex-direction: column; + align-items: center; + } + + .primaryButton, + .secondaryButton { + width: 100%; + max-width: 320px; + } +} + +@media (max-width: 480px) { + .primaryButton, + .secondaryButton { + height: 60px; + font-size: 16px; + padding: 0 36px; + } +} + +@media (prefers-reduced-motion: reduce) { + .eyebrow, + .headline, + .description, + .ctaButtons { + animation: none; + } + + .primaryButton::before, + .particle { + animation: none !important; + } + + .primaryButton:hover { + transform: translateY(-3px) scale(1.02); + } + + .secondaryButton:hover { + transform: translateY(-3px); + } + + .primaryButton:hover .arrow, + .secondaryButton:hover .playIcon { + transform: translateX(4px); + } +} From 0d4754570745495c4110f4d38bd619f94c7110f4 Mon Sep 17 00:00:00 2001 From: Rivka Weiss Date: Sun, 25 Jan 2026 16:01:28 +0200 Subject: [PATCH 5/8] style(client): update navigation and footer styling Enhance Navbar and Footer components with improved styling, better responsiveness, and modernized visual design to match the new home page interface. Co-Authored-By: Claude Sonnet 4.5 --- client/src/components/layout/Footer.jsx | 27 +++ .../src/components/layout/Footer.module.css | 156 +++++++++++++++++- client/src/components/layout/Navbar.jsx | 15 +- .../src/components/layout/Navbar.module.css | 23 ++- 4 files changed, 215 insertions(+), 6 deletions(-) diff --git a/client/src/components/layout/Footer.jsx b/client/src/components/layout/Footer.jsx index 12645cc..696ef1a 100644 --- a/client/src/components/layout/Footer.jsx +++ b/client/src/components/layout/Footer.jsx @@ -1,9 +1,18 @@ import { useNavigate } from 'react-router-dom'; +import { useState } from 'react'; import styles from './Footer.module.css'; export default function Footer() { const navigate = useNavigate(); const currentYear = new Date().getFullYear(); + const [email, setEmail] = useState(''); + + const handleNewsletterSubmit = (e) => { + e.preventDefault(); + // TODO: Implement newsletter signup + console.log('Newsletter signup:', email); + setEmail(''); + }; return (