diff --git a/index.html b/index.html index e0dcd1d..40739de 100644 --- a/index.html +++ b/index.html @@ -31,6 +31,11 @@ + + + + + diff --git a/src/App.css b/src/App.css index 1eaa180..f7c95b5 100644 --- a/src/App.css +++ b/src/App.css @@ -69,57 +69,71 @@ /* *=========== Green theme =========== */ @layer base { - :root { - --background: 0 0% 100%; - --foreground: 240 10% 3.9%; - --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; - --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - --primary: 142.1 76.2% 36.3%; - --primary-foreground: 355.7 100% 97.3%; - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - --ring: 142.1 76.2% 36.3%; - --radius: 0.5rem; - } - - .dark { - --background: 20 14.3% 4.1%; - --foreground: 0 0% 95%; - --card: 24 9.8% 10%; - --card-foreground: 0 0% 95%; - --popover: 0 0% 9%; - --popover-foreground: 0 0% 95%; - --primary: 142.1 70.6% 45.3%; - --primary-foreground: 144.9 80.4% 10%; - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - --muted: 0 0% 15%; - --muted-foreground: 240 5% 64.9%; - --accent: 12 6.5% 15.1%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 85.7% 97.3%; - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 142.4 71.8% 29.2%; - } + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 166, 95%, 29%; + --primary-foreground: 355.7 100% 97.3%; + --secondary: 50, 96%, 59%; + --secondary-foreground: 240 5.9% 10%; + --muted: 50, 96%, 59%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 50, 96%, 59%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 142.1 76.2% 36.3%; + --radius: 0.5rem; + + /* Fonts */ + --font-dotgothic16: "DotGothic16", sans-serif; + --font-space-mono: "Space Mono", monospace; + } + + .dark { + --background: 20 14.3% 4.1%; + --foreground: 0 0% 95%; + --card: 24 9.8% 10%; + --card-foreground: 0 0% 95%; + --popover: 0 0% 9%; + --popover-foreground: 0 0% 95%; + --primary: 166, 95%, 29%; + --primary-foreground: 144.9 80.4% 10%; + --secondary: 50, 96%, 59%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 15%; + --muted-foreground: 240 5% 64.9%; + --accent: 12 6.5% 15.1%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 85.7% 97.3%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 142.4 71.8% 29.2%; + } } @layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } -} + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground font-space-mono; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + @apply font-dotgothic16; + } +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 0aab127..80ce3ad 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,7 +9,7 @@ import {ScrollToTop} from "@/components/ScrollToTop"; import {Services} from "@/containers/Services"; import {Sponsors} from "@/containers/Sponsors"; import {Team} from "@/containers/Team"; -import {Applications} from "@/containers/Testimonials"; +import {Applications} from "@/containers/Applications"; import "./App.css"; diff --git a/src/assets/images/flag-cameroon.webp b/src/assets/images/flag-cameroon.webp new file mode 100644 index 0000000..3a046a1 Binary files /dev/null and b/src/assets/images/flag-cameroon.webp differ diff --git a/src/assets/index.ts b/src/assets/index.ts new file mode 100644 index 0000000..0f5a18f --- /dev/null +++ b/src/assets/index.ts @@ -0,0 +1,8 @@ +// logo +import logoLight from "./logo/light.svg"; +import logoDark from "./logo/dark.svg"; + +// Images +import cameroonFlag from "./images/flag-cameroon.webp"; + +export { logoLight, logoDark, cameroonFlag }; \ No newline at end of file diff --git a/src/components/HeroNetwork.tsx b/src/components/HeroNetwork.tsx new file mode 100644 index 0000000..4a19d09 --- /dev/null +++ b/src/components/HeroNetwork.tsx @@ -0,0 +1,112 @@ +import { useEffect, useRef } from "react"; + +const COLORS = ["#fcd116", "#ce1126", "#009a44", "#ffffff"]; +const NODE_COUNT = 70; +const MAX_DIST = 130; + +interface Node { + x: number; + y: number; + vx: number; + vy: number; + r: number; + color: string; + opacity: number; +} + +const hexAlpha = (hex: string, alpha: number): string => + hex + Math.floor(alpha * 255).toString(16).padStart(2, "0"); + +export default function HeroNetwork() { + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + let animId: number; + let nodes: Node[] = []; + + const initNodes = () => { + nodes = Array.from({ length: NODE_COUNT }, () => ({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + vx: (Math.random() - 0.5) * 0.4, + vy: (Math.random() - 0.5) * 0.4, + r: Math.random() * 2 + 1, + color: COLORS[Math.floor(Math.random() * COLORS.length)], + opacity: Math.random() * 0.5 + 0.25, + })); + }; + + const resize = () => { + canvas.width = canvas.offsetWidth; + canvas.height = canvas.offsetHeight; + initNodes(); + }; + + const draw = () => { + const { width: W, height: H } = canvas; + ctx.clearRect(0, 0, W, H); + + for (const n of nodes) { + n.x += n.vx; + n.y += n.vy; + if (n.x < 0 || n.x > W) n.vx *= -1; + if (n.y < 0 || n.y > H) n.vy *= -1; + } + + for (let i = 0; i < nodes.length; i++) { + for (let j = i + 1; j < nodes.length; j++) { + const dx = nodes[i].x - nodes[j].x; + const dy = nodes[i].y - nodes[j].y; + const d = Math.sqrt(dx * dx + dy * dy); + if (d < MAX_DIST) { + const alpha = (1 - d / MAX_DIST) * 0.22; + ctx.beginPath(); + ctx.moveTo(nodes[i].x, nodes[i].y); + ctx.lineTo(nodes[j].x, nodes[j].y); + ctx.strokeStyle = hexAlpha(nodes[i].color, alpha); + ctx.lineWidth = 0.6; + ctx.stroke(); + } + } + } + + for (const n of nodes) { + ctx.beginPath(); + ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2); + ctx.fillStyle = hexAlpha(n.color, n.opacity); + ctx.fill(); + } + + animId = requestAnimationFrame(draw); + }; + + const ro = new ResizeObserver(() => { + cancelAnimationFrame(animId); + resize(); + draw(); + }); + + ro.observe(canvas); + resize(); + draw(); + + return () => { + cancelAnimationFrame(animId); + ro.disconnect(); + }; + }, []); + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index e487f68..77c0144 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -1,101 +1,58 @@ +import { logoLight, logoDark } from "@/assets"; + export const LogoIcon = () => { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + <> +
+ Python Cameroon +
+ +
+ Python Cameroon +
+ ); }; export const MedalIcon = () => { return ( - - - - - - - - - - - - + + Free Icons + + {/* Ribbon left */} + + {/* Ribbon detail lines */} + + + {/* Medal circle outer */} + + {/* Medal circle inner ring */} + + {/* Star in center */} + - - - - - - - - - - - - - - - - ); }; @@ -108,10 +65,7 @@ export const MapIcon = () => { className="w-14 fill-primary" > Free Icons - + { className="w-14 fill-primary" > Free Icons - + { className="w-14 fill-primary" > Free Icons - + { className="w-12 fill-primary" > Free Icons - + { className="w-12 fill-primary" > Free Icons - + { className="w-12 fill-primary" > Free Icons - + { className="w-12 fill-primary" > Free Icons - + { }} > { animate={{ opacity: counterInView ? [0.1, 0.3, 0.1] : 0, background: [ - "radial-gradient(circle, rgba(var(--primary-rgb), 0.3) 0%, transparent 70%)", - "radial-gradient(circle, rgba(var(--primary-rgb), 0.5) 0%, transparent 80%)", - "radial-gradient(circle, rgba(var(--primary-rgb), 0.3) 0%, transparent 70%)" + "radial-gradient(circle, hsl(var(--primary-rgb)) 0%, transparent 70%)", + "radial-gradient(circle, hsl(var(--primary-rgb)) 0%, transparent 80%)", + "radial-gradient(circle, hsl(var(--primary-rgb)) 0%, transparent 70%)" ] }} transition={{ @@ -150,7 +150,7 @@ export const Statistics = () => { /> { /> { {/* Subtle underline animation on hover */} { const { language, toggleLanguage } = useLanguage(); return ( -
{ /> {
{ onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > + + {/* Animated border glow */} { }} > { }} transition={{ duration: 6, repeat: Infinity, ease: "easeInOut" }} /> - - {/* Enhanced content section */} @@ -206,13 +206,13 @@ export const About = () => { > {/* Enhanced heading with staggered animation */} { }} > About{" "} - - {/* Glowing effect behind "About" */} - diff --git a/src/containers/Testimonials.tsx b/src/containers/Applications.tsx similarity index 95% rename from src/containers/Testimonials.tsx rename to src/containers/Applications.tsx index c75e705..53ca2fa 100644 --- a/src/containers/Testimonials.tsx +++ b/src/containers/Applications.tsx @@ -1,7 +1,7 @@ import { useState, useRef } from "react"; import { motion, useInView, AnimatePresence } from "framer-motion"; import { Card, CardHeader, CardTitle } from "@/components/ui/card"; -import { Code2, Database, Brain, Zap, Shield, Gamepad2, Sparkles, ArrowRight } from "lucide-react"; +import { Code2, Database, Brain, Zap, Shield, Gamepad2, Sparkles, ArrowRight, Info } from "lucide-react"; interface ApplicationProps { image: string; @@ -54,9 +54,9 @@ export const Applications = () => { icon: , techStack: ["TensorFlow", "PyTorch", "Scikit-learn", "OpenCV"], color: { - primary: "from-purple-500 to-pink-500", - secondary: "bg-purple-500/10", - accent: "border-purple-500/30" + primary: "from-secondary to-pink-500", + secondary: "bg-secondary/10", + accent: "border-secondary/30" } }, { @@ -121,7 +121,7 @@ export const Applications = () => { /> { > Explore { Python is used in various fields, from web development to artificial intelligence.
- - Hover over each section to learn more. + + Hover over each section to learn more. {/* Animated underline */} @@ -449,7 +449,7 @@ export const Applications = () => { transition={{ delay: 0.2, duration: 0.3 }} >

{title}

-

+

{description}

@@ -485,33 +485,32 @@ export const Applications = () => { > - + Ready to start your Python journey? - + Join Python Cameroon { +const AnimatedCharacter = ({ + character, + delay = 0, +}: AnimatedCharacterProps) => { return ( }; // Split text into animated characters -const SplitTextAnimation = ({ text, className = "", delay = 0 }: SplitTextAnimationProps) => { +const SplitTextAnimation = ({ + text, + className = "", + delay = 0, +}: SplitTextAnimationProps) => { return ( {text.split("").map((char: string, index: number) => ( - ))} @@ -59,40 +74,40 @@ const BubbleBackground = () => { ))} @@ -105,48 +120,48 @@ const EnhancedImage = ({ src, alt }: EnhancedImageProps) => { const imageRef = useRef(null); const mouseX = useMotionValue(0); const mouseY = useMotionValue(0); - + const rotateX = useTransform(mouseY, [-300, 300], [10, -10]); const rotateY = useTransform(mouseX, [-300, 300], [-10, 10]); const glowX = useTransform(mouseX, [-300, 300], [0, 100], { clamp: false }); const glowY = useTransform(mouseY, [-300, 300], [0, 100], { clamp: false }); - + const handleMouseMove = (e: React.MouseEvent) => { if (!imageRef.current) return; - + const rect = imageRef.current.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; mouseX.set(e.clientX - centerX); mouseY.set(e.clientY - centerY); }; - + const handleMouseLeave = () => { mouseX.set(0); mouseY.set(0); }; return ( - - { animate={{ scale: 1 }} transition={{ duration: 1.5 }} /> - `radial-gradient(circle at ${gx}% ${gy}%, rgba(var(--primary-rgb), 0.4), transparent 60%)` - ) + ([gx, gy]) => + `radial-gradient(circle at ${gx}% ${gy}%, rgba(var(--primary-rgb), 0.4), transparent 60%)` + ), }} /> - - {/* Reflective base */} - - - {/* Animated corners */} - {[0, 1, 2, 3].map((i) => { - const isTop = i < 2; - const isLeft = i % 2 === 0; - return ( - - ); - })} ); }; export const Hero = () => { - const [mapUrl, setMapUrl] = useState(""); const [isVisible, setIsVisible] = useState(true); const controls = useAnimation(); - + useEffect(() => { - setMapUrl("https://www.motosha.com/files/preview/2000x1336/24824-flag-of-cameroon.jpg"); - // Initialize animations controls.start({ opacity: 1, y: 0, - transition: { duration: 0.7, ease: "easeOut" } + transition: { duration: 0.7, ease: "easeOut" }, }); - + // Intersection observer for exit animations const observer = new IntersectionObserver( ([entry]) => { @@ -233,15 +206,15 @@ export const Hero = () => { }, { threshold: 0.2 } ); - + const section = document.querySelector(".hero-section"); if (section) observer.observe(section); - + return () => { if (section) observer.unobserve(section); }; }, [controls]); - + // Text animation sequence const titleVariants = { hidden: { opacity: 0 }, @@ -249,31 +222,31 @@ export const Hero = () => { opacity: 1, transition: { staggerChildren: 0.1, - delayChildren: 0.3 - } - } + delayChildren: 0.3, + }, + }, }; - + const wordVariants = { hidden: { y: 20, opacity: 0 }, - visible: { - y: 0, + visible: { + y: 0, opacity: 1, - transition: { + transition: { type: "spring", - damping: 12 - } - } + damping: 12, + }, + }, }; - + return ( - - + {/* Floating grid lines effect */}
@@ -293,53 +266,50 @@ export const Hero = () => { initial={{ width: 0 }} animate={{ width: "100%" }} transition={{ duration: 2, delay: i * 0.1 }} - style={{ top: `${(i+1) * 20}%`, position: "absolute" }} + style={{ top: `${(i + 1) * 20}%`, position: "absolute" }} /> ))}
- + {/* Text content with animations */} - -

- {" "} - + is

{" "}

- {

-
+
- - Unleash your creativity with Python. Whether it's building web apps, automating tasks, or exploring AI – Python makes it all possible! - + {/* Animated underline */} - - + {/* Floating action buttons */} - { > Explore Python - - + - Newsletter @@ -460,54 +426,10 @@ export const Hero = () => { {/* Cameroon Map Display with enhanced effects */}
- {mapUrl && ( - - )} + + - - {/* Tech decorations */} - - - 🐍 - - - - - - - 🚀 - - -
); -}; \ No newline at end of file +}; diff --git a/src/containers/HowItWorks.tsx b/src/containers/HowItWorks.tsx index 4e8ce39..bad67bb 100644 --- a/src/containers/HowItWorks.tsx +++ b/src/containers/HowItWorks.tsx @@ -67,7 +67,7 @@ const AnimatedFeatureCard = ({ icon, title, description, index }: AnimatedFeatur className="relative perspective-1000" > - {description} - +
{ style={{ filter: "blur(80px)" }} /> { className="relative inline-block" > { { { ))} - - - - - - - - ); }; diff --git a/src/containers/Services.tsx b/src/containers/Services.tsx index 9282f38..4a75427 100644 --- a/src/containers/Services.tsx +++ b/src/containers/Services.tsx @@ -56,7 +56,7 @@ export const Services = () => { style={{ filter: "blur(80px)" }} /> { transition={{ duration: 5, repeat: Infinity, repeatType: "reverse" }} > - Client-Centric{" "} + Client-Centric { background: "linear-gradient(to right, rgba(var(--primary-rgb), 0.2), rgba(147, 51, 234, 0.2), rgba(var(--primary-rgb), 0.2))", }} /> - + {" "} Services { /> -
+
{serviceList.map(({ icon, title, description }: ServiceProps, index) => ( { > { }} > @@ -188,7 +188,7 @@ export const Services = () => { {title} @@ -200,7 +200,7 @@ export const Services = () => { animate={{ opacity: 1 }} transition={{ delay: 0.6 + index * 0.1, duration: 0.5 }} > - + {description} @@ -233,7 +233,7 @@ export const Services = () => { style={{ transformStyle: "preserve-3d" }} > { /> { transition={{ duration: 5, repeat: Infinity, repeatType: "reverse" }} > { {/* Animated divider */} @@ -190,7 +190,7 @@ export const Sponsors = () => { > {/* Rotating background gradient */} { {/* Animated underline */} { {/* Bottom border animation */} { return ( @@ -77,52 +76,59 @@ export const Navbar = () => { const [isOpen, setIsOpen] = useState(false); const [scrolled, setScrolled] = useState(false); const [activeSection, setActiveSection] = useState(""); - useEffect(() => { const handleScroll = () => { setScrolled(window.scrollY > 10); - + // Update active section based on scroll - const sections = routeList.map(route => { - const id = route.href.substring(1); - const element = document.getElementById(id); - if (element) { - const rect = element.getBoundingClientRect(); - const isInView = rect.top <= window.innerHeight/2 && rect.bottom >= window.innerHeight/2; - return { id, isInView }; - } - return null; - }).filter(Boolean); - - const currentSection = sections.find(section => section?.isInView)?.id; + const sections = routeList + .map((route) => { + const id = route.href.substring(1); + const element = document.getElementById(id); + if (element) { + const rect = element.getBoundingClientRect(); + const isInView = + rect.top <= window.innerHeight / 2 && + rect.bottom >= window.innerHeight / 2; + return { id, isInView }; + } + return null; + }) + .filter(Boolean); + + const currentSection = sections.find((section) => section?.isInView)?.id; if (currentSection) { setActiveSection(currentSection); } }; - + window.addEventListener("scroll", handleScroll); - handleScroll(); - + handleScroll(); + return () => window.removeEventListener("scroll", handleScroll); }, []); return ( - {/* Background glow effect */}
-
-
@@ -144,9 +150,10 @@ export const Navbar = () => { className="relative" > + {/* Glow effect */} - { style={{ filter: "blur(10px)" }} /> - - PyCM - - {/* Mobile navigation */}
- + { {/* Notification dot */} { - + - + Python Cameroon @@ -216,20 +216,22 @@ export const Navbar = () => { animate={{ opacity: 1, y: 0 }} transition={{ delay: index * 0.1 }} className="w-full" - > + {" "} + setIsOpen(false)} className="w-full flex justify-center text-lg relative overflow-hidden group hover:bg-accent hover:text-accent-foreground rounded-md px-4 py-2 transition-colors" > {label} - + ))}
- +
{ href="https://github.com/pythoncameroon" target="_blank" className={`w-full border ${buttonVariants({ - variant: "secondary", + variant: "outline", })} group relative overflow-hidden`} > -
+
- + Github
- + { -
{/* Desktop navigation */} +
{" "} + {/* Desktop navigation */}
); })} -
{ rel="noreferrer noopener" href="https://github.com/pythoncameroon" target="_blank" - className={`border ${buttonVariants({ variant: "secondary" })} group relative overflow-hidden`} + className={`border ${buttonVariants({ + variant: "outline", + })} group relative overflow-hidden`} > -
+
- Github + + Github +
@@ -316,8 +328,8 @@ export const Navbar = () => { > - -