feat(ui): enhance temp-placeholder with animations and visual polish
- Add interactive link animations with orange glow and draw-in underlines - Implement scroll progress indicator (orange vertical bar) - Add animated section dividers that expand on scroll into view - Create loading skeleton for YouTube iframe previews - Animate accordion chevron to orange when open - Add hover tooltip to BIOHAZARD easter egg title - Add scroll-triggered fade-in for pigeon easter egg - Add subtle separation between contact links - Extract reusable components: ScrollProgressBar, SectionDivider, VideoPreview - Maintain dark VFX aesthetic with snappy framer-motion animations
This commit is contained in:
parent
3bafa982ee
commit
9845081330
@ -28,7 +28,10 @@ export function HorizontalAccordion({
|
||||
>
|
||||
{trigger}
|
||||
<motion.div
|
||||
animate={{ rotate: isOpen ? 90 : 0 }}
|
||||
animate={{
|
||||
rotate: isOpen ? 90 : 0,
|
||||
color: isOpen ? '#ff4d00' : '#d1d5db'
|
||||
}}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
|
||||
22
src/components/ScrollProgressBar.tsx
Normal file
22
src/components/ScrollProgressBar.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useScroll, useSpring } from "framer-motion";
|
||||
|
||||
export function ScrollProgressBar() {
|
||||
const { scrollYProgress } = useScroll();
|
||||
const scaleY = useSpring(scrollYProgress, {
|
||||
stiffness: 100,
|
||||
damping: 30,
|
||||
restDelta: 0.001
|
||||
});
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="fixed right-0 top-0 bottom-0 w-[3px] origin-top z-50 pointer-events-none"
|
||||
style={{
|
||||
scaleY,
|
||||
backgroundColor: '#ff4d00',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
20
src/components/SectionDivider.tsx
Normal file
20
src/components/SectionDivider.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface SectionDividerProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function SectionDivider({ className = "" }: SectionDividerProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className={`h-[1px] bg-gray-800 my-8 md:my-12 ${className}`}
|
||||
initial={{ scaleX: 0 }}
|
||||
whileInView={{ scaleX: 1 }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
transition={{ duration: 0.6, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -3,29 +3,15 @@
|
||||
import { CursorDotBackground } from "./CursorDotBackground";
|
||||
import { HorizontalAccordion } from "./HorizontalAccordion";
|
||||
import { InstagramFeed } from "./InstagramFeed";
|
||||
import { ScrollProgressBar } from "./ScrollProgressBar";
|
||||
import { SectionDivider } from "./SectionDivider";
|
||||
import { VideoPreview } from "./VideoPreview";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { DepthMap } from "./DepthMap";
|
||||
import Image from "next/image";
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
|
||||
|
||||
// Helper function to extract YouTube video ID from URL
|
||||
function extractYouTubeVideoId(url: string): string | null {
|
||||
const patterns = [
|
||||
/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\n?#]+)/,
|
||||
/youtube\.com\/embed\/([^&\n?#]+)/
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = url.match(pattern);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Animation variants for page load
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
@ -106,6 +92,8 @@ export function TempPlaceholder() {
|
||||
return () => window.removeEventListener("resize", measure);
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<ScrollProgressBar />
|
||||
<section className="py-8 md:py-16 bg-black text-white min-h-screen">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-3xl">
|
||||
<div className="p-4 sm:p-6 md:p-8 relative">
|
||||
@ -192,6 +180,8 @@ export function TempPlaceholder() {
|
||||
</div>
|
||||
</HorizontalAccordion>
|
||||
</motion.div>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger asChild>
|
||||
<motion.h1
|
||||
onClick={(e) => {
|
||||
setMousePosition({ x: e.clientX, y: e.clientY });
|
||||
@ -210,6 +200,11 @@ export function TempPlaceholder() {
|
||||
BIOHAZARD
|
||||
</span>
|
||||
</motion.h1>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-auto px-3 py-2 bg-black border-gray-800 text-gray-300 text-sm">
|
||||
Click to reveal
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
|
||||
<AnimatePresence>
|
||||
{isEasterEggOpen && (
|
||||
@ -323,23 +318,41 @@ export function TempPlaceholder() {
|
||||
better at VFX than we are at web design, I promise.
|
||||
</motion.p>
|
||||
|
||||
<SectionDivider />
|
||||
|
||||
<motion.p
|
||||
className="mb-4 text-base sm:text-lg"
|
||||
variants={itemVariants}
|
||||
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||
>
|
||||
<strong>Here's our reel:</strong>{" "}
|
||||
<a
|
||||
<motion.a
|
||||
href="https://f.io/Wgx3EAHu"
|
||||
className="hover:underline transition-all duration-300 hover:brightness-110 hover:scale-105 inline-block break-words"
|
||||
className="inline-block break-words relative"
|
||||
style={{ color: '#ff4d00' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{
|
||||
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
|
||||
}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<span className="relative inline-block">
|
||||
Biohazard Reel Mar 2025 - Frame.io
|
||||
</a>
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-0 h-[1px] bg-current"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left', width: '100%' }}
|
||||
/>
|
||||
</span>
|
||||
</motion.a>
|
||||
</motion.p>
|
||||
|
||||
<SectionDivider />
|
||||
|
||||
<motion.p
|
||||
className="mb-4 md:mb-6 text-base sm:text-lg"
|
||||
variants={itemVariants}
|
||||
@ -359,15 +372,29 @@ export function TempPlaceholder() {
|
||||
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||
>
|
||||
<span className="text-gray-400 mr-3 mt-1">•</span>
|
||||
<a
|
||||
<motion.a
|
||||
href="https://www.instagram.com/biohazardvfx/"
|
||||
className="hover:underline transition-all duration-200 hover:brightness-105 block leading-relaxed"
|
||||
className="block leading-relaxed relative"
|
||||
style={{ color: '#ff4d00' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{
|
||||
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
|
||||
}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<span className="relative inline-block">
|
||||
Gstar Raw - Pommelhorse
|
||||
</a>
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-0 h-[1px] bg-current"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left', width: '100%' }}
|
||||
/>
|
||||
</span>
|
||||
</motion.a>
|
||||
</motion.li>
|
||||
<motion.li
|
||||
className="flex items-start"
|
||||
@ -377,28 +404,36 @@ export function TempPlaceholder() {
|
||||
<span className="text-gray-400 mr-3 mt-1">•</span>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<a
|
||||
<motion.a
|
||||
href="https://www.youtube.com/watch?v=4QIZE708gJ4"
|
||||
className="hover:underline transition-all duration-200 hover:brightness-105 block leading-relaxed"
|
||||
className="block leading-relaxed relative"
|
||||
style={{ color: '#ff4d00' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{
|
||||
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
|
||||
}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<span className="relative inline-block">
|
||||
Post Malone - I Had Some Help<br className="sm:hidden" />
|
||||
<span className="sm:hidden">(feat. Morgan Wallen)</span>
|
||||
<span className="hidden sm:inline"> (feat. Morgan Wallen)</span>
|
||||
</a>
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-0 h-[1px] bg-current"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left', width: '100%' }}
|
||||
/>
|
||||
</span>
|
||||
</motion.a>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
|
||||
<iframe
|
||||
width="320"
|
||||
height="180"
|
||||
src="https://www.youtube.com/embed/4QIZE708gJ4"
|
||||
<VideoPreview
|
||||
videoId="4QIZE708gJ4"
|
||||
title="Post Malone - I Had Some Help (feat. Morgan Wallen)"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="rounded-md"
|
||||
/>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
@ -411,26 +446,34 @@ export function TempPlaceholder() {
|
||||
<span className="text-gray-400 mr-3 mt-1">•</span>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<a
|
||||
<motion.a
|
||||
href="https://www.youtube.com/watch?v=z2tUpLHdd4M"
|
||||
className="hover:underline transition-all duration-200 hover:brightness-105 block leading-relaxed"
|
||||
className="block leading-relaxed relative"
|
||||
style={{ color: '#ff4d00' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{
|
||||
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
|
||||
}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<span className="relative inline-block">
|
||||
The Wait Is Over | OFFICIAL TRAILER
|
||||
</a>
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-0 h-[1px] bg-current"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left', width: '100%' }}
|
||||
/>
|
||||
</span>
|
||||
</motion.a>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
|
||||
<iframe
|
||||
width="320"
|
||||
height="180"
|
||||
src="https://www.youtube.com/embed/z2tUpLHdd4M"
|
||||
<VideoPreview
|
||||
videoId="z2tUpLHdd4M"
|
||||
title="The Wait Is Over | OFFICIAL TRAILER"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="rounded-md"
|
||||
/>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
@ -443,26 +486,34 @@ export function TempPlaceholder() {
|
||||
<span className="text-gray-400 mr-3 mt-1">•</span>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<a
|
||||
<motion.a
|
||||
href="https://www.youtube.com/watch?v=RCZ9wl1Up40"
|
||||
className="hover:underline transition-all duration-200 hover:brightness-105 block leading-relaxed"
|
||||
className="block leading-relaxed relative"
|
||||
style={{ color: '#ff4d00' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{
|
||||
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
|
||||
}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<span className="relative inline-block">
|
||||
2hollis Star Album Trailer
|
||||
</a>
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-0 h-[1px] bg-current"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left', width: '100%' }}
|
||||
/>
|
||||
</span>
|
||||
</motion.a>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
|
||||
<iframe
|
||||
width="320"
|
||||
height="180"
|
||||
src="https://www.youtube.com/embed/RCZ9wl1Up40"
|
||||
<VideoPreview
|
||||
videoId="RCZ9wl1Up40"
|
||||
title="2hollis Star Album Trailer"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="rounded-md"
|
||||
/>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
@ -475,26 +526,34 @@ export function TempPlaceholder() {
|
||||
<span className="text-gray-400 mr-3 mt-1">•</span>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<a
|
||||
<motion.a
|
||||
href="https://www.youtube.com/watch?v=yLxoVrFpLmQ"
|
||||
className="hover:underline transition-all duration-200 hover:brightness-105 block leading-relaxed"
|
||||
className="block leading-relaxed relative"
|
||||
style={{ color: '#ff4d00' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{
|
||||
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
|
||||
}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<span className="relative inline-block">
|
||||
Thanksgiving With Kai, Kevin & Druski
|
||||
</a>
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-0 h-[1px] bg-current"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left', width: '100%' }}
|
||||
/>
|
||||
</span>
|
||||
</motion.a>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
|
||||
<iframe
|
||||
width="320"
|
||||
height="180"
|
||||
src="https://www.youtube.com/embed/yLxoVrFpLmQ"
|
||||
<VideoPreview
|
||||
videoId="yLxoVrFpLmQ"
|
||||
title="Thanksgiving With Kai, Kevin & Druski"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="rounded-md"
|
||||
/>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
@ -507,34 +566,44 @@ export function TempPlaceholder() {
|
||||
<span className="text-gray-400 mr-3 mt-1">•</span>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<a
|
||||
<motion.a
|
||||
href="https://www.youtube.com/watch?v=a2Zqdo9RbNs"
|
||||
className="hover:underline transition-all duration-200 hover:brightness-105 block leading-relaxed"
|
||||
className="block leading-relaxed relative"
|
||||
style={{ color: '#ff4d00' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{
|
||||
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
|
||||
}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<span className="relative inline-block">
|
||||
ENHYPEN (엔하이픈) Bad Desire<br className="sm:hidden" />
|
||||
<span className="sm:hidden">(With or Without You) Official MV</span>
|
||||
<span className="hidden sm:inline"> (With or Without You) Official MV</span>
|
||||
</a>
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-0 h-[1px] bg-current"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left', width: '100%' }}
|
||||
/>
|
||||
</span>
|
||||
</motion.a>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
|
||||
<iframe
|
||||
width="320"
|
||||
height="180"
|
||||
src="https://www.youtube.com/embed/a2Zqdo9RbNs"
|
||||
<VideoPreview
|
||||
videoId="a2Zqdo9RbNs"
|
||||
title="ENHYPEN (엔하이픈) Bad Desire (With or Without You) Official MV"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="rounded-md"
|
||||
/>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</motion.li>
|
||||
</motion.ul>
|
||||
|
||||
<SectionDivider />
|
||||
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||
@ -542,37 +611,67 @@ export function TempPlaceholder() {
|
||||
<InstagramFeed />
|
||||
</motion.div>
|
||||
|
||||
<SectionDivider />
|
||||
|
||||
<motion.p
|
||||
className="mb-2 text-sm sm:text-base text-gray-300"
|
||||
className="mb-4 text-sm sm:text-base text-gray-300"
|
||||
variants={itemVariants}
|
||||
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||
>
|
||||
Contact us:{" "}
|
||||
<a
|
||||
<motion.a
|
||||
href="mailto:contact@biohazardvfx.com"
|
||||
className="hover:underline break-words inline-block"
|
||||
className="break-words inline-block relative"
|
||||
style={{ color: '#ff4d00' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{
|
||||
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
|
||||
}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<span className="relative inline-block">
|
||||
contact@biohazardvfx.com
|
||||
</a>
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-0 h-[1px] bg-current"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left', width: '100%' }}
|
||||
/>
|
||||
</span>
|
||||
</motion.a>
|
||||
</motion.p>
|
||||
<motion.p
|
||||
className="text-sm sm:text-base text-gray-300"
|
||||
className="text-sm sm:text-base text-gray-300 pb-6 border-b border-gray-800"
|
||||
variants={itemVariants}
|
||||
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||
>
|
||||
File a complaint:{" "}
|
||||
<a
|
||||
<motion.a
|
||||
href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
className="hover:underline break-words inline-block"
|
||||
className="break-words inline-block relative"
|
||||
style={{ color: '#ff4d00' }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{
|
||||
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
|
||||
}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<span className="relative inline-block">
|
||||
help@biohazardvfx.com
|
||||
</a>
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-0 h-[1px] bg-current"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
style={{ transformOrigin: 'left', width: '100%' }}
|
||||
/>
|
||||
</span>
|
||||
</motion.a>
|
||||
</motion.p>
|
||||
|
||||
<motion.p
|
||||
@ -581,8 +680,10 @@ export function TempPlaceholder() {
|
||||
setIsPigeonEggOpen(true);
|
||||
}}
|
||||
className="text-xs text-gray-600 mt-4 cursor-pointer hover:text-gray-500 transition-colors"
|
||||
variants={itemVariants}
|
||||
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
transition={{ duration: 0.6, ease: "easeOut", delay: 0.2 }}
|
||||
>
|
||||
No pigeons allowed in this zone
|
||||
</motion.p>
|
||||
@ -590,5 +691,6 @@ export function TempPlaceholder() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
44
src/components/VideoPreview.tsx
Normal file
44
src/components/VideoPreview.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
interface VideoPreviewProps {
|
||||
videoId: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export function VideoPreview({ videoId, title }: VideoPreviewProps) {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="relative w-full" style={{ width: 320, height: 180 }}>
|
||||
<AnimatePresence>
|
||||
{isLoading && (
|
||||
<motion.div
|
||||
initial={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="absolute inset-0 bg-black rounded-md flex items-center justify-center"
|
||||
>
|
||||
<motion.div
|
||||
animate={{ opacity: [0.5, 1, 0.5] }}
|
||||
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
|
||||
className="w-16 h-16 border-2 border-gray-800 rounded-md"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<iframe
|
||||
width="320"
|
||||
height="180"
|
||||
src={`https://www.youtube.com/embed/${videoId}`}
|
||||
title={title}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="rounded-md border-0"
|
||||
onLoad={() => setIsLoading(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user