diff --git a/src/assets/index.ts b/src/assets/index.ts index 0f5a18f..c35cca0 100644 --- a/src/assets/index.ts +++ b/src/assets/index.ts @@ -5,4 +5,23 @@ import logoDark from "./logo/dark.svg"; // Images import cameroonFlag from "./images/flag-cameroon.webp"; -export { logoLight, logoDark, cameroonFlag }; \ No newline at end of file +// Organizers +import steve from "./organizers/Steve.webp"; +import edmond from "./organizers/edmondMakolle.webp"; +import ange from "./organizers/angeDemanou.webp"; +import djoko from "./organizers/djoko.webp"; +import abuambou from "./organizers/abuambouEvodia.webp"; +import essi from "./organizers/essi.webp"; + + +export { + logoLight, + logoDark, + cameroonFlag, + steve, + edmond, + ange, + djoko, + abuambou, + essi +}; \ No newline at end of file diff --git a/src/assets/organizers/abuambouEvodia.webp b/src/assets/organizers/abuambouEvodia.webp new file mode 100644 index 0000000..8352208 Binary files /dev/null and b/src/assets/organizers/abuambouEvodia.webp differ diff --git a/src/assets/organizers/angeDemanou.webp b/src/assets/organizers/angeDemanou.webp new file mode 100644 index 0000000..7fc2e99 Binary files /dev/null and b/src/assets/organizers/angeDemanou.webp differ diff --git a/src/assets/organizers/djoko.webp b/src/assets/organizers/djoko.webp new file mode 100644 index 0000000..35cc131 Binary files /dev/null and b/src/assets/organizers/djoko.webp differ diff --git a/src/assets/organizers/edmondMakolle.webp b/src/assets/organizers/edmondMakolle.webp new file mode 100644 index 0000000..5fdf815 Binary files /dev/null and b/src/assets/organizers/edmondMakolle.webp differ diff --git a/src/assets/organizers/essi.webp b/src/assets/organizers/essi.webp new file mode 100644 index 0000000..8d176ec Binary files /dev/null and b/src/assets/organizers/essi.webp differ diff --git a/src/assets/organizers/steve.webp b/src/assets/organizers/steve.webp new file mode 100644 index 0000000..18b291a Binary files /dev/null and b/src/assets/organizers/steve.webp differ diff --git a/src/containers/Hero.tsx b/src/containers/Hero.tsx index 560b293..3cc34f0 100644 --- a/src/containers/Hero.tsx +++ b/src/containers/Hero.tsx @@ -241,7 +241,7 @@ export const Hero = () => { return ( diff --git a/src/containers/Team.tsx b/src/containers/Team.tsx index 1a16e80..7ce4ddd 100644 --- a/src/containers/Team.tsx +++ b/src/containers/Team.tsx @@ -1,281 +1,40 @@ -import { useState, useRef, useEffect } from "react"; +import { useState, useRef } from "react"; import { motion, useInView, AnimatePresence } from "framer-motion"; -import { Linkedin, Github, X, Users, Star, MapPin, Sparkles, User } from "lucide-react"; - -interface TeamProps { - imageUrl: string; - name: string; - position: string; - description: string; - socialNetworks: SociaNetworkslProps[]; - githubUsername?: string; // Add GitHub username for avatar fallback -} - -interface SociaNetworkslProps { - name: string; - url: string; -} - -const teamList: TeamProps[] = [ - { - imageUrl: "https://media.licdn.com/dms/image/v2/D4E03AQHnmVB6FQ2UQA/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1726093532105?e=1744848000&v=beta&t=sJUwfm7EKodWlW-yPcahAIAuktZ3s1NAHr_Dxap68e8", - name: "Steve Yonkeu", - position: "Backend and Cloud Engineer", - description: "A backend engineer and tech community leader, Co-founder of Django Cameroon and Python Cameroon.", - githubUsername: "yokwejuste", // Add GitHub username for fallback - socialNetworks: [ - { - name: "Linkedin", - url: "https://www.linkedin.com/in/yokwejuste/", - }, - { - name: "Github", - url: "https://github.com/yokwejuste", - }, - { - name: "X", - url: "https://x.com/yokwejuste", - }, - ], - }, - { - imageUrl: "https://media.licdn.com/dms/image/v2/D4E03AQHmVQ509cUVaA/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1718527201412?e=1744848000&v=beta&t=KcXhaBXUij0f5Qkc7zgmHNw86ecmWXhOXUhAFBXDiAA", - name: "Edmond Makolle", - position: "Backend Engineer", - description: "Python backend engineer focused on server logic, databases, and performance.", - githubUsername: "Edmond22-prog", - socialNetworks: [ - { - name: "Linkedin", - url: "https://www.linkedin.com/in/edmond-makolle-99716b1a2/", - }, - { - name: "Github", - url: "https://github.com/Edmond22-prog", - }, - { - name: "X", - url: "https://x.com/EdmondMakolle", - }, - ], - }, - { - imageUrl: "https://media.licdn.com/dms/image/v2/D4E03AQE_tzGrQnf2QQ/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1704332517739?e=1744848000&v=beta&t=zdmpiFY5KuuMPOl6y-GQC_hnZ4QRcTkjGNfuuj4DUdI", - name: "Loni Tande", - position: "Data Engineer", - description: "Passionate about AI models, deep learning systems, Big DATA and Robotics.", - githubUsername: "Mimi97-aqua", - socialNetworks: [ - { - name: "Linkedin", - url: "https://www.linkedin.com/in/lonitandemiriamebenye/", - }, - { - name: "Github", - url: "https://github.com/Mimi97-aqua/", - }, - { - name: "X", - url: "https://x.com/VesekeM", - }, - ], - }, - { - imageUrl: "https://media.licdn.com/dms/image/v2/D4E03AQHb_R-FzrNhgw/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1712785496110?e=1744848000&v=beta&t=rpf_JSd4x0Wrpl6rzBMhf5B7klBrkJMNVR_QCG2Yie4", - name: "Joel Fah", - position: "UI/UX Designer", - description: "Passionate about UI/UX Designs", - githubUsername: "Joel-Fah", - socialNetworks: [ - { - name: "Linkedin", - url: "https://www.linkedin.com/in/joelfah/", - }, - { - name: "Github", - url: "https://github.com/Joel-Fah", - }, - { - name: "X", - url: "https://x.com/FahDejon", - }, - ], - }, - { - imageUrl: "https://media.licdn.com/dms/image/v2/D4E03AQETL5spp-s2xg/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1730360240208?e=1744848000&v=beta&t=I_NbXLR9bh3_gauXxvGv73MCROlS4vhnb5hiSm95RzY", - name: "ImaJin (Jr Patrick)", - position: "Data Engineer", - description: "Automating deployments and managing of large datasets.", - githubUsername: "ImaJin14", - socialNetworks: [ - { - name: "Linkedin", - url: "https://www.linkedin.com/in/imajin14/", - }, - { - name: "Github", - url: "https://github.com/ImaJin14", - }, - { - name: "X", - url: "https://x.com/Jr_Patrick14", - }, - ], - }, -]; +import { + Users, + Star, + Sparkles, + User, + Globe, + Linkedin, +} from "lucide-react"; +import { teamData } from "@/data/team"; +import type { TeamProps } from "@/types/sections"; // Enhanced image component with fallback system -const ProfileImage = ({ member, isHovered }: { member: TeamProps, isHovered: boolean }) => { - const [imageError, setImageError] = useState(false); - const [imageSrc, setImageSrc] = useState(member.imageUrl); - const [isLoading, setIsLoading] = useState(true); - const [fallbackAttempt, setFallbackAttempt] = useState(0); - - // Function to extract LinkedIn profile ID from URL - const getLinkedInProfileId = (linkedinUrl: string) => { - const match = linkedinUrl.match(/\/in\/([^/]+)/); - return match ? match[1] : null; - }; - - // Get LinkedIn URL from social networks - const linkedInNetwork = member.socialNetworks.find(network => network.name === "Linkedin"); - const linkedInProfileId = linkedInNetwork ? getLinkedInProfileId(linkedInNetwork.url) : null; - - // Fallback image sources in order of preference - const getFallbackImages = () => { - const fallbacks = []; - - // 1. Try alternative LinkedIn image URLs (different formats) - if (linkedInProfileId) { - // Try different LinkedIn image endpoints - fallbacks.push(`https://media.licdn.com/dms/image/v2/D4E03AQH${linkedInProfileId}/profile-displayphoto-shrink_200_200/profile-displayphoto-shrink_200_200/0/${linkedInProfileId}?e=2147483647&v=beta&t=${Date.now()}`); - } - - // 2. GitHub avatar (most reliable) - if (member.githubUsername) { - fallbacks.push(`https://github.com/${member.githubUsername}.png?size=200`); - fallbacks.push(`https://avatars.githubusercontent.com/${member.githubUsername}?s=200&v=4`); - } - - // 3. Gravatar based on name - const createHash = (str: string) => { - let hash = 0; - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Convert to 32-bit integer - } - return Math.abs(hash).toString(16); - }; - const nameHash = createHash(member.name.toLowerCase().replace(/\s+/g, '')); - fallbacks.push(`https://www.gravatar.com/avatar/${nameHash}?d=identicon&s=200`); - - // 4. UI Avatars (generates avatar from initials) - fallbacks.push(`https://ui-avatars.com/api/?name=${encodeURIComponent(member.name)}&size=200&background=random&color=fff&bold=true&format=png`); - - // 5. Robohash (fun robot avatars) - fallbacks.push(`https://robohash.org/${encodeURIComponent(member.name)}?size=200x200&set=set1`); - - return fallbacks; - }; - - const tryNextFallback = async () => { - const fallbacks = getFallbackImages(); - - if (fallbackAttempt < fallbacks.length) { - const nextSrc = fallbacks[fallbackAttempt]; - setFallbackAttempt(prev => prev + 1); - - // Test if the image loads - try { - const img = new Image(); - img.crossOrigin = "anonymous"; - - await new Promise((resolve, reject) => { - img.onload = () => { - setImageSrc(nextSrc); - setIsLoading(false); - setImageError(true); // Mark as using fallback - resolve(true); - }; - img.onerror = reject; - img.src = nextSrc; - }); - } catch (error) { - // If this fallback fails, try the next one - if (fallbackAttempt + 1 < fallbacks.length) { - setTimeout(tryNextFallback, 100); - } else { - // All fallbacks failed, show placeholder - setImageSrc(''); - setIsLoading(false); - setImageError(true); - } - } - } else { - // All fallbacks exhausted - setImageSrc(''); - setIsLoading(false); - setImageError(true); - } - }; - - const handleImageError = () => { - if (!imageError) { - setImageError(true); - setFallbackAttempt(0); - tryNextFallback(); - } - }; - - const handleImageLoad = () => { - setIsLoading(false); - }; - - useEffect(() => { - // Reset states when member changes - setImageError(false); - setImageSrc(member.imageUrl); - setIsLoading(true); - setFallbackAttempt(0); - }, [member.imageUrl, member.name]); - +const ProfileImage = ({ + member, + isHovered, +}: { + member: TeamProps; + isHovered: boolean; +}) => { return ( - {/* Loading spinner */} - - {isLoading && ( - - - - )} - - {/* Profile Image */} - {imageSrc ? ( + {member.image ? ( )} - + {/* Overlay effect on hover */} - - {/* Fallback indicator */} - {imageError && imageSrc && ( - - {member.githubUsername && imageSrc.includes('github') ? ( - - ) : ( - - )} - - )} ); }; @@ -327,17 +69,6 @@ export const Team = () => { const sectionRef = useRef(null); const isInView = useInView(sectionRef, { once: false, amount: 0.2 }); - const socialIcon = (iconName: string) => { - switch (iconName) { - case "Linkedin": - return ; - case "Github": - return ; - case "X": - return ; - } - }; - return ( { > {/* Animated background elements */}
- - - @@ -378,40 +114,41 @@ export const Team = () => { {/* Floating particles */} - {isInView && Array.from({ length: 10 }).map((_, i) => ( - - ))} + {isInView && + Array.from({ length: 10 }).map((_, i) => ( + + ))}
{/* Section header */} - { - Our Amazing Team + + Our Amazing Team + - { > Meet the Python Cameroon Team {/* Glowing effect behind text */} - - + {/* Animated divider */} - { animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }} transition={{ delay: 0.6, duration: 0.8 }} > - Dedicated innovators advancing Python development in Cameroon through collaboration and expertise. + Dedicated innovators advancing Python development in Cameroon through + collaboration and expertise. {/* Team grid */} - {teamList.map( - (member: TeamProps, index) => { - const isHovered = hoveredMember === member.name; - - return ( + > + {" "} + {teamData.map((member: TeamProps, index) => { + const isHovered = hoveredMember === member.name; + + return ( + setHoveredMember(member.name)} + onMouseLeave={() => setHoveredMember(null)} + > + {/* Animated border gradient */} setHoveredMember(member.name)} - onMouseLeave={() => setHoveredMember(null)} - > - {/* Animated border gradient */} - + transition={{ + opacity: { duration: 0.3 }, + background: { duration: 4, repeat: Infinity }, + }} + /> + + {" "} + {/* Profile image container */} {/* Profile image container */} + className="relative mb-6" + whileHover={{ scale: 1.05 }} + transition={{ duration: 0.3 }} + > + {/* Rotating background gradient */} - {/* Rotating background gradient */} + className="absolute -inset-3 bg-gradient-to-r from-primary/30 via-secondary/30 to-primary/30 rounded-full -z-10" + animate={{ + rotate: isHovered ? 360 : 0, + scale: isHovered ? 1.1 : 1, + opacity: isHovered ? 0.7 : 0.3, + }} + transition={{ + rotate: { duration: 8, ease: "linear" }, + scale: { duration: 0.3 }, + opacity: { duration: 0.3 }, + }} + style={{ filter: "blur(15px)" }} + /> + {/* Use the ProfileImage component with fallback system */} + + + {/* Pulsing ring effect */} + {isHovered && ( - {/* Use the ProfileImage component with fallback system */} - - - {/* Pulsing ring effect */} - {isHovered && ( - - )} - {/* Member info */} - {" "} + {/* Member info */} + + - - {member.name} - - - - - - {member.position} - - - - - {member.description} - - + {member.name} + - {/* Social links */} - - {member.socialNetworks.map(({ name: socialName, url }: SociaNetworkslProps, socialIndex) => ( - - {socialIcon(socialName)} - - ))} + + {member.role} + - - {/* Subtle glow effect on hover */} - {isHovered && ( - + {/* Social links */} + + {member.links.linkedIn && ( + + whileTap={{ scale: 0.95 }} + initial={{ opacity: 0, scale: 0.8 }} + animate={{ opacity: 1, scale: 1 }} + transition={{ + delay: 0.4, + duration: 0.3, + }} + > + + + )} + {member.links.website && ( + + + )} - - {/* Sparkle effects on hover */} - - {isHovered && ( - <> - {Array.from({ length: 3 }).map((_, i) => ( - - - - ))} - - )} - + {/* Subtle glow effect on hover */} + {isHovered && ( + + )} + {/* Sparkle effects on hover */} + + {isHovered && ( + <> + {Array.from({ length: 3 }).map((_, i) => ( + + + + ))} + + )} + - ); - } - )} + + ); + })}
); diff --git a/src/data/team.ts b/src/data/team.ts new file mode 100644 index 0000000..0ee7cb9 --- /dev/null +++ b/src/data/team.ts @@ -0,0 +1,53 @@ +import { edmond, steve, ange, djoko, abuambou, essi } from "@/assets"; + +export const teamData = [{ + name: "Steve Yonkeu", + role: "Lead Python Cameroon", + image: steve, + links: { + linkedIn: "https://www.linkedin.com/in/yokwejuste", + website: "https://yokwejuste.me", + } + }, + { + name: "Edmond Makolle", + role: "Co-Lead, Mentorship / Learning Coordinator", + image: edmond, + links: { + linkedIn: "https://www.linkedin.com/in/edmondmakolle", + } + }, + { + name: "Ange Demanou", + role: "Social Media Manager", + image: ange, + links: { + linkedIn: "https://www.linkedin.com/in/ange-demanou-367466340", + } + }, + { + name: "Djoko Christian", + role: "Open Source Coordinator", + image: djoko, + links: { + linkedIn: "https://www.linkedin.com/in/djoko-christian", + } + }, + { + name: "Abuambou Evodia", + role: "Project Coordinator", + image: abuambou, + links: { + linkedIn: "https://www.linkedin.com/in/abuambou-evodia-ruth-6b1b51270", + } + }, + { + name: "Essi Junior", + role: "UI/UX and Graphic Designer", + image: essi, + links: { + linkedIn: "https://www.linkedin.com/in/essijunior/", + website: "https://essijunior.com", + } + }, +]; \ No newline at end of file diff --git a/src/types/sections.ts b/src/types/sections.ts new file mode 100644 index 0000000..0164105 --- /dev/null +++ b/src/types/sections.ts @@ -0,0 +1,12 @@ + +type TeamProps = { + name: string; + image: string; + role: string; + links: { + linkedIn: string; + website?: string; + }; +} + +export type {TeamProps} \ No newline at end of file