Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/AppContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import ContributorProfile from "./pages/ContributorProfile.jsx";
import Contributors from "./pages/Contributors.jsx";
import DocsHub from "./pages/DocsHub.jsx";
import SettingsMenu from "./components/SettingsMenu.jsx";
import Discover from "./pages/Discover.jsx";
import ChatBot from "./components/ChatBot.jsx";
import NotFound from "./pages/NotFound.jsx";
import { Ion } from "cesium";
Expand All @@ -47,7 +48,7 @@ const AppContent = () => {
{settings[0].enabled && <NotifierSat />}
{isPointerEnabled && <CursorEffects isActive={isPointerEnabled} />}
<DiamondCursor isActive={isPointerEnabled} />
<Loader/>
<Loader />
<ScrollToTop />
<SettingsMenu />
<ChatBot />
Expand All @@ -58,6 +59,7 @@ const AppContent = () => {
<Route path="/" element={<Landing />} />
<Route path="/newsletter" element={<Newsletter />} />
<Route path="/events" element={<Events />} />
<Route path="/discover" element={<Discover />} />
<Route path="/calendar" element={<EventCalendarPage />} />
<Route path="/projects" element={<Projects />} />
<Route path="/login" element={<Login />} />
Expand Down
208 changes: 118 additions & 90 deletions src/components/Events.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Footer from "./footer";
const Events = () => {
const [filterType, setFilterType] = useState("all");
const timelineRef = useRef(null);

// Use framer-motion's useScroll for smooth timeline animation
const { scrollYProgress } = useScroll({
target: timelineRef,
Expand All @@ -30,7 +30,7 @@ const Events = () => {

// Transform scroll progress to height percentage
const heightProgress = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);

useLenis();

useLenis();
Expand Down Expand Up @@ -117,7 +117,7 @@ const Events = () => {
},
];

const calendarEventsData =
const calendarEventsData =
{
"events": [
{
Expand Down Expand Up @@ -349,11 +349,10 @@ const Events = () => {
<button
key={type}
onClick={() => filterEvents(type)}
className={`relative text-xs sm:text-sm md:text-base px-3 sm:px-4 py-1.5 rounded-full transition-all duration-300 whitespace-nowrap font-medium ${
filterType === type
? "text-blue-400 font-semibold"
: "text-gray-300 hover:text-white"
}`}
className={`relative text-xs sm:text-sm md:text-base px-3 sm:px-4 py-1.5 rounded-full transition-all duration-300 whitespace-nowrap font-medium ${filterType === type
? "text-blue-400 font-semibold"
: "text-gray-300 hover:text-white"
}`}
>
{type === "all" && "All Events"}
{type === "past" && "Past Events"}
Expand All @@ -372,18 +371,18 @@ const Events = () => {
filterType === "all"
? "5%"
: filterType === "past"
? "25%"
: filterType === "ongoing"
? "48%"
: "76%",
? "25%"
: filterType === "ongoing"
? "48%"
: "76%",
width:
filterType === "all"
? "80px"
: filterType === "past"
? "95px"
: filterType === "ongoing"
? "120px"
: "110px",
? "95px"
: filterType === "ongoing"
? "120px"
: "110px",
}}
transition={{ type: "spring", stiffness: 350, damping: 25 }}
/>
Expand Down Expand Up @@ -415,23 +414,23 @@ const Events = () => {
>
<source src={event.videoSrc} type="video/mp4" />
</video>

{/* Date Badge */}
<div className="absolute top-3 left-3 sm:top-4 sm:left-4 z-10">
<div className="relative bg-white/20 backdrop-blur-lg text-white font-medium px-5 py-2.5 rounded-xl border border-white/40 shadow-[0_4px_20px_rgba(255,255,255,0.15)] hover:bg-white/30 hover:border-white/60 hover:shadow-[0_4px_30px_rgba(255,255,255,0.25)] transition-all duration-300 ease-out">
<p className="text-xs sm:text-sm font-semibold">
{new Date(event.startDate).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
{new Date(event.startDate).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})}
</p>
</div>
</div>

{/* Overlay Gradient */}
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent opacity-60 group-hover:opacity-80 transition-opacity duration-300"></div>

{/* Content */}
<div className="absolute bottom-0 left-0 right-0 w-full p-3 sm:p-4 md:p-5 text-white transform translate-y-0 group-hover:translate-y-0 transition-transform duration-300">
<h2 className="text-sm sm:text-base md:text-lg lg:text-xl font-bold mb-1 sm:mb-1.5 md:mb-2 line-clamp-1 drop-shadow-lg">
Expand All @@ -454,7 +453,7 @@ const Events = () => {
<h2 className="timeline-title">
SAST Events Timeline
</h2>

<div className="timeline-wrapper">
{/* Animated Timeline line - Desktop */}
<div className="timeline-line-desktop relative">
Expand All @@ -475,7 +474,7 @@ const Events = () => {
}}
/>
</div>

{/* Animated Mobile timeline line */}
<div className="timeline-line-mobile relative">
<motion.div
Expand All @@ -496,71 +495,15 @@ const Events = () => {
/>
</div>

{timelineData.map((event, index) => {
// Calculate when each item should appear based on scroll progress
const itemOffset = index / (timelineData.length - 1);
const itemProgress = useTransform(
scrollYProgress,
[Math.max(0, itemOffset - 0.15), Math.min(1, itemOffset + 0.05)],
[0, 1]
);

return (
<motion.div
key={index}
style={{
opacity: itemProgress,
}}
className={`timeline-item ${index % 2 === 0 ? 'reverse' : ''}`}
>
{/* Timeline dot with scale animation */}
<motion.div
style={{
scale: itemProgress,
}}
className={`timeline-dot ${event.status === 'upcoming' ? 'pulse' : ''}`}
>
{event.status === 'upcoming' && (
<motion.div
animate={{
scale: [1, 1.3, 1],
opacity: [0.7, 1, 0.7],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
className="absolute inset-0 rounded-full bg-blue-400"
/>
)}
</motion.div>

{/* Content card */}
<motion.div
style={{
opacity: itemProgress,
x: index % 2 === 0 ? useTransform(itemProgress, [0, 1], [50, 0]) : useTransform(itemProgress, [0, 1], [-50, 0])
}}
className={`timeline-content ${index % 2 === 0 ? 'reverse' : 'normal'}`}
>
<div className="timeline-card">
<div className={`timeline-card-inner ${index % 2 === 0 ? 'reverse' : ''}`}>
<span className={`timeline-badge ${event.status === 'completed' ? 'completed' : 'upcoming'}`}>
{event.category} • {event.status === 'completed' ? 'Completed' : 'Upcoming'}
</span>
<h3 className="timeline-event-title">{event.title}</h3>
<p className="timeline-event-date">{event.date}</p>
<p className="timeline-event-description">{event.description}</p>
</div>
</div>
</motion.div>

{/* Spacer for desktop */}
<div className="timeline-spacer"></div>
</motion.div>
);
})}
{timelineData.map((event, index) => (
<TimelineItem
key={index}
event={event}
index={index}
totalItems={timelineData.length}
scrollYProgress={scrollYProgress}
/>
))}
</div>
</div>
</section>
Expand All @@ -569,4 +512,89 @@ const Events = () => {
);
};

const TimelineItem = ({ event, index, totalItems, scrollYProgress }) => {
// Calculate when each item should appear based on scroll progress
const itemOffset = index / (totalItems - 1);
const itemProgress = useTransform(
scrollYProgress,
[Math.max(0, itemOffset - 0.15), Math.min(1, itemOffset + 0.05)],
[0, 1]
);

const xPosEven = useTransform(itemProgress, [0, 1], [50, 0]);
const xPosOdd = useTransform(itemProgress, [0, 1], [-50, 0]);

const xPos = index % 2 === 0 ? xPosEven : xPosOdd;

return (
<motion.div
style={{
opacity: itemProgress,
}}
className={`timeline-item ${index % 2 === 0 ? 'reverse' : ''}`}
>
{/* Timeline dot with scale animation */}
<motion.div
style={{
scale: itemProgress,
}}
className={`timeline-dot ${event.status === 'upcoming' ? 'pulse' : ''}`}
>
{event.status === 'upcoming' && (
<motion.div
animate={{
scale: [1, 1.3, 1],
opacity: [0.7, 1, 0.7],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
className="absolute inset-0 rounded-full bg-blue-400"
/>
)}
</motion.div>

{/* Content card */}
<motion.div
style={{
opacity: itemProgress,
x: xPos
}}
className={`timeline-content ${index % 2 === 0 ? 'reverse' : 'normal'}`}
>
<div className="timeline-card">
<div className={`timeline-card-inner ${index % 2 === 0 ? 'reverse' : ''}`}>
<span className={`timeline-badge ${event.status === 'completed' ? 'completed' : 'upcoming'}`}>
{event.category} • {event.status === 'completed' ? 'Completed' : 'Upcoming'}
</span>
<h3 className="timeline-event-title">{event.title}</h3>
<p className="timeline-event-date">{event.date}</p>
<p className="timeline-event-description">{event.description}</p>
</div>
</div>
</motion.div>

{/* Spacer for desktop */}
<div className="timeline-spacer"></div>
</motion.div>
);
};

import PropTypes from 'prop-types';

TimelineItem.propTypes = {
event: PropTypes.shape({
status: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
date: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
}).isRequired,
index: PropTypes.number.isRequired,
totalItems: PropTypes.number.isRequired,
scrollYProgress: PropTypes.object.isRequired,
};

export default Events;
13 changes: 7 additions & 6 deletions src/components/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const Navbar = () => {
const [menuOpen, setMenuOpen] = useState(false);
const [exploreOpen, setExploreOpen] = useState(false);
const [communityOpen, setCommunityOpen] = useState(false);

const { scrollYProgress } = useScroll();

useMotionValueEvent(scrollYProgress, "change", (current) => {
Expand All @@ -37,6 +37,7 @@ const Navbar = () => {

const exploreItems = [
{ name: "Events", path: "/events" },
{ name: "Discover", path: "/discover" },
{ name: "Projects", path: "/projects" },
{ name: "Astronomy News", path: "/news" },
{ name: "Track", path: "/track" },
Expand Down Expand Up @@ -155,8 +156,8 @@ const Navbar = () => {
<NavLink to="/" onClick={closeMenu} className="flex items-center">
<img src={logo} alt="SAST Logo" className="w-12 h-12" />
</NavLink>
<button
onClick={toggleMenu}
<button
onClick={toggleMenu}
className="relative w-12 h-12 flex flex-col items-center justify-center gap-1.5 rounded-lg hover:bg-white/5 active:bg-white/10 transition-colors duration-200"
aria-label="Toggle menu"
aria-expanded={menuOpen}
Expand Down Expand Up @@ -258,10 +259,10 @@ const Navbar = () => {
Contact
</a>
</li>
</ul>
</motion.div>
</ul >
</motion.div >
)}
</div>
</div >
</>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/context/MessagesContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const MessagesProvider = ({ children }) => {
// POST to backend
try {
const res = await fetch(
`${backendUrl}/bot` || "http://localhost:3000/bot",
(backendUrl ? `${backendUrl}/bot` : "http://localhost:3000/bot"),
{
method: "POST",
headers: { "Content-Type": "application/json" },
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useMessages.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useMessages } from "../context/MessagesContext";
import { useMessages as useMessagesContext } from "../context/MessagesContext";

export const useMessages = () => {
const context = useMessages();
const context = useMessagesContext();

if (!context) {
throw new Error("useMessages must be used within a MessagesProvider");
Expand Down
1 change: 0 additions & 1 deletion src/pages/ContributorProfile.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable react/prop-types */
import React from "react";
import { Link, useParams } from "react-router-dom";
import { Github, Linkedin, Globe, Twitter } from "lucide-react";
import { getContributorBySlug } from "../lib/contributors/data";
Expand Down
Loading
Loading