Skip to content

Commit c7cb74b

Browse files
Refactor Carousel component for improved readability
1 parent c564a37 commit c7cb74b

File tree

2 files changed

+57
-173
lines changed

2 files changed

+57
-173
lines changed

jaseci-org/layouts/components/landing/Carousel.js

Lines changed: 51 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import { useState, useEffect, useRef } from "react";
44
import PropTypes from "prop-types";
55

6-
// --- Custom Hook for Intersection Observer ---
76
const useIntersectionObserver = (ref, options) => {
87
const [inView, setInView] = useState(false);
98

@@ -15,98 +14,27 @@ const useIntersectionObserver = (ref, options) => {
1514
}
1615
}, options);
1716

18-
if (ref.current) {
19-
observer.observe(ref.current);
20-
}
21-
22-
return () => {
23-
if (ref.current) {
24-
observer.unobserve(ref.current);
25-
}
26-
};
17+
if (ref.current) observer.observe(ref.current);
18+
return () => { if (ref.current) observer.unobserve(ref.current); };
2719
}, [ref, options]);
2820

2921
return inView;
3022
};
3123

32-
// Continuous carousel - shows 3 cards, scrolls one at a time, loops infinitely
3324
const Carousel = ({ slides, title, sectionId }) => {
34-
if (!slides || !Array.isArray(slides) || slides.length === 0) {
35-
return null;
36-
}
25+
if (!slides || !Array.isArray(slides) || slides.length === 0) return null;
3726

3827
const titleRef = useRef(null);
39-
const carouselRef = useRef(null);
40-
const navRef = useRef(null);
28+
const gridRef = useRef(null);
4129

4230
const titleInView = useIntersectionObserver(titleRef, { threshold: 0.1 });
43-
const carouselInView = useIntersectionObserver(carouselRef, { threshold: 0.2 });
44-
const navInView = useIntersectionObserver(navRef, { threshold: 0.1 });
45-
46-
const [currentIndex, setCurrentIndex] = useState(0);
47-
const [isAnimating, setIsAnimating] = useState(false);
48-
const [animationDirection, setAnimationDirection] = useState('next');
49-
const autoplayInterval = useRef(null);
50-
const totalSlides = slides.length;
51-
52-
// Get visible cards (3 cards starting from currentIndex, wrapping around)
53-
const getVisibleCards = () => {
54-
const cards = [];
55-
for (let i = 0; i < 3; i++) {
56-
const index = (currentIndex + i) % totalSlides;
57-
cards.push({ ...slides[index], originalIndex: index });
58-
}
59-
return cards;
60-
};
61-
62-
const nextSlide = () => {
63-
if (isAnimating) return;
64-
setAnimationDirection('next');
65-
setIsAnimating(true);
66-
setTimeout(() => {
67-
setCurrentIndex((prev) => (prev + 1) % totalSlides);
68-
setIsAnimating(false);
69-
}, 300);
70-
};
71-
72-
const prevSlide = () => {
73-
if (isAnimating) return;
74-
setAnimationDirection('prev');
75-
setIsAnimating(true);
76-
setTimeout(() => {
77-
setCurrentIndex((prev) => (prev - 1 + totalSlides) % totalSlides);
78-
setIsAnimating(false);
79-
}, 300);
80-
};
81-
82-
const goToSlide = (index) => {
83-
if (isAnimating || index === currentIndex) return;
84-
setAnimationDirection(index > currentIndex ? 'next' : 'prev');
85-
setIsAnimating(true);
86-
setTimeout(() => {
87-
setCurrentIndex(index);
88-
setIsAnimating(false);
89-
}, 300);
90-
};
91-
92-
const startAutoPlay = () => {
93-
stopAutoPlay();
94-
autoplayInterval.current = setInterval(nextSlide, 4000);
95-
};
96-
97-
const stopAutoPlay = () => {
98-
clearInterval(autoplayInterval.current);
99-
};
100-
101-
useEffect(() => {
102-
startAutoPlay();
103-
return () => stopAutoPlay();
104-
}, []);
105-
106-
const visibleCards = getVisibleCards();
31+
const gridInView = useIntersectionObserver(gridRef, { threshold: 0.1 });
10732

10833
return (
109-
<section className="py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-dark-bg via-medium-bg to-dark-bg relative overflow-hidden" id={sectionId}>
34+
<section
35+
className="py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-dark-bg via-medium-bg to-dark-bg relative overflow-hidden"
36+
id={sectionId}
37+
>
11038
{/* Background decorative elements */}
11139
<div className="absolute inset-0 opacity-[0.06] overflow-hidden">
11240
<div className="absolute top-20 left-32 w-96 h-96 bg-gradient-to-br from-primary-orange via-orange-500 to-amber-600 rounded-full blur-3xl animate-pulse"></div>
@@ -118,10 +46,9 @@ const Carousel = ({ slides, title, sectionId }) => {
11846
{title && (
11947
<div
12048
ref={titleRef}
121-
className={`
122-
text-center mb-12 sm:mb-14 lg:mb-16 transition-all duration-700 ease-out
123-
${titleInView ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'}
124-
`}
49+
className={`text-center mb-12 sm:mb-14 lg:mb-16 transition-all duration-700 ease-out ${
50+
titleInView ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10"
51+
}`}
12552
>
12653
<h2 className="text-2xl sm:text-3xl lg:text-4xl font-bold text-white mb-4 bg-gradient-to-r from-white via-primary-orange to-amber-500 bg-clip-text text-transparent">
12754
{title}
@@ -133,96 +60,51 @@ const Carousel = ({ slides, title, sectionId }) => {
13360
</div>
13461
)}
13562

63+
{/* All cards displayed at once — 1 col mobile, 2 col tablet, 4 col desktop */}
13664
<div
137-
ref={carouselRef}
138-
className={`
139-
relative transition-all duration-700 ease-out delay-200
140-
${carouselInView ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'}
141-
`}
142-
onMouseEnter={stopAutoPlay}
143-
onMouseLeave={startAutoPlay}
65+
ref={gridRef}
66+
className={`grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8 transition-all duration-700 ease-out delay-200 ${
67+
gridInView ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10"
68+
}`}
14469
>
145-
{/* Cards Container */}
146-
<div className={`grid grid-cols-1 lg:grid-cols-3 gap-6 lg:gap-8 transition-all duration-300 ${isAnimating ? (animationDirection === 'next' ? 'opacity-0 -translate-x-4' : 'opacity-0 translate-x-4') : 'opacity-100 translate-x-0'}`}>
147-
{visibleCards.map((card, idx) => (
148-
<div
149-
key={`${card.originalIndex}-${currentIndex}-${idx}`}
150-
className="bg-gradient-to-br from-dark-bg/90 via-medium-bg/80 to-dark-bg/90 backdrop-blur-md rounded-2xl border border-light-bg/30 p-8 sm:p-10 shadow-card hover:shadow-card-hover transition-all duration-300 hover:border-primary-orange/50 hover:scale-[1.03] hover:-translate-y-2 group"
151-
>
152-
{/* Card Header */}
153-
<div className="flex items-center gap-5 mb-6">
154-
<div className="w-16 h-16 sm:w-18 sm:h-18 bg-gradient-to-br from-primary-orange/25 to-primary-yellow/25 backdrop-blur-md rounded-2xl border border-primary-orange/40 flex items-center justify-center text-3xl sm:text-4xl group-hover:scale-110 group-hover:shadow-glow-sm transition-all duration-300 shadow-lg">
155-
{card.icon}
156-
</div>
157-
<div>
158-
<h3 className="text-white font-bold text-xl sm:text-2xl group-hover:text-primary-orange transition-colors duration-300">
159-
{card.title}
160-
</h3>
161-
</div>
70+
{slides.map((card, idx) => (
71+
<div
72+
key={idx}
73+
className="bg-gradient-to-br from-dark-bg/90 via-medium-bg/80 to-dark-bg/90 backdrop-blur-md rounded-2xl border border-light-bg/30 p-8 sm:p-10 shadow-card hover:shadow-card-hover transition-all duration-300 hover:border-primary-orange/50 hover:scale-[1.03] hover:-translate-y-2 group flex flex-col"
74+
>
75+
{/* Card Header */}
76+
<div className="flex items-center gap-5 mb-6">
77+
<div className="w-16 h-16 bg-gradient-to-br from-primary-orange/25 to-primary-yellow/25 backdrop-blur-md rounded-2xl border border-primary-orange/40 flex items-center justify-center group-hover:scale-110 group-hover:shadow-glow-sm transition-all duration-300 shadow-lg shrink-0">
78+
{card.icon}
16279
</div>
163-
164-
{/* Card Content */}
165-
<p className="text-gray-300 text-base sm:text-lg leading-relaxed mb-8">
166-
{card.description}
167-
</p>
168-
169-
{/* Card Action */}
170-
{card.link && (
171-
<div className="flex justify-start">
172-
<a
173-
href={card.link}
174-
target="_blank"
175-
rel="noopener noreferrer"
176-
className="inline-flex items-center justify-center px-6 py-3 bg-gradient-to-r from-primary-orange/20 to-primary-yellow/20 text-primary-orange font-semibold rounded-xl border border-primary-orange/40 hover:bg-gradient-to-r hover:from-primary-orange hover:to-primary-yellow hover:text-white transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-primary-orange/20 text-base"
177-
>
178-
{card.linkText}
179-
<svg className="ml-2 w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
180-
<path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" />
181-
</svg>
182-
</a>
183-
</div>
184-
)}
80+
<h3 className="text-white font-bold text-xl sm:text-2xl group-hover:text-primary-orange transition-colors duration-300">
81+
{card.title}
82+
</h3>
18583
</div>
186-
))}
187-
</div>
18884

189-
{/* Navigation */}
190-
<div
191-
ref={navRef}
192-
className={`
193-
flex justify-center items-center gap-6 mt-12 transition-all duration-700 ease-out delay-300
194-
${navInView ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'}
195-
`}
196-
>
197-
<button
198-
onClick={prevSlide}
199-
className="bg-gradient-to-r from-primary-orange to-primary-yellow text-white w-12 h-12 sm:w-14 sm:h-14 rounded-xl flex items-center justify-center cursor-pointer transition-all duration-300 text-2xl font-bold hover:scale-110 hover:shadow-xl hover:shadow-primary-orange/30 hover:-translate-y-0.5"
200-
aria-label="Previous slide"
201-
>
202-
203-
</button>
204-
<div className="flex gap-3">
205-
{slides.map((_, index) => (
206-
<div
207-
key={index}
208-
onClick={() => goToSlide(index)}
209-
className={`w-3 h-3 sm:w-3.5 sm:h-3.5 rounded-full cursor-pointer transition-all duration-300 ${
210-
currentIndex === index
211-
? "bg-gradient-to-r from-primary-orange to-primary-yellow scale-125 shadow-lg shadow-primary-orange/50"
212-
: "bg-gray-600 hover:bg-gray-500 hover:scale-110"
213-
}`}
214-
aria-label={`Go to slide ${index + 1}`}
215-
/>
216-
))}
85+
{/* Card Content */}
86+
<p className="text-gray-300 text-base sm:text-lg leading-relaxed mb-8 flex-1">
87+
{card.description}
88+
</p>
89+
90+
{/* Card Action */}
91+
{card.link && (
92+
<div className="flex justify-start">
93+
<a
94+
href={card.link}
95+
target="_blank"
96+
rel="noopener noreferrer"
97+
className="inline-flex items-center justify-center px-6 py-3 bg-gradient-to-r from-primary-orange/20 to-primary-yellow/20 text-primary-orange font-semibold rounded-xl border border-primary-orange/40 hover:bg-gradient-to-r hover:from-primary-orange hover:to-primary-yellow hover:text-white transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-primary-orange/20 text-base"
98+
>
99+
{card.linkText}
100+
<svg className="ml-2 w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
101+
<path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" />
102+
</svg>
103+
</a>
104+
</div>
105+
)}
217106
</div>
218-
<button
219-
onClick={nextSlide}
220-
className="bg-gradient-to-r from-primary-orange to-primary-yellow text-white w-12 h-12 sm:w-14 sm:h-14 rounded-xl flex items-center justify-center cursor-pointer transition-all duration-300 text-2xl font-bold hover:scale-110 hover:shadow-xl hover:shadow-primary-orange/30 hover:-translate-y-0.5"
221-
aria-label="Next slide"
222-
>
223-
224-
</button>
225-
</div>
107+
))}
226108
</div>
227109
</div>
228110
</section>

jaseci-org/lib/landing-page-content.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1+
import { HiCodeBracket, HiComputerDesktop, HiRocketLaunch, HiSparkles } from "react-icons/hi2";
2+
13
export const gettingStartedSlides = [
24
{
3-
icon: "📘",
5+
icon: <HiCodeBracket className="w-8 h-8 text-primary-orange" />,
46
title: "Jac Lang",
57
description:
68
"The core language that supersets Python and JavaScript. Build AI systems with graph-based Object-Spatial Programming.",
79
link: "https://docs.jaseci.org/jac_book/",
810
linkText: "Read Handbook",
911
},
1012
{
11-
icon: "🖥️",
13+
icon: <HiComputerDesktop className="w-8 h-8 text-primary-orange" />,
1214
title: "jac-client",
1315
description:
1416
"Build full-stack web applications entirely in Jac. React components, state management, and backend APIs in one file.",
1517
link: "https://docs.jaseci.org/learn/jac-client/",
1618
linkText: "Explore Client",
1719
},
1820
{
19-
icon: "🚀",
21+
icon: <HiRocketLaunch className="w-8 h-8 text-primary-orange" />,
2022
title: "jac-scale",
2123
description:
2224
"Zero to infinite scale without code changes. Deploy to Kubernetes with auto-provisioned databases and authentication.",
2325
link: "https://docs.jaseci.org/learn/jac-scale/",
2426
linkText: "Learn Scale",
2527
},
2628
{
27-
icon: "🤖",
29+
icon: <HiSparkles className="w-8 h-8 text-primary-orange" />,
2830
title: "byLLM",
2931
description:
3032
"AI integration without prompt engineering. Replace function bodies with LLMs.",

0 commit comments

Comments
 (0)