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}
|
{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" />
|
||||||
|
|||||||
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 { 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 & Druski
|
<span className="relative inline-block">
|
||||||
</a>
|
Thanksgiving With Kai, Kevin & 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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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