233 lines
11 KiB
TypeScript
233 lines
11 KiB
TypeScript
"use client"
|
||
|
||
import { ArrowLeft2, Setting2 } from 'iconsax-react';
|
||
import Image from 'next/image';
|
||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||
import Link from 'next/link';
|
||
import HoverCards from '@/components/cards/HoverCards';
|
||
import { Button } from '@/components/ui/button';
|
||
|
||
|
||
interface Page {
|
||
id: number;
|
||
content: React.ReactNode;
|
||
videoSrc: string;
|
||
}
|
||
|
||
export default function Reader() {
|
||
const [currentPageIndex, setCurrentPageIndex] = useState(0);
|
||
const [transitioning, setTransitioning] = useState(false);
|
||
const videoRefs = useRef<(HTMLVideoElement | null)[]>([]);
|
||
|
||
// 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();
|
||
}
|
||
}, []);
|
||
|
||
// Add previous page handler
|
||
const handlePreviousPage = () => {
|
||
if (transitioning) return;
|
||
|
||
setTransitioning(true);
|
||
|
||
if (currentPageIndex > 0) {
|
||
setCurrentPageIndex(prev => prev - 1);
|
||
} else {
|
||
setCurrentPageIndex(pages.length - 1);
|
||
}
|
||
|
||
setTimeout(() => {
|
||
setTransitioning(false);
|
||
}, 1000);
|
||
};
|
||
|
||
// Add useEffect for wheel event listener
|
||
useEffect(() => {
|
||
window.addEventListener('wheel', handleWheel);
|
||
return () => {
|
||
window.removeEventListener('wheel', handleWheel);
|
||
};
|
||
}, [currentPageIndex, transitioning, handleWheel]); // Add dependencies
|
||
|
||
// Content structured to match your design
|
||
const pages: Page[] = [
|
||
{
|
||
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="/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>
|
||
<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! Didn’t 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/background2.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">"Wade Harper! What is up, old friend! It's been too long, man!" exclaims HANDSOME TWIN #1.</p>
|
||
<p className="text-lg">HANDSOME TWIN #2, more anxious and pushy, quickly interjects, "So do you have it for us?"</p>
|
||
<p className="text-lg">Wade reaches into his breast pocket.</p>
|
||
<p className="text-lg">"Yes, I do."</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">"Man, yes! Didn't I tell you not to question this man! I knew he was going to come through for us!" Handsome Twin #1 gloats.</p>
|
||
<p className="text-lg">Handsome Twin #2 sighs in satisfaction. "<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' />," 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">"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 Evan’s attention.
|
||
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,” Wade explains.</p>
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
];
|
||
|
||
// Add this function to validate video sources
|
||
const isValidVideoSrc = (src: string): boolean => {
|
||
return Boolean(src && src.length > 0);
|
||
};
|
||
|
||
useEffect(() => {
|
||
// Start playing the current video when the page changes
|
||
if (videoRefs.current[currentPageIndex]) {
|
||
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]);
|
||
|
||
const handleNextPage = () => {
|
||
if (transitioning) return;
|
||
|
||
setTransitioning(true);
|
||
|
||
if (currentPageIndex < pages.length - 1) {
|
||
setCurrentPageIndex(prev => prev + 1);
|
||
} else {
|
||
setCurrentPageIndex(0);
|
||
}
|
||
|
||
setTimeout(() => {
|
||
setTransitioning(false);
|
||
}, 1000);
|
||
};
|
||
|
||
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 */}
|
||
|
||
|
||
<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>
|
||
);
|
||
}
|