177 lines
6.7 KiB
TypeScript
177 lines
6.7 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
||
import { motion, AnimatePresence } from 'framer-motion';
|
||
|
||
declare global {
|
||
interface Window {
|
||
instgrm?: {
|
||
Embeds: {
|
||
process: () => void;
|
||
};
|
||
};
|
||
}
|
||
}
|
||
|
||
const posts = [
|
||
'https://www.instagram.com/p/DSOFijljukL/',
|
||
'https://www.instagram.com/p/DSGh7rMjVes/',
|
||
'https://www.instagram.com/p/DRIIUECj4f6/',
|
||
'https://www.instagram.com/p/DJOvmvcIoo7/',
|
||
'https://www.instagram.com/p/DJOu5b7IL4t/',
|
||
'https://www.instagram.com/p/DIQnhO0oJgw/',
|
||
'https://www.instagram.com/p/DIJUVqbI4EH/',
|
||
'https://www.instagram.com/p/DHlvDNyIDRa/',
|
||
'https://www.instagram.com/p/DHlub_iojwv/',
|
||
'https://www.instagram.com/p/DJOvdVLIZpM/',
|
||
];
|
||
|
||
const InstagramFeed: React.FC = () => {
|
||
const [selectedPost, setSelectedPost] = useState<string | null>(null);
|
||
|
||
// Double the list for seamless infinite marquee scroll
|
||
const duplicatedPosts = [...posts, ...posts];
|
||
|
||
useEffect(() => {
|
||
// Process Instagram embeds whenever the component mounts or the lightbox opens
|
||
if (window.instgrm) {
|
||
window.instgrm.Embeds.process();
|
||
} else {
|
||
const script = document.createElement('script');
|
||
script.src = '//www.instagram.com/embed.js';
|
||
script.async = true;
|
||
document.body.appendChild(script);
|
||
}
|
||
}, [selectedPost]);
|
||
|
||
return (
|
||
<>
|
||
<section className="py-24 px-6 md:px-12 bg-stone-50 dark:bg-stone-900 overflow-hidden">
|
||
<div className="max-w-[1400px] mx-auto">
|
||
<span className="block font-body text-xs uppercase tracking-[0.3em] text-stone-400 mb-6">
|
||
Follow Along
|
||
</span>
|
||
<div className="flex items-end justify-between mb-16 px-2">
|
||
<h2 className="font-display text-4xl md:text-5xl text-text-main dark:text-white leading-none">
|
||
From the Studio
|
||
</h2>
|
||
<a
|
||
href="https://www.instagram.com/knuth.ceramics"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="text-xs font-bold uppercase tracking-widest text-stone-500 hover:text-stone-900 dark:hover:text-white transition-colors"
|
||
>
|
||
@knuth.ceramics →
|
||
</a>
|
||
</div>
|
||
|
||
{/* Infinite Carousel */}
|
||
<div className="relative group overflow-hidden">
|
||
<style>{`
|
||
@keyframes marquee {
|
||
0% { transform: translateX(0); }
|
||
100% { transform: translateX(-${posts.length * 342}px); /* 326px width + 16px gap */ }
|
||
}
|
||
.animate-marquee {
|
||
animation: marquee 50s linear infinite;
|
||
}
|
||
.animate-marquee:hover {
|
||
animation-play-state: paused;
|
||
}
|
||
`}</style>
|
||
<div className="flex gap-4 animate-marquee w-max py-4">
|
||
{duplicatedPosts.map((permalink, idx) => (
|
||
<div
|
||
key={idx}
|
||
className="relative flex-shrink-0 w-[326px] overflow-hidden rounded-[8px] group/item cursor-pointer bg-white"
|
||
>
|
||
{/* Invisible Overlay to capture clicks.
|
||
Because iframes block events, we put a div above it.
|
||
On hover it reveals a subtle mask to indicate interactivity. */}
|
||
<div
|
||
className="absolute inset-0 z-10 bg-black/0 group-hover/item:bg-black/50 transition-colors duration-300 flex flex-col items-center justify-center opacity-0 group-hover/item:opacity-100"
|
||
onClick={() => setSelectedPost(permalink)}
|
||
>
|
||
<p className="text-white font-display text-lg px-4 text-center font-bold drop-shadow-md">
|
||
View Post
|
||
</p>
|
||
</div>
|
||
|
||
{/* The Instagram Embed itself.
|
||
By omitting data-instgrm-captioned we hide the caption/hashtags directly. */}
|
||
<div className="pointer-events-none">
|
||
<blockquote
|
||
className="instagram-media"
|
||
data-instgrm-permalink={`${permalink}?utm_source=ig_embed&utm_campaign=loading`}
|
||
data-instgrm-version="14"
|
||
style={{
|
||
background: '#FFF',
|
||
border: 0,
|
||
margin: 0,
|
||
maxWidth: '540px',
|
||
minWidth: '326px',
|
||
padding: 0,
|
||
width: '100%',
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* Lightbox Modal */}
|
||
<AnimatePresence>
|
||
{selectedPost && (
|
||
<motion.div
|
||
className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4 overflow-y-auto pt-[100px]"
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
onClick={() => setSelectedPost(null)}
|
||
>
|
||
<motion.div
|
||
className="relative max-w-lg w-full bg-white dark:bg-stone-900 rounded-xl overflow-hidden my-auto"
|
||
initial={{ scale: 0.9, opacity: 0 }}
|
||
animate={{ scale: 1, opacity: 1 }}
|
||
exit={{ scale: 0.9, opacity: 0 }}
|
||
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
{/* Close button inside modal container */}
|
||
<button
|
||
className="absolute top-2 right-2 text-stone-500 bg-white border border-stone-200 shadow-md rounded-full w-8 h-8 flex items-center justify-center hover:bg-stone-100 transition-colors z-[60]"
|
||
onClick={() => setSelectedPost(null)}
|
||
>
|
||
<span className="font-bold">×</span>
|
||
</button>
|
||
|
||
{/* Instagram Embed WITH caption shown in the Lightbox */}
|
||
<div className="w-full bg-white mt-12 pb-4 px-2">
|
||
<blockquote
|
||
className="instagram-media"
|
||
data-instgrm-captioned
|
||
data-instgrm-permalink={`${selectedPost}?utm_source=ig_embed&utm_campaign=loading`}
|
||
data-instgrm-version="14"
|
||
style={{
|
||
background: '#FFF',
|
||
border: 0,
|
||
boxShadow: 'none',
|
||
margin: '0',
|
||
maxWidth: '540px',
|
||
minWidth: '326px',
|
||
padding: 0,
|
||
width: '100%',
|
||
}}
|
||
/>
|
||
</div>
|
||
</motion.div>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default InstagramFeed;
|