diff --git a/apps/web/app/partners/[slug]/page.tsx b/apps/web/app/partners/[slug]/page.tsx index 7e3e973..446f0ef 100644 --- a/apps/web/app/partners/[slug]/page.tsx +++ b/apps/web/app/partners/[slug]/page.tsx @@ -4,6 +4,7 @@ import { Footer } from "@/components/footer"; import { PartnerHero } from "@/components/sections/partner-hero"; import { PartnerBio } from "@/components/sections/partner-bio"; import { PartnerVideo } from "@/components/sections/partner-video"; +import { CurvedCarousel } from "@/components/ui/curved-carousel"; import { PartnerCTA } from "@/components/sections/partner-cta"; import { PartnerFeatures } from "@/components/sections/partner-features"; import { TrustMetricsSection } from "@/components/sections/trust-metrics-section"; @@ -18,6 +19,7 @@ interface PartnerData { ctaMessage?: string; youtubeId?: string; ctaUrl?: string; + featuredVideos?: { id: string; videoId: string; title: string }[]; } const PARTNERS_DATA: Record = { @@ -28,7 +30,15 @@ const PARTNERS_DATA: Record = { quote: "Life is short and working for other people sucks", ctaMessage: "Trade with the broker I trust. Join me at RestroFX and experience trading the way it was meant to be. Raw spreads, lightning-fast execution, and a platform that puts you first.", youtubeId: "01loBLlZRHw", - ctaUrl: "https://portal.restrofx.com/r/glaPWwHQ" + ctaUrl: "https://portal.restrofx.com/r/glaPWwHQ", + featuredVideos: [ + { id: "1", videoId: "R2djd5ACzPM", title: "i'm finally buying my dream car" }, + { id: "2", videoId: "_QmCh4dNVGE", title: "Don't Trade Every Pair | Here's What Actually Works" }, + { id: "3", videoId: "DV6cte3H9rc", title: "Pulled $15k profit - here's every single trade" }, + { id: "4", videoId: "KhLUPlL777U", title: "Why You Should Reconsider Trading This year" }, + { id: "5", videoId: "SyC37iKc2wE", title: "I Made $20k Trading Silver | Here's My Exact Strategy" }, + { id: "6", videoId: "rExdi9Vzkxk", title: "Is Trading Really Worth It? My 6 Years of Results" } + ] }, "default": { name: "Our Global Partner", @@ -76,6 +86,16 @@ export default function PartnerProfilePage({ params }: { params: { slug: string )} + {partner.featuredVideos && ( + + + + )} +
diff --git a/apps/web/components/ui/curved-carousel.tsx b/apps/web/components/ui/curved-carousel.tsx new file mode 100644 index 0000000..9a7c1ab --- /dev/null +++ b/apps/web/components/ui/curved-carousel.tsx @@ -0,0 +1,145 @@ +"use client"; + +import { motion, useMotionValue, useSpring, useTransform } from "framer-motion"; +import { useState, useRef, useEffect } from "react"; +import Image from "next/image"; +import { Play } from "lucide-react"; + +interface CarouselItem { + id: string; + title: string; + videoId: string; +} + +interface CurvedCarouselProps { + items: CarouselItem[]; + title?: string; + subtitle?: string; +} + +export function CurvedCarousel({ items, title, subtitle }: CurvedCarouselProps) { + const [active, setActive] = useState(0); + const containerRef = useRef(null); + const x = useMotionValue(0); + + // Spring for smooth dragging + const springX = useSpring(x, { stiffness: 300, damping: 30 }); + + // Calculate the rotation based on the number of items + // We want to map the drag distance to an index + const itemWidth = 300; // estimated width of a card + + const handleDragEnd = (_: any, info: any) => { + // Determine the closest index based on the drag offset + const velocity = info.velocity.x; + + // Simple snapping + if (Math.abs(velocity) > 500) { + if (velocity > 0) setActive((prev) => Math.max(0, prev - 1)); + else setActive((prev) => Math.min(items.length - 1, prev + 1)); + } else { + const index = Math.round(-x.get() / itemWidth); + setActive(Math.max(0, Math.min(items.length - 1, index))); + } + }; + + useEffect(() => { + x.set(-active * itemWidth); + }, [active, x]); + + return ( +
+
+ {title &&

{title}

} + {subtitle &&

{subtitle}

} +
+ +
+ + {items.map((item, index) => { + // Map the global x motion value to local item transforms + // eslint-disable-next-line react-hooks/rules-of-hooks + const itemRotation = useTransform(springX, + [- (index + 1) * itemWidth, - index * itemWidth, - (index - 1) * itemWidth], + [-45, 0, 45] + ); + // eslint-disable-next-line react-hooks/rules-of-hooks + const itemScale = useTransform(springX, + [- (index + 1) * itemWidth, - index * itemWidth, - (index - 1) * itemWidth], + [0.8, 1, 0.8] + ); + // eslint-disable-next-line react-hooks/rules-of-hooks + const itemZ = useTransform(springX, + [- (index + 1) * itemWidth, - index * itemWidth, - (index - 1) * itemWidth], + [-200, 0, -200] + ); + // eslint-disable-next-line react-hooks/rules-of-hooks + const itemOpacity = useTransform(springX, + [- (index + 2) * itemWidth, - index * itemWidth, - (index - 2) * itemWidth], + [0, 1, 0] + ); + + return ( + window.open(`https://youtube.com/watch?v=${item.videoId}`, '_blank')} + > + {item.title} + + {/* Overlay */} +
+ + + +

+ {item.title} +

+
+ + {/* Glassmorphic Reflection Overlay */} +
+ + ); + })} + +
+ +
+ {items.map((_, i) => ( +
+
+ ); +}