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}
-
-
- ))}
-
-
-
-
-
-
-
- );
-};
-
-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.map((_, index) => (
+ handleItemClick(index)}
+ transition="all 0.3s"
+ />
+ ))}
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ Meet the Team
+
+
+
+
+ {items.map((item, index) => (
+ handleItemClick(index)}
+ >
+
+
+ {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
+ }) => (
+
+
+
+ );
+
+ return (
+
+
+
+
+ 2025
+
+
+ Meet the team
+
+
+
+
+ Co - Directors
+
+
+
+
+
+
+
+ Shreenija
+
+
+
+
+
+
+
+ Cole
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default TeamPage;