From 8b4eecca74395756d6b423ab7f6aede40540f2e9 Mon Sep 17 00:00:00 2001 From: laveshparyani Date: Wed, 25 Mar 2026 14:54:39 +0530 Subject: [PATCH 1/2] feat(partners): add 3D curved video carousel to Solo E TV profile This commit adds a panoramic 3D curved slider built with React Three Fiber to showcase the latest YouTube videos from Solo E TV. It includes a custom shader-like curvature effect, horizontal scroll navigation, and a centered video playback modal. Also updates PARTNERS_DATA with the 6 most recent videos fetched from their channel. --- apps/web/app/partners/[slug]/page.tsx | 43 ++++- .../sections/partner-video-carousel.tsx | 174 ++++++++++++++++++ 2 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 apps/web/components/sections/partner-video-carousel.tsx diff --git a/apps/web/app/partners/[slug]/page.tsx b/apps/web/app/partners/[slug]/page.tsx index 7e3e973..d9f3d9c 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 { PartnerVideoCarousel } from "@/components/sections/partner-video-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; + extraVideos?: { id: string; title: string; thumbnail: string }[]; } const PARTNERS_DATA: Record = { @@ -27,8 +29,39 @@ const PARTNERS_DATA: Record = { imageUrl: "/images/soloetv.png", 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", + extraVideos: [ + { + id: "R2djd5ACzPM", + thumbnail: "https://i.ytimg.com/vi/R2djd5ACzPM/hqdefault.jpg", + title: "i'm finally buying my dream car" + }, + { + id: "_QmCh4dNVGE", + thumbnail: "https://i.ytimg.com/vi/_QmCh4dNVGE/hqdefault.jpg", + title: "Don't Trade Every Pair | Here's What Actually Works" + }, + { + id: "DV6cte3H9rc", + thumbnail: "https://i.ytimg.com/vi/DV6cte3H9rc/hqdefault.jpg", + title: "Pulled $15k profit - here's every single trade (GatesFX)" + }, + { + id: "KhLUPlL777U", + thumbnail: "https://i.ytimg.com/vi/KhLUPlL777U/hqdefault.jpg", + title: "Why You Should Reconsider Trading This year" + }, + { + id: "SyC37iKc2wE", + thumbnail: "https://i.ytimg.com/vi/SyC37iKc2wE/hqdefault.jpg", + title: "I Made $20k Trading Silver | Here's My Exact Strategy" + }, + { + id: "rExdi9Vzkxk", + thumbnail: "https://i.ytimg.com/vi/rExdi9Vzkxk/hqdefault.jpg", + title: "Is Trading Really Worth It? My 6 Years of Results" + } + ] }, "default": { name: "Our Global Partner", @@ -76,6 +109,12 @@ export default function PartnerProfilePage({ params }: { params: { slug: string )} + {partner.extraVideos && ( + + + + )} +
diff --git a/apps/web/components/sections/partner-video-carousel.tsx b/apps/web/components/sections/partner-video-carousel.tsx new file mode 100644 index 0000000..161cc20 --- /dev/null +++ b/apps/web/components/sections/partner-video-carousel.tsx @@ -0,0 +1,174 @@ +"use client"; + +import React, { useRef, useState } from "react"; +import { Canvas, useFrame } from "@react-three/fiber"; +import { Image, ScrollControls, useScroll, useCursor } from "@react-three/drei"; +import * as THREE from "three"; +import { motion, AnimatePresence } from "framer-motion"; +import { ArrowLeft, ArrowRight } from "lucide-react"; + +interface VideoData { + id: string; + title: string; + thumbnail: string; +} + +const CURVE_AMOUNT = 0.35; +const GAP = 2.5; + +function VideoItem({ + url, + index, + total, + onSelect +}: { + url: string; + title: string; + index: number; + total: number; + onSelect: () => void; +}) { + const mesh = useRef(null!); + const [hovered, setHovered] = useState(false); + useCursor(hovered); + + useFrame(() => { + if (!mesh.current) return; + + // Calculate distance from center based on world position + const worldPos = new THREE.Vector3(); + mesh.current.getWorldPosition(worldPos); + const dist = worldPos.x; + + // Apply curvature in Z and rotation based on X position + mesh.current.position.z = -Math.pow(Math.abs(dist), 2) * CURVE_AMOUNT; + mesh.current.rotation.y = -dist * 0.15; + + // Scale up on hover + const targetScale = hovered ? 1.1 : 1; + mesh.current.scale.lerp(new THREE.Vector3(targetScale, targetScale, 1), 0.1); + }); + + return ( + + setHovered(true)} + onPointerOut={() => setHovered(false)} + onClick={onSelect} + > + + + + ); +} + +function Scene({ videos, onVideoSelect }: { videos: VideoData[], onVideoSelect: (id: string) => void }) { + const scroll = useScroll(); + const group = useRef(null!); + + useFrame(() => { + if (!group.current) return; + // Maps scroll 0-1 to X position + const offset = scroll.offset; + const scrollWidth = (videos.length - 1) * GAP; + group.current.position.x = -offset * scrollWidth + (scrollWidth / 2); + }); + + return ( + + {videos.map((video, i) => ( + onVideoSelect(video.id)} + /> + ))} + + ); +} + +export function PartnerVideoCarousel({ videos }: { videos: VideoData[] }) { + const [selectedVideo, setSelectedVideo] = useState(null); + + return ( +
+
+ + My Latest Content + + + Scroll to explore • Click to watch + +
+ + + + + + + + + + {/* Video Overlay / Modal */} + + {selectedVideo && ( + setSelectedVideo(null)} + > + e.stopPropagation()} + > +