diwali banner, portfolio content changes, and some hero text changes and slider add on homepage
This commit is contained in:
394
components/CaseStudySlider.tsx
Normal file
394
components/CaseStudySlider.tsx
Normal file
@@ -0,0 +1,394 @@
|
||||
// components/CaseStudySlider.tsx
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Activity,
|
||||
ArrowUpRight,
|
||||
Building2,
|
||||
ChevronLeft, ChevronRight,
|
||||
FlaskConical,
|
||||
Globe,
|
||||
Heart,
|
||||
Package,
|
||||
PartyPopper,
|
||||
ShoppingCart,
|
||||
Star,
|
||||
Tractor,
|
||||
TrendingUp,
|
||||
Trophy,
|
||||
Tv,
|
||||
Users,
|
||||
Utensils
|
||||
} from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import ambleCase from "../src/images/amble-case.webp";
|
||||
import amozCase from "../src/images/amozCase.webp";
|
||||
import dkCase from "../src/images/dkCase.webp";
|
||||
import farmCase from "../src/images/farmCase.webp";
|
||||
import gtCase from "../src/images/gt-case.webp";
|
||||
import leanCase from "../src/images/leanCase.webp";
|
||||
import niftyCase from "../src/images/niftyCase.webp";
|
||||
import prospertyCase from "../src/images/prospertyCase.webp";
|
||||
import ranoutofCase from "../src/images/ranoutof-case.webp";
|
||||
import RrCase from "../src/images/resturant-reward-case.webp";
|
||||
import seezunCase from "../src/images/seezun-case.webp";
|
||||
import simplitendCase from "../src/images/simplitendCase.webp";
|
||||
import tcCase from "../src/images/tc-case.webp";
|
||||
import vib360Case from "../src/images/vib360Case.webp";
|
||||
import wokaCase from "../src/images/woka-case.webp";
|
||||
import { ImageWithFallback } from "./figma/ImageWithFallback";
|
||||
import { Badge } from "./ui/badge";
|
||||
import { Button } from "./ui/button";
|
||||
import { Card, CardContent } from "./ui/card";
|
||||
|
||||
// Internal data (no need to pass props)
|
||||
const defaultCaseStudies = [
|
||||
{
|
||||
id: 1,
|
||||
title: "RanOutOf",
|
||||
image: ranoutofCase,
|
||||
category: "Lifestyle",
|
||||
industry: "Consumer",
|
||||
featured: true,
|
||||
link: "/projects/ranoutof",
|
||||
icon: Package
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Seezun",
|
||||
image: seezunCase,
|
||||
category: "E-commerce",
|
||||
industry: "Retail",
|
||||
featured: true,
|
||||
link: "/projects/seezun",
|
||||
icon: ShoppingCart
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Woka",
|
||||
image: wokaCase,
|
||||
category: "Health & Fitness",
|
||||
industry: "Healthcare",
|
||||
featured: true,
|
||||
link: "/projects/woka",
|
||||
icon: Tv
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "Traders Circuit",
|
||||
image: tcCase,
|
||||
category: "FinTech",
|
||||
industry: "Finance",
|
||||
featured: true,
|
||||
link: "/projects/traderscircuit",
|
||||
icon: TrendingUp
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: "Good Times",
|
||||
image: gtCase,
|
||||
category: "Events",
|
||||
industry: "Entertainment",
|
||||
featured: true,
|
||||
link: "/projects/goodtimes",
|
||||
icon: PartyPopper
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
title: "Resturant Reward App",
|
||||
image: RrCase,
|
||||
category: "Loyalty & Rewards",
|
||||
industry: "Hospitality",
|
||||
featured: false,
|
||||
link: "/comming-soon",
|
||||
icon: Utensils
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: "Amble",
|
||||
image: ambleCase,
|
||||
category: "Social",
|
||||
industry: "Heritage",
|
||||
featured: false,
|
||||
link: "/projects/amble",
|
||||
icon: Users
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: "Amoz",
|
||||
image: amozCase,
|
||||
category: "Lifestyle",
|
||||
industry: "Consumer",
|
||||
featured: false,
|
||||
link: "/projects/amoz",
|
||||
icon: Heart
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
title: "Dorf Ketal",
|
||||
image: dkCase,
|
||||
category: "Lifestyle",
|
||||
industry: "Chemicals",
|
||||
featured: false,
|
||||
link: "/comming-soon",
|
||||
icon: FlaskConical
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: "VIB360",
|
||||
image: vib360Case,
|
||||
category: "Lifestyle",
|
||||
industry: "Consumer",
|
||||
featured: false,
|
||||
link: "/projects/vib360",
|
||||
icon: Activity
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
title: "Nifty 11",
|
||||
image: niftyCase,
|
||||
category: "Lifestyle",
|
||||
industry: "Consumer",
|
||||
featured: false,
|
||||
link: "/comming-soon",
|
||||
icon: Trophy
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
title: "Prosperty",
|
||||
image: prospertyCase,
|
||||
category: "Lifestyle",
|
||||
industry: "Real Estate",
|
||||
featured: false,
|
||||
link: "/projects/prosperty",
|
||||
icon: Building2
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
title: "Simpletend",
|
||||
image: simplitendCase,
|
||||
category: "Health & Fitness",
|
||||
industry: "Healthcare",
|
||||
featured: false,
|
||||
link: "/projects/simpletend",
|
||||
icon: Activity
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
title: "Farm Feeder",
|
||||
image: farmCase,
|
||||
category: "AgriTech",
|
||||
industry: "Agriculture",
|
||||
featured: false,
|
||||
link: "/comming-soon",
|
||||
icon: Tractor
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
title: "Lean In World",
|
||||
image: leanCase,
|
||||
category: "AgriTech",
|
||||
industry: "Agriculture",
|
||||
featured: false,
|
||||
link: "/comming-soon",
|
||||
icon: Globe
|
||||
},
|
||||
];
|
||||
|
||||
interface CaseStudySliderProps {
|
||||
autoPlay?: boolean;
|
||||
autoPlayInterval?: number;
|
||||
}
|
||||
|
||||
export const CaseStudySlider = ({
|
||||
autoPlay = false,
|
||||
autoPlayInterval = 4000,
|
||||
}: CaseStudySliderProps) => {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [isAutoPlaying, setIsAutoPlaying] = useState(autoPlay);
|
||||
const [visibleSlides, setVisibleSlides] = useState(3);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const caseStudies = defaultCaseStudies;
|
||||
|
||||
const nextSlide = () => {
|
||||
setCurrentIndex((prev) => {
|
||||
const nextIndex = prev + 1;
|
||||
return nextIndex > caseStudies.length - visibleSlides ? 0 : nextIndex;
|
||||
});
|
||||
};
|
||||
|
||||
const prevSlide = () => {
|
||||
setCurrentIndex((prev) => {
|
||||
const prevIndex = prev - 1;
|
||||
return prevIndex < 0 ? caseStudies.length - visibleSlides : prevIndex;
|
||||
});
|
||||
};
|
||||
|
||||
const goToSlide = (index: number) => {
|
||||
setCurrentIndex(Math.min(index, caseStudies.length - visibleSlides));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAutoPlaying || !autoPlay) return;
|
||||
const interval = setInterval(nextSlide, autoPlayInterval);
|
||||
return () => clearInterval(interval);
|
||||
}, [isAutoPlaying, autoPlay, autoPlayInterval, visibleSlides]);
|
||||
|
||||
// Calculate visible slides on resize
|
||||
useEffect(() => {
|
||||
const updateVisibleSlides = () => {
|
||||
if (!containerRef.current) return;
|
||||
const width = containerRef.current.offsetWidth;
|
||||
if (width < 640) setVisibleSlides(1);
|
||||
else if (width < 1024) setVisibleSlides(2);
|
||||
else setVisibleSlides(3);
|
||||
};
|
||||
|
||||
updateVisibleSlides();
|
||||
window.addEventListener('resize', updateVisibleSlides);
|
||||
return () => window.removeEventListener('resize', updateVisibleSlides);
|
||||
}, []);
|
||||
|
||||
const maxIndex = Math.max(0, caseStudies.length - visibleSlides);
|
||||
|
||||
// Calculate the actual translation percentage
|
||||
const getTranslationPercentage = () => {
|
||||
if (visibleSlides >= caseStudies.length) return 0;
|
||||
return (currentIndex * (100 / visibleSlides));
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
className="py-20 pt-10"
|
||||
onMouseEnter={() => setIsAutoPlaying(false)}
|
||||
onMouseLeave={() => setIsAutoPlaying(autoPlay)}
|
||||
ref={containerRef}
|
||||
>
|
||||
<div className="container mx-auto px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-end mb-8">
|
||||
{caseStudies.length > visibleSlides && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={prevSlide}
|
||||
disabled={currentIndex === 0}
|
||||
className="h-10 w-10 rounded-full border-white/20 hover:border-accent/50 disabled:opacity-50"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={nextSlide}
|
||||
disabled={currentIndex >= maxIndex}
|
||||
className="h-10 w-10 rounded-full border-white/20 hover:border-accent/50 disabled:opacity-50"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Slider Container */}
|
||||
<div className="relative overflow-hidden" ref={sliderRef}>
|
||||
<motion.div
|
||||
className="flex gap-6"
|
||||
animate={{
|
||||
x: `-${getTranslationPercentage()}%`
|
||||
}}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 400,
|
||||
damping: 40,
|
||||
duration: 0.6
|
||||
}}
|
||||
style={{ willChange: "transform" }}
|
||||
>
|
||||
{caseStudies.map((study, index) => {
|
||||
const IconComponent = study.icon;
|
||||
return (
|
||||
<motion.div
|
||||
key={study.id}
|
||||
className="flex-shrink-0"
|
||||
style={{
|
||||
width: `calc(${100 / visibleSlides}% - 1.5rem)`
|
||||
}}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: index * 0.1,
|
||||
ease: "easeOut"
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
className="bg-card/50 backdrop-blur-md border-white/10 hover:border-accent/30 transition-all duration-500 shadow-lg hover:shadow-2xl hover:shadow-accent/10 rounded-2xl overflow-hidden h-[full] cursor-pointer"
|
||||
onClick={() => navigate(study.link)}
|
||||
>
|
||||
<CardContent className="p-0 h-full CardContentOverride">
|
||||
{/* Image */}
|
||||
<div className="relative overflow-hidden">
|
||||
<ImageWithFallback
|
||||
src={study.image}
|
||||
alt={study.title}
|
||||
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
|
||||
|
||||
{/* Category Badge */}
|
||||
<div className="absolute top-2 left-4">
|
||||
<Badge className="bg-accent/90 text-white border-0 text-xs">
|
||||
{study.category}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Featured Badge */}
|
||||
{study.featured && (
|
||||
<div className="absolute top-2 right-4">
|
||||
<div className="bg-amber-500/90 text-white px-2 py-1 rounded-full text-xs font-medium flex items-center gap-1">
|
||||
<Star className="w-3 h-3 fill-current" />
|
||||
Featured
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Icon */}
|
||||
<div className="absolute bottom-4 left-4">
|
||||
<div className="w-10 h-10 bg-white/20 backdrop-blur-md rounded-full flex items-center justify-center">
|
||||
<IconComponent className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Dots Indicator */}
|
||||
{caseStudies.length > visibleSlides && (
|
||||
<div className="flex justify-center items-center gap-2 mt-8">
|
||||
{Array.from({ length: maxIndex + 1 }).map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => goToSlide(index)}
|
||||
className={`w-2 h-2 rounded-full transition-all duration-300 ${
|
||||
index === currentIndex
|
||||
? "bg-accent w-6"
|
||||
: "bg-white/30 hover:bg-white/50"
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user