Enhance Reader component with structured content and navigation handlers; implement HoverCards for dynamic media display.

This commit is contained in:
Yussif Yahuza 2025-04-28 14:42:01 +00:00
parent a2a9bb9b18
commit 46a0008551
2 changed files with 298 additions and 206 deletions

View File

@ -15,218 +15,302 @@ interface Page {
} }
export default function Reader() { export default function Reader() {
const [currentPageIndex, setCurrentPageIndex] = useState(0); const [currentPageIndex, setCurrentPageIndex] = useState(0);
const [transitioning, setTransitioning] = useState(false); const [transitioning, setTransitioning] = useState(false);
const videoRefs = useRef<(HTMLVideoElement | null)[]>([]); const videoRefs = useRef<(HTMLVideoElement | null)[]>([]);
// Add wheel event handler // Content structured to match your design
const handleWheel = useCallback((event: WheelEvent) => { const pages: Page[] = [
if (transitioning) return; {
id: 1,
videoSrc: "/videos/background1.mp4",
content: (
<>
<h1 className="text-4xl font-bold mb-8 text-center">BRUTAL</h1>
<div className="space-y-6 text-center max-w-3xl">
<p className="text-lg">
Through the rain, flickering neon lights spell out of{" "}
<HoverCards
triggerText="SEPHORA"
videourl="/images/card1.png"
isImage={true}
description="Bloomberg, COMEX, Dubai Gold & Commodities Exchange, ICE Benchmark Administration, London Metal Exchange, Multi Commodity Exchange of India, Nasdaq, Shanghai Gold Exchange, Shanghai Futures Exchange, Tokyo Commodities Exchange, World Gold Council;"
link=" https://www.gold.org/goldhub/data/gold-trading"
/>{" "}
and illuminate an entrance to nightclub.
</p>
<p className="text-lg">
A stunning light show cascades across a dance floor crowded by
partiers and adorned by dozens of video monitors.
</p>
<p className="text-lg">
WADE HARPER, an anxious businessman dressed in a black suit,
follows two burly bouncers up a flight of stairs toward the{" "}
<HoverCards
triggerText="VIP Suite"
videourl="/videos/background2.mp4"
description='"Man, yes! Didnt I tell you not to question this man! I knew he was going to come through for us!," Handsome Twin #1 gloats. Handsome Twin #2 sighs in satisfaction. “Gold!,” he says, his tense demeanor turning to relief. '
/>{" "}
at the back of the warehouse.
</p>
</div>
</>
),
},
{
id: 2,
videoSrc: "/videos/background3.mp4",
// Scroll down content: (
if (event.deltaY > 0) { <>
handleNextPage(); <h1 className="text-4xl font-bold mb-8 text-center">BRUTAL</h1>
} <div className="space-y-6 text-center max-w-3xl">
// Scroll up <p className="text-lg">
else if (event.deltaY < 0) { &quot;Wade Harper! What is up, old friend! It&#39;s been too long,
handlePreviousPage(); man!&quot; exclaims HANDSOME TWIN #1.
} </p>
}, []); <p className="text-lg">
HANDSOME TWIN #2, more anxious and pushy, quickly interjects,
// Add previous page handler &quot;So do you have it for us?&quot;
const handlePreviousPage = () => { </p>
if (transitioning) return; <p className="text-lg">Wade reaches into his breast pocket.</p>
<p className="text-lg">&quot;Yes, I do.&quot;</p>
setTransitioning(true); <div className="text-lg">
Wade considers the{" "}
if (currentPageIndex > 0) { <HoverCards
setCurrentPageIndex(prev => prev - 1); triggerText="USB drive"
} else { videourl="/videos/usb.mp4"
setCurrentPageIndex(pages.length - 1); description="The USB drive Wade carries holds classified footage from a secret government surveillance project called Project Echo, which monitored paranormal activities around an abandoned research facility in Nevada."
} />{" "}
in his hand and fiddles with the device. The twins smile widely
setTimeout(() => { with delight.
setTransitioning(false); </div>
}, 1000); </div>
}; </>
),
// Add useEffect for wheel event listener },
useEffect(() => { {
window.addEventListener('wheel', handleWheel); id: 3,
return () => { videoSrc: "/videos/background2.mp4",
window.removeEventListener('wheel', handleWheel); content: (
}; <>
}, [currentPageIndex, transitioning, handleWheel]); // Add dependencies <h1 className="text-4xl font-bold mb-8 text-center">BRUTAL</h1>
<div className="space-y-6 text-center max-w-3xl">
// Content structured to match your design <p className="text-lg">
const pages: Page[] = [ Man, yes! Didn&#39;t I tell you not to question this man! I knew
{ he was going to come through for us!&quot; Handsome Twin #1
id: 1, gloats.
videoSrc: "/videos/background1.mp4", </p>
content: ( <p className="text-lg">
<> Handsome Twin #2 sighs in satisfaction. &quot;
<h1 className="text-4xl font-bold mb-8 text-center">BRUTAL</h1> <HoverCards
<div className="space-y-6 text-center max-w-3xl"> triggerText="Gold"
<p className="text-lg">Through the rain, flickering neon lights spell out of <HoverCards triggerText="SEPHORA" videourl="/videos/usb.mp4" description="Bloomberg, COMEX, Dubai Gold & Commodities Exchange, ICE Benchmark Administration, London Metal Exchange, Multi Commodity Exchange of India, Nasdaq, Shanghai Gold Exchange, Shanghai Futures Exchange, Tokyo Commodities Exchange, World Gold Council;" link=' https://www.gold.org/goldhub/data/gold-trading' /> and illuminate an entrance to nightclub.</p> videourl="/videos/trend.mp4"
<p className="text-lg">A stunning light show cascades across a dance floor crowded by partiers and adorned by dozens of video monitors.</p> description="Bloomberg, COMEX, Dubai Gold & Commodities Exchange, ICE Benchmark Administration, London Metal Exchange, Multi Commodity Exchange of India, Nasdaq, Shanghai Gold Exchange, Shanghai Futures Exchange, Tokyo Commodities Exchange, World Gold Council;"
<p className="text-lg">WADE HARPER, an anxious businessman dressed in a black suit, follows two burly bouncers up a flight of stairs toward the <HoverCards triggerText="VIP Suite" videourl="/videos/background2.mp4" description='"Man, yes! Didnt I tell you not to question this man! I knew he was going to come through for us!," Handsome Twin #1 gloats. Handsome Twin #2 sighs in satisfaction. “Gold!,” he says, his tense demeanor turning to relief. ' /> at the back of the warehouse.</p> link=" https://www.gold.org/goldhub/data/gold-trading"
</div> />
</> ,&quot; he says, his tense demeanor turning to relief.
) </p>
}, <p className="text-lg">
{ Wade hands the device to Handsome Twin #2.
id: 2, </p>
videoSrc: "/videos/background2.mp4", <p className="text-lg">
content: ( &quot;You will find all of the credentials you need on the drive.
<> The shipment will arrive at the{" "}
<h1 className="text-4xl font-bold mb-8 text-center">BRUTAL</h1> <HoverCards
<div className="space-y-6 text-center max-w-3xl"> triggerText="Port of Dreytown"
<p className="text-lg">&quot;Wade Harper! What is up, old friend! It&apos;s been too long, man!&quot; exclaims HANDSOME TWIN #1.</p> videourl="/videos/man.mp4"
<p className="text-lg">HANDSOME TWIN #2, more anxious and pushy, quickly interjects, &quot;So do you have it for us?&quot;</p> description="A young, sobbing visitor sat unusually close to the pulpit in the empty church, catching Pastor Evans attention.
<p className="text-lg">Wade reaches into his breast pocket.</p>
<p className="text-lg">&quot;Yes, I do.&quot;</p>
<p className="text-lg">Wade considers the <HoverCards triggerText="USB drive" videourl="/videos/usb.mp4" description="The USB drive Wade carries holds classified footage from a secret government surveillance project called Project Echo, which monitored paranormal activities around an abandoned research facility in Nevada." /> in his hand and fiddles with the device. The twins smile widely with delight.</p>
</div>
</>
)
},
{
id: 3,
videoSrc: "/videos/background3.mp4",
content: (
<>
<h1 className="text-4xl font-bold mb-8 text-center">BRUTAL</h1>
<div className="space-y-6 text-center max-w-3xl">
<p className="text-lg">&quot;Man, yes! Didn&apos;t I tell you not to question this man! I knew he was going to come through for us!&quot; Handsome Twin #1 gloats.</p>
<p className="text-lg">Handsome Twin #2 sighs in satisfaction. &quot;<HoverCards triggerText="Gold" videourl="/videos/trend.mp4" description="Bloomberg, COMEX, Dubai Gold & Commodities Exchange, ICE Benchmark Administration, London Metal Exchange, Multi Commodity Exchange of India, Nasdaq, Shanghai Gold Exchange, Shanghai Futures Exchange, Tokyo Commodities Exchange, World Gold Council;" link='https://www.gold.org/goldhub/data/gold-trading' />,&quot; he says, his tense demeanor turning to relief.</p>
<p className="text-lg">Wade hands the device to Handsome Twin #2.</p>
<p className="text-lg">&quot;You will find all of the credentials you need on the drive. The shipment will arrive at the <HoverCards triggerText="Port of Dreytown" videourl="/videos/man.mp4"
description="A young, sobbing visitor sat unusually close to the pulpit in the empty church, catching Pastor Evans attention.
Typically, even regular members avoided those front pews, out of reverence, fear, or habit. Typically, even regular members avoided those front pews, out of reverence, fear, or habit.
But this man seemed untouched by such conventions, and that stood out to the pastor..." link='' /> tomorrow night,&rdquo; Wade explains.</p> But this man seemed untouched by such conventions, and that stood out to the pastor..."
</div> link=""
</> />{" "}
) tomorrow night,&quot; Wade explains.
} </p>
]; </div>
</>
),
},
];
// Add this function to validate video sources const handleNextPage = useCallback(() => {
const isValidVideoSrc = (src: string): boolean => { if (transitioning) return;
return Boolean(src && src.length > 0);
setTransitioning(true);
if (currentPageIndex < pages.length - 1) {
setCurrentPageIndex((prev) => prev + 1);
} else {
setCurrentPageIndex(0);
}
setTimeout(() => {
setTransitioning(false);
}, 1000);
}, [currentPageIndex, transitioning, pages.length]);
// Add previous page handler
const handlePreviousPage = useCallback(() => {
if (transitioning) return;
setTransitioning(true);
if (currentPageIndex > 0) {
setCurrentPageIndex((prev) => prev - 1);
} else {
setCurrentPageIndex(pages.length - 1);
}
setTimeout(() => {
setTransitioning(false);
}, 1000);
}, [currentPageIndex, transitioning, pages.length]);
// Add wheel event handler
const handleWheel = useCallback(
(event: WheelEvent) => {
if (transitioning) return;
// Scroll down
if (event.deltaY > 0) {
handleNextPage();
}
// Scroll up
else if (event.deltaY < 0) {
handlePreviousPage();
}
},
[handleNextPage, handlePreviousPage, transitioning]
);
// Add useEffect for wheel event listener
useEffect(() => {
window.addEventListener("wheel", handleWheel);
return () => {
window.removeEventListener("wheel", handleWheel);
}; };
}, [currentPageIndex, transitioning, handleWheel]); // Add dependencies
useEffect(() => { // Add this function to validate video sources
// Start playing the current video when the page changes const isValidVideoSrc = (src: string): boolean => {
if (videoRefs.current[currentPageIndex]) { return Boolean(src && src.length > 0);
videoRefs.current.forEach((video, index) => { };
if (index === currentPageIndex && video) {
video.currentTime = 0; useEffect(() => {
video.play().catch(err => console.error("Error playing video:", err)); // Start playing the current video when the page changes
} else if (video) { if (videoRefs.current[currentPageIndex]) {
video.pause(); videoRefs.current.forEach((video, index) => {
} if (index === currentPageIndex && video) {
}); video.currentTime = 0;
video
.play()
.catch((err) => console.error("Error playing video:", err));
} else if (video) {
video.pause();
} }
}, [currentPageIndex]); });
}
}, [currentPageIndex]);
const handleNextPage = () => { return (
if (transitioning) return; <div className="h-screen overflow-hidden relative bg-black">
{/* NavBar */}
<div className="w-full h-[80px] fixed top-0 z-30 flex items-center justify-between px-6 bg-transparent">
{/* Logo */}
setTransitioning(true); <div className="flex items-center text-white">
<Link href="/creator" className="mr-4 ">
if (currentPageIndex < pages.length - 1) { <Button size="icon" className="bg-white">
setCurrentPageIndex(prev => prev + 1); <ArrowLeft2 size="24" color="#555555" />
} else { </Button>
setCurrentPageIndex(0); </Link>
} <Image
src="/images/logo2.png"
setTimeout(() => { alt="Wodey"
setTransitioning(false); width={60}
}, 1000); height={60}
}; className="mr-2"
/>
return ( <span className="text-xl font-semibold">Wodey</span>
<div className="h-screen overflow-hidden relative bg-black">
{/* NavBar */}
<div className='w-full h-[80px] fixed top-0 z-30 flex items-center justify-between px-6 bg-transparent'>
{/* Logo */}
<div className="flex items-center text-white">
<Link href='/creator' className='mr-4 '>
<Button size="icon" className="bg-white">
<ArrowLeft2 size="24" color="#555555" />
</Button>
</Link>
<Image src="/images/logo2.png" alt="Wodey" width={60} height={60} className='mr-2' />
<span className="text-xl font-semibold">Wodey</span>
</div>
{/* Brutal Logo - Center */}
<div className="absolute left-1/2 transform -translate-x-1/2">
<Image src="/images/brutal.png" alt="Wodey" width={91} height={55} className='mr-2' />
</div>
{/* Settings */}
<button className='flex items-center text-white'>
<Setting2 size={20} className="mr-2" color='#ffffff' />
<span>Settings</span>
</button>
</div>
{/* Video Sections */}
<div className="relative h-full">
{pages.map((page, index) => (
<section
key={page.id}
className={`absolute w-full h-full transition-opacity duration-1000 ${currentPageIndex === index ? 'opacity-100 z-10' : 'opacity-0 z-0'
}`}
>
{/* Background Video */}
<video
ref={(el: HTMLVideoElement | null) => { videoRefs.current[index] = el }}
className="absolute top-0 left-0 w-full h-full object-cover"
muted
loop
playsInline
src={isValidVideoSrc(page.videoSrc) ? page.videoSrc : undefined}
poster="/images/fallback-background.png" // Add a fallback image
onError={(e) => {
console.warn(`Failed to load video: ${page.videoSrc}`);
// Optionally set a fallback background color or image
e.currentTarget.style.backgroundColor = '#000000';
}}
></video>
{/* Dark Overlay */}
<div className="absolute inset-0 bg-black opacity-60"></div>
{/* Content */}
<div className="absolute inset-0 flex items-center justify-center text-white z-10 px-5">
<div className="mt-16 max-w-4xl">
{page.content}
</div>
</div>
</section>
))}
</div>
{/* Navigation Button - Down Arrow */}
<button
onClick={handleNextPage}
disabled={transitioning}
className="fixed bottom-8 left-1/2 transform -translate-x-1/2 z-30 bg-transparent text-white rounded-full w-12 h-12 flex items-center justify-center transition-opacity duration-300 hover:opacity-70"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-8 w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
</div> </div>
);
{/* Brutal Logo - Center */}
<div className="absolute left-1/2 transform -translate-x-1/2">
<Image
src="/images/brutal.png"
alt="Wodey"
width={91}
height={55}
className="mr-2"
/>
</div>
{/* Settings */}
<button className="flex items-center text-white">
<Setting2 size={20} className="mr-2" color="#ffffff" />
<span>Settings</span>
</button>
</div>
{/* Video Sections */}
<div className="relative h-full">
{pages.map((page, index) => (
<section
key={page.id}
className={`absolute w-full h-full transition-opacity duration-1000 ${
currentPageIndex === index ? "opacity-100 z-10" : "opacity-0 z-0"
}`}
>
{/* Background Video */}
<video
ref={(el: HTMLVideoElement | null) => {
videoRefs.current[index] = el;
}}
className="absolute top-0 left-0 w-full h-full object-cover"
muted
loop
playsInline
src={isValidVideoSrc(page.videoSrc) ? page.videoSrc : undefined}
poster="/images/fallback-background.png" // Add a fallback image
onError={(e) => {
console.warn(`Failed to load video: ${page.videoSrc}`);
// Optionally set a fallback background color or image
e.currentTarget.style.backgroundColor = "#000000";
}}
></video>
{/* Dark Overlay */}
<div className="absolute inset-0 bg-black opacity-60"></div>
{/* Content */}
<div className="absolute inset-0 flex items-center justify-center text-white z-10 px-5">
<div className="mt-16 max-w-4xl">{page.content}</div>
</div>
</section>
))}
</div>
{/* Navigation Button - Down Arrow */}
<button
onClick={handleNextPage}
disabled={transitioning}
className="fixed bottom-8 left-1/2 transform -translate-x-1/2 z-30 bg-transparent text-white rounded-full w-12 h-12 flex items-center justify-center transition-opacity duration-300 hover:opacity-70"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-8 w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
</div>
);
} }

View File

@ -1,6 +1,7 @@
'use client' 'use client'
import React from 'react' import React from 'react'
import Image from 'next/image'
import Link from 'next/link' import Link from 'next/link'
import { import {
HoverCard, HoverCard,
@ -13,7 +14,8 @@ export interface HoverCardsProps {
videourl: string videourl: string
description: string description: string
linkText?: string linkText?: string
link?: string link?: string,
isImage?: boolean,
} }
export default function HoverCards({ export default function HoverCards({
@ -21,7 +23,8 @@ export default function HoverCards({
videourl, videourl,
description, description,
link = '#', link = '#',
linkText = 'Purchase & Read More' linkText = 'Purchase & Read More',
isImage
}: HoverCardsProps) { }: HoverCardsProps) {
return ( return (
<HoverCard> <HoverCard>
@ -31,6 +34,9 @@ export default function HoverCards({
<HoverCardContent className="w-80"> <HoverCardContent className="w-80">
<div className="max-w-xs bg-white rounded-lg overflow-hidden shadow-lg"> <div className="max-w-xs bg-white rounded-lg overflow-hidden shadow-lg">
<div className="relative h-48 w-full"> <div className="relative h-48 w-full">
{isImage ?
<Image src={videourl} alt={videourl} width={100} height={100} className="absolute top-0 left-0 w-full h-full object-cover" />
:
<video <video
className="absolute top-0 left-0 w-full h-full object-cover" className="absolute top-0 left-0 w-full h-full object-cover"
muted muted
@ -39,6 +45,8 @@ export default function HoverCards({
autoPlay autoPlay
src={videourl} src={videourl}
></video> ></video>
}
</div> </div>