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:
Nicholai 2025-10-23 00:13:21 -06:00
parent 3bafa982ee
commit 9845081330
5 changed files with 313 additions and 122 deletions

View File

@ -28,7 +28,10 @@ export function HorizontalAccordion({
> >
{trigger} {trigger}
<motion.div <motion.div
animate={{ rotate: isOpen ? 90 : 0 }} animate={{
rotate: isOpen ? 90 : 0,
color: isOpen ? '#ff4d00' : '#d1d5db'
}}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
> >
<ChevronRight className="h-4 w-4" /> <ChevronRight className="h-4 w-4" />

View 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',
}}
/>
);
}

View 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' }}
/>
);
}

View File

@ -3,29 +3,15 @@
import { CursorDotBackground } from "./CursorDotBackground"; import { CursorDotBackground } from "./CursorDotBackground";
import { HorizontalAccordion } from "./HorizontalAccordion"; import { HorizontalAccordion } from "./HorizontalAccordion";
import { InstagramFeed } from "./InstagramFeed"; import { InstagramFeed } from "./InstagramFeed";
import { ScrollProgressBar } from "./ScrollProgressBar";
import { SectionDivider } from "./SectionDivider";
import { VideoPreview } from "./VideoPreview";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import { DepthMap } from "./DepthMap"; import { DepthMap } from "./DepthMap";
import Image from "next/image"; import Image from "next/image";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"; 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 // Animation variants for page load
const containerVariants = { const containerVariants = {
hidden: { opacity: 0 }, hidden: { opacity: 0 },
@ -106,8 +92,10 @@ export function TempPlaceholder() {
return () => window.removeEventListener("resize", measure); return () => window.removeEventListener("resize", measure);
}, []); }, []);
return ( return (
<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"> <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"> <div className="p-4 sm:p-6 md:p-8 relative">
<CursorDotBackground <CursorDotBackground
dotSize={2} dotSize={2}
@ -192,24 +180,31 @@ export function TempPlaceholder() {
</div> </div>
</HorizontalAccordion> </HorizontalAccordion>
</motion.div> </motion.div>
<motion.h1 <HoverCard openDelay={300}>
onClick={(e) => { <HoverCardTrigger asChild>
setMousePosition({ x: e.clientX, y: e.clientY }); <motion.h1
setIsEasterEggOpen(true); onClick={(e) => {
}} setMousePosition({ x: e.clientX, y: e.clientY });
className="text-4xl sm:text-5xl md:text-7xl lg:text-8xl xl:text-9xl font-black mb-4 md:mb-6 font-exo-2 text-center mx-auto leading-none cursor-pointer transition-opacity hover:opacity-80" setIsEasterEggOpen(true);
style={{ }}
color: '#000000', className="text-4xl sm:text-5xl md:text-7xl lg:text-8xl xl:text-9xl font-black mb-4 md:mb-6 font-exo-2 text-center mx-auto leading-none cursor-pointer transition-opacity hover:opacity-80"
textShadow: '2px 2px 0px #ff4d00, 4px 4px 0px #ff4d00', style={{
width: titleWidth ? `${titleWidth}px` : undefined color: '#000000',
}} textShadow: '2px 2px 0px #ff4d00, 4px 4px 0px #ff4d00',
variants={itemVariants} width: titleWidth ? `${titleWidth}px` : undefined
transition={{ duration: 0.4, ease: "easeOut" }} }}
> variants={itemVariants}
<span ref={bioTextRef} className="inline-block" style={{ fontSize: bioFontSizePx ? `${bioFontSizePx}px` : undefined }}> transition={{ duration: 0.4, ease: "easeOut" }}
BIOHAZARD >
</span> <span ref={bioTextRef} className="inline-block" style={{ fontSize: bioFontSizePx ? `${bioFontSizePx}px` : undefined }}>
</motion.h1> 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> <AnimatePresence>
{isEasterEggOpen && ( {isEasterEggOpen && (
@ -323,23 +318,41 @@ export function TempPlaceholder() {
better at VFX than we are at web design, I promise. better at VFX than we are at web design, I promise.
</motion.p> </motion.p>
<SectionDivider />
<motion.p <motion.p
className="mb-4 text-base sm:text-lg" className="mb-4 text-base sm:text-lg"
variants={itemVariants} variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }} transition={{ duration: 0.4, ease: "easeOut" }}
> >
<strong>Here's our reel:</strong>{" "} <strong>Here's our reel:</strong>{" "}
<a <motion.a
href="https://f.io/Wgx3EAHu" 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' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
> >
Biohazard Reel Mar 2025 - Frame.io <span className="relative inline-block">
</a> Biohazard Reel Mar 2025 - Frame.io
<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>
<SectionDivider />
<motion.p <motion.p
className="mb-4 md:mb-6 text-base sm:text-lg" className="mb-4 md:mb-6 text-base sm:text-lg"
variants={itemVariants} variants={itemVariants}
@ -359,15 +372,29 @@ export function TempPlaceholder() {
transition={{ duration: 0.4, ease: "easeOut" }} transition={{ duration: 0.4, ease: "easeOut" }}
> >
<span className="text-gray-400 mr-3 mt-1"></span> <span className="text-gray-400 mr-3 mt-1"></span>
<a <motion.a
href="https://www.instagram.com/biohazardvfx/" 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' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
> >
Gstar Raw - Pommelhorse <span className="relative inline-block">
</a> Gstar Raw - Pommelhorse
<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>
<motion.li <motion.li
className="flex items-start" className="flex items-start"
@ -377,28 +404,36 @@ export function TempPlaceholder() {
<span className="text-gray-400 mr-3 mt-1"></span> <span className="text-gray-400 mr-3 mt-1"></span>
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<a <motion.a
href="https://www.youtube.com/watch?v=4QIZE708gJ4" 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' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
> >
Post Malone - I Had Some Help<br className="sm:hidden" /> <span className="relative inline-block">
<span className="sm:hidden">(feat. Morgan Wallen)</span> Post Malone - I Had Some Help<br className="sm:hidden" />
<span className="hidden sm:inline"> (feat. Morgan Wallen)</span> <span className="sm:hidden">(feat. Morgan Wallen)</span>
</a> <span className="hidden sm:inline"> (feat. Morgan Wallen)</span>
<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> </HoverCardTrigger>
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50"> <HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
<iframe <VideoPreview
width="320" videoId="4QIZE708gJ4"
height="180"
src="https://www.youtube.com/embed/4QIZE708gJ4"
title="Post Malone - I Had Some Help (feat. Morgan Wallen)" 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> </HoverCardContent>
</HoverCard> </HoverCard>
@ -411,26 +446,34 @@ export function TempPlaceholder() {
<span className="text-gray-400 mr-3 mt-1"></span> <span className="text-gray-400 mr-3 mt-1"></span>
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<a <motion.a
href="https://www.youtube.com/watch?v=z2tUpLHdd4M" 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' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
> >
The Wait Is Over | OFFICIAL TRAILER <span className="relative inline-block">
</a> The Wait Is Over | OFFICIAL TRAILER
<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> </HoverCardTrigger>
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50"> <HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
<iframe <VideoPreview
width="320" videoId="z2tUpLHdd4M"
height="180"
src="https://www.youtube.com/embed/z2tUpLHdd4M"
title="The Wait Is Over | OFFICIAL TRAILER" 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> </HoverCardContent>
</HoverCard> </HoverCard>
@ -443,26 +486,34 @@ export function TempPlaceholder() {
<span className="text-gray-400 mr-3 mt-1"></span> <span className="text-gray-400 mr-3 mt-1"></span>
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<a <motion.a
href="https://www.youtube.com/watch?v=RCZ9wl1Up40" 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' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
> >
2hollis Star Album Trailer <span className="relative inline-block">
</a> 2hollis Star Album Trailer
<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> </HoverCardTrigger>
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50"> <HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
<iframe <VideoPreview
width="320" videoId="RCZ9wl1Up40"
height="180"
src="https://www.youtube.com/embed/RCZ9wl1Up40"
title="2hollis Star Album Trailer" title="2hollis Star Album Trailer"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="rounded-md"
/> />
</HoverCardContent> </HoverCardContent>
</HoverCard> </HoverCard>
@ -475,26 +526,34 @@ export function TempPlaceholder() {
<span className="text-gray-400 mr-3 mt-1"></span> <span className="text-gray-400 mr-3 mt-1"></span>
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<a <motion.a
href="https://www.youtube.com/watch?v=yLxoVrFpLmQ" 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' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
> >
Thanksgiving With Kai, Kevin &amp; Druski <span className="relative inline-block">
</a> Thanksgiving With Kai, Kevin &amp; Druski
<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> </HoverCardTrigger>
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50"> <HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
<iframe <VideoPreview
width="320" videoId="yLxoVrFpLmQ"
height="180"
src="https://www.youtube.com/embed/yLxoVrFpLmQ"
title="Thanksgiving With Kai, Kevin & Druski" title="Thanksgiving With Kai, Kevin & Druski"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="rounded-md"
/> />
</HoverCardContent> </HoverCardContent>
</HoverCard> </HoverCard>
@ -507,34 +566,44 @@ export function TempPlaceholder() {
<span className="text-gray-400 mr-3 mt-1"></span> <span className="text-gray-400 mr-3 mt-1"></span>
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<a <motion.a
href="https://www.youtube.com/watch?v=a2Zqdo9RbNs" 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' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
> >
ENHYPEN () Bad Desire<br className="sm:hidden" /> <span className="relative inline-block">
<span className="sm:hidden">(With or Without You) Official MV</span> ENHYPEN () Bad Desire<br className="sm:hidden" />
<span className="hidden sm:inline"> (With or Without You) Official MV</span> <span className="sm:hidden">(With or Without You) Official MV</span>
</a> <span className="hidden sm:inline"> (With or Without You) Official MV</span>
<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> </HoverCardTrigger>
<HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50"> <HoverCardContent className="w-80 p-0 bg-black border-gray-800 z-50">
<iframe <VideoPreview
width="320" videoId="a2Zqdo9RbNs"
height="180"
src="https://www.youtube.com/embed/a2Zqdo9RbNs"
title="ENHYPEN (엔하이픈) Bad Desire (With or Without You) Official MV" 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> </HoverCardContent>
</HoverCard> </HoverCard>
</motion.li> </motion.li>
</motion.ul> </motion.ul>
<SectionDivider />
<motion.div <motion.div
variants={itemVariants} variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }} transition={{ duration: 0.4, ease: "easeOut" }}
@ -542,37 +611,67 @@ export function TempPlaceholder() {
<InstagramFeed /> <InstagramFeed />
</motion.div> </motion.div>
<SectionDivider />
<motion.p <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} variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }} transition={{ duration: 0.4, ease: "easeOut" }}
> >
Contact us:{" "} Contact us:{" "}
<a <motion.a
href="mailto:contact@biohazardvfx.com" href="mailto:contact@biohazardvfx.com"
className="hover:underline break-words inline-block" className="break-words inline-block relative"
style={{ color: '#ff4d00' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
> >
contact@biohazardvfx.com <span className="relative inline-block">
</a> contact@biohazardvfx.com
<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>
<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} variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }} transition={{ duration: 0.4, ease: "easeOut" }}
> >
File a complaint:{" "} File a complaint:{" "}
<a <motion.a
href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
className="hover:underline break-words inline-block" className="break-words inline-block relative"
style={{ color: '#ff4d00' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
> >
help@biohazardvfx.com <span className="relative inline-block">
</a> help@biohazardvfx.com
<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>
<motion.p <motion.p
@ -581,8 +680,10 @@ export function TempPlaceholder() {
setIsPigeonEggOpen(true); setIsPigeonEggOpen(true);
}} }}
className="text-xs text-gray-600 mt-4 cursor-pointer hover:text-gray-500 transition-colors" className="text-xs text-gray-600 mt-4 cursor-pointer hover:text-gray-500 transition-colors"
variants={itemVariants} initial={{ opacity: 0, y: 10 }}
transition={{ duration: 0.4, ease: "easeOut" }} 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 No pigeons allowed in this zone
</motion.p> </motion.p>
@ -590,5 +691,6 @@ export function TempPlaceholder() {
</div> </div>
</div> </div>
</section> </section>
</>
); );
} }

View 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>
);
}