diff --git a/apps/info/src/App.tsx b/apps/info/src/App.tsx index f22e5c70..bb0a09e1 100644 --- a/apps/info/src/App.tsx +++ b/apps/info/src/App.tsx @@ -4,6 +4,7 @@ import { Home } from "./routes/Home"; import { Archive } from "./routes/Archive"; import { FAQ } from "./routes/FAQ"; import { Navbar } from "./components/Navbar"; +import TeamPage from "./routes/Team"; function App() { return ( @@ -13,6 +14,7 @@ function App() { } /> } /> } /> + } /> ); diff --git a/apps/info/src/components/Carousel.tsx b/apps/info/src/components/Carousel.tsx deleted file mode 100644 index 7ae950ad..00000000 --- a/apps/info/src/components/Carousel.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import React, { useState } from "react"; -import { Box, Heading, Flex, Image, Text, Button } from "@chakra-ui/react"; - -interface CarouselItem { - title: string; - image: string; -} - -interface CircularCarouselProps { - items: CarouselItem[]; -} - -const CircularCarousel: React.FC = ({ items }) => { - const [activeIndex, setActiveIndex] = useState(0); - const [isAnimating, setIsAnimating] = useState(false); - - const handleItemClick = (index: number) => { - if (isAnimating || index === activeIndex) return; - - setIsAnimating(true); - - // rotating shortest path - const totalItems = items.length; - const clockwiseSteps = (index - activeIndex + totalItems) % totalItems; - const counterClockwiseSteps = - (activeIndex - index + totalItems) % totalItems; - - const shouldGoClockwise = clockwiseSteps <= counterClockwiseSteps; - const stepsToTake = shouldGoClockwise - ? clockwiseSteps - : counterClockwiseSteps; - const direction = shouldGoClockwise ? 1 : -1; - - if (stepsToTake === 1) { - setActiveIndex(index); - setTimeout(() => setIsAnimating(false), 500); - return; - } - - let currentStep = 0; - - const animateStep = () => { - if (currentStep >= stepsToTake) { - setIsAnimating(false); - return; - } - - setActiveIndex((prev) => (prev + direction + totalItems) % totalItems); - currentStep++; - - setTimeout(animateStep, 300); - }; - - animateStep(); - }; - - const handleNavClick = (direction: "prev" | "next") => { - if (isAnimating) return; - - setIsAnimating(true); - const newIndex = - direction === "prev" - ? (activeIndex - 1 + items.length) % items.length - : (activeIndex + 1) % items.length; - - setActiveIndex(newIndex); - setTimeout(() => setIsAnimating(false), 500); - }; - - const getItemStyle = (index: number) => { - const totalItems = items.length; - const angleDegree = 360 / totalItems; - const baseAngle = angleDegree * (index - activeIndex); - const radians = (baseAngle * Math.PI) / 180; - - const horizontalRadius = 300; - const verticalRadius = 180; - - const x = horizontalRadius * Math.sin(radians); - const y = verticalRadius * Math.cos(radians); - const zIndex = Math.round(50 - y / 10); - - const scale = 0.8 + ((y + verticalRadius) / (verticalRadius * 2)) * 0.3; - const isFrontItem = index === activeIndex; - - return { - transform: `translate(${x}px, ${y}px) scale(${scale})`, - zIndex, - opacity: 0.8 + ((y + verticalRadius) / (verticalRadius * 2)) * 0.4, - boxShadow: isFrontItem - ? "0 10px 30px rgba(0,0,0,0.4)" - : "0 5px 15px rgba(0,0,0,0.2)" - }; - }; - - return ( - - - Meet the Team - - - - - {items.map((item, index) => ( - handleItemClick(index)} - > - {item.title} - - {item.title} - - - ))} - - - - - - - - ); -}; - -export default CircularCarousel; diff --git a/apps/info/src/components/Foldable.tsx b/apps/info/src/components/Foldable.tsx index 85c70a23..d3c7d47e 100644 --- a/apps/info/src/components/Foldable.tsx +++ b/apps/info/src/components/Foldable.tsx @@ -1,169 +1,923 @@ -// credit to : https://www.frontend.fyi/tutorials/making-a-foldable-map-with-framer-motion - -import React, { useState } from "react"; -import { Box, Flex, Text, Button, useColorModeValue } from "@chakra-ui/react"; -import { motion, AnimatePresence } from "framer-motion"; - -const PANEL_W = 250; -const PANEL_H = 480; -const PANELS = [-2, -1, 0, 1, 2]; - -const FoldableFAQ: React.FC = () => { - const bg = useColorModeValue("white", "gray.800"); - const altBg = useColorModeValue("gray.50", "gray.700"); - const borderColor = useColorModeValue("gray.200", "gray.600"); - const shadow = useColorModeValue("rgba(0,0,0,0.1)", "rgba(0,0,0,0.4)"); - const [open, setOpen] = useState(false); - - const deckVariants = { - closed: { - scale: 0.96, - x: "-50%", - transition: { type: "spring", stiffness: 80, damping: 20 } - }, - open: { - scale: 0.82, - x: "-50%", // STILL CENTER IT ONCE WE OPEN - transition: { - type: "spring", - stiffness: 80, - damping: 20, - delayChildren: 0.1, - staggerChildren: 0.18 +import React, { useState, useEffect } from "react"; +import { + Box, + Flex, + Text, + Heading, + VStack, + ChakraProvider, + extendTheme, + useColorModeValue +} from "@chakra-ui/react"; +import { + motion, + useMotionValue, + useTransform, + useMotionValueEvent, + useSpring, + AnimatePresence +} from "framer-motion"; + +const theme = extendTheme({ + styles: { global: { body: { bg: "gray.50" } } } +}); + +const MotionBox = motion(Box); +const MotionFlex = motion(Flex); +const MotionHeading = motion(Heading); +const MotionText = motion(Text); + +interface FaqItem { + question: string; + answer: string; +} + +interface FoldableFAQProps { + title?: string; +} + +const faqItems: FaqItem[] = [ + { + question: "Lorem ipsum dolor sit amet?", + answer: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ac felis eget justo cursus finibus in vel nibh." + }, + { + question: "Nulla facilisi mauris vel?", + answer: + "Nulla facilisi. Mauris vel erat nec justo cursus auctor. Suspendisse potenti. Sed mattis neque ut ex volutpat." + }, + { + question: "Fusce sagittis magna vel?", + answer: + "Fusce sagittis magna vel nunc porttitor, nec hendrerit magna posuere. Sed porttitor eros vitae diam finibus." + }, + { + question: "Vestibulum ante ipsum primis?", + answer: + "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec lacinia sapien in elit." + }, + { + question: "Cras at justo sit amet?", + answer: + "Cras at justo sit amet erat molestie varius. Integer hendrerit dolor sed eleifend sodales. Phasellus magna elit." + } +]; + +const FoldableFAQ: React.FC = ({ title = "FAQ" }) => { + const [isMobile, setIsMobile] = useState(false); + const [size, setSize] = useState({ width: 0, height: 0 }); + + useEffect(() => { + const update = () => { + const mobile = window.innerWidth < 768; + setIsMobile(mobile); + + if (mobile) { + const width = Math.min(window.innerWidth * 0.9, 400); + const height = Math.min(window.innerHeight * 0.8, 700); + setSize({ width, height }); + } else { + const width = Math.min(window.innerWidth * 0.92, 1200); + const height = width * 0.55; + setSize({ width, height }); } - } + }; + update(); + window.addEventListener("resize", update); + return () => window.removeEventListener("resize", update); + }, []); + + const bgColor = useColorModeValue("white", "gray.800"); + const cardBg = useColorModeValue("gray.50", "gray.700"); + const textColor = useColorModeValue("gray.700", "white"); + const accent = useColorModeValue("blue.500", "blue.300"); + + if (isMobile) { + return ( + + ); + } + + return ( + + ); +}; + +const DesktopFoldableFAQ: React.FC<{ + title: string; + size: { width: number; height: number }; + bgColor: string; + cardBg: string; + textColor: string; + accent: string; +}> = ({ title, size, bgColor, cardBg, textColor, accent }) => { + const maxDrag = 380; + const xDrag = useMotionValue(0); + const xSpring = useSpring(xDrag, { stiffness: 400, damping: 35 }); + const componentX = useTransform(xSpring, [0, maxDrag], [0, -maxDrag]); + const [isFolded, setIsFolded] = useState(true); + + const toggleFoldState = () => { + const targetValue = isFolded ? maxDrag : 0; + xDrag.set(targetValue); }; - const panelVariants = (side: "left" | "right" | "center") => ({ - initial: { - rotateY: side === "left" ? -90 : side === "right" ? 90 : 0, - opacity: side === "center" ? 1 : 0 - }, - open: { - rotateY: 0, - opacity: 1, - transition: { type: "spring", stiffness: 70, damping: 16, mass: 0.6 } - }, - closed: { - rotateY: side === "left" ? -90 : side === "right" ? 90 : 0, - opacity: side === "center" ? 1 : 0, - transition: { duration: 0.4 } - } + useMotionValueEvent(xDrag, "change", (x) => { + if (x > maxDrag * 0.8 && isFolded) setIsFolded(false); + if (x < maxDrag * 0.2 && !isFolded) setIsFolded(true); }); - const leftOf = (idx: number) => `${idx * PANEL_W}px`; + useEffect(() => { + const handleKeyPress = (event: KeyboardEvent) => { + if (event.code === "Space" || event.code === "Enter") { + event.preventDefault(); + toggleFoldState(); + } + }; + + window.addEventListener("keydown", handleKeyPress); + return () => window.removeEventListener("keydown", handleKeyPress); + }, [isFolded]); + + const windmillEffect = [ + { rotateZ: -90, rotateY: -45, scale: 0.8, rotateX: 0, y: 0, skewY: 0 }, + { rotateZ: -45, rotateY: -22, scale: 0.9, rotateX: 0, y: 0, skewY: 0 }, + { rotateZ: 0, rotateY: 0, scale: 1, rotateX: 0, y: 0, skewY: 0 }, + { rotateZ: 45, rotateY: 22, scale: 0.9, rotateX: 0, y: 0, skewY: 0 }, + { rotateZ: 90, rotateY: 45, scale: 0.8, rotateX: 0, y: 0, skewY: 0 } + ]; + + const panelShift = [220, 110, 0, -110, -220]; + const globalRotateY = useTransform(xSpring, [0, maxDrag], [0, 3]); + const globalZ = useTransform(xSpring, [0, maxDrag], [0, 50]); + + const panel0Transforms = { + x: useTransform(xSpring, [0, maxDrag], [panelShift[0], 0]), + rotateY: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[0].rotateY, 0] + ), + rotateX: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[0].rotateX, 0] + ), + rotateZ: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[0].rotateZ, 0] + ), + scale: useTransform(xSpring, [0, maxDrag], [windmillEffect[0].scale, 1]), + y: useTransform(xSpring, [0, maxDrag], [windmillEffect[0].y, 0]), + skewY: useTransform(xSpring, [0, maxDrag], [windmillEffect[0].skewY, 0]), + z: useTransform(xSpring, [0, maxDrag], [0, 0 * 15]), + opacity: useTransform(xSpring, [0, maxDrag * 0.3, maxDrag], [0.4, 0.8, 1]) + }; + + const panel1Transforms = { + x: useTransform(xSpring, [0, maxDrag], [panelShift[1], 0]), + rotateY: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[1].rotateY, 0] + ), + rotateX: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[1].rotateX, 0] + ), + rotateZ: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[1].rotateZ, 0] + ), + scale: useTransform(xSpring, [0, maxDrag], [windmillEffect[1].scale, 1]), + y: useTransform(xSpring, [0, maxDrag], [windmillEffect[1].y, 0]), + skewY: useTransform(xSpring, [0, maxDrag], [windmillEffect[1].skewY, 0]), + z: useTransform(xSpring, [0, maxDrag], [0, 1 * 15]), + opacity: useTransform(xSpring, [0, maxDrag * 0.3, maxDrag], [0.4, 0.8, 1]) + }; + + const panel2Transforms = { + x: useTransform(xSpring, [0, maxDrag], [panelShift[2], 0]), + rotateY: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[2].rotateY, 0] + ), + rotateX: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[2].rotateX, 0] + ), + rotateZ: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[2].rotateZ, 0] + ), + scale: useTransform(xSpring, [0, maxDrag], [windmillEffect[2].scale, 1]), + y: useTransform(xSpring, [0, maxDrag], [windmillEffect[2].y, 0]), + skewY: useTransform(xSpring, [0, maxDrag], [windmillEffect[2].skewY, 0]), + z: useTransform(xSpring, [0, maxDrag], [0, 2 * 15]), + opacity: useTransform(xSpring, [0, maxDrag * 0.3, maxDrag], [0.4, 0.8, 1]) + }; + + const panel3Transforms = { + x: useTransform(xSpring, [0, maxDrag], [panelShift[3], 0]), + rotateY: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[3].rotateY, 0] + ), + rotateX: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[3].rotateX, 0] + ), + rotateZ: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[3].rotateZ, 0] + ), + scale: useTransform(xSpring, [0, maxDrag], [windmillEffect[3].scale, 1]), + y: useTransform(xSpring, [0, maxDrag], [windmillEffect[3].y, 0]), + skewY: useTransform(xSpring, [0, maxDrag], [windmillEffect[3].skewY, 0]), + z: useTransform(xSpring, [0, maxDrag], [0, 3 * 15]), + opacity: useTransform(xSpring, [0, maxDrag * 0.3, maxDrag], [0.4, 0.8, 1]) + }; + + const panel4Transforms = { + x: useTransform(xSpring, [0, maxDrag], [panelShift[4], 0]), + rotateY: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[4].rotateY, 0] + ), + rotateX: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[4].rotateX, 0] + ), + rotateZ: useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[4].rotateZ, 0] + ), + scale: useTransform(xSpring, [0, maxDrag], [windmillEffect[4].scale, 1]), + y: useTransform(xSpring, [0, maxDrag], [windmillEffect[4].y, 0]), + skewY: useTransform(xSpring, [0, maxDrag], [windmillEffect[4].skewY, 0]), + z: useTransform(xSpring, [0, maxDrag], [0, 4 * 15]), + opacity: useTransform(xSpring, [0, maxDrag * 0.3, maxDrag], [0.4, 0.8, 1]) + }; + + const panelTransforms = [ + panel0Transforms, + panel1Transforms, + panel2Transforms, + panel3Transforms, + panel4Transforms + ]; + return ( - + + + (t > maxDrag * 0.45 ? maxDrag : 0) + }} + style={{ x: xDrag, width: size.width, height: size.height }} + cursor="grab" + _active={{ cursor: "grabbing" }} + _hover={{ + boxShadow: isFolded + ? "0 8px 20px -6px rgba(0, 0, 0, 0.12), 0 8px 8px -6px rgba(0, 0, 0, 0.05)" + : undefined + }} + position="relative" + borderRadius="lg" + boxShadow="2xl" + overflow="hidden" + bg={bgColor} + animate={{ + boxShadow: isFolded + ? "0 6px 16px -4px rgba(0, 0, 0, 0.08), 0 6px 6px -4px rgba(0, 0, 0, 0.03)" + : "0 16px 32px -10px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.04)" + }} + transition={{ duration: 0.3 }} + onClick={toggleFoldState} + tabIndex={0} + role="button" + aria-label={isFolded ? "Open FAQ" : "Close FAQ"} + > + + {faqItems.map((faq, i) => { + const transforms = panelTransforms[i]; + return ( + 0 ? "1px solid" : undefined} + borderColor="gray.100" + bg={bgColor} + boxShadow="xl" + overflow="hidden" + > + + + + {!isFolded && ( + + + {faq.question} + + + {faq.answer} + + + )} + + + + + ); + })} + + + + {isFolded && ( + + + {title} + + + Click or drag to open →
+ + Press Space or Enter + +
+
+ )} +
+
+
+
+
+ ); +}; + +const MobileFoldableFAQ: React.FC<{ + title: string; + size: { width: number; height: number }; + bgColor: string; + cardBg: string; + textColor: string; + accent: string; +}> = ({ title, size, bgColor, cardBg, textColor, accent }) => { + const maxDrag = 300; + const yDrag = useMotionValue(0); + const ySpring = useSpring(yDrag, { stiffness: 400, damping: 35 }); + const componentY = useTransform(ySpring, [0, maxDrag], [0, -maxDrag]); + + const [isFolded, setIsFolded] = useState(true); + + const toggleFoldState = () => { + const targetValue = isFolded ? maxDrag : 0; + yDrag.set(targetValue); + }; + + useMotionValueEvent(yDrag, "change", (y) => { + if (y > maxDrag * 0.7 && isFolded) setIsFolded(false); + if (y < maxDrag * 0.3 && !isFolded) setIsFolded(true); + }); + + useEffect(() => { + const handleKeyPress = (event: KeyboardEvent) => { + if (event.code === "Space" || event.code === "Enter") { + event.preventDefault(); + toggleFoldState(); + } + }; + + window.addEventListener("keydown", handleKeyPress); + return () => window.removeEventListener("keydown", handleKeyPress); + }, [isFolded]); + + const stackEffect = [ + { y: -120, scale: 0.85, rotateX: 20, opacity: 0.6 }, + { y: -60, scale: 0.9, rotateX: 15, opacity: 0.7 }, + { y: 0, scale: 1, rotateX: 0, opacity: 1 }, + { y: 60, scale: 0.9, rotateX: -15, opacity: 0.7 }, + { y: 120, scale: 0.85, rotateX: -20, opacity: 0.6 } + ]; + + const panel0Transforms = { + y: useTransform(ySpring, [0, maxDrag], [stackEffect[0].y, 0]), + scale: useTransform(ySpring, [0, maxDrag], [stackEffect[0].scale, 1]), + rotateX: useTransform(ySpring, [0, maxDrag], [stackEffect[0].rotateX, 0]), + opacity: useTransform( + ySpring, + [0, maxDrag * 0.3, maxDrag], + [stackEffect[0].opacity, 0.8, 1] + ), + z: useTransform(ySpring, [0, maxDrag], [-(0 * 10), 0 * 5]) + }; + + const panel1Transforms = { + y: useTransform(ySpring, [0, maxDrag], [stackEffect[1].y, 0]), + scale: useTransform(ySpring, [0, maxDrag], [stackEffect[1].scale, 1]), + rotateX: useTransform(ySpring, [0, maxDrag], [stackEffect[1].rotateX, 0]), + opacity: useTransform( + ySpring, + [0, maxDrag * 0.3, maxDrag], + [stackEffect[1].opacity, 0.8, 1] + ), + z: useTransform(ySpring, [0, maxDrag], [-(1 * 10), 1 * 5]) + }; + + const panel2Transforms = { + y: useTransform(ySpring, [0, maxDrag], [stackEffect[2].y, 0]), + scale: useTransform(ySpring, [0, maxDrag], [stackEffect[2].scale, 1]), + rotateX: useTransform(ySpring, [0, maxDrag], [stackEffect[2].rotateX, 0]), + opacity: useTransform( + ySpring, + [0, maxDrag * 0.3, maxDrag], + [stackEffect[2].opacity, 0.8, 1] + ), + z: useTransform(ySpring, [0, maxDrag], [-(2 * 10), 2 * 5]) + }; + + const panel3Transforms = { + y: useTransform(ySpring, [0, maxDrag], [stackEffect[3].y, 0]), + scale: useTransform(ySpring, [0, maxDrag], [stackEffect[3].scale, 1]), + rotateX: useTransform(ySpring, [0, maxDrag], [stackEffect[3].rotateX, 0]), + opacity: useTransform( + ySpring, + [0, maxDrag * 0.3, maxDrag], + [stackEffect[3].opacity, 0.8, 1] + ), + z: useTransform(ySpring, [0, maxDrag], [-(3 * 10), 3 * 5]) + }; + + const panel4Transforms = { + y: useTransform(ySpring, [0, maxDrag], [stackEffect[4].y, 0]), + scale: useTransform(ySpring, [0, maxDrag], [stackEffect[4].scale, 1]), + rotateX: useTransform(ySpring, [0, maxDrag], [stackEffect[4].rotateX, 0]), + opacity: useTransform( + ySpring, + [0, maxDrag * 0.3, maxDrag], + [stackEffect[4].opacity, 0.8, 1] + ), + z: useTransform(ySpring, [0, maxDrag], [-(4 * 10), 4 * 5]) + }; + + const panelTransforms = [ + panel0Transforms, + panel1Transforms, + panel2Transforms, + panel3Transforms, + panel4Transforms + ]; + + const globalRotateX = useTransform(ySpring, [0, maxDrag], [0, -2]); + const globalZ = useTransform(ySpring, [0, maxDrag], [0, 30]); + + return ( + + - - {PANELS.map((i) => { - const isCentre = i === 0; - const side: "left" | "right" | "center" = - i < 0 ? "left" : i > 0 ? "right" : "center"; - - return ( - - {(isCentre || open) && ( - (t > maxDrag * 0.5 ? maxDrag : 0) + }} + style={{ + y: yDrag, + width: size.width, + height: size.height + }} + cursor="grab" + _active={{ cursor: "grabbing" }} + _hover={{ + boxShadow: isFolded + ? "0 12px 30px -6px rgba(0, 0, 0, 0.15), 0 12px 12px -6px rgba(0, 0, 0, 0.06)" + : undefined + }} + position="relative" + borderRadius="xl" + boxShadow="2xl" + overflow="hidden" + bg={bgColor} + animate={{ + boxShadow: isFolded + ? "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)" + : "0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.05)" + }} + transition={{ duration: 0.3 }} + onClick={toggleFoldState} + aria-label={isFolded ? "Open FAQ" : "Close FAQ"} + > + + {faqItems.map((faq, i) => { + const transforms = panelTransforms[i]; + + return ( + 0 ? "1px solid" : undefined} + borderColor="gray.100" + bg={bgColor} + boxShadow="lg" + overflow="hidden" + > + + + + {!isFolded && ( + + + {faq.question} + + + {faq.answer} + + + )} + + + + + ); + })} + + + + {isFolded && ( + + + {title} + + - {isCentre ? ( - <> - - FAQ - - - - - ) : ( - <> - - - - )} - - )} - - ); - })} - + Tap or swipe down to open ↓
+ + Press Space or Enter + + + + )} + + +
-
+ ); }; diff --git a/apps/info/src/components/Navbar.tsx b/apps/info/src/components/Navbar.tsx index 0f4b3970..1f097c71 100644 --- a/apps/info/src/components/Navbar.tsx +++ b/apps/info/src/components/Navbar.tsx @@ -17,6 +17,9 @@ export const Navbar = () => { FAQ + + Team + diff --git a/apps/info/src/pages/Home/Carousel.tsx b/apps/info/src/pages/Home/Carousel.tsx new file mode 100644 index 00000000..23ae2d5e --- /dev/null +++ b/apps/info/src/pages/Home/Carousel.tsx @@ -0,0 +1,337 @@ +// interface CarouselItem { +// title: string; +// image: string; +// } + +// interface CircularCarouselProps { +// items: CarouselItem[]; +// } + +import { useState } from "react"; +import { + Box, + Heading, + Flex, + Image, + Text, + Button, + useBreakpointValue +} from "@chakra-ui/react"; + +const CircularCarousel = () => { + const items = [ + { title: "RP 2025 Site", image: "/images/rp2025site.png" }, + { title: "RP 2025 Site 2", image: "/images/rp2025site.png" }, + { title: "RP 2025 Site 3", image: "/images/rp2025site.png" } + ]; + + const [activeIndex, setActiveIndex] = useState(0); + const [isAnimating, setIsAnimating] = useState(false); + + const isMobile = useBreakpointValue({ base: true, md: false }) ?? true; + + const defaultConfig = { + containerHeight: "500px", + headingSize: "3xl", + headingMb: 6, + horizontalRadius: 120, + verticalRadius: 80, + itemWidth: "250px", + itemHeight: "180px", + itemMarginLeft: "-90px", + itemMarginTop: "-60px", + ellipseWidth: "260px", + ellipseHeight: "180px", + buttonSpacing: 4, + buttonSize: "md", + buttonPx: 6, + navMt: 8, + fontSize: "16px" + }; + + const desktopConfig = { + containerHeight: "700px", + headingSize: "5xl", + headingMb: 10, + horizontalRadius: 300, + verticalRadius: 180, + itemWidth: "300px", + itemHeight: "200px", + itemMarginLeft: "-140px", + itemMarginTop: "-90px", + ellipseWidth: "620px", + ellipseHeight: "380px", + buttonSpacing: 6, + buttonSize: "lg", + buttonPx: 8, + navMt: 40, + fontSize: "22px" + }; + + const config = + useBreakpointValue({ + base: defaultConfig, + md: desktopConfig + }) ?? defaultConfig; + + const handleItemClick = (index: number) => { + if (isAnimating || index === activeIndex) return; + + setIsAnimating(true); + + const totalItems = items.length; + const clockwiseSteps = (index - activeIndex + totalItems) % totalItems; + const counterClockwiseSteps = + (activeIndex - index + totalItems) % totalItems; + + const shouldGoClockwise = clockwiseSteps <= counterClockwiseSteps; + const stepsToTake = shouldGoClockwise + ? clockwiseSteps + : counterClockwiseSteps; + const direction = shouldGoClockwise ? 1 : -1; + + if (stepsToTake === 1) { + setActiveIndex(index); + setTimeout(() => setIsAnimating(false), 500); + return; + } + + let currentStep = 0; + + const animateStep = () => { + if (currentStep >= stepsToTake) { + setIsAnimating(false); + return; + } + + setActiveIndex((prev) => (prev + direction + totalItems) % totalItems); + currentStep++; + + setTimeout(animateStep, 300); + }; + + animateStep(); + }; + + const handleNavClick = (direction: string) => { + if (isAnimating) return; + + setIsAnimating(true); + const newIndex = + direction === "prev" + ? (activeIndex - 1 + items.length) % items.length + : (activeIndex + 1) % items.length; + + setActiveIndex(newIndex); + setTimeout(() => setIsAnimating(false), 500); + }; + + const getItemStyle = (index: number) => { + const totalItems = items.length; + const angleDegree = 360 / totalItems; + const baseAngle = angleDegree * (index - activeIndex); + const radians = (baseAngle * Math.PI) / 180; + + const x = config.horizontalRadius * Math.sin(radians); + const y = config.verticalRadius * Math.cos(radians); + const zIndex = Math.round(50 - y / 10); + + const scale = + 0.8 + ((y + config.verticalRadius) / (config.verticalRadius * 2)) * 0.3; + const isFrontItem = index === activeIndex; + + return { + transform: `translate(${x}px, ${y}px) scale(${scale})`, + zIndex, + opacity: + 0.8 + ((y + config.verticalRadius) / (config.verticalRadius * 2)) * 0.4, + boxShadow: isFrontItem + ? "0 10px 30px rgba(0,0,0,0.4)" + : "0 5px 15px rgba(0,0,0,0.2)" + }; + }; + + if (isMobile) { + return ( + + + Meet the Team + + + + {items[activeIndex].title} + + {items[activeIndex].title} + + + + + {items.map((_, index) => ( + handleItemClick(index)} + transition="all 0.3s" + /> + ))} + + + + + + + + ); + } + + return ( + + + Meet the Team + + + + + {items.map((item, index) => ( + handleItemClick(index)} + > + {item.title} + + {item.title} + + + ))} + + + + + + + + ); +}; + +export default CircularCarousel; diff --git a/apps/info/src/routes/Home.tsx b/apps/info/src/routes/Home.tsx index 5e08c035..fa9c50ba 100644 --- a/apps/info/src/routes/Home.tsx +++ b/apps/info/src/routes/Home.tsx @@ -1,4 +1,5 @@ // src/routes/Home.tsx +import CircularCarousel from "@/pages/Home/Carousel"; import { ExhibitSection } from "../pages/Home/ExhibitSection"; import { Header } from "../pages/Home/Header"; import Stats from "../pages/Home/Stats"; @@ -9,6 +10,7 @@ export const Home = () => {
+ ); }; diff --git a/apps/info/src/routes/Team.tsx b/apps/info/src/routes/Team.tsx new file mode 100644 index 00000000..7faf4d05 --- /dev/null +++ b/apps/info/src/routes/Team.tsx @@ -0,0 +1,468 @@ +import React from "react"; +import { + Box, + Container, + Heading, + Text, + VStack, + Image, + useBreakpointValue, + Divider, + Flex +} from "@chakra-ui/react"; + +const TeamPage: React.FC = () => { + const sidebarWidth = useBreakpointValue({ + base: "60px", + md: "80px", + lg: "120px" + }); + const headingSize = useBreakpointValue({ + base: "lg", + md: "xl", + lg: "2xl", + xl: "3xl" + }); + const spacing = useBreakpointValue({ base: 3, md: 4, lg: 6, xl: 8 }); + + const blankImage = + "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; + + const TeamSection: React.FC<{ + title: string; + children: React.ReactNode; + sectionBg?: string; + }> = ({ title, children, sectionBg = "white" }) => ( + + + + + {title} + + + + + {children} + + + + ); + + const ImageBox: React.FC<{ + width: string; + height: string; + isHighlighted?: boolean; + mobileWidth?: string; + mobileHeight?: string; + }> = ({ + width, + height, + isHighlighted = false, + mobileWidth, + mobileHeight + }) => ( + + Team member + + ); + + return ( + + + + + 2025 + + + Meet the team + + + + + Co - Directors + + + + + Shreenija + + + Shreenija + + + + + Cole + + + Cole + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default TeamPage;