315 lines
11 KiB
TypeScript
315 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)[]>([]);
|
||
|
||
// 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="/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! 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/background3.mp4",
|
||
|
||
content: (
|
||
<>
|
||
<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>
|
||
<div 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.
|
||
</div>
|
||
</div>
|
||
</>
|
||
),
|
||
},
|
||
{
|
||
id: 3,
|
||
videoSrc: "/videos/background2.mp4",
|
||
content: (
|
||
<>
|
||
<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>
|
||
</>
|
||
),
|
||
},
|
||
];
|
||
|
||
const handleNextPage = useCallback(() => {
|
||
if (transitioning) return;
|
||
|
||
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
|
||
|
||
// 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]);
|
||
|
||
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>
|
||
);
|
||
}
|