Final
This commit is contained in:
@@ -1,73 +1,143 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useStore } from '../src/context/StoreContext';
|
||||
|
||||
const Collections: React.FC = () => {
|
||||
const { products } = useStore();
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
Shop 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"
|
||||
>
|
||||
Curated series of functional objects. From our 'Sandstone' mugs to 'Seafoam' vases, each collection celebrates the palette of the Texas coast.
|
||||
</motion.p>
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-12 gap-y-16 lg:gap-x-16 px-4">
|
||||
{products.map((collection, index) => (
|
||||
<Link to={`/collections/${collection.slug}`} key={collection.id} className="block group cursor-pointer">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
>
|
||||
<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-110"
|
||||
/>
|
||||
{/* Quick overlay info */}
|
||||
<div className="absolute bottom-6 left-6 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<span className="bg-white dark:bg-black px-4 py-2 text-xs uppercase tracking-widest text-text-main dark:text-white">View Item</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-baseline pr-2">
|
||||
<div>
|
||||
<h2 className="font-display text-3xl font-light text-text-main dark:text-white mb-1 group-hover:underline decoration-1 underline-offset-4">
|
||||
{collection.title}
|
||||
</h2>
|
||||
</div>
|
||||
<span className="text-lg font-light text-text-main dark:text-white">
|
||||
${collection.price}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Collections;
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user