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>
|
||||
);
|
||||
};
|
||||
280
components/DedicatedTeamPricing.tsx
Normal file
280
components/DedicatedTeamPricing.tsx
Normal file
@@ -0,0 +1,280 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { Check, Users, Clock, Sparkles } from "lucide-react";
|
||||
import { GridPattern } from "./GridPattern";
|
||||
import { Badge } from "./ui/badge";
|
||||
|
||||
interface PricingTier {
|
||||
name: string;
|
||||
price: string;
|
||||
pricePerMonth: string;
|
||||
description: string;
|
||||
isPopular?: boolean;
|
||||
teamSize: string;
|
||||
projectManager: string;
|
||||
uiuxDesigners: string;
|
||||
frontendDevs: string;
|
||||
backendDevs: string;
|
||||
qaTesters: string;
|
||||
leadTime: string;
|
||||
minEngagement: string;
|
||||
}
|
||||
|
||||
const pricingTiers: PricingTier[] = [
|
||||
{
|
||||
name: "Small",
|
||||
price: "$7,500",
|
||||
pricePerMonth: "/mo",
|
||||
description: "Maintenance & updates",
|
||||
teamSize: "3-5 Members",
|
||||
projectManager: "Part-time (≤ 32 hrs/month)",
|
||||
uiuxDesigners: "Part-time",
|
||||
frontendDevs: "Shared",
|
||||
backendDevs: "Shared",
|
||||
qaTesters: "Part-time",
|
||||
leadTime: "< 3 Days",
|
||||
minEngagement: "1 Month"
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
price: "$12,000",
|
||||
pricePerMonth: "/mo",
|
||||
description: "Ongoing MVP development",
|
||||
isPopular: true,
|
||||
teamSize: "4-8 Members",
|
||||
projectManager: "Shared (≤ 80 hrs/month)",
|
||||
uiuxDesigners: "Shared",
|
||||
frontendDevs: "Shared",
|
||||
backendDevs: "Shared",
|
||||
qaTesters: "Shared",
|
||||
leadTime: "< 2 Weeks",
|
||||
minEngagement: "2 Months"
|
||||
},
|
||||
{
|
||||
name: "Large",
|
||||
price: "$22,680",
|
||||
pricePerMonth: "/mo",
|
||||
description: "Long-term growth & GTM",
|
||||
teamSize: "6-10 Members",
|
||||
projectManager: "Dedicated full-time",
|
||||
uiuxDesigners: "Full-time",
|
||||
frontendDevs: "Full-time",
|
||||
backendDevs: "Full-time",
|
||||
qaTesters: "Full-time",
|
||||
leadTime: "< 4 Weeks",
|
||||
minEngagement: "3 Months"
|
||||
}
|
||||
];
|
||||
|
||||
const includedFeatures = [
|
||||
{
|
||||
left: "Access to WDI Code Library",
|
||||
right: "Direct Communication"
|
||||
},
|
||||
{
|
||||
left: "Time Zone Overlap: 3 Hours",
|
||||
right: "Resource Replacement (within 1 week)"
|
||||
},
|
||||
{
|
||||
left: "No Obligation Trial (conditional)",
|
||||
right: "IPR & Code Ownership"
|
||||
},
|
||||
{
|
||||
left: "Termination Notice: 1 Month",
|
||||
right: "Performance Guarantee"
|
||||
}
|
||||
];
|
||||
|
||||
const comparisonRows = [
|
||||
{ label: "Team Size", key: "teamSize" as keyof PricingTier },
|
||||
{ label: "Project Manager", key: "projectManager" as keyof PricingTier },
|
||||
{ label: "UI/UX Designers", key: "uiuxDesigners" as keyof PricingTier },
|
||||
{ label: "Frontend Developers", key: "frontendDevs" as keyof PricingTier },
|
||||
{ label: "Backend Developers", key: "backendDevs" as keyof PricingTier },
|
||||
{ label: "QA Testers", key: "qaTesters" as keyof PricingTier },
|
||||
{ label: "Lead Time to Start", key: "leadTime" as keyof PricingTier },
|
||||
{ label: "Minimum Engagement Period", key: "minEngagement" as keyof PricingTier }
|
||||
];
|
||||
|
||||
export const DedicatedTeamPricing = () => {
|
||||
return (
|
||||
<section className="relative py-20 bg-background overflow-hidden">
|
||||
<GridPattern strokeDasharray="4 2" />
|
||||
|
||||
<div className="relative z-10 container mx-auto px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 mb-4">
|
||||
<Users className="w-6 h-6 text-accent" />
|
||||
<h2 className="text-4xl lg:text-5xl font-semibold text-foreground font-manrope">
|
||||
Dedicated Team Pricing
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-muted-foreground text-lg max-w-2xl mx-auto font-manrope">
|
||||
Scale your development with flexible team structures tailored to your project needs
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Pricing Cards Row */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
className="grid md:grid-cols-3 gap-6 mb-12 max-w-6xl mx-auto"
|
||||
>
|
||||
{pricingTiers.map((tier, index) => (
|
||||
<motion.div
|
||||
key={tier.name}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className={`relative bg-card/50 backdrop-blur-sm border rounded-[20px] p-6 transition-all duration-300 ${
|
||||
tier.isPopular
|
||||
? 'border-blue-500/50 shadow-xl shadow-blue-500/10 scale-105'
|
||||
: 'border-border/50 hover:border-accent/30'
|
||||
}`}
|
||||
>
|
||||
{tier.isPopular && (
|
||||
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
|
||||
<Badge className="bg-blue-600 text-white px-4 py-1 rounded-full font-manrope">
|
||||
Most Popular
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-center mb-6">
|
||||
<h3 className="text-2xl font-semibold text-foreground mb-2 font-manrope">
|
||||
{tier.name}
|
||||
</h3>
|
||||
<div className="flex items-baseline justify-center mb-2">
|
||||
<span className="text-4xl font-semibold text-foreground font-manrope">
|
||||
{tier.price}
|
||||
</span>
|
||||
<span className="text-xl text-muted-foreground font-manrope">
|
||||
{tier.pricePerMonth}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground font-manrope">
|
||||
{tier.description}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Comparison Table */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-6xl mx-auto mb-16"
|
||||
>
|
||||
<div className="bg-card/30 backdrop-blur-md border border-border/50 rounded-[20px] overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse font-manrope">
|
||||
{/* Table Header - Hidden on mobile, shown on desktop */}
|
||||
<thead className="hidden md:table-header-group">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="p-6 text-left font-semibold text-foreground font-manrope"></th>
|
||||
{pricingTiers.map((tier) => (
|
||||
<th key={tier.name} className="p-6 text-center font-semibold text-foreground font-manrope">
|
||||
{tier.name}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{/* Table Body */}
|
||||
<tbody>
|
||||
{comparisonRows.map((row, rowIndex) => (
|
||||
<motion.tr
|
||||
key={row.label}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: rowIndex * 0.05 }}
|
||||
viewport={{ once: true }}
|
||||
className="border-b border-border/30 last:border-b-0 hover:bg-card/50 transition-colors duration-200"
|
||||
>
|
||||
<td className="p-6 font-medium text-muted-foreground font-manrope align-top">
|
||||
<div className="md:hidden mb-3">{row.label}</div>
|
||||
<div className="hidden md:block">{row.label}</div>
|
||||
|
||||
{/* Mobile: Stack values vertically with tier name */}
|
||||
<div className="grid grid-cols-1 md:hidden gap-3 mt-3">
|
||||
{pricingTiers.map((tier) => (
|
||||
<div key={tier.name} className="flex justify-between items-center bg-card/30 rounded-lg p-3">
|
||||
<span className="text-sm text-muted-foreground font-manrope">{tier.name}:</span>
|
||||
<span className="text-foreground font-manrope text-right">{tier[row.key]}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Desktop: Show values in columns */}
|
||||
{pricingTiers.map((tier) => (
|
||||
<td key={tier.name} className="hidden md:table-cell p-6 text-center text-foreground font-manrope align-top">
|
||||
{tier[row.key]}
|
||||
</td>
|
||||
))}
|
||||
</motion.tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Included in All Plans */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-4xl mx-auto"
|
||||
>
|
||||
<div className="bg-gradient-to-br from-accent/5 to-blue-500/5 backdrop-blur-sm border border-accent/20 rounded-[20px] p-8">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-accent/20 rounded-full flex items-center justify-center">
|
||||
<Sparkles className="w-6 h-6 text-accent" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-semibold text-foreground font-manrope">
|
||||
Included in All Plans
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{includedFeatures.map((feature, index) => (
|
||||
<div key={index} className="space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-accent/20 rounded-full flex items-center justify-center mt-0.5">
|
||||
<Check className="w-3 h-3 text-accent" />
|
||||
</div>
|
||||
<span className="text-foreground font-manrope leading-relaxed">
|
||||
{feature.left}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-accent/20 rounded-full flex items-center justify-center mt-0.5">
|
||||
<Check className="w-3 h-3 text-accent" />
|
||||
</div>
|
||||
<span className="text-foreground font-manrope leading-relaxed">
|
||||
{feature.right}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
571
components/DiwaliHeroSection.tsx
Normal file
571
components/DiwaliHeroSection.tsx
Normal file
@@ -0,0 +1,571 @@
|
||||
import { Button } from "./ui/button";
|
||||
import { Calendar, Briefcase, Sparkles } from "lucide-react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
// Firework component
|
||||
const Firework = ({ delay = 0, top, left, colors }: { delay?: number; top: string; left: string; colors: string[] }) => {
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute pointer-events-none"
|
||||
style={{ top, left }}
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{
|
||||
opacity: [0, 1, 1, 0],
|
||||
scale: [0, 1.5, 2, 2.5],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
delay,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
>
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" className="w-48 h-48">
|
||||
{/* Firework burst rays */}
|
||||
{Array.from({ length: 24 }).map((_, i) => {
|
||||
const angle = (i * 360) / 24;
|
||||
const colorIndex = i % colors.length;
|
||||
return (
|
||||
<motion.line
|
||||
key={i}
|
||||
x1="100"
|
||||
y1="100"
|
||||
x2="100"
|
||||
y2="20"
|
||||
stroke={colors[colorIndex]}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
transform={`rotate(${angle} 100 100)`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: [0, 1, 0.8, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
delay: delay + i * 0.02,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{/* Center burst */}
|
||||
<motion.circle
|
||||
cx="100"
|
||||
cy="100"
|
||||
r="4"
|
||||
fill={colors[0]}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: [0, 1, 0.5, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
delay,
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
// Sparkle Star component
|
||||
const SparkleStar = ({ delay = 0, top, left, size = 16 }: { delay?: number; top: string; left: string; size?: number }) => {
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute pointer-events-none"
|
||||
style={{ top, left }}
|
||||
animate={{
|
||||
scale: [0, 1, 0],
|
||||
opacity: [0, 1, 0],
|
||||
rotate: [0, 180, 360],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2.5,
|
||||
repeat: Infinity,
|
||||
delay,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
<Sparkles className="text-[#ffaa40]" size={size} />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
// Marigold Flower component
|
||||
const MarigoldFlower = ({ size = "md", delay = 0 }: { size?: "sm" | "md" | "lg"; delay?: number }) => {
|
||||
const sizes = {
|
||||
sm: 16,
|
||||
md: 20,
|
||||
lg: 24,
|
||||
};
|
||||
|
||||
const flowerSize = sizes[size];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="relative"
|
||||
style={{ width: flowerSize, height: flowerSize }}
|
||||
animate={{
|
||||
rotate: [0, 360],
|
||||
scale: [1, 1.05, 1],
|
||||
}}
|
||||
transition={{
|
||||
rotate: { duration: 20, repeat: Infinity, ease: "linear" },
|
||||
scale: { duration: 2, repeat: Infinity, ease: "easeInOut", delay },
|
||||
}}
|
||||
>
|
||||
{/* Flower petals */}
|
||||
<svg viewBox="0 0 100 100" className="w-full h-full">
|
||||
{/* Outer petals */}
|
||||
{Array.from({ length: 16 }).map((_, i) => {
|
||||
const angle = (i * 360) / 16;
|
||||
return (
|
||||
<ellipse
|
||||
key={i}
|
||||
cx="50"
|
||||
cy="25"
|
||||
rx="8"
|
||||
ry="15"
|
||||
fill={`url(#gradient-${i % 3})`}
|
||||
transform={`rotate(${angle} 50 50)`}
|
||||
opacity={0.9}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Inner petals */}
|
||||
{Array.from({ length: 12 }).map((_, i) => {
|
||||
const angle = (i * 360) / 12;
|
||||
return (
|
||||
<ellipse
|
||||
key={`inner-${i}`}
|
||||
cx="50"
|
||||
cy="30"
|
||||
rx="6"
|
||||
ry="12"
|
||||
fill={`url(#gradient-${(i + 1) % 3})`}
|
||||
transform={`rotate(${angle} 50 50)`}
|
||||
opacity={0.95}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Center */}
|
||||
<circle cx="50" cy="50" r="8" fill="#d97706" />
|
||||
<circle cx="50" cy="50" r="5" fill="#b45309" opacity={0.8} />
|
||||
|
||||
{/* Gradient definitions */}
|
||||
<defs>
|
||||
<radialGradient id="gradient-0">
|
||||
<stop offset="0%" stopColor="#fbbf24" />
|
||||
<stop offset="100%" stopColor="#f59e0b" />
|
||||
</radialGradient>
|
||||
<radialGradient id="gradient-1">
|
||||
<stop offset="0%" stopColor="#fcd34d" />
|
||||
<stop offset="100%" stopColor="#fbbf24" />
|
||||
</radialGradient>
|
||||
<radialGradient id="gradient-2">
|
||||
<stop offset="0%" stopColor="#f59e0b" />
|
||||
<stop offset="100%" stopColor="#d97706" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
{/* Glow effect */}
|
||||
<div className="absolute inset-0 bg-[#ffaa40] rounded-full blur-sm opacity-30"></div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
// Flower Garland (Toran) component
|
||||
const FlowerGarland = ({ left, flowerCount = 8, delay = 0 }: { left: string; flowerCount?: number; delay?: number }) => {
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute top-0 z-20 pointer-events-none"
|
||||
style={{ left }}
|
||||
initial={{ y: -20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 1, delay }}
|
||||
>
|
||||
{/* String/thread */}
|
||||
<div className="absolute left-1/2 -translate-x-1/2 w-0.5 bg-gradient-to-b from-[#78350f] to-transparent opacity-50"
|
||||
style={{ height: `${flowerCount * 35}px` }}></div>
|
||||
|
||||
{/* Flowers on string */}
|
||||
<div className="relative flex flex-col items-center gap-1">
|
||||
{Array.from({ length: flowerCount }).map((_, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="relative"
|
||||
animate={{
|
||||
y: [0, 5, 0],
|
||||
rotate: [0, 3, -3, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2 + i * 0.2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
delay: delay + i * 0.1,
|
||||
}}
|
||||
>
|
||||
<MarigoldFlower
|
||||
size={i % 3 === 0 ? "lg" : i % 2 === 0 ? "md" : "sm"}
|
||||
delay={i * 0.2}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
// Decorative Hanging Lantern component
|
||||
const DecorativeLantern = () => {
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute top-0 left-1/2 -translate-x-1/2 z-30 pointer-events-none"
|
||||
initial={{ y: -100, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 1.2, delay: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<motion.div
|
||||
animate={{
|
||||
y: [0, 12, 0],
|
||||
rotate: [0, 2, -2, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 4,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
<svg width="120" height="200" viewBox="0 0 120 200" fill="none">
|
||||
{/* Hanging String */}
|
||||
<line x1="60" y1="0" x2="60" y2="35" stroke="#4a4a4a" strokeWidth="1.5" />
|
||||
|
||||
{/* Top Cap - Cyan/Blue */}
|
||||
<rect x="45" y="35" width="30" height="8" fill="#22d3ee" rx="2" />
|
||||
<rect x="42" y="43" width="36" height="3" fill="#06b6d4" />
|
||||
|
||||
{/* Top decoration line - Purple accent */}
|
||||
<rect x="40" y="46" width="40" height="2" fill="#8b5cf6" rx="1" />
|
||||
|
||||
{/* Main Lantern Body - Golden Orange with curves */}
|
||||
<path
|
||||
d="M 50 50
|
||||
Q 35 70, 40 95
|
||||
Q 42 110, 50 120
|
||||
L 70 120
|
||||
Q 78 110, 80 95
|
||||
Q 85 70, 70 50
|
||||
Z"
|
||||
fill="url(#lanternGradient)"
|
||||
stroke="#f59e0b"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
|
||||
{/* Inner decorative pattern - curved diamond */}
|
||||
<path
|
||||
d="M 60 65
|
||||
Q 50 75, 60 85
|
||||
Q 70 75, 60 65
|
||||
Z"
|
||||
fill="none"
|
||||
stroke="#d97706"
|
||||
strokeWidth="1.5"
|
||||
opacity="0.7"
|
||||
/>
|
||||
<path
|
||||
d="M 60 85
|
||||
Q 50 95, 60 105
|
||||
Q 70 95, 60 85
|
||||
Z"
|
||||
fill="none"
|
||||
stroke="#d97706"
|
||||
strokeWidth="1.5"
|
||||
opacity="0.7"
|
||||
/>
|
||||
|
||||
{/* Vertical center line */}
|
||||
<line x1="60" y1="70" x2="60" y2="100" stroke="#d97706" strokeWidth="1" opacity="0.5" />
|
||||
|
||||
{/* Highlight shine */}
|
||||
<ellipse cx="55" cy="75" rx="8" ry="15" fill="#fff9e6" opacity="0.15" />
|
||||
|
||||
{/* Bottom Cap - Cyan/Blue */}
|
||||
<rect x="40" y="120" width="40" height="2" fill="#8b5cf6" rx="1" />
|
||||
<rect x="42" y="122" width="36" height="3" fill="#06b6d4" />
|
||||
<rect x="45" y="125" width="30" height="8" fill="#22d3ee" rx="2" />
|
||||
|
||||
{/* Hanging Tassels - Cyan strands */}
|
||||
{Array.from({ length: 7 }).map((_, i) => {
|
||||
const x = 45 + i * 5;
|
||||
return (
|
||||
<g key={i}>
|
||||
<motion.line
|
||||
x1={x}
|
||||
y1="133"
|
||||
x2={x}
|
||||
y2={158 + Math.sin(i) * 8}
|
||||
stroke="#22d3ee"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{
|
||||
pathLength: 1,
|
||||
y: [0, 3, 0],
|
||||
}}
|
||||
transition={{
|
||||
pathLength: { duration: 0.8, delay: 0.5 + i * 0.05 },
|
||||
y: { duration: 2 + i * 0.1, repeat: Infinity, ease: "easeInOut" }
|
||||
}}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Gradient Definitions */}
|
||||
<defs>
|
||||
<linearGradient id="lanternGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#fb923c" />
|
||||
<stop offset="30%" stopColor="#f59e0b" />
|
||||
<stop offset="60%" stopColor="#fbbf24" />
|
||||
<stop offset="100%" stopColor="#f59e0b" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
{/* Glow effect */}
|
||||
<div className="absolute top-[50px] left-1/2 -translate-x-1/2 w-20 h-24 bg-[#ffaa40] rounded-full blur-2xl opacity-20"></div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export function DiwaliHeroSection() {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<section id="hero" className="relative min-h-screen flex items-center justify-center pt-20 overflow-hidden bg-[#0E0E0E]">
|
||||
{/* Dark Background with subtle glow */}
|
||||
<div className="absolute inset-0 bg-gradient-radial from-[#1a1a1a] via-[#0E0E0E] to-[#000000]"></div>
|
||||
|
||||
{/* Decorative Hanging Lantern - Center Top */}
|
||||
<DecorativeLantern />
|
||||
|
||||
{/* Marigold Flower Garlands (Torans) - Minimal placement */}
|
||||
<FlowerGarland left="8%" flowerCount={7} delay={0.2} />
|
||||
<FlowerGarland left="25%" flowerCount={6} delay={0.4} />
|
||||
<FlowerGarland left="75%" flowerCount={7} delay={0.3} />
|
||||
<FlowerGarland left="92%" flowerCount={6} delay={0.5} />
|
||||
|
||||
{/* Animated Fireworks */}
|
||||
<Firework delay={0} top="10%" left="15%" colors={["#E5195E", "#ff6b9d", "#E5195E"]} />
|
||||
<Firework delay={1.5} top="20%" left="70%" colors={["#9c40ff", "#c084fc", "#9c40ff"]} />
|
||||
<Firework delay={0.8} top="60%" left="80%" colors={["#ffaa40", "#fcd34d", "#ffaa40"]} />
|
||||
<Firework delay={2.2} top="15%" left="45%" colors={["#ff6b00", "#fb923c", "#ff6b00"]} />
|
||||
<Firework delay={1.2} top="70%" left="20%" colors={["#E5195E", "#9c40ff", "#ffaa40"]} />
|
||||
<Firework delay={2.8} top="40%" left="90%" colors={["#fcd34d", "#ffaa40", "#ff6b00"]} />
|
||||
|
||||
{/* Floating Sparkles */}
|
||||
{[...Array(25)].map((_, i) => (
|
||||
<SparkleStar
|
||||
key={i}
|
||||
delay={Math.random() * 3}
|
||||
top={`${Math.random() * 100}%`}
|
||||
left={`${Math.random() * 100}%`}
|
||||
size={12 + Math.random() * 8}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Small floating particles */}
|
||||
{[...Array(40)].map((_, i) => (
|
||||
<motion.div
|
||||
key={`particle-${i}`}
|
||||
className="absolute w-1 h-1 bg-[#ffaa40] rounded-full pointer-events-none"
|
||||
style={{
|
||||
top: `${Math.random() * 100}%`,
|
||||
left: `${Math.random() * 100}%`,
|
||||
}}
|
||||
animate={{
|
||||
y: [0, -50, 0],
|
||||
opacity: [0, 0.8, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3 + Math.random() * 2,
|
||||
repeat: Infinity,
|
||||
delay: Math.random() * 4,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="container mx-auto px-6 lg:px-8 relative z-30">
|
||||
<div className="max-w-5xl mx-auto text-center">
|
||||
{/* Top Badge */}
|
||||
<motion.div
|
||||
className="inline-flex items-center gap-2 mb-8"
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
>
|
||||
<motion.span
|
||||
animate={{
|
||||
rotate: [0, 10, -10, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
className="text-3xl"
|
||||
>
|
||||
🪔
|
||||
</motion.span>
|
||||
<span className="text-2xl font-manrope font-semibold">
|
||||
<span className="text-[#E5195E]">WDI</span>
|
||||
<span className="bg-gradient-to-r from-[#ffaa40] via-[#fff9e6] to-[#ffaa40] bg-clip-text text-transparent"> Wishes you</span>
|
||||
</span>
|
||||
<motion.span
|
||||
animate={{
|
||||
rotate: [0, -10, 10, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
delay: 0.5,
|
||||
}}
|
||||
className="text-3xl"
|
||||
>
|
||||
🪔
|
||||
</motion.span>
|
||||
</motion.div>
|
||||
|
||||
{/* Main Heading */}
|
||||
<motion.h1
|
||||
className="text-5xl sm:text-6xl md:text-7xl lg:text-8xl mb-8"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, delay: 0.4 }}
|
||||
style={{
|
||||
fontFamily: "'Cinzel Decorative', cursive",
|
||||
fontWeight: 900,
|
||||
letterSpacing: "0.05em",
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
<span className="inline-block bg-gradient-to-r from-[#fff9e6] via-[#ffaa40] to-[#fff9e6] bg-clip-text text-transparent animate-gradient bg-[length:200%_100%]">
|
||||
Happy Diwali
|
||||
</span>
|
||||
</motion.h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<motion.p
|
||||
className="text-lg sm:text-xl md:text-2xl text-gray-300 mb-12 max-w-3xl mx-auto leading-relaxed font-manrope"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.6 }}
|
||||
>
|
||||
Wishing you a Diwali filled with{" "}
|
||||
<span className="text-[#ffaa40] font-semibold">light</span>,{" "}
|
||||
<span className="text-[#E5195E] font-semibold">success</span>, and{" "}
|
||||
<span className="text-[#9c40ff] font-semibold">lightning-fast launches</span>.
|
||||
</motion.p>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<motion.div
|
||||
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.8 }}
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
className="relative overflow-hidden group px-8 py-6 text-base font-manrope bg-gradient-to-r from-[#E5195E] to-[#ff6b9d] hover:from-[#ff6b9d] hover:to-[#E5195E] border-0 shadow-[0_0_30px_rgba(229,25,94,0.3)] hover:shadow-[0_0_40px_rgba(229,25,94,0.5)] transition-all duration-300"
|
||||
onClick={() => navigate("/start-a-project")}
|
||||
>
|
||||
<Calendar className="w-5 h-5" />
|
||||
<span className="relative z-10">Start Your Bright Journey</span>
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-gradient-to-r from-[#ffaa40]/20 to-transparent"
|
||||
animate={{
|
||||
x: ["-100%", "100%"],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
className="relative overflow-hidden group px-8 py-6 text-base font-manrope bg-[rgba(255,255,255,0.05)] backdrop-blur-md border border-[#ffaa40]/50 hover:bg-[rgba(255,255,255,0.1)] hover:border-[#ffaa40] shadow-[0_0_20px_rgba(255,170,64,0.2)] hover:shadow-[0_0_30px_rgba(255,170,64,0.4)] transition-all duration-300"
|
||||
onClick={() => navigate("/services")}
|
||||
>
|
||||
<Briefcase className="w-5 h-5" />
|
||||
<span className="relative z-10 text-white">Discover Our Solutions</span>
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-gradient-to-r from-[#E5195E]/10 to-transparent"
|
||||
animate={{
|
||||
x: ["-100%", "100%"],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2.5,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
delay: 0.5,
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
{/* Bottom decorative text */}
|
||||
<motion.div
|
||||
className="mt-16 text-sm text-gray-400 font-manrope"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 1, delay: 1.2 }}
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
May this festival bring prosperity to your business
|
||||
<motion.span
|
||||
animate={{ scale: [1, 1.2, 1] }}
|
||||
transition={{ duration: 1.5, repeat: Infinity }}
|
||||
>
|
||||
🌟
|
||||
</motion.span>
|
||||
</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom scroll indicator */}
|
||||
<motion.div
|
||||
className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-30"
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 1.4 }}
|
||||
>
|
||||
<motion.div
|
||||
animate={{ y: [0, 8, 0] }}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
<svg className="w-6 h-6 text-[#ffaa40]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
|
||||
</svg>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
116
components/PackagesSection.tsx
Normal file
116
components/PackagesSection.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { Tablet, Rocket, Crown, Check } from "lucide-react";
|
||||
import { GridPattern } from "./GridPattern";
|
||||
|
||||
interface Package {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
timeline: string;
|
||||
priceRange: string;
|
||||
features: string[];
|
||||
}
|
||||
|
||||
const packages: Package[] = [
|
||||
{
|
||||
icon: <Tablet className="w-8 h-8" />,
|
||||
title: "The Blueprint",
|
||||
timeline: "Product Definition in 1 Week",
|
||||
priceRange: "$1,500 - $2,500",
|
||||
features: [
|
||||
"Strategy & roadmap",
|
||||
"User flows & wireframes",
|
||||
"Technical architecture"
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: <Rocket className="w-8 h-8" />,
|
||||
title: "The Prototype",
|
||||
timeline: "Working Demo in 2 Weeks",
|
||||
priceRange: "$6,000 - $8,000",
|
||||
features: [
|
||||
"Functional web or mobile prototype",
|
||||
"Core features implemented",
|
||||
"User testing ready"
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: <Crown className="w-8 h-8" />,
|
||||
title: "The Launchpad",
|
||||
timeline: "Market-Ready MVP in 6 Weeks",
|
||||
priceRange: "$20,000 - $30,000",
|
||||
features: [
|
||||
"Full website or app",
|
||||
"SEO-optimized & scalable",
|
||||
"Ready for launch"
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const PackagesSection = () => {
|
||||
return (
|
||||
<section className="relative py-20 bg-background overflow-hidden">
|
||||
<GridPattern strokeDasharray="4 2" />
|
||||
|
||||
<div className="relative z-10 container mx-auto px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-3xl lg:text-5xl font-semibold text-foreground mb-4">
|
||||
Choose Your Speed. We'll Deliver Your Launch.
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-7xl mx-auto">
|
||||
{packages.map((pkg, index) => (
|
||||
<motion.div
|
||||
key={pkg.title}
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.15 }}
|
||||
viewport={{ once: true }}
|
||||
className="group relative bg-card/50 backdrop-blur-sm border border-border/50 rounded-[20px] p-8 hover:border-accent/50 hover:shadow-xl hover:shadow-accent/5 hover:scale-[1.02] transition-all duration-300"
|
||||
>
|
||||
{/* Icon */}
|
||||
<div className="mb-6 inline-flex items-center justify-center w-16 h-16 rounded-full bg-accent/10 text-accent group-hover:bg-accent/20 group-hover:scale-110 transition-all duration-300">
|
||||
{pkg.icon}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-2xl font-semibold text-foreground mb-2 group-hover:text-accent transition-colors duration-300">
|
||||
{pkg.title}
|
||||
</h3>
|
||||
|
||||
{/* Timeline */}
|
||||
<p className="text-muted-foreground mb-4">
|
||||
{pkg.timeline}
|
||||
</p>
|
||||
|
||||
{/* Price Range */}
|
||||
<div className="text-3xl font-semibold text-foreground mb-8">
|
||||
{pkg.priceRange}
|
||||
</div>
|
||||
|
||||
{/* Features List */}
|
||||
<ul className="space-y-4">
|
||||
{pkg.features.map((feature, idx) => (
|
||||
<li key={idx} className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0 mt-0.5">
|
||||
<Check className="w-5 h-5 text-accent" />
|
||||
</div>
|
||||
<span className="text-muted-foreground leading-relaxed">
|
||||
{feature}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
155
components/VibeProgrammingPackages.tsx
Normal file
155
components/VibeProgrammingPackages.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { FileText, Rocket, Crown, Check, ArrowRight, Zap } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import { GridPattern } from "./GridPattern";
|
||||
|
||||
interface Package {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
timeline: string;
|
||||
timelineValue: string;
|
||||
outcome: string;
|
||||
outcomeValue: string;
|
||||
priceRange: string;
|
||||
isPopular?: boolean;
|
||||
}
|
||||
|
||||
const packages: Package[] = [
|
||||
{
|
||||
icon: <FileText className="w-10 h-10" />,
|
||||
title: "The Blueprint",
|
||||
description: "Your idea, structured. We shape product vision for your website, app, or solution—prioritize features, and map technical architecture into a tangible plan.",
|
||||
timeline: "Timeline:",
|
||||
timelineValue: "1 Week",
|
||||
outcome: "Outcome:",
|
||||
outcomeValue: "Clarity, without ambiguity.",
|
||||
priceRange: "$1,500 – $2,500+",
|
||||
isPopular: false
|
||||
},
|
||||
{
|
||||
icon: <Rocket className="w-10 h-10" />,
|
||||
title: "The Prototype",
|
||||
description: "Your product, visualized. High-fidelity designs, user flows, and a clickable web or mobile prototype that communicates your vision to stakeholders and investors.",
|
||||
timeline: "Timeline:",
|
||||
timelineValue: "2 Weeks or Less",
|
||||
outcome: "Outcome:",
|
||||
outcomeValue: "A working model to validate and showcase.",
|
||||
priceRange: "$6,000 – $8,000+",
|
||||
isPopular: true
|
||||
},
|
||||
{
|
||||
icon: <Crown className="w-10 h-10" />,
|
||||
title: "The Launchpad (MVP)",
|
||||
description: "Your product, live. A fully functional, market-ready website or mobile app MVP delivered with speed, precision, SEO optimization, and scalability.",
|
||||
timeline: "Timeline:",
|
||||
timelineValue: "Under 6 Weeks",
|
||||
outcome: "Outcome:",
|
||||
outcomeValue: "A product in users' hands.",
|
||||
priceRange: "$20,000 – $30,000+",
|
||||
isPopular: false
|
||||
}
|
||||
];
|
||||
|
||||
export const VibeProgrammingPackages = () => {
|
||||
return (
|
||||
<section className="relative py-20 bg-background overflow-hidden">
|
||||
<GridPattern strokeDasharray="4 2" />
|
||||
|
||||
<div className="relative z-10 container mx-auto px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 mb-4">
|
||||
<Zap className="w-6 h-6 text-accent" />
|
||||
<h2 className="text-4xl lg:text-5xl font-semibold text-foreground font-manrope">
|
||||
Vibe Programming Packages
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-muted-foreground text-lg max-w-2xl mx-auto font-manrope">
|
||||
Where your vision takes form. From clarity to click-through to launch.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Package Cards */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-7xl mx-auto">
|
||||
{packages.map((pkg, index) => (
|
||||
<motion.div
|
||||
key={pkg.title}
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.15 }}
|
||||
viewport={{ once: true }}
|
||||
className="group relative bg-card/50 backdrop-blur-sm border border-border/50 rounded-[20px] p-8 hover:border-accent/50 hover:shadow-xl hover:shadow-accent/5 hover:scale-[1.02] transition-all duration-300"
|
||||
>
|
||||
{/* Most Popular Badge */}
|
||||
{pkg.isPopular && (
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2">
|
||||
<span className="bg-blue-600 text-white px-4 py-1 rounded-full text-sm font-medium font-manrope">
|
||||
Most Popular
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Icon */}
|
||||
<div className={`mb-6 inline-flex items-center justify-center w-16 h-16 rounded-full ${pkg.isPopular ? 'bg-blue-600/20 text-blue-400' : 'bg-accent/10 text-accent'} group-hover:scale-110 transition-all duration-300 ${pkg.isPopular ? '' : ''}`}>
|
||||
{pkg.icon}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-2xl font-semibold text-foreground mb-4 group-hover:text-accent transition-colors duration-300 font-manrope">
|
||||
{pkg.title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-muted-foreground mb-6 leading-relaxed min-h-[120px] font-manrope">
|
||||
{pkg.description}
|
||||
</p>
|
||||
|
||||
{/* Timeline */}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<span className="text-sm text-muted-foreground font-manrope">{pkg.timeline}</span>
|
||||
<span className="text-sm text-foreground font-medium font-manrope">{pkg.timelineValue}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Outcome */}
|
||||
<div className="mb-6 pb-6 border-b border-border/50 min-h-[58px]">
|
||||
<div className="flex items-start justify-between">
|
||||
<span className="text-sm text-muted-foreground font-manrope">{pkg.outcome}</span>
|
||||
<span className="text-sm text-foreground font-medium text-right font-manrope">{pkg.outcomeValue}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Price Range */}
|
||||
<div className="text-3xl font-semibold text-foreground mb-6 font-manrope">
|
||||
{pkg.priceRange}
|
||||
</div>
|
||||
|
||||
{/* Get Started Button */}
|
||||
<Button
|
||||
variant={pkg.isPopular ? "default" : "outline"}
|
||||
className={`w-full py-6 text-base font-medium rounded-xl group/btn transition-all duration-300 font-manrope ${
|
||||
pkg.isPopular
|
||||
? 'bg-blue-600 hover:bg-blue-700 text-white border-0'
|
||||
: 'border-border/50 hover:border-accent/50 hover:bg-accent/10'
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center justify-center gap-2">
|
||||
Get Started
|
||||
<ArrowRight className="w-4 h-4 group-hover/btn:translate-x-1 transition-transform duration-300" />
|
||||
</span>
|
||||
</Button>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
127
components/portfolio/PortfolioChallengeSolution.tsx
Normal file
127
components/portfolio/PortfolioChallengeSolution.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
|
||||
interface Challenge {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Technology {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface Highlight {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface PortfolioChallengeSolutionProps {
|
||||
challengesTitle?: string;
|
||||
challenges: Challenge[];
|
||||
solutionTitle?: string;
|
||||
technologyStack: Technology[];
|
||||
highlights: Highlight[];
|
||||
}
|
||||
|
||||
const PortfolioChallengeSolution: React.FC<PortfolioChallengeSolutionProps> = ({
|
||||
challengesTitle = "Challenges & Constraints",
|
||||
challenges,
|
||||
solutionTitle = "Solution Architecture",
|
||||
technologyStack,
|
||||
highlights,
|
||||
}) => {
|
||||
return (
|
||||
<section className="py-24 bg-background">
|
||||
<div className="container mx-auto px-4 lg:px-6">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="grid lg:grid-cols-2 gap-20">
|
||||
{/* Challenges */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h2 className="text-3xl lg:text-4xl font-semibold text-foreground mb-12">
|
||||
{challengesTitle}
|
||||
</h2>
|
||||
<div className="space-y-6">
|
||||
{challenges.map((challenge, index) => (
|
||||
<motion.div
|
||||
key={challenge.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-background/50 rounded-xl p-6 border border-border/50 hover:border-accent/20 transition-colors duration-300"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="text-accent mt-1">{challenge.icon}</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">
|
||||
{challenge.title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
{challenge.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Solution Architecture */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h2 className="text-3xl lg:text-4xl font-semibold text-foreground mb-12">
|
||||
{solutionTitle}
|
||||
</h2>
|
||||
<div className="bg-background/50 rounded-2xl p-8 border border-border/50">
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-foreground mb-6">
|
||||
Technology Stack
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{technologyStack.map((tech, idx) => (
|
||||
<div key={idx} className="flex items-center gap-3">
|
||||
{tech.icon}
|
||||
<span className="text-muted-foreground">{tech.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-foreground mb-6">
|
||||
Key Highlights
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{highlights.map((item, idx) => (
|
||||
<div key={idx} className="flex items-start gap-3">
|
||||
<CheckCircle className="w-4 h-4 text-accent mt-0.5 flex-shrink-0" />
|
||||
<span className="text-muted-foreground text-base">
|
||||
{item.text}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PortfolioChallengeSolution;
|
||||
73
components/portfolio/PortfolioCoreFeatures.tsx
Normal file
73
components/portfolio/PortfolioCoreFeatures.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
// components/PortfolioCoreFeatures.tsx
|
||||
import { motion } from "framer-motion";
|
||||
import { ReactNode } from "react";
|
||||
import { Card, CardContent } from "@/components/ui/card"; // adjust path as per your project
|
||||
|
||||
interface Feature {
|
||||
icon: ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface PortfolioCoreFeaturesProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
features: Feature[];
|
||||
}
|
||||
|
||||
export function PortfolioCoreFeatures({
|
||||
title,
|
||||
subtitle,
|
||||
features,
|
||||
}: PortfolioCoreFeaturesProps) {
|
||||
return (
|
||||
<section className="py-24 bg-card/30">
|
||||
<div className="container mx-auto px-4 lg:px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-6xl mx-auto"
|
||||
>
|
||||
{/* Section Heading */}
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-5xl font-semibold text-foreground mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
{subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<Card className="h-full bg-card/50 border-border/50 hover:border-accent/30 transition-all duration-300 group">
|
||||
<CardContent className="p-8">
|
||||
<div className="text-accent mb-4 group-hover:scale-110 transition-transform duration-300">
|
||||
{feature.icon}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-foreground mb-4">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
78
components/portfolio/PortfolioDevelopmentProcess.tsx
Normal file
78
components/portfolio/PortfolioDevelopmentProcess.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
// components/PortfolioDevelopmentProcess.tsx
|
||||
import { motion } from "framer-motion";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface Phase {
|
||||
icon: ReactNode;
|
||||
phase: string;
|
||||
duration: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface PortfolioDevelopmentProcessProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
phases: Phase[];
|
||||
}
|
||||
|
||||
export function PortfolioDevelopmentProcess({
|
||||
title,
|
||||
subtitle,
|
||||
phases,
|
||||
}: PortfolioDevelopmentProcessProps) {
|
||||
return (
|
||||
<section className="py-24 bg-card/30">
|
||||
<div className="container mx-auto px-4 lg:px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-6xl mx-auto"
|
||||
>
|
||||
{/* Section Heading */}
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-5xl font-semibold text-foreground mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
{subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Phases Grid */}
|
||||
<div className="grid lg:grid-cols-5 gap-8">
|
||||
{phases.map((phase, index) => (
|
||||
<motion.div
|
||||
key={phase.phase}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className="relative"
|
||||
>
|
||||
<div className="bg-card/50 rounded-xl p-6 border border-border/50 hover:border-accent/30 transition-all duration-300 h-full">
|
||||
<div className="text-accent mb-4">{phase.icon}</div>
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">
|
||||
{phase.phase}
|
||||
</h3>
|
||||
<div className="text-accent font-medium mb-3">
|
||||
{phase.duration}
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||
{phase.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Connector Line */}
|
||||
{index < phases.length - 1 && (
|
||||
<div className="hidden lg:block absolute top-1/2 -right-4 w-8 h-0.5 bg-border transform -translate-y-1/2" />
|
||||
)}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
35
components/portfolio/PortfolioExecutiveSummary.tsx
Normal file
35
components/portfolio/PortfolioExecutiveSummary.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
// components/portfolio/PortfolioExecutiveSummary.tsx
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface PortfolioExecutiveSummaryProps {
|
||||
title?: string;
|
||||
content: string;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
export const PortfolioExecutiveSummary = ({
|
||||
title = "Executive Summary",
|
||||
content,
|
||||
backgroundColor = "bg-card/30"
|
||||
}: PortfolioExecutiveSummaryProps) => {
|
||||
return (
|
||||
<section className={`py-24 ${backgroundColor}`}>
|
||||
<div className="container mx-auto px-4 lg:px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-4xl mx-auto text-center"
|
||||
>
|
||||
<h2 className="text-3xl lg:text-5xl font-semibold text-foreground mb-8">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground leading-relaxed">
|
||||
{content}
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
96
components/portfolio/PortfolioHero.tsx
Normal file
96
components/portfolio/PortfolioHero.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
// components/portfolio/PortfolioHero.tsx
|
||||
import { motion } from "framer-motion";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { GridPattern } from "../GridPattern";
|
||||
import { ImageWithFallback } from "../figma/ImageWithFallback";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface PortfolioHeroProps {
|
||||
badgeText?: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
subtitleTwo?: string;
|
||||
imageUrl: string;
|
||||
imageAlt: string;
|
||||
}
|
||||
|
||||
export const PortfolioHero = ({
|
||||
badgeText = "Portfolio",
|
||||
title,
|
||||
subtitle,
|
||||
subtitleTwo,
|
||||
imageUrl,
|
||||
imageAlt,
|
||||
}: PortfolioHeroProps) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<section className="relative pt-32 pb-24 bg-background overflow-hidden">
|
||||
<GridPattern strokeDasharray="4 2" />
|
||||
|
||||
<div className="relative z-10 container mx-auto px-4 lg:px-6">
|
||||
{/* Back Button */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="mb-12"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigate("/case-studies")}
|
||||
className="text-muted-foreground hover:text-foreground flex items-center gap-2 px-0 hover:bg-transparent"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Back to Portfolio
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid lg:grid-cols-12 gap-16 items-center">
|
||||
{/* Content - Left Aligned */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="lg:col-span-7"
|
||||
>
|
||||
<div className="mb-6">
|
||||
<Badge variant="secondary" className="text-accent border-accent/20 bg-accent/10">
|
||||
{badgeText}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl lg:text-6xl font-semibold text-foreground mb-8 leading-tight">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-muted-foreground mb-6 leading-relaxed max-w-2xl">
|
||||
{subtitle}
|
||||
</p>
|
||||
<p className="text-xl text-muted-foreground mb-10 leading-relaxed max-w-2xl">
|
||||
{subtitleTwo}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Project Image */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
className="lg:col-span-5"
|
||||
>
|
||||
<div className="relative aspect-[4/3] overflow-hidden bg-card/30 rounded-2xl border border-border/50 p-4">
|
||||
<ImageWithFallback
|
||||
src={imageUrl}
|
||||
alt={imageAlt}
|
||||
className="w-full h-full object-contain object-center rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
119
components/portfolio/PortfolioLessonsSection.tsx
Normal file
119
components/portfolio/PortfolioLessonsSection.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface LessonSectionProps {
|
||||
title: string;
|
||||
description: string;
|
||||
workedTitle: string;
|
||||
workedIcon: React.ReactNode;
|
||||
workedColor: string;
|
||||
workedLessons: string[];
|
||||
improveTitle: string;
|
||||
improveIcon: React.ReactNode;
|
||||
improveColor: string;
|
||||
improveLessons: string[];
|
||||
}
|
||||
|
||||
const PortfolioLessonsSection: React.FC<LessonSectionProps> = ({
|
||||
title,
|
||||
description,
|
||||
workedTitle,
|
||||
workedIcon,
|
||||
workedColor,
|
||||
workedLessons,
|
||||
improveTitle,
|
||||
improveIcon,
|
||||
improveColor,
|
||||
improveLessons,
|
||||
}) => {
|
||||
return (
|
||||
<section className="py-24 bg-card/30">
|
||||
<div className="container mx-auto px-4 lg:px-6">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-3xl lg:text-5xl font-semibold text-foreground mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
{description}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Lessons Grid */}
|
||||
<div className="grid lg:grid-cols-2 gap-12">
|
||||
{/* Worked Well */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className={`rounded-2xl p-8 border`}
|
||||
style={{
|
||||
backgroundColor: `${workedColor}0D`, // light transparent bg
|
||||
borderColor: `${workedColor}33`, // faint border
|
||||
}}
|
||||
>
|
||||
<h3 className="text-2xl font-semibold text-foreground mb-8 flex items-center gap-3">
|
||||
<span className="text-xl" style={{ color: workedColor }}>
|
||||
{workedIcon}
|
||||
</span>
|
||||
{workedTitle}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{workedLessons.map((lesson, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full mt-2 flex-shrink-0"
|
||||
style={{ backgroundColor: workedColor }}
|
||||
/>
|
||||
<span className="text-muted-foreground">{lesson}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Areas for Improvement */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className={`rounded-2xl p-8 border`}
|
||||
style={{
|
||||
backgroundColor: `${improveColor}0D`,
|
||||
borderColor: `${improveColor}33`,
|
||||
}}
|
||||
>
|
||||
<h3 className="text-2xl font-semibold text-foreground mb-8 flex items-center gap-3">
|
||||
<span className="text-xl" style={{ color: improveColor }}>
|
||||
{improveIcon}
|
||||
</span>
|
||||
{improveTitle}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{improveLessons.map((lesson, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full mt-2 flex-shrink-0"
|
||||
style={{ backgroundColor: improveColor }}
|
||||
/>
|
||||
<span className="text-muted-foreground">{lesson}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PortfolioLessonsSection;
|
||||
233
components/portfolio/PortfolioProjectDetails.tsx
Normal file
233
components/portfolio/PortfolioProjectDetails.tsx
Normal file
@@ -0,0 +1,233 @@
|
||||
// components/portfolio/PortfolioProjectDetails.tsx
|
||||
import { motion } from "framer-motion";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Code,
|
||||
ShoppingCart,
|
||||
Calendar,
|
||||
Users,
|
||||
Smartphone
|
||||
} from "lucide-react";
|
||||
|
||||
interface Technology {
|
||||
name: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
interface ProjectDetails {
|
||||
technologies: Technology[];
|
||||
industries: string[];
|
||||
duration: string;
|
||||
teamSize: string;
|
||||
platforms: string[];
|
||||
}
|
||||
|
||||
interface PortfolioProjectDetailsProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
details: ProjectDetails;
|
||||
achievements?: Array<{
|
||||
label: string;
|
||||
value: string;
|
||||
description: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const PortfolioProjectDetails = ({
|
||||
title = "Project Details",
|
||||
description = "Detailed overview of the project including technologies, timeline, and team composition.",
|
||||
details,
|
||||
achievements = []
|
||||
}: PortfolioProjectDetailsProps) => {
|
||||
return (
|
||||
<section className="py-24 bg-card/30 relative overflow-hidden">
|
||||
{/* Background Elements */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-accent/5 via-transparent to-green-500/5" />
|
||||
<div className="absolute top-20 right-20 w-64 h-64 bg-accent/10 rounded-full blur-3xl opacity-20" />
|
||||
<div className="absolute bottom-20 left-20 w-48 h-48 bg-green-500/10 rounded-full blur-3xl opacity-20" />
|
||||
|
||||
<div className="container mx-auto px-4 lg:px-6 relative z-10">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-3xl lg:text-5xl font-semibold text-foreground mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
{description}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Project Meta Information Grid */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
className="grid lg:grid-cols-2 gap-8 mb-20"
|
||||
>
|
||||
{/* Technologies & Industries Card */}
|
||||
<div className="bg-background/40 backdrop-blur-xl rounded-2xl p-8 border border-border/30 hover:border-accent/20 transition-all duration-500 group">
|
||||
<div className="space-y-8">
|
||||
{/* Technologies */}
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-accent/10 backdrop-blur-sm rounded-xl border border-accent/20 flex items-center justify-center group-hover:bg-accent/20 transition-all duration-300">
|
||||
<Code className="w-6 h-6 text-accent" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-foreground">Technologies</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{details.technologies.map((tech) => (
|
||||
<Badge
|
||||
key={tech.name}
|
||||
variant="outline"
|
||||
className="text-base border-border/40 bg-background/30 hover:bg-accent/10 hover:border-accent/40 flex items-center gap-2 px-4 py-2 transition-all duration-300"
|
||||
>
|
||||
<span className="text-accent">{tech.icon}</span>
|
||||
{tech.name}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Industries */}
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-green-500/10 backdrop-blur-sm rounded-xl border border-green-500/20 flex items-center justify-center group-hover:bg-green-500/20 transition-all duration-300">
|
||||
<ShoppingCart className="w-6 h-6 text-green-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-foreground">Industries</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{details.industries.map((industry) => (
|
||||
<Badge
|
||||
key={industry}
|
||||
variant="secondary"
|
||||
className="text-base bg-green-500/10 border-green-500/20 text-green-100 hover:bg-green-500/20 px-4 py-2 transition-all duration-300"
|
||||
>
|
||||
{industry}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeline & Team Card */}
|
||||
<div className="bg-background/40 backdrop-blur-xl rounded-2xl p-8 border border-border/30 hover:border-blue-400/20 transition-all duration-500 group">
|
||||
<div className="space-y-8">
|
||||
{/* Duration */}
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-blue-500/10 backdrop-blur-sm rounded-xl border border-blue-500/20 flex items-center justify-center group-hover:bg-blue-500/20 transition-all duration-300">
|
||||
<Calendar className="w-6 h-6 text-blue-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-foreground">Project Timeline</h3>
|
||||
</div>
|
||||
<p className="text-lg text-muted-foreground pl-15">{details.duration}</p>
|
||||
</div>
|
||||
|
||||
{/* Team */}
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-purple-500/10 backdrop-blur-sm rounded-xl border border-purple-500/20 flex items-center justify-center group-hover:bg-purple-500/20 transition-all duration-300">
|
||||
<Users className="w-6 h-6 text-purple-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-foreground">Team Composition</h3>
|
||||
</div>
|
||||
<p className="text-lg text-muted-foreground pl-15">{details.teamSize}</p>
|
||||
</div>
|
||||
|
||||
{/* Platforms */}
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-orange-500/10 backdrop-blur-sm rounded-xl border border-orange-500/20 flex items-center justify-center group-hover:bg-orange-500/20 transition-all duration-300">
|
||||
<Smartphone className="w-6 h-6 text-orange-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-foreground">Target Platforms</h3>
|
||||
</div>
|
||||
<div className="flex gap-3 pl-15">
|
||||
{details.platforms.map((platform) => (
|
||||
<Badge
|
||||
key={platform}
|
||||
variant="outline"
|
||||
className="text-base border-orange-400/40 bg-orange-500/10 text-orange-100 hover:bg-orange-500/20 px-3 py-1"
|
||||
>
|
||||
{platform}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Key Achievements Section */}
|
||||
{achievements.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
className="mb-16"
|
||||
>
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-semibold text-foreground mb-6">
|
||||
Key Impact & Results
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
Measurable outcomes that demonstrate the project's success
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{achievements.map((achievement, index) => (
|
||||
<motion.div
|
||||
key={achievement.label}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 + index * 0.1 }}
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
y: -4,
|
||||
transition: { duration: 0.3, ease: "easeOut" }
|
||||
}}
|
||||
className="bg-background/50 backdrop-blur-xl rounded-2xl p-8 border border-border/40 hover:border-accent/30 hover:bg-background/60 transition-all duration-500 group cursor-pointer relative overflow-hidden"
|
||||
>
|
||||
{/* Card Background Gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-accent/5 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 text-center">
|
||||
{/* Value */}
|
||||
<div className="text-3xl lg:text-4xl font-bold text-accent mb-4 group-hover:text-accent transition-colors duration-300">
|
||||
{achievement.value}
|
||||
</div>
|
||||
|
||||
{/* Label */}
|
||||
<div className="text-xl font-semibold text-foreground mb-3 group-hover:text-foreground transition-colors duration-300">
|
||||
{achievement.label}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="text-base text-muted-foreground leading-relaxed group-hover:text-muted-foreground transition-colors duration-300">
|
||||
{achievement.description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hover Effect Line */}
|
||||
<div className="absolute bottom-0 left-0 w-full h-1 bg-gradient-to-r from-accent to-accent/50 transform scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left" />
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
75
components/portfolio/PortfolioProjectOverview.tsx
Normal file
75
components/portfolio/PortfolioProjectOverview.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
// components/PortfolioProjectOverview.tsx
|
||||
import { motion } from "framer-motion";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface PortfolioProjectOverviewProps {
|
||||
icon: ReactNode;
|
||||
title: string;
|
||||
description?: string;
|
||||
points?: string[];
|
||||
borderColor?: string;
|
||||
hoverColor?: string;
|
||||
}
|
||||
|
||||
export function PortfolioProjectOverview({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
points,
|
||||
borderColor = "border-accent/20",
|
||||
hoverColor = "accent",
|
||||
}: PortfolioProjectOverviewProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
y: -4,
|
||||
transition: { duration: 0.3, ease: "easeOut" },
|
||||
}}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className={`bg-card/30 rounded-2xl p-8 border-2 ${borderColor} cursor-pointer group
|
||||
hover:border-${hoverColor}/40 hover:bg-card/40
|
||||
hover:shadow-lg hover:shadow-${hoverColor}/10
|
||||
transition-all duration-300 ease-out`}
|
||||
>
|
||||
{/* Icon */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
className="flex justify-start mb-6"
|
||||
>
|
||||
<div
|
||||
className={`w-16 h-16 bg-background/20 backdrop-blur-sm rounded-full
|
||||
border border-border/30 flex items-center justify-center
|
||||
group-hover:border-${hoverColor}/50 group-hover:bg-${hoverColor}/10
|
||||
transition-all duration-300 ease-out`}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-2xl font-semibold text-foreground mb-6">{title}</h3>
|
||||
|
||||
{/* Content */}
|
||||
{description && (
|
||||
<p className="text-muted-foreground leading-relaxed">{description}</p>
|
||||
)}
|
||||
{points && (
|
||||
<div className="space-y-3">
|
||||
{points.map((point, i) => (
|
||||
<div key={i} className="flex items-start gap-3">
|
||||
<span className="w-5 h-5 text-accent mt-0.5 flex-shrink-0">✔</span>
|
||||
<span className="text-muted-foreground text-base">{point}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
92
components/portfolio/PortfolioResultsImpact.tsx
Normal file
92
components/portfolio/PortfolioResultsImpact.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
// components/PortfolioResultsImpact.tsx
|
||||
import { motion } from "framer-motion";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
|
||||
interface Metric {
|
||||
value: string;
|
||||
label: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface PortfolioResultsImpactProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
metrics: Metric[];
|
||||
achievements: string[];
|
||||
}
|
||||
|
||||
export function PortfolioResultsImpact({
|
||||
title,
|
||||
subtitle,
|
||||
metrics,
|
||||
achievements,
|
||||
}: PortfolioResultsImpactProps) {
|
||||
return (
|
||||
<section className="py-24 bg-background">
|
||||
<div className="container mx-auto px-4 lg:px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-6xl mx-auto"
|
||||
>
|
||||
{/* Heading */}
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-5xl font-semibold text-foreground mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
{subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Metrics */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
|
||||
{metrics.map((metric, index) => (
|
||||
<motion.div
|
||||
key={metric.label}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-background/50 rounded-xl p-6 border border-border/50 hover:border-accent/30 transition-all duration-300 text-center"
|
||||
>
|
||||
<div className="text-3xl font-bold text-accent mb-2">
|
||||
{metric.value}
|
||||
</div>
|
||||
<div className="text-lg font-semibold text-foreground mb-2">
|
||||
{metric.label}
|
||||
</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{metric.description}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Achievements */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-background/50 rounded-2xl p-8 border border-border/50"
|
||||
>
|
||||
<h3 className="text-2xl font-semibold text-foreground mb-6">
|
||||
Technical Achievements
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{achievements.map((achievement, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<CheckCircle className="w-5 h-5 text-accent mt-0.5 flex-shrink-0" />
|
||||
<span className="text-muted-foreground">{achievement}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
75
components/portfolio/PortfolioRoadmapSection.tsx
Normal file
75
components/portfolio/PortfolioRoadmapSection.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface RoadmapItem {
|
||||
phase: string;
|
||||
features: string[];
|
||||
}
|
||||
|
||||
interface PortfolioRoadmapSectionProps {
|
||||
title: string;
|
||||
description: string;
|
||||
roadmapItems: RoadmapItem[];
|
||||
icon?: React.ReactNode; // optional (default ArrowRight)
|
||||
}
|
||||
|
||||
const PortfolioRoadmapSection: React.FC<PortfolioRoadmapSectionProps> = ({
|
||||
title,
|
||||
description,
|
||||
roadmapItems,
|
||||
icon,
|
||||
}) => {
|
||||
return (
|
||||
<section className="py-24 bg-background">
|
||||
<div className="container mx-auto px-4 lg:px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-6xl mx-auto"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-5xl font-semibold text-foreground mb-6">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Roadmap Grid */}
|
||||
<div className="grid lg:grid-cols-2 gap-12">
|
||||
{roadmapItems.map((roadmap, index) => (
|
||||
<motion.div
|
||||
key={roadmap.phase}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-background/50 rounded-2xl p-8 border border-border/50 hover:border-accent/30 transition-all duration-300"
|
||||
>
|
||||
<h3 className="text-2xl font-semibold text-foreground mb-6">
|
||||
{roadmap.phase}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{roadmap.features.map((feature, featureIndex) => (
|
||||
<div key={featureIndex} className="flex items-start gap-3">
|
||||
<span className="w-5 h-5 text-accent mt-0.5 flex-shrink-0">
|
||||
{icon}
|
||||
</span>
|
||||
<span className="text-muted-foreground">{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PortfolioRoadmapSection;
|
||||
70
components/portfolio/PortfolioTestimonial.tsx
Normal file
70
components/portfolio/PortfolioTestimonial.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Star } from "lucide-react";
|
||||
|
||||
interface PortfolioTestimonialProps {
|
||||
logo: string | React.ReactNode; // can be an image URL or React component
|
||||
alt?: string;
|
||||
rating?: number; // default 5
|
||||
testimonial: string;
|
||||
clientName: string;
|
||||
clientRole: string;
|
||||
maxWidth?: string; // optional, e.g., "max-w-4xl"
|
||||
}
|
||||
|
||||
const PortfolioTestimonial: React.FC<PortfolioTestimonialProps> = ({
|
||||
logo,
|
||||
alt = "Client Logo",
|
||||
rating = 5,
|
||||
testimonial,
|
||||
clientName,
|
||||
clientRole,
|
||||
maxWidth = "max-w-4xl",
|
||||
}) => {
|
||||
return (
|
||||
<section className="py-24 bg-card/30">
|
||||
<div className="container mx-auto px-4 lg:px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className={`mx-auto text-center ${maxWidth}`}
|
||||
>
|
||||
<div className="bg-card/50 rounded-2xl p-12 border border-border/50">
|
||||
{/* Logo */}
|
||||
{typeof logo === "string" ? (
|
||||
<div className="flex justify-center mb-8">
|
||||
<img src={logo} alt={alt} className="h-12 w-auto object-contain" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center mb-8">{logo}</div>
|
||||
)}
|
||||
|
||||
{/* Rating */}
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="flex text-yellow-400">
|
||||
{[...Array(rating)].map((_, i) => (
|
||||
<Star key={i} className="w-6 h-6 fill-current" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Testimonial */}
|
||||
<blockquote className="text-2xl lg:text-3xl text-foreground mb-8 leading-relaxed italic">
|
||||
{testimonial}
|
||||
</blockquote>
|
||||
|
||||
{/* Client Info */}
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="text-lg font-semibold text-foreground">{clientName}</div>
|
||||
<div className="text-muted-foreground">{clientRole}</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PortfolioTestimonial;
|
||||
Reference in New Issue
Block a user