144 lines
6.8 KiB
TypeScript
144 lines
6.8 KiB
TypeScript
import React from 'react';
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
import { useStore } from '../src/context/StoreContext';
|
|
import { CollectionItem } from '../types';
|
|
import SEO, { SITE_URL } from '../components/SEO';
|
|
|
|
const Collections: React.FC = () => {
|
|
const { products } = useStore();
|
|
const [selectedProduct, setSelectedProduct] = React.useState<CollectionItem | null>(null);
|
|
|
|
React.useEffect(() => {
|
|
if (!selectedProduct) {
|
|
return undefined;
|
|
}
|
|
|
|
const handleEscape = (event: KeyboardEvent) => {
|
|
if (event.key === 'Escape') {
|
|
setSelectedProduct(null);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('keydown', handleEscape);
|
|
return () => window.removeEventListener('keydown', handleEscape);
|
|
}, [selectedProduct]);
|
|
|
|
const collectionsSchema = {
|
|
"@context": "https://schema.org",
|
|
"@type": "CollectionPage",
|
|
"name": "Handcrafted Ceramic Collection | KNUTH Ceramics",
|
|
"description": "Browse KNUTH Ceramics' handcrafted stoneware collection. Each piece is wheel-thrown in Corpus Christi, Texas, inspired by the Gulf Coast. Vases, bowls, tableware, and more from $45.",
|
|
"url": `${SITE_URL}/collections`,
|
|
"breadcrumb": {
|
|
"@type": "BreadcrumbList",
|
|
"itemListElement": [
|
|
{ "@type": "ListItem", "position": 1, "name": "Home", "item": `${SITE_URL}/` },
|
|
{ "@type": "ListItem", "position": 2, "name": "Collections", "item": `${SITE_URL}/collections` }
|
|
]
|
|
},
|
|
"numberOfItems": products.length,
|
|
"itemListElement": products.map((p, i) => ({
|
|
"@type": "ListItem",
|
|
"position": i + 1,
|
|
"url": `${SITE_URL}/collections/${p.slug}`,
|
|
"name": p.title
|
|
}))
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<SEO
|
|
title="Handcrafted Ceramic Collection | KNUTH Ceramics"
|
|
description="Browse KNUTH Ceramics' handcrafted stoneware collection. Each piece is wheel-thrown in Corpus Christi, Texas. Vases, bowls, and tableware from $45 — made in the Gulf Coast tradition."
|
|
canonical={`${SITE_URL}/collections`}
|
|
schema={collectionsSchema}
|
|
/>
|
|
<section className="pt-32 pb-24 px-6 md:px-12 bg-stone-50 dark:bg-stone-900 min-h-screen">
|
|
<div className="max-w-[1920px] mx-auto">
|
|
{/* Header */}
|
|
<div className="mb-24 text-center">
|
|
<motion.h1
|
|
initial={{ y: 20, opacity: 0 }}
|
|
animate={{ y: 0, opacity: 1 }}
|
|
transition={{ delay: 0.5, duration: 0.8 }}
|
|
className="font-display text-5xl md:text-7xl font-light mb-6 text-text-main dark:text-white"
|
|
>
|
|
Collection
|
|
</motion.h1>
|
|
<motion.p
|
|
initial={{ y: 20, opacity: 0 }}
|
|
animate={{ y: 0, opacity: 1 }}
|
|
transition={{ delay: 0.7, duration: 0.8 }}
|
|
className="font-body text-stone-500 max-w-xl mx-auto text-lg font-light leading-relaxed"
|
|
>
|
|
A gallery of handmade objects. Select any piece to view it larger, then close when you are done.
|
|
</motion.p>
|
|
</div>
|
|
|
|
{/* Grid */}
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-x-6 gap-y-10 lg:gap-x-10 px-4">
|
|
{products.map((collection, index) => (
|
|
<motion.button
|
|
key={collection.id}
|
|
type="button"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: index * 0.1 }}
|
|
onClick={() => setSelectedProduct(collection)}
|
|
className="group block cursor-pointer text-left"
|
|
>
|
|
<div className="relative overflow-hidden mb-6 aspect-[4/5] bg-stone-100">
|
|
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/5 transition-colors duration-500 z-10" />
|
|
<img
|
|
src={collection.image}
|
|
alt={collection.title}
|
|
className="w-full h-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
|
|
/>
|
|
</div>
|
|
</motion.button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<AnimatePresence>
|
|
{selectedProduct && (
|
|
<motion.div
|
|
className="fixed inset-0 z-[70] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 sm:p-8"
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
onClick={() => setSelectedProduct(null)}
|
|
>
|
|
<motion.div
|
|
className="relative flex w-full max-w-6xl items-center justify-center"
|
|
initial={{ scale: 0.96, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
exit={{ scale: 0.96, opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
onClick={(event) => event.stopPropagation()}
|
|
>
|
|
<img
|
|
src={selectedProduct.image}
|
|
alt={selectedProduct.title}
|
|
className="max-h-[92vh] max-w-full object-contain"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setSelectedProduct(null)}
|
|
className="absolute right-3 top-3 text-4xl font-normal leading-none text-red-500 transition-colors hover:text-red-400"
|
|
aria-label="Close image preview"
|
|
>
|
|
x
|
|
</button>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Collections;
|