Initial commit of project structure
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Node.js
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Python
|
||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# System
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React, { Suspense, lazy } from 'react';
|
||||||
|
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||||
|
import Header from './components/Header';
|
||||||
|
import Footer from './components/Footer';
|
||||||
|
import ScrollToTop from './components/ScrollToTop';
|
||||||
|
import RouteTransition from './components/RouteTransition';
|
||||||
|
|
||||||
|
// Lazy load pages for better performance
|
||||||
|
const Home = lazy(() => import('./pages/Home'));
|
||||||
|
const Collections = lazy(() => import('./pages/Collections'));
|
||||||
|
const Atelier = lazy(() => import('./pages/Atelier'));
|
||||||
|
const Editorial = lazy(() => import('./pages/Editorial'));
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<ScrollToTop />
|
||||||
|
<Header />
|
||||||
|
<RouteTransition>
|
||||||
|
<Suspense fallback={<div className="h-screen w-full bg-stone-100 dark:bg-stone-900" />}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/collections" element={<Collections />} />
|
||||||
|
<Route path="/atelier" element={<Atelier />} />
|
||||||
|
<Route path="/editorial" element={<Editorial />} />
|
||||||
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
</RouteTransition>
|
||||||
|
<Footer />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<div align="center">
|
||||||
|
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
# Run and deploy your AI Studio app
|
||||||
|
|
||||||
|
This contains everything you need to run your app locally.
|
||||||
|
|
||||||
|
View your app in AI Studio: https://ai.studio/apps/drive/1xMGYSZE5oFy0eXRMfs741pq-jMBBDS7Y
|
||||||
|
|
||||||
|
## Run Locally
|
||||||
|
|
||||||
|
**Prerequisites:** Node.js
|
||||||
|
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
`npm install`
|
||||||
|
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||||
|
3. Run the app:
|
||||||
|
`npm run dev`
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { COLLECTIONS } from '../constants';
|
||||||
|
import { CollectionItem } from '../types';
|
||||||
|
|
||||||
|
const cardVariants = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
y: 80,
|
||||||
|
rotateX: 15,
|
||||||
|
},
|
||||||
|
visible: (index: number) => ({
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
rotateX: 0,
|
||||||
|
transition: {
|
||||||
|
delay: index * 0.15,
|
||||||
|
duration: 0.8,
|
||||||
|
ease: [0.25, 0.46, 0.45, 0.94],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const Collections: React.FC = () => {
|
||||||
|
const col1 = [COLLECTIONS[0], COLLECTIONS[1]];
|
||||||
|
const col2 = [COLLECTIONS[2], COLLECTIONS[3]];
|
||||||
|
const col3 = [COLLECTIONS[4], COLLECTIONS[5]];
|
||||||
|
|
||||||
|
const renderCard = (item: CollectionItem, index: number) => (
|
||||||
|
<motion.a
|
||||||
|
key={item.id}
|
||||||
|
className="group block cursor-pointer"
|
||||||
|
href="#"
|
||||||
|
variants={cardVariants}
|
||||||
|
initial="hidden"
|
||||||
|
whileInView="visible"
|
||||||
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
|
custom={index}
|
||||||
|
>
|
||||||
|
<div className={`relative overflow-hidden ${item.aspectRatio} mb-6`}>
|
||||||
|
{/* Image with clean hover effect */}
|
||||||
|
<motion.img
|
||||||
|
alt={`${item.title} collection`}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
src={item.image}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
transition={{ duration: 0.6, ease: [0.25, 0.46, 0.45, 0.94] }}
|
||||||
|
/>
|
||||||
|
{/* Subtle overlay that fades out on hover */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 bg-black/5"
|
||||||
|
initial={{ opacity: 1 }}
|
||||||
|
whileHover={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
/>
|
||||||
|
{/* Clean reveal line effect on hover */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute bottom-0 left-0 right-0 h-1 bg-white/80"
|
||||||
|
initial={{ scaleX: 0 }}
|
||||||
|
whileHover={{ scaleX: 1 }}
|
||||||
|
transition={{ duration: 0.5, ease: "easeOut" }}
|
||||||
|
style={{ transformOrigin: "left" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<motion.div
|
||||||
|
className="flex justify-between items-center border-t border-gray-400/50 dark:border-gray-800 pt-4"
|
||||||
|
initial={{ opacity: 0.8 }}
|
||||||
|
whileHover={{ opacity: 1 }}
|
||||||
|
>
|
||||||
|
<h3 className="font-display text-3xl font-light text-text-main dark:text-white group-hover:italic transition-all duration-300">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<motion.span
|
||||||
|
className="text-xs uppercase tracking-widest text-text-muted"
|
||||||
|
whileHover={{ x: 5 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
{item.number}
|
||||||
|
</motion.span>
|
||||||
|
</motion.div>
|
||||||
|
</motion.a>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="py-32 bg-warm-grey dark:bg-[#141210] transition-colors duration-500">
|
||||||
|
<div className="max-w-[1920px] mx-auto px-6 md:px-12">
|
||||||
|
<motion.div
|
||||||
|
className="flex flex-col md:flex-row justify-between items-end mb-20 md:mb-32 px-4"
|
||||||
|
initial={{ opacity: 0, y: 40 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
<h2 className="font-display text-5xl md:text-7xl font-thin text-text-main dark:text-white">
|
||||||
|
Curated <span className="italic text-text-muted">Editions</span>
|
||||||
|
</h2>
|
||||||
|
<p className="hidden md:block font-body text-sm text-text-muted max-w-xs leading-relaxed text-right">
|
||||||
|
Explore our seasonal collections, fired in small batches.
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 lg:gap-16">
|
||||||
|
<div className="flex flex-col space-y-16 md:space-y-32">
|
||||||
|
{col1.map((item, idx) => renderCard(item, idx))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col space-y-16 md:space-y-32 md:pt-32">
|
||||||
|
{col2.map((item, idx) => renderCard(item, idx + 2))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col space-y-16 md:space-y-32 md:pt-16 lg:pt-0">
|
||||||
|
{col3.map((item, idx) => renderCard(item, idx + 4))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Collections;
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { gsap } from 'gsap';
|
||||||
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
|
const FeatureSection: React.FC = () => {
|
||||||
|
const sectionRef = useRef<HTMLDivElement>(null);
|
||||||
|
const imageRef = useRef<HTMLDivElement>(null);
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const section = sectionRef.current;
|
||||||
|
const image = imageRef.current;
|
||||||
|
const content = contentRef.current;
|
||||||
|
|
||||||
|
if (!section || !image || !content) return;
|
||||||
|
|
||||||
|
// Image reveal animation
|
||||||
|
gsap.fromTo(
|
||||||
|
image,
|
||||||
|
{ clipPath: 'inset(100% 0 0 0)', opacity: 0 },
|
||||||
|
{
|
||||||
|
clipPath: 'inset(0% 0 0 0)',
|
||||||
|
opacity: 1,
|
||||||
|
duration: 1.2,
|
||||||
|
ease: 'power3.out',
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: section,
|
||||||
|
start: 'top 60%',
|
||||||
|
toggleActions: 'play none none reverse',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Content fade in animation
|
||||||
|
gsap.fromTo(
|
||||||
|
content,
|
||||||
|
{ x: -60, opacity: 0 },
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
opacity: 1,
|
||||||
|
duration: 1,
|
||||||
|
ease: 'power3.out',
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: section,
|
||||||
|
start: 'top 50%',
|
||||||
|
toggleActions: 'play none none reverse',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section ref={sectionRef} className="py-32 md:py-48 bg-sage dark:bg-stone-900 overflow-hidden relative transition-colors duration-500">
|
||||||
|
<div className="max-w-[1800px] mx-auto px-6">
|
||||||
|
<div className="relative flex flex-col md:block">
|
||||||
|
<div className="hidden md:block absolute -top-24 left-10 z-0 select-none opacity-[0.03] dark:opacity-[0.05] pointer-events-none">
|
||||||
|
<span className="font-display text-[20rem] leading-none text-black dark:text-white">CRAFT</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={imageRef}
|
||||||
|
className="w-full md:w-3/5 h-[600px] md:h-[800px] ml-auto relative z-10 shadow-2xl bg-center bg-cover bg-no-repeat"
|
||||||
|
style={{ backgroundImage: "url('/ceramic-cups.png')" }}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div ref={contentRef} className="relative z-20 mt-[-100px] md:mt-0 md:absolute md:top-1/2 md:left-20 md:-translate-y-1/2 bg-white dark:bg-stone-900 p-12 md:p-20 shadow-xl max-w-xl mx-auto md:mx-0">
|
||||||
|
<span className="block w-12 h-[1px] bg-text-main mb-8"></span>
|
||||||
|
<h2 className="font-display text-4xl md:text-5xl font-light mb-8 text-text-main dark:text-white leading-tight">
|
||||||
|
The Art of <br /><i className="font-thin">Slow Living</i>
|
||||||
|
</h2>
|
||||||
|
<p className="font-body font-light text-text-muted dark:text-gray-400 mb-10 leading-loose text-sm">
|
||||||
|
We believe in the beauty of handmade objects. Our collection features a curated selection of ceramics designed to elevate the everyday. From sturdy mugs for your morning coffee to elegant vases that breathe life into a room, each piece is crafted with patience and intention.
|
||||||
|
</p>
|
||||||
|
<a className="group inline-flex items-center text-xs uppercase tracking-[0.2em] text-text-main dark:text-white font-medium" href="#">
|
||||||
|
Read Our Story <span className="ml-2 group-hover:translate-x-1 transition-transform">→</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FeatureSection;
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { FOOTER_LINKS } from '../constants';
|
||||||
|
|
||||||
|
const Footer: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<footer className="bg-primary dark:bg-black text-white pt-32 pb-12 px-6 md:px-12 border-t border-stone-800">
|
||||||
|
<div className="max-w-[1920px] mx-auto">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 lg:gap-24 mb-32">
|
||||||
|
{/* Brand & Mission */}
|
||||||
|
<div className="lg:col-span-5 flex flex-col justify-between h-full">
|
||||||
|
<div>
|
||||||
|
<h2 className="font-display text-6xl md:text-8xl leading-none tracking-tighter mb-8 bg-gradient-to-br from-white to-stone-400 bg-clip-text text-transparent">
|
||||||
|
HOTCHPOTSH
|
||||||
|
</h2>
|
||||||
|
<p className="font-body text-lg font-light text-stone-400 leading-relaxed max-w-md">
|
||||||
|
Handcrafted ceramics for the modern home. Created with intention, fired with patience, and delivered with care.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-16 lg:mt-0">
|
||||||
|
<h4 className="text-sm font-bold uppercase tracking-widest mb-6">Join our newsletter</h4>
|
||||||
|
<form className="flex flex-col sm:flex-row gap-4 max-w-md" onSubmit={(e) => e.preventDefault()}>
|
||||||
|
<input
|
||||||
|
className="bg-white/5 border border-white/10 text-white placeholder-stone-500 focus:outline-none focus:border-white/30 text-sm px-6 py-4 w-full transition-colors"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="bg-white text-black px-8 py-4 text-xs font-bold uppercase tracking-widest hover:bg-stone-200 transition-colors"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Subscribe
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Links Columns */}
|
||||||
|
<div className="lg:col-span-7 grid grid-cols-2 md:grid-cols-3 gap-12 pt-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs font-bold uppercase tracking-widest mb-8 text-stone-500">{FOOTER_LINKS[0].title}</h4>
|
||||||
|
<ul className="space-y-6">
|
||||||
|
{FOOTER_LINKS[0].links.map((link) => (
|
||||||
|
<li key={link.label}>
|
||||||
|
<a className="text-lg font-light hover:text-stone-400 hover:pl-2 transition-all duration-300 block" href={link.href}>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs font-bold uppercase tracking-widest mb-8 text-stone-500">{FOOTER_LINKS[1].title}</h4>
|
||||||
|
<ul className="space-y-6">
|
||||||
|
{FOOTER_LINKS[1].links.map((link) => (
|
||||||
|
<li key={link.label}>
|
||||||
|
<a className="text-lg font-light hover:text-stone-400 hover:pl-2 transition-all duration-300 block" href={link.href}>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs font-bold uppercase tracking-widest mb-8 text-stone-500">{FOOTER_LINKS[2].title}</h4>
|
||||||
|
<ul className="space-y-6">
|
||||||
|
{FOOTER_LINKS[2].links.map((link) => (
|
||||||
|
<li key={link.label}>
|
||||||
|
<a className="text-lg font-light hover:text-stone-400 hover:pl-2 transition-all duration-300 block" href={link.href}>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Bar */}
|
||||||
|
<div className="border-t border-white/10 pt-12 flex flex-col md:flex-row justify-between items-center text-xs text-stone-500 tracking-widest uppercase font-light">
|
||||||
|
<p>© 2024 HOTCHPOTSH Ceramics. All rights reserved.</p>
|
||||||
|
<div className="flex space-x-8 mt-6 md:mt-0">
|
||||||
|
<a className="hover:text-white transition-colors" href="#">Privacy</a>
|
||||||
|
<a className="hover:text-white transition-colors" href="#">Terms</a>
|
||||||
|
<a className="hover:text-white transition-colors" href="#">Cookies</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { GALLERY_IMAGES } from '../constants';
|
||||||
|
|
||||||
|
interface GalleryImage {
|
||||||
|
src: string;
|
||||||
|
likes: number;
|
||||||
|
comments: number;
|
||||||
|
caption: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GallerySection: React.FC = () => {
|
||||||
|
const [selectedImage, setSelectedImage] = useState<GalleryImage | null>(null);
|
||||||
|
|
||||||
|
// Double the images for seamless infinite scroll
|
||||||
|
const duplicatedImages = [...GALLERY_IMAGES, ...GALLERY_IMAGES] as GalleryImage[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="py-20 bg-white dark:bg-background-dark overflow-hidden">
|
||||||
|
<div className="max-w-[1920px] mx-auto px-4">
|
||||||
|
<motion.div
|
||||||
|
className="flex justify-between items-center mb-8 px-2"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-purple-500 via-pink-500 to-orange-400 p-[2px]">
|
||||||
|
<div className="w-full h-full rounded-full bg-white dark:bg-background-dark flex items-center justify-center">
|
||||||
|
<span className="font-display text-lg">H</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-display text-xl text-text-main dark:text-white">@hotchpotsh_ceramics</h4>
|
||||||
|
<p className="text-xs text-text-muted">24.8k followers</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a className="px-6 py-2 border border-text-main dark:border-white text-xs uppercase tracking-widest text-text-main dark:text-white hover:bg-text-main hover:text-white dark:hover:bg-white dark:hover:text-black transition-all duration-300 rounded-full" href="#">
|
||||||
|
Follow
|
||||||
|
</a>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Infinite Carousel */}
|
||||||
|
<div className="relative group overflow-hidden">
|
||||||
|
<style>{`
|
||||||
|
@keyframes marquee {
|
||||||
|
0% { transform: translateX(0); }
|
||||||
|
100% { transform: translateX(-${GALLERY_IMAGES.length * 304}px); }
|
||||||
|
}
|
||||||
|
.animate-marquee {
|
||||||
|
animation: marquee 40s linear infinite;
|
||||||
|
}
|
||||||
|
.animate-marquee:hover {
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<div className="flex gap-4 animate-marquee w-max py-4">
|
||||||
|
{duplicatedImages.map((img, idx) => (
|
||||||
|
<motion.div
|
||||||
|
key={idx}
|
||||||
|
className="relative flex-shrink-0 w-72 h-72 overflow-hidden cursor-pointer rounded-lg"
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
onClick={() => setSelectedImage(img)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt={img.caption}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
src={img.src}
|
||||||
|
/>
|
||||||
|
{/* Instagram-style hover overlay */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 bg-black/50 flex items-center justify-center gap-8 text-white"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
whileHover={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="material-symbols-outlined" style={{ fontVariationSettings: "'FILL' 1" }}>favorite</span>
|
||||||
|
<span className="font-bold">{img.likes.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="material-symbols-outlined">chat_bubble</span>
|
||||||
|
<span className="font-bold">{img.comments}</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Lightbox Modal */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{
|
||||||
|
selectedImage && (
|
||||||
|
<motion.div
|
||||||
|
className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
onClick={() => setSelectedImage(null)}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
className="relative max-w-4xl w-full bg-white dark:bg-stone-900 rounded-xl overflow-hidden flex flex-col md:flex-row"
|
||||||
|
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()}
|
||||||
|
>
|
||||||
|
{/* Image */}
|
||||||
|
<div className="md:w-2/3 aspect-square md:aspect-auto">
|
||||||
|
<img
|
||||||
|
src={selectedImage.src}
|
||||||
|
alt={selectedImage.caption}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Side panel */}
|
||||||
|
<div className="md:w-1/3 p-6 flex flex-col">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center gap-3 pb-4 border-b border-stone-200 dark:border-stone-700">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-purple-500 via-pink-500 to-orange-400 p-[2px]">
|
||||||
|
<div className="w-full h-full rounded-full bg-white dark:bg-stone-900 flex items-center justify-center">
|
||||||
|
<span className="font-display text-sm">H</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="font-bold text-sm text-text-main dark:text-white">hotchpotsh_ceramics</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Caption */}
|
||||||
|
<div className="flex-1 py-4">
|
||||||
|
<p className="text-sm text-text-main dark:text-white">
|
||||||
|
<span className="font-bold">hotchpotsh_ceramics</span> {selectedImage.caption}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="border-t border-stone-200 dark:border-stone-700 pt-4">
|
||||||
|
<div className="flex gap-4 mb-4">
|
||||||
|
<button className="hover:scale-110 transition-transform">
|
||||||
|
<span className="material-symbols-outlined text-2xl text-text-main dark:text-white" style={{ fontVariationSettings: "'FILL' 1" }}>favorite</span>
|
||||||
|
</button>
|
||||||
|
<button className="hover:scale-110 transition-transform">
|
||||||
|
<span className="material-symbols-outlined text-2xl text-text-main dark:text-white">chat_bubble</span>
|
||||||
|
</button>
|
||||||
|
<button className="hover:scale-110 transition-transform">
|
||||||
|
<span className="material-symbols-outlined text-2xl text-text-main dark:text-white">send</span>
|
||||||
|
</button>
|
||||||
|
<button className="hover:scale-110 transition-transform ml-auto">
|
||||||
|
<span className="material-symbols-outlined text-2xl text-text-main dark:text-white">bookmark</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-text-main dark:text-white">{selectedImage.likes.toLocaleString()} likes</p>
|
||||||
|
<p className="text-xs text-text-muted mt-1">{selectedImage.comments} comments</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Close button */}
|
||||||
|
<button
|
||||||
|
className="absolute top-4 right-4 text-white bg-black/50 rounded-full p-2 hover:bg-black/70 transition-colors"
|
||||||
|
onClick={() => setSelectedImage(null)}
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined">close</span>
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</AnimatePresence >
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GallerySection;
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { NAV_ITEMS } from '../constants';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
const Header: React.FC = () => {
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
setScrolled(window.scrollY > 50);
|
||||||
|
};
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={`fixed top-0 w-full z-50 transition-all duration-500 ${scrolled
|
||||||
|
? 'bg-white/80 dark:bg-black/80 backdrop-blur-xl py-2 border-b border-stone-200/50 dark:border-stone-800/50'
|
||||||
|
: 'bg-transparent py-6'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="max-w-[1920px] mx-auto px-6 md:px-12">
|
||||||
|
<div className="flex justify-between items-center h-20">
|
||||||
|
{/* Mobile Menu Button */}
|
||||||
|
<div className="flex items-center md:hidden">
|
||||||
|
<button
|
||||||
|
className="text-text-main dark:text-white p-2 hover:bg-stone-100 dark:hover:bg-stone-800 rounded-full transition-colors"
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined">menu</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logo */}
|
||||||
|
<div className="flex-shrink-0 relative group cursor-pointer">
|
||||||
|
<Link className="font-display text-4xl md:text-5xl font-light tracking-widest uppercase text-text-main dark:text-white" to="/">
|
||||||
|
HOTCHPOTSH
|
||||||
|
</Link>
|
||||||
|
{/* Subtle glow effect on hover */}
|
||||||
|
<div className="absolute -inset-4 bg-white/20 blur-xl opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop Nav */}
|
||||||
|
<nav className="hidden md:flex space-x-12">
|
||||||
|
{NAV_ITEMS.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.label}
|
||||||
|
className="group relative text-xs uppercase tracking-[0.2em] text-text-main dark:text-white hover:text-black dark:hover:text-white transition-colors duration-300 py-2"
|
||||||
|
to={item.label === 'Collections' ? '/collections' : item.label === 'Atelier' ? '/atelier' : '/editorial'}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
{/* Underline Reveal Animation */}
|
||||||
|
<span className="absolute bottom-0 left-0 w-0 h-[1px] bg-black dark:bg-white transition-all duration-300 group-hover:w-full" />
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Icons */}
|
||||||
|
<div className="flex items-center space-x-6 text-text-main dark:text-white">
|
||||||
|
<button className="hover:scale-110 transition-transform duration-300 hidden sm:block p-2">
|
||||||
|
<span className="material-symbols-outlined text-xl font-light">search</span>
|
||||||
|
</button>
|
||||||
|
<a className="hover:scale-110 transition-transform duration-300 relative group p-2" href="#">
|
||||||
|
<span className="material-symbols-outlined text-xl font-light">shopping_bag</span>
|
||||||
|
<span className="absolute top-0 right-0 bg-black dark:bg-white text-white dark:text-black text-[9px] w-4 h-4 flex items-center justify-center rounded-full opacity-0 scale-50 group-hover:opacity-100 group-hover:scale-100 transition-all duration-300">2</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu Overlay */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{isMenuOpen && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, height: 0 }}
|
||||||
|
animate={{ opacity: 1, height: 'auto' }}
|
||||||
|
exit={{ opacity: 0, height: 0 }}
|
||||||
|
className="md:hidden absolute top-full left-0 w-full bg-white/95 dark:bg-black/95 backdrop-blur-xl border-b border-stone-200 dark:border-stone-800 shadow-2xl overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col p-8 space-y-6">
|
||||||
|
{NAV_ITEMS.map((item, idx) => (
|
||||||
|
<Link
|
||||||
|
key={item.label}
|
||||||
|
to={item.label === 'Collections' ? '/collections' : item.label === 'Atelier' ? '/atelier' : '/editorial'}
|
||||||
|
className="text-lg uppercase tracking-[0.2em] text-text-main dark:text-white hover:pl-4 transition-all duration-300 border-l-2 border-transparent hover:border-black dark:hover:border-white"
|
||||||
|
onClick={() => setIsMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<motion.span
|
||||||
|
initial={{ x: -20, opacity: 0 }}
|
||||||
|
animate={{ x: 0, opacity: 1 }}
|
||||||
|
transition={{ delay: idx * 0.1 }}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</motion.span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Hero: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<section className="relative min-h-screen pt-24 w-full flex flex-col md:flex-row items-center overflow-hidden bg-background-light dark:bg-background-dark">
|
||||||
|
<div className="w-full md:w-5/12 h-full flex flex-col justify-center px-6 md:pl-20 md:pr-12 py-20 z-10">
|
||||||
|
<span className="font-body text-xs uppercase tracking-[0.3em] text-text-muted mb-8 ml-1 block">
|
||||||
|
New Collection 2024
|
||||||
|
</span>
|
||||||
|
<h1 className="font-display text-6xl md:text-7xl lg:text-8xl xl:text-9xl text-text-main dark:text-white font-thin leading-[0.9] mb-10">
|
||||||
|
Form <br /><span className="italic pl-12 md:pl-20 text-text-muted">of</span> Earth
|
||||||
|
</h1>
|
||||||
|
<p className="font-body text-text-muted dark:text-gray-400 text-sm md:text-base font-light mb-12 max-w-sm leading-loose ml-1">
|
||||||
|
Discover the imperfect perfection of hand-thrown stoneware. Pieces that bring silence and intention to your daily rituals.
|
||||||
|
</p>
|
||||||
|
<div className="ml-1">
|
||||||
|
<a className="inline-block border-b border-text-main dark:border-white pb-1 text-text-main dark:text-white font-body text-xs uppercase tracking-[0.2em] hover:text-text-muted transition-colors duration-300" href="#">
|
||||||
|
View The Collection
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-7/12 h-[60vh] md:h-screen relative">
|
||||||
|
<div className="absolute inset-0 bg-stone-200 dark:bg-stone-800">
|
||||||
|
<img
|
||||||
|
alt="Minimalist ceramic vase with single branch"
|
||||||
|
className="w-full h-full object-cover object-center brightness-95"
|
||||||
|
src="/pottery-studio.png"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-10 left-10 md:left-auto md:right-20 bg-background-light/90 dark:bg-background-dark/90 backdrop-blur p-6 max-w-xs hidden md:block shadow-sm">
|
||||||
|
<p className="font-display italic text-xl text-text-main dark:text-gray-200">
|
||||||
|
"In emptiness, there is fullness."
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Hero;
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { gsap } from 'gsap';
|
||||||
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
|
const horizontalImages = [
|
||||||
|
{ src: '/pottery-vase.png', title: 'Handcrafted Vases', description: 'Each vase tells a story of patience and craft' },
|
||||||
|
{ src: '/pottery-bowls.png', title: 'Artisan Bowls', description: 'Organic forms inspired by nature' },
|
||||||
|
{ src: '/pottery-plates.png', title: 'Dinner Collection', description: 'Elevate your everyday dining experience' },
|
||||||
|
{ src: '/pottery-studio.png', title: 'Our Studio', description: 'Where creativity meets tradition' },
|
||||||
|
{ src: '/ceramic-cups.png', title: 'Ceramic Cups', description: 'Handmade with love and intention' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const HorizontalScrollSection: React.FC = () => {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
const scrollContainer = scrollRef.current;
|
||||||
|
|
||||||
|
if (!container || !scrollContainer) return;
|
||||||
|
|
||||||
|
const scrollWidth = scrollContainer.scrollWidth - window.innerWidth;
|
||||||
|
|
||||||
|
const tween = gsap.to(scrollContainer, {
|
||||||
|
x: -scrollWidth,
|
||||||
|
ease: 'none',
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: container,
|
||||||
|
start: 'top top',
|
||||||
|
end: () => `+=${scrollWidth * 0.5}`,
|
||||||
|
scrub: 1,
|
||||||
|
pin: true,
|
||||||
|
anticipatePin: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
tween.scrollTrigger?.kill();
|
||||||
|
tween.kill();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section ref={containerRef} className="relative overflow-hidden bg-clay-dark">
|
||||||
|
<div
|
||||||
|
ref={scrollRef}
|
||||||
|
className="flex h-screen items-center"
|
||||||
|
>
|
||||||
|
{horizontalImages.map((image, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="relative flex-shrink-0 w-[90vw] md:w-[75vw] h-screen flex items-center justify-center p-4 md:p-8"
|
||||||
|
>
|
||||||
|
<div className="relative w-full h-full max-w-5xl max-h-[80vh] overflow-hidden rounded-lg shadow-2xl group">
|
||||||
|
<img
|
||||||
|
src={image.src}
|
||||||
|
alt={image.title}
|
||||||
|
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent" />
|
||||||
|
<div className="absolute bottom-0 left-0 p-12 text-white">
|
||||||
|
<h3 className="font-display text-5xl md:text-6xl font-light mb-4">{image.title}</h3>
|
||||||
|
<p className="font-body text-lg font-light opacity-80 max-w-md">{image.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-1/2 right-8 -translate-y-1/2 text-white/20 font-display text-[15rem] leading-none select-none pointer-events-none">
|
||||||
|
{String(index + 1).padStart(2, '0')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex items-center gap-4 text-white/60">
|
||||||
|
<span className="material-symbols-outlined text-sm">arrow_back</span>
|
||||||
|
<span className="text-xs uppercase tracking-[0.3em] font-light">Scroll to explore</span>
|
||||||
|
<span className="material-symbols-outlined text-sm">arrow_forward</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HorizontalScrollSection;
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { JOURNAL_ENTRIES } from '../constants';
|
||||||
|
|
||||||
|
const JournalSection: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<section className="relative py-32 bg-terracotta-soft dark:bg-black overflow-hidden transition-colors duration-500">
|
||||||
|
<div className="absolute inset-0 z-0 mix-blend-multiply opacity-30">
|
||||||
|
<img
|
||||||
|
alt="Atmospheric studio background"
|
||||||
|
className="w-full h-full object-cover blur-3xl scale-110 grayscale"
|
||||||
|
src="https://lh3.googleusercontent.com/aida-public/AB6AXuAipMlYLTcRT_hdc3VePfFIlrA56VzZ5G2y3gcRfmIZMERwGFKq2N19Gqo6mw7uZowXmjl2eJ89TI3Mcud2OyOfadO3mPVF_v0sI0OHupqM49WEFcWzH-Wbu3DL6bQ46F2Y8SIAk-NUQy8psjcIdBKRrM8fqdn4eOPANYTXpVxkLMAm4R0Axy4aEKNdmj917ZKKTxvXB-J8nGlITJkJ-ua7XcZOwGnfK5ttzyWW35A0oOSffCf972gmpV27wrVQgYJNLS7UyDdyQIQ"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-w-[1920px] mx-auto px-6 md:px-12 relative z-10">
|
||||||
|
<div className="flex justify-between items-baseline mb-20 border-b border-text-main/20 dark:border-gray-800 pb-6">
|
||||||
|
<h2 className="font-display text-6xl font-thin text-text-main dark:text-white">The <span className="italic">Journal</span></h2>
|
||||||
|
<a className="text-xs uppercase tracking-[0.2em] text-text-main dark:text-white hover:text-text-muted transition-colors" href="#">View Archive</a>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||||
|
{JOURNAL_ENTRIES.map((entry) => (
|
||||||
|
<article key={entry.id} className={`group cursor-pointer ${entry.marginTop ? 'lg:mt-20' : ''}`}>
|
||||||
|
<div className="relative h-[500px] overflow-hidden mb-8 shadow-md">
|
||||||
|
<img
|
||||||
|
alt={entry.title}
|
||||||
|
className="w-full h-full object-cover transition-transform duration-[1.5s] group-hover:scale-105"
|
||||||
|
src={entry.image}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-black/10 group-hover:bg-black/0 transition-colors duration-500"></div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="flex items-center space-x-4 mb-4">
|
||||||
|
<span className="text-[10px] uppercase tracking-[0.2em] text-text-muted border border-text-muted/30 px-2 py-1 rounded-full">{entry.category}</span>
|
||||||
|
<span className="text-[10px] uppercase tracking-[0.2em] text-text-muted">{entry.date}</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-display text-3xl text-text-main dark:text-white mb-4 leading-tight group-hover:underline decoration-1 underline-offset-4">
|
||||||
|
{entry.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm font-light text-text-muted dark:text-gray-400 leading-loose max-w-sm">
|
||||||
|
{entry.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JournalSection;
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
const PageLoader: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-stone-100 dark:bg-stone-900">
|
||||||
|
<div className="relative">
|
||||||
|
{/* Animated Pottery Outline */}
|
||||||
|
<motion.svg
|
||||||
|
width="120"
|
||||||
|
height="160"
|
||||||
|
viewBox="0 0 120 160"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<motion.path
|
||||||
|
d="M30 150C30 155.523 34.4772 160 40 160H80C85.5228 160 90 155.523 90 150V140H30V150Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
className="text-stone-800 dark:text-stone-200"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 1 }}
|
||||||
|
transition={{ duration: 1.5, ease: "easeInOut", repeat: Infinity, repeatType: "reverse" }}
|
||||||
|
/>
|
||||||
|
<motion.path
|
||||||
|
d="M30 140C30 140 10 100 10 60C10 32.3858 32.3858 10 60 10C87.6142 10 110 32.3858 110 60C110 100 90 140 90 140H30Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
className="text-stone-800 dark:text-stone-200"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 1 }}
|
||||||
|
transition={{ duration: 2, ease: "easeInOut", repeat: Infinity, repeatType: "reverse", delay: 0.5 }}
|
||||||
|
/>
|
||||||
|
<motion.ellipse
|
||||||
|
cx="60"
|
||||||
|
cy="10"
|
||||||
|
rx="25"
|
||||||
|
ry="5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
className="text-stone-800 dark:text-stone-200"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 1 }}
|
||||||
|
transition={{ duration: 1.5, ease: "easeInOut", repeat: Infinity, repeatType: "reverse", delay: 1 }}
|
||||||
|
/>
|
||||||
|
</motion.svg>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="mt-8 text-center"
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.8, repeat: Infinity, repeatType: "reverse" }}
|
||||||
|
>
|
||||||
|
<span className="font-display text-sm tracking-[0.3em] uppercase text-stone-500">Shaping</span>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageLoader;
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const QuoteSection: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<section className="py-32 bg-clay-dark dark:bg-black border-y border-stone-800/50 transition-colors duration-500">
|
||||||
|
<div className="max-w-5xl mx-auto px-6 text-center">
|
||||||
|
<span className="material-symbols-outlined text-4xl mb-8 text-stone-500 font-thin">format_quote</span>
|
||||||
|
<h3 className="font-display text-3xl md:text-5xl font-thin leading-snug text-stone-100 dark:text-stone-200 mb-10 italic">
|
||||||
|
"My pottery is designed to be both beautiful and practical. From minimalist vases to durable dinner plates, each piece has its own character."
|
||||||
|
</h3>
|
||||||
|
<p className="font-body text-xs uppercase tracking-[0.2em] text-stone-400">
|
||||||
|
Anonymous — Verified Collector
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QuoteSection;
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import PageLoader from './PageLoader';
|
||||||
|
|
||||||
|
const RouteTransition: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Trigger loading on route change
|
||||||
|
setIsLoading(true);
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
}, 2000); // 2 seconds loader on every page transition
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{isLoading && (
|
||||||
|
<motion.div
|
||||||
|
key="loader"
|
||||||
|
exit={{ opacity: 0, transition: { duration: 0.5 } }}
|
||||||
|
className="fixed inset-0 z-[60]" // Higher z-index to cover everything
|
||||||
|
>
|
||||||
|
<PageLoader />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
<div className={isLoading ? 'opacity-0' : 'opacity-100 transition-opacity duration-500'}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RouteTransition;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function ScrollToTop() {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { NavItem, CollectionItem, JournalEntry, FooterSection } from './types';
|
||||||
|
|
||||||
|
export const NAV_ITEMS: NavItem[] = [
|
||||||
|
{ label: 'Collections', href: '#' },
|
||||||
|
{ label: 'Atelier', href: '#' },
|
||||||
|
{ label: 'Editorial', href: '#' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const COLLECTIONS: CollectionItem[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Tableware',
|
||||||
|
number: '01',
|
||||||
|
image: '/collection-tableware.png',
|
||||||
|
aspectRatio: 'aspect-[3/4]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Lighting',
|
||||||
|
number: '04',
|
||||||
|
image: '/collection-lighting.png',
|
||||||
|
aspectRatio: 'aspect-[4/3]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: 'Vases',
|
||||||
|
number: '02',
|
||||||
|
image: '/collection-vases.png',
|
||||||
|
aspectRatio: 'aspect-square',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: 'Serving',
|
||||||
|
number: '05',
|
||||||
|
image: '/pottery-bowls.png',
|
||||||
|
aspectRatio: 'aspect-[3/4]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'Kitchenware',
|
||||||
|
number: '03',
|
||||||
|
image: '/collection-kitchenware.png',
|
||||||
|
aspectRatio: 'aspect-[3/5]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: 'Textiles',
|
||||||
|
number: '06',
|
||||||
|
image: '/pottery-plates.png',
|
||||||
|
aspectRatio: 'aspect-square',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const JOURNAL_ENTRIES: JournalEntry[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
category: 'Studio',
|
||||||
|
date: 'Oct 03',
|
||||||
|
title: 'Product Photography for Small Businesses',
|
||||||
|
description: "Learning that beautiful products aren't enough on their own — you also need beautiful photos to tell the story.",
|
||||||
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAipMlYLTcRT_hdc3VePfFIlrA56VzZ5G2y3gcRfmIZMERwGFKq2N19Gqo6mw7uZowXmjl2eJ89TI3Mcud2OyOfadO3mPVF_v0sI0OHupqM49WEFcWzH-Wbu3DL6bQ46F2Y8SIAk-NUQy8psjcIdBKRrM8fqdn4eOPANYTXpVxkLMAm4R0Axy4aEKNdmj917ZKKTxvXB-J8nGlITJkJ-ua7XcZOwGnfK5ttzyWW35A0oOSffCf972gmpV27wrVQgYJNLS7UyDdyQIQ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
category: 'Guide',
|
||||||
|
date: 'Jul 15',
|
||||||
|
title: 'The Art of Packaging',
|
||||||
|
description: "A practical guide for potters who want to package and send their handmade ceramics with care and confidence.",
|
||||||
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAaWGnX_NYT3S_lOflL2NJZGbWge4AAkvra4ymvF8ag-c1UKsOAIB-rsLVQXW5xIlPZipDiK8-ysPyv22xdgsvzs4EOXSSCcrT4Lb2YCe0u5orxRaZEA5TgxeoKq15zaWKSlmnHyPGjPd_7yglpfO13eZmbU5KaxFJ1KGO0UAxoO9BpsyCYgbgINMoSz3epGe5ZdwBWRH-5KCzjoLuXimFTLcd5bqg9T1YofTxgy2hWBMJzKkafyEniq8dP6hMmfNCLVcCHHHx0hRU',
|
||||||
|
marginTop: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
category: 'Wellness',
|
||||||
|
date: 'Jun 11',
|
||||||
|
title: 'Finding Motivation in Clay',
|
||||||
|
description: "10 gentle, practical tips to help potters find motivation during slow or uncertain moments in the creative process.",
|
||||||
|
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuB8NOE5fGfN4d87cbcB27_Sh-nrlZlqxsTlYKbCZk98SoL-gHsPSWFNuxd1DxBq0g8Qysh0RBZ_btu-_WaH68UjV8SXPUalyxREvUqao4oXmra--pWAsaooWwKvWCzReYZ8kj7G-KIYIAo5mqudzB8n9C6-HVTNPPx9QgZHr_YsojMxlmmVcQ5bqk7-Lp0KtSAiVIPD2-1UE1dMGnkVSLUXKdgA65JIh8M3TtNkaJTGONuFKoTERrYOWe7u2BILnqyukTzlNcvK7Sc',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GALLERY_IMAGES = [
|
||||||
|
{ src: '/ceramic-cups.png', likes: 2847, comments: 124, caption: 'Morning rituals ☕' },
|
||||||
|
{ src: '/pottery-vase.png', likes: 3521, comments: 89, caption: 'Crafted with intention 🏺' },
|
||||||
|
{ src: '/pottery-bowls.png', likes: 1956, comments: 67, caption: 'Wabi-sabi collection' },
|
||||||
|
{ src: '/pottery-plates.png', likes: 4102, comments: 156, caption: 'Ready for your table ✨' },
|
||||||
|
{ src: '/pottery-studio.png', likes: 5234, comments: 203, caption: 'Where the magic happens' },
|
||||||
|
{ src: '/collection-tableware.png', likes: 2678, comments: 94, caption: 'Stacked with love' },
|
||||||
|
{ src: '/collection-vases.png', likes: 3189, comments: 112, caption: 'Organic forms' },
|
||||||
|
{ src: '/collection-kitchenware.png', likes: 1847, comments: 78, caption: 'Matcha time 🍵' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const FOOTER_LINKS: FooterSection[] = [
|
||||||
|
{
|
||||||
|
title: 'Shop',
|
||||||
|
links: [
|
||||||
|
{ label: 'All Ceramics', href: '#' },
|
||||||
|
{ label: 'New Arrivals', href: '#' },
|
||||||
|
{ label: 'Best Sellers', href: '#' },
|
||||||
|
{ label: 'Gift Cards', href: '#' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Company',
|
||||||
|
links: [
|
||||||
|
{ label: 'Our Story', href: '#' },
|
||||||
|
{ label: 'Sustainability', href: '#' },
|
||||||
|
{ label: 'Careers', href: '#' },
|
||||||
|
{ label: 'Press', href: '#' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Support',
|
||||||
|
links: [
|
||||||
|
{ label: 'FAQ', href: '#' },
|
||||||
|
{ label: 'Shipping', href: '#' },
|
||||||
|
{ label: 'Returns', href: '#' },
|
||||||
|
{ label: 'Contact', href: '#' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { useEffect, useRef, RefObject } from 'react';
|
||||||
|
import { gsap } from 'gsap';
|
||||||
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
|
interface ScrollAnimationOptions {
|
||||||
|
trigger?: RefObject<HTMLElement>;
|
||||||
|
start?: string;
|
||||||
|
end?: string;
|
||||||
|
scrub?: boolean | number;
|
||||||
|
markers?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useScrollFadeIn = (
|
||||||
|
ref: RefObject<HTMLElement>,
|
||||||
|
options: ScrollAnimationOptions = {}
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const element = ref.current;
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
gsap.fromTo(
|
||||||
|
element,
|
||||||
|
{ opacity: 0, y: 60 },
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 1,
|
||||||
|
ease: 'power3.out',
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: options.trigger?.current || element,
|
||||||
|
start: options.start || 'top 85%',
|
||||||
|
end: options.end || 'top 20%',
|
||||||
|
toggleActions: 'play none none reverse',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
|
||||||
|
};
|
||||||
|
}, [ref, options]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useParallax = (
|
||||||
|
ref: RefObject<HTMLElement>,
|
||||||
|
speed: number = 0.5
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const element = ref.current;
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
gsap.to(element, {
|
||||||
|
yPercent: -50 * speed,
|
||||||
|
ease: 'none',
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: element,
|
||||||
|
start: 'top bottom',
|
||||||
|
end: 'bottom top',
|
||||||
|
scrub: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
|
||||||
|
};
|
||||||
|
}, [ref, speed]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useStaggerReveal = (
|
||||||
|
containerRef: RefObject<HTMLElement>,
|
||||||
|
childSelector: string,
|
||||||
|
options: ScrollAnimationOptions = {}
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const children = container.querySelectorAll(childSelector);
|
||||||
|
|
||||||
|
gsap.fromTo(
|
||||||
|
children,
|
||||||
|
{ opacity: 0, y: 40 },
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.8,
|
||||||
|
stagger: 0.15,
|
||||||
|
ease: 'power2.out',
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: container,
|
||||||
|
start: options.start || 'top 80%',
|
||||||
|
toggleActions: 'play none none reverse',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
|
||||||
|
};
|
||||||
|
}, [containerRef, childSelector, options]);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>HOTCHPOTSH Ceramics - Editorial Collection</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;1,300;1,400&family=Manrope:wght@200;300;400;500&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: "#292524", // Warm Charcoal
|
||||||
|
secondary: "#78716C", // Warm Stone
|
||||||
|
"background-light": "#F5F4F0", // Soft Sand / Alabaster (Hero)
|
||||||
|
"background-dark": "#1C1917", // Dark Warm Grey
|
||||||
|
// New Sectional Colors
|
||||||
|
"sage": "#D4D9D1", // Soft Sage Green
|
||||||
|
"warm-grey": "#DAD7D4", // Warm Grey
|
||||||
|
"clay-dark": "#33302D", // Deep Charcoal / Clay
|
||||||
|
"terracotta-soft": "#E6DDD5", // Pale Ochre / Soft Terracotta
|
||||||
|
"accent-sand": "#E7E5E4",
|
||||||
|
"accent-warm": "#D6D3D1",
|
||||||
|
"text-main": "#1C1917",
|
||||||
|
"text-muted": "#57534E",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
display: ["Cormorant Garamond", "serif"],
|
||||||
|
body: ["Manrope", "sans-serif"],
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
'10xl': '10rem',
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
'128': '32rem',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings:
|
||||||
|
'FILL' 0,
|
||||||
|
'wght' 300,
|
||||||
|
'GRAD' 0,
|
||||||
|
'opsz' 24
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
.parallax-bg {
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #F5F4F0;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #D6D3D1;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #78716C;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"react-dom/": "https://esm.sh/react-dom@^19.2.3/",
|
||||||
|
"react/": "https://esm.sh/react@^19.2.3/",
|
||||||
|
"react": "https://esm.sh/react@^19.2.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="font-body bg-background-light dark:bg-background-dark text-text-main dark:text-gray-200 antialiased transition-colors duration-500 selection:bg-stone-200 selection:text-black">
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
if (!rootElement) {
|
||||||
|
throw new Error("Could not find root element to mount to");
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "IKKAI Ceramics",
|
||||||
|
"description": "A high-fidelity recreation of the IKKAI Ceramics editorial e-commerce website featuring a minimalist design, custom typography, and responsive layout.",
|
||||||
|
"requestFramePermissions": []
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "ikkai-ceramics",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^12.26.0",
|
||||||
|
"gsap": "^3.14.2",
|
||||||
|
"react": "^19.2.3",
|
||||||
|
"react-dom": "^19.2.3",
|
||||||
|
"react-router-dom": "^7.12.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.14.0",
|
||||||
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
"typescript": "~5.8.2",
|
||||||
|
"vite": "^6.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
const Atelier: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="bg-stone-50 dark:bg-stone-900 min-h-screen pt-32 pb-24">
|
||||||
|
<div className="max-w-[1920px] mx-auto px-6 md:px-12">
|
||||||
|
{/* Intro */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 mb-32 items-center">
|
||||||
|
<div className="md:col-span-5 md:col-start-2">
|
||||||
|
<motion.span
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 1 }}
|
||||||
|
className="block text-xs uppercase tracking-[0.3em] text-stone-400 mb-6"
|
||||||
|
>
|
||||||
|
The Studio
|
||||||
|
</motion.span>
|
||||||
|
<motion.h1
|
||||||
|
initial={{ y: 30, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ delay: 0.2, duration: 0.8 }}
|
||||||
|
className="font-display text-5xl md:text-7xl lg:text-8xl leading-none text-text-main dark:text-white mb-8"
|
||||||
|
>
|
||||||
|
Formed by<br />Hand & Fire
|
||||||
|
</motion.h1>
|
||||||
|
<motion.p
|
||||||
|
initial={{ y: 30, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ delay: 0.4, duration: 0.8 }}
|
||||||
|
className="font-body text-lg font-light text-stone-500 leading-relaxed max-w-lg"
|
||||||
|
>
|
||||||
|
Our atelier is a sanctuary of slow creation. Located in the quiet hills, we practice the ancient art of wheel-throwing, honoring the raw beauty of natural clay.
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-12 lg:col-span-6 relative h-[600px] lg:h-[800px] w-full">
|
||||||
|
<motion.div
|
||||||
|
initial={{ clipPath: 'inset(100% 0 0 0)' }}
|
||||||
|
animate={{ clipPath: 'inset(0% 0 0 0)' }}
|
||||||
|
transition={{ delay: 0.2, duration: 1.5, ease: "easeOut" }}
|
||||||
|
className="h-full w-full"
|
||||||
|
>
|
||||||
|
<img src="/pottery-studio.png" alt="Atelier Studio" className="w-full h-full object-cover" />
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Philosophy Section */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 border-t border-stone-200 dark:border-stone-800 pt-24">
|
||||||
|
{[
|
||||||
|
{ title: "Material", text: "We work exclusively with locally sourced stoneware clay bodies, rich in iron and character." },
|
||||||
|
{ title: "Process", text: "Every piece is wheel-thrown, trimmed, and glazed by hand, ensuring no two objects are identical." },
|
||||||
|
{ title: "Function", text: "Designed to be used and loved. Our ceramics are durable, food-safe, and meant for daily rituals." }
|
||||||
|
].map((item, idx) => (
|
||||||
|
<motion.div
|
||||||
|
key={item.title}
|
||||||
|
initial={{ y: 20, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: idx * 0.2, duration: 0.8 }}
|
||||||
|
className="p-8 hover:bg-white dark:hover:bg-black transition-colors duration-500"
|
||||||
|
>
|
||||||
|
<h3 className="font-display text-2xl mb-4 text-text-main dark:text-white">{item.title}</h3>
|
||||||
|
<p className="font-body font-light text-stone-500 leading-relaxed">{item.text}</p>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Atelier;
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { COLLECTIONS } from '../constants';
|
||||||
|
|
||||||
|
const Collections: React.FC = () => {
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Collections
|
||||||
|
</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. Each collection explores a distinct form language and glaze palette.
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-16">
|
||||||
|
{COLLECTIONS.map((item, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={item.id}
|
||||||
|
initial={{ y: 40, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ delay: 0.2 + (index * 0.1), duration: 0.8, ease: "easeOut" }}
|
||||||
|
className="group cursor-pointer"
|
||||||
|
>
|
||||||
|
{/* Image Container with Darker Background for Contrast */}
|
||||||
|
<div className={`relative overflow-hidden mb-6 ${item.aspectRatio || 'aspect-[3/4]'} bg-stone-200 dark:bg-stone-800`}>
|
||||||
|
<motion.img
|
||||||
|
src={item.image}
|
||||||
|
alt={item.title}
|
||||||
|
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105 relative z-10"
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
/>
|
||||||
|
{/* Overlay on hover */}
|
||||||
|
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors duration-500 z-20 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-end border-b border-stone-200 dark:border-stone-800 pb-4">
|
||||||
|
<div>
|
||||||
|
<span className="text-xs uppercase tracking-widest text-stone-400 mb-1 block">{item.number}</span>
|
||||||
|
<h3 className="font-display text-2xl text-text-main dark:text-white">{item.title}</h3>
|
||||||
|
</div>
|
||||||
|
<span className="material-symbols-outlined opacity-0 -translate-x-4 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-300 text-stone-400">arrow_forward</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Collections;
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { JOURNAL_ENTRIES } from '../constants';
|
||||||
|
|
||||||
|
const Editorial: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="bg-white dark:bg-black min-h-screen pt-32 pb-24">
|
||||||
|
<div className="max-w-[1920px] mx-auto px-6 md:px-12">
|
||||||
|
<div className="text-center mb-24">
|
||||||
|
<motion.span
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
className="font-display text-xs tracking-[0.3em] uppercase mb-4 block text-stone-400"
|
||||||
|
>
|
||||||
|
The Journal
|
||||||
|
</motion.span>
|
||||||
|
<motion.h1
|
||||||
|
initial={{ y: 20, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ delay: 0.2 }}
|
||||||
|
className="font-display text-6xl md:text-9xl font-light text-text-main dark:text-white"
|
||||||
|
>
|
||||||
|
Editorial
|
||||||
|
</motion.h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Featured Article */}
|
||||||
|
<div className="relative w-full h-[70vh] mb-24 cursor-pointer group overflow-hidden">
|
||||||
|
<motion.img
|
||||||
|
initial={{ scale: 1.1 }}
|
||||||
|
animate={{ scale: 1 }}
|
||||||
|
transition={{ duration: 1.5 }}
|
||||||
|
src={JOURNAL_ENTRIES[0].image}
|
||||||
|
alt="Featured Article"
|
||||||
|
className="w-full h-full object-cover transition-transform duration-[2s] group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-black/20 group-hover:bg-black/40 transition-colors duration-500" />
|
||||||
|
<div className="absolute bottom-0 left-0 p-8 md:p-16 text-white w-full md:w-2/3">
|
||||||
|
<span className="uppercase tracking-widest text-xs border border-white/30 px-3 py-1 mb-6 inline-block backdrop-blur-sm">Featured Story</span>
|
||||||
|
<h2 className="font-display text-4xl md:text-6xl mb-6 leading-tight">{JOURNAL_ENTRIES[0].title}</h2>
|
||||||
|
<p className="font-body text-lg md:text-xl font-light opacity-90 max-w-xl">{JOURNAL_ENTRIES[0].description}</p>
|
||||||
|
<div className="mt-8 flex items-center space-x-2 text-xs uppercase tracking-widest">
|
||||||
|
<span>Read Article</span>
|
||||||
|
<span className="material-symbols-outlined text-sm">arrow_forward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Article Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-20 max-w-5xl mx-auto">
|
||||||
|
{JOURNAL_ENTRIES.slice(1).map((entry, idx) => (
|
||||||
|
<motion.div
|
||||||
|
key={entry.id}
|
||||||
|
initial={{ y: 40, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: idx * 0.2 }}
|
||||||
|
className="group cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="aspect-[4/3] overflow-hidden mb-8">
|
||||||
|
<img src={entry.image} alt={entry.title} className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-4 mb-4 text-xs uppercase tracking-widest text-stone-400">
|
||||||
|
<span>{entry.category}</span>
|
||||||
|
<span className="w-1 h-1 bg-stone-300 rounded-full" />
|
||||||
|
<span>{entry.date}</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-display text-3xl mb-4 text-text-main dark:text-white group-hover:underline decoration-1 underline-offset-4">{entry.title}</h3>
|
||||||
|
<p className="font-body font-light text-stone-500">{entry.description}</p>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
{/* Dummy extra entry to fill grid */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 40, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: 0.4 }}
|
||||||
|
className="group cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="aspect-[4/3] overflow-hidden mb-8 bg-stone-100 dark:bg-stone-800 flex items-center justify-center">
|
||||||
|
<img src="/collection-tableware.png" alt="Archive" className="w-full h-full object-cover opacity-80 transition-transform duration-700 group-hover:scale-105" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-4 mb-4 text-xs uppercase tracking-widest text-stone-400">
|
||||||
|
<span>Archive</span>
|
||||||
|
<span className="w-1 h-1 bg-stone-300 rounded-full" />
|
||||||
|
<span>2023</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-display text-3xl mb-4 text-text-main dark:text-white group-hover:underline decoration-1 underline-offset-4">Explore Past Issues</h3>
|
||||||
|
<p className="font-body font-light text-stone-500">Dive into our archive of stories, guides, and studio updates.</p>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Editorial;
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Hero from '../components/Hero';
|
||||||
|
import FeatureSection from '../components/FeatureSection';
|
||||||
|
import HorizontalScrollSection from '../components/HorizontalScrollSection';
|
||||||
|
import Collections from '../components/Collections';
|
||||||
|
import QuoteSection from '../components/QuoteSection';
|
||||||
|
import JournalSection from '../components/JournalSection';
|
||||||
|
import GallerySection from '../components/GallerySection';
|
||||||
|
|
||||||
|
const Home: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<Hero />
|
||||||
|
<FeatureSection />
|
||||||
|
<HorizontalScrollSection />
|
||||||
|
<Collections />
|
||||||
|
<QuoteSection />
|
||||||
|
<JournalSection />
|
||||||
|
<GallerySection />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
After Width: | Height: | Size: 622 KiB |
|
After Width: | Height: | Size: 566 KiB |
|
After Width: | Height: | Size: 794 KiB |
|
After Width: | Height: | Size: 808 KiB |
|
After Width: | Height: | Size: 631 KiB |
|
After Width: | Height: | Size: 695 KiB |
|
After Width: | Height: | Size: 696 KiB |
|
After Width: | Height: | Size: 879 KiB |
|
After Width: | Height: | Size: 701 KiB |
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": [
|
||||||
|
"ES2022",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"allowJs": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
export interface NavItem {
|
||||||
|
label: string;
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollectionItem {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
number: string;
|
||||||
|
image: string;
|
||||||
|
aspectRatio: string; // Tailwind class like aspect-[3/4]
|
||||||
|
gridClasses?: string; // Optional layout adjustments
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JournalEntry {
|
||||||
|
id: number;
|
||||||
|
category: string;
|
||||||
|
date: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
image: string;
|
||||||
|
marginTop?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FooterSection {
|
||||||
|
title: string;
|
||||||
|
links: NavItem[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import path from 'path';
|
||||||
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, '.', '');
|
||||||
|
return {
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
},
|
||||||
|
plugins: [react()],
|
||||||
|
define: {
|
||||||
|
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||||
|
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, '.'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,379 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>IKKAI Ceramics - Editorial Collection</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;1,300;1,400&family=Manrope:wght@200;300;400;500&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: "#292524", // Warm Charcoal
|
||||||
|
secondary: "#78716C", // Warm Stone
|
||||||
|
"background-light": "#F5F4F0", // Soft Sand / Alabaster (Hero)
|
||||||
|
"background-dark": "#1C1917", // Dark Warm Grey
|
||||||
|
// New Sectional Colors
|
||||||
|
"sage": "#D4D9D1", // Soft Sage Green
|
||||||
|
"warm-grey": "#DAD7D4", // Warm Grey
|
||||||
|
"clay-dark": "#33302D", // Deep Charcoal / Clay
|
||||||
|
"terracotta-soft": "#E6DDD5", // Pale Ochre / Soft Terracotta
|
||||||
|
"accent-sand": "#E7E5E4",
|
||||||
|
"accent-warm": "#D6D3D1",
|
||||||
|
"text-main": "#1C1917",
|
||||||
|
"text-muted": "#57534E",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
display: ["Cormorant Garamond", "serif"],
|
||||||
|
body: ["Manrope", "sans-serif"],
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
'10xl': '10rem',
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
'128': '32rem',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings:
|
||||||
|
'FILL' 0,
|
||||||
|
'wght' 300,
|
||||||
|
'GRAD' 0,
|
||||||
|
'opsz' 24
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
.parallax-bg {
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #F5F4F0;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #D6D3D1;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #78716C;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="font-body bg-background-light dark:bg-background-dark text-text-main dark:text-gray-200 antialiased transition-colors duration-500 selection:bg-stone-200 selection:text-black">
|
||||||
|
<div class="bg-primary dark:bg-black text-white text-[10px] tracking-[0.2em] text-center py-3 uppercase font-light">
|
||||||
|
Complimentary shipping on orders over €200
|
||||||
|
</div>
|
||||||
|
<header class="fixed top-0 w-full z-50 bg-background-light/80 dark:bg-background-dark/80 backdrop-blur-md border-b border-stone-200/50 dark:border-stone-800/50 transition-all duration-300">
|
||||||
|
<div class="max-w-[1920px] mx-auto px-6 md:px-12">
|
||||||
|
<div class="flex justify-between items-center h-24">
|
||||||
|
<div class="flex items-center md:hidden">
|
||||||
|
<button class="text-text-main dark:text-white p-2" type="button">
|
||||||
|
<span class="material-symbols-outlined">menu</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<a class="font-display text-4xl md:text-5xl font-light tracking-widest uppercase text-text-main dark:text-white" href="#">
|
||||||
|
IKKAI
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<nav class="hidden md:flex space-x-16">
|
||||||
|
<a class="text-xs uppercase tracking-[0.15em] text-text-muted dark:text-gray-400 hover:text-primary dark:hover:text-white transition-colors duration-300" href="#">Collections</a>
|
||||||
|
<a class="text-xs uppercase tracking-[0.15em] text-text-muted dark:text-gray-400 hover:text-primary dark:hover:text-white transition-colors duration-300" href="#">Atelier</a>
|
||||||
|
<a class="text-xs uppercase tracking-[0.15em] text-text-muted dark:text-gray-400 hover:text-primary dark:hover:text-white transition-colors duration-300" href="#">Editorial</a>
|
||||||
|
</nav>
|
||||||
|
<div class="flex items-center space-x-6 text-text-main dark:text-white">
|
||||||
|
<button class="hover:text-text-muted transition-colors hidden sm:block">
|
||||||
|
<span class="material-symbols-outlined text-xl font-light">search</span>
|
||||||
|
</button>
|
||||||
|
<a class="hover:text-text-muted transition-colors relative group" href="#">
|
||||||
|
<span class="material-symbols-outlined text-xl font-light">shopping_bag</span>
|
||||||
|
<span class="absolute -top-1 -right-2 bg-text-main dark:bg-white text-white dark:text-black text-[9px] w-4 h-4 flex items-center justify-center rounded-full opacity-0 group-hover:opacity-100 transition-opacity">2</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<section class="relative min-h-screen pt-24 w-full flex flex-col md:flex-row items-center overflow-hidden bg-background-light dark:bg-background-dark">
|
||||||
|
<div class="w-full md:w-5/12 h-full flex flex-col justify-center px-6 md:pl-20 md:pr-12 py-20 z-10">
|
||||||
|
<span class="font-body text-xs uppercase tracking-[0.3em] text-text-muted mb-8 ml-1 block">New Collection 2024</span>
|
||||||
|
<h1 class="font-display text-6xl md:text-7xl lg:text-8xl xl:text-9xl text-text-main dark:text-white font-thin leading-[0.9] mb-10">
|
||||||
|
Form <br/><span class="italic pl-12 md:pl-20 text-text-muted">of</span> Earth
|
||||||
|
</h1>
|
||||||
|
<p class="font-body text-text-muted dark:text-gray-400 text-sm md:text-base font-light mb-12 max-w-sm leading-loose ml-1">
|
||||||
|
Discover the imperfect perfection of hand-thrown stoneware. Pieces that bring silence and intention to your daily rituals.
|
||||||
|
</p>
|
||||||
|
<div class="ml-1">
|
||||||
|
<a class="inline-block border-b border-text-main dark:border-white pb-1 text-text-main dark:text-white font-body text-xs uppercase tracking-[0.2em] hover:text-text-muted transition-colors duration-300" href="#">
|
||||||
|
View The Collection
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-7/12 h-[60vh] md:h-screen relative">
|
||||||
|
<div class="absolute inset-0 bg-stone-200 dark:bg-stone-800">
|
||||||
|
<img alt="Minimalist ceramic vase with single branch" class="w-full h-full object-cover object-center brightness-95" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBvZAnP7XY-eGn5eL8LHgtIhpGoP7m9sPeESpuqYuRRomwq-VbkWnk3og2Zz3Y008nJOg3m1_HX59c-oDrJrtdXltWuBceYV0538LLAwbtslwnO_7BOuBw5y4v-4m9JtFou3lwflr2jbi_6zW8EZaxmGL6_EqVOkYct5HiXbw0JYTYhxPegtBET_-AeTOqJHvuDJGSzRAImHVh74ucDQgnl6QzlQZ17IKZU8o-1SdfLMvL8EvTb-jAeb7wv-wHpLSPbHK4XwYiVszk"/>
|
||||||
|
</div>
|
||||||
|
<div class="absolute bottom-10 left-10 md:left-auto md:right-20 bg-background-light/90 dark:bg-background-dark/90 backdrop-blur p-6 max-w-xs hidden md:block shadow-sm">
|
||||||
|
<p class="font-display italic text-xl text-text-main dark:text-gray-200">"In emptiness, there is fullness."</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="py-32 md:py-48 bg-sage dark:bg-stone-900 overflow-hidden relative transition-colors duration-500">
|
||||||
|
<div class="max-w-[1800px] mx-auto px-6">
|
||||||
|
<div class="relative flex flex-col md:block">
|
||||||
|
<div class="hidden md:block absolute -top-24 left-10 z-0 select-none opacity-[0.03] dark:opacity-[0.05] pointer-events-none">
|
||||||
|
<span class="font-display text-[20rem] leading-none text-black dark:text-white">CRAFT</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-3/5 h-[600px] md:h-[800px] ml-auto relative z-10 parallax-bg shadow-2xl" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAwcT6-AV8qpFzjZnVg9E60XgAEjN8kSfvJhBviAQySkGqDm950ofKMSXpKUvN44YobZIMvBeV-QcLz_xE7hQKdYdSIjoPasavbYAMbtqN4XySDFYqxgVq34e2R0BJJqX0WAzSFTcTd1WbnDjhlb8Vr8NyVtoB-09ArCsO6ZwnpvplPNHWwqzA0pebI6c32n8BPTMwvL5MuqUV8T5-tEw6MiNVyXJKGX-EIAxboK60MBn0tdYRBneueLDgcjvJ-s7R6yVBe1H4j1kc');">
|
||||||
|
</div>
|
||||||
|
<div class="relative z-20 mt-[-100px] md:mt-0 md:absolute md:top-1/2 md:left-20 md:-translate-y-1/2 bg-white dark:bg-stone-900 p-12 md:p-20 shadow-xl max-w-xl mx-auto md:mx-0">
|
||||||
|
<span class="block w-12 h-[1px] bg-text-main mb-8"></span>
|
||||||
|
<h2 class="font-display text-4xl md:text-5xl font-light mb-8 text-text-main dark:text-white leading-tight">
|
||||||
|
The Art of <br/><i class="font-thin">Slow Living</i>
|
||||||
|
</h2>
|
||||||
|
<p class="font-body font-light text-text-muted dark:text-gray-400 mb-10 leading-loose text-sm">
|
||||||
|
We believe in the beauty of handmade objects. Our collection features a curated selection of ceramics designed to elevate the everyday. From sturdy mugs for your morning coffee to elegant vases that breathe life into a room, each piece is crafted with patience and intention.
|
||||||
|
</p>
|
||||||
|
<a class="group inline-flex items-center text-xs uppercase tracking-[0.2em] text-text-main dark:text-white font-medium" href="#">
|
||||||
|
Read Our Story <span class="ml-2 group-hover:translate-x-1 transition-transform">→</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="py-32 bg-warm-grey dark:bg-[#141210] transition-colors duration-500">
|
||||||
|
<div class="max-w-[1920px] mx-auto px-6 md:px-12">
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-end mb-20 md:mb-32 px-4">
|
||||||
|
<h2 class="font-display text-5xl md:text-7xl font-thin text-text-main dark:text-white">Curated <span class="italic text-text-muted">Editions</span></h2>
|
||||||
|
<p class="hidden md:block font-body text-sm text-text-muted max-w-xs leading-relaxed text-right">
|
||||||
|
Explore our seasonal collections, fired in small batches.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 lg:gap-16">
|
||||||
|
<div class="flex flex-col space-y-16 md:space-y-32">
|
||||||
|
<a class="group block cursor-pointer" href="#">
|
||||||
|
<div class="relative overflow-hidden aspect-[3/4] mb-6">
|
||||||
|
<img alt="Tableware collection" class="w-full h-full object-cover transition-transform duration-[1.5s] ease-out group-hover:scale-110" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDEMG0U2yN-srAgpA4aSXewbFyRyFWnm181AquAJCRzLwgPHNHbs-fxFKQ8DMbozvyRU-s0LUPRKoZtht1-Lp3RDOfKE3jCrAD_A4tl9BXwHGUcAPWj0jBq3C9plosFkHIzYUDBtbq_Azg3RK2csufB9tH_tIJhMW--_IIfZeAltM9sgTD5wAPRPIUyV-0iemF2eWLZnx0IfTLZSkN930lHZ6aHxWChqHqoVMUTdYxqHPt0tpUW3C082em7_4fuoRpWdf4_flYYoO4"/>
|
||||||
|
<div class="absolute inset-0 bg-black/10 group-hover:bg-transparent transition-colors duration-500"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center border-t border-gray-400/50 dark:border-gray-800 pt-4">
|
||||||
|
<h3 class="font-display text-3xl font-light text-text-main dark:text-white group-hover:italic transition-all">Tableware</h3>
|
||||||
|
<span class="text-xs uppercase tracking-widest text-text-muted">01</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="group block cursor-pointer" href="#">
|
||||||
|
<div class="relative overflow-hidden aspect-[4/3] mb-6">
|
||||||
|
<img alt="Lamp Shades collection" class="w-full h-full object-cover transition-transform duration-[1.5s] ease-out group-hover:scale-110" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAz5MOY7i5TxXxrGVaW7nItrMPEhwnNz5VkQ7BwzWHUBMfV3j8A42PekcfAMOXu7nP2pX7m-Trx0lBWwFq4RuDfJMghT-DwyJAP4nT2sTCgX_WosvcMQfj5koFU-CLX7CMboAxAPXWUWe3Q8xU4Zl0kysFKLG34fR_GaRlN0diovvLg1SQ6fLq2dMRg2o523onwafjD0f6XBDxbtWBsnfIp_2U1_0zFahOkW2JyyJhIZFVCTiP61CY2rkwqtmupBjzzY7iKcMtszhE"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center border-t border-gray-400/50 dark:border-gray-800 pt-4">
|
||||||
|
<h3 class="font-display text-3xl font-light text-text-main dark:text-white group-hover:italic transition-all">Lighting</h3>
|
||||||
|
<span class="text-xs uppercase tracking-widest text-text-muted">04</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col space-y-16 md:space-y-32 md:pt-32">
|
||||||
|
<a class="group block cursor-pointer" href="#">
|
||||||
|
<div class="relative overflow-hidden aspect-square mb-6">
|
||||||
|
<img alt="Home Decor collection" class="w-full h-full object-cover transition-transform duration-[1.5s] ease-out group-hover:scale-110" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBV8jgjafGxXCrSmiIUCN5Zwqv0cl-Ivw5hjQoa5ejFBx3a0C7zgeI_pywBpee7f0scHB_zrYQbI0zryX0P_F2w_xefVbl_8vkvSRMPhsqrs_z9u16FlDVgmXX9_PxhC8oRWZmGbtHsvXhfDEtvAi94tBJeQYTdG2a-XJ7gB0F8GLyvVl7_NHu9iB_TyVhbKIOv354VUmcNAehnGfuK0fTtAjQr0qxaHt8CD9pLJvfTeVJZF2VPRgToY5dN4eqRTRJrQPuLIW2aP9k"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center border-t border-gray-400/50 dark:border-gray-800 pt-4">
|
||||||
|
<h3 class="font-display text-3xl font-light text-text-main dark:text-white group-hover:italic transition-all">Vases</h3>
|
||||||
|
<span class="text-xs uppercase tracking-widest text-text-muted">02</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="group block cursor-pointer" href="#">
|
||||||
|
<div class="relative overflow-hidden aspect-[3/4] mb-6">
|
||||||
|
<img alt="Accessories collection" class="w-full h-full object-cover transition-transform duration-[1.5s] ease-out group-hover:scale-110" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDlTM_GLiQWReZzrfgwPZXJ2OjRaelPwrF2URWBLiqQjOdyxy0onUB9FoX4kcIzTnQSRIfRZsg6Dt5BS6j5bE6SYhdkZ60HAOZJNybnBvZqfICwldKNMiTg9-fm4X1otiHO8vO_Hr-DuwsaE818YSDiW2vyVH947T8peRurHz-sYZu9gJgq9R4D3BtLrdbf9R6MaYmqGZ47NAwHV1BHicOSMFGxfhK-p5exDM963E8qBTwl3PEXcRdnAq6-B-ada0XJ3jz8iA4Cavo"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center border-t border-gray-400/50 dark:border-gray-800 pt-4">
|
||||||
|
<h3 class="font-display text-3xl font-light text-text-main dark:text-white group-hover:italic transition-all">Serving</h3>
|
||||||
|
<span class="text-xs uppercase tracking-widest text-text-muted">05</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col space-y-16 md:space-y-32 md:pt-16 lg:pt-0">
|
||||||
|
<a class="group block cursor-pointer" href="#">
|
||||||
|
<div class="relative overflow-hidden aspect-[3/5] mb-6">
|
||||||
|
<img alt="Matcha Bowls collection" class="w-full h-full object-cover transition-transform duration-[1.5s] ease-out group-hover:scale-110" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD_jVXnz1bLom_g2aBEGMY2fmAe2vuv4pwP_NdhGGqtKhcIr36eQ-BQ5d3cMXXjIDURtvBt9juNVX8kJG1T404125GGwEoQmMCD4IawdGY7hDBeByI8PG9Z8Ioc-skCG9X5bdU9-O4PS6KBglPV8fnkyG6FjPkN0RdGvHWMZQ6iInrJhOqiqX3r-6YvmIpGSi5FoXyFnmcfLnf1faHq8kWIMuv0WgLHSlOFlB5MeIJAQwvuk5Gbk4drXCt2heYy5WRWIdutVdiQOa4"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center border-t border-gray-400/50 dark:border-gray-800 pt-4">
|
||||||
|
<h3 class="font-display text-3xl font-light text-text-main dark:text-white group-hover:italic transition-all">Kitchenware</h3>
|
||||||
|
<span class="text-xs uppercase tracking-widest text-text-muted">03</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="group block cursor-pointer" href="#">
|
||||||
|
<div class="relative overflow-hidden aspect-square mb-6">
|
||||||
|
<img alt="Furoshiki collection" class="w-full h-full object-cover transition-transform duration-[1.5s] ease-out group-hover:scale-110" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAI1p48nya5N3xLUE8fx6a0Cwniu_QuS5yo-wq6PE1W77n0orf57QyF1g4426uqGtiv02HHHXd40Sq81usdXnpOqvLiviW_gSGdtlorcOpaSl6R8k23cG_I-5v4pPVJiaTPqrhK1U3VtxLX5Bpj8x7NOtZT4K1jtI4NHt-S1A0GvBjM7jCfH_0y8Xw8L_R5br8I8_KmdbC7ACaNd4OAZUpJdt4UUANVy664jG4m9dZshHpa8Og4aFzZ1CRxmQExSVEzc0CKZ9GSLB0"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center border-t border-gray-400/50 dark:border-gray-800 pt-4">
|
||||||
|
<h3 class="font-display text-3xl font-light text-text-main dark:text-white group-hover:italic transition-all">Textiles</h3>
|
||||||
|
<span class="text-xs uppercase tracking-widest text-text-muted">06</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="py-32 bg-clay-dark dark:bg-black border-y border-stone-800/50 transition-colors duration-500">
|
||||||
|
<div class="max-w-5xl mx-auto px-6 text-center">
|
||||||
|
<span class="material-symbols-outlined text-4xl mb-8 text-stone-500 font-thin">format_quote</span>
|
||||||
|
<h3 class="font-display text-3xl md:text-5xl font-thin leading-snug text-stone-100 dark:text-stone-200 mb-10 italic">
|
||||||
|
"My pottery is designed to be both beautiful and practical. From minimalist vases to durable dinner plates, each piece has its own character."
|
||||||
|
</h3>
|
||||||
|
<p class="font-body text-xs uppercase tracking-[0.2em] text-stone-400">
|
||||||
|
Anonymous — Verified Collector
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="relative py-32 bg-terracotta-soft dark:bg-black overflow-hidden transition-colors duration-500">
|
||||||
|
<div class="absolute inset-0 z-0 mix-blend-multiply opacity-30">
|
||||||
|
<img alt="Atmospheric studio background" class="w-full h-full object-cover blur-3xl scale-110 grayscale" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAipMlYLTcRT_hdc3VePfFIlrA56VzZ5G2y3gcRfmIZMERwGFKq2N19Gqo6mw7uZowXmjl2eJ89TI3Mcud2OyOfadO3mPVF_v0sI0OHupqM49WEFcWzH-Wbu3DL6bQ46F2Y8SIAk-NUQy8psjcIdBKRrM8fqdn4eOPANYTXpVxkLMAm4R0Axy4aEKNdmj917ZKKTxvXB-J8nGlITJkJ-ua7XcZOwGnfK5ttzyWW35A0oOSffCf972gmpV27wrVQgYJNLS7UyDdyQIQ"/>
|
||||||
|
</div>
|
||||||
|
<div class="max-w-[1920px] mx-auto px-6 md:px-12 relative z-10">
|
||||||
|
<div class="flex justify-between items-baseline mb-20 border-b border-text-main/20 dark:border-gray-800 pb-6">
|
||||||
|
<h2 class="font-display text-6xl font-thin text-text-main dark:text-white">The <span class="italic">Journal</span></h2>
|
||||||
|
<a class="text-xs uppercase tracking-[0.2em] text-text-main dark:text-white hover:text-text-muted transition-colors" href="#">View Archive</a>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||||
|
<article class="group cursor-pointer">
|
||||||
|
<div class="relative h-[500px] overflow-hidden mb-8 shadow-md">
|
||||||
|
<img alt="Pottery workshop" class="w-full h-full object-cover transition-transform duration-[1.5s] group-hover:scale-105" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAipMlYLTcRT_hdc3VePfFIlrA56VzZ5G2y3gcRfmIZMERwGFKq2N19Gqo6mw7uZowXmjl2eJ89TI3Mcud2OyOfadO3mPVF_v0sI0OHupqM49WEFcWzH-Wbu3DL6bQ46F2Y8SIAk-NUQy8psjcIdBKRrM8fqdn4eOPANYTXpVxkLMAm4R0Axy4aEKNdmj917ZKKTxvXB-J8nGlITJkJ-ua7XcZOwGnfK5ttzyWW35A0oOSffCf972gmpV27wrVQgYJNLS7UyDdyQIQ"/>
|
||||||
|
<div class="absolute inset-0 bg-black/10 group-hover:bg-black/0 transition-colors duration-500"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-center space-x-4 mb-4">
|
||||||
|
<span class="text-[10px] uppercase tracking-[0.2em] text-text-muted border border-text-muted/30 px-2 py-1 rounded-full">Studio</span>
|
||||||
|
<span class="text-[10px] uppercase tracking-[0.2em] text-text-muted">Oct 03</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-display text-3xl text-text-main dark:text-white mb-4 leading-tight group-hover:underline decoration-1 underline-offset-4">Product Photography for Small Businesses</h3>
|
||||||
|
<p class="text-sm font-light text-text-muted dark:text-gray-400 leading-loose max-w-sm">
|
||||||
|
Learning that beautiful products aren't enough on their own — you also need beautiful photos to tell the story.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="group cursor-pointer lg:mt-20">
|
||||||
|
<div class="relative h-[500px] overflow-hidden mb-8 shadow-md">
|
||||||
|
<img alt="Packing ceramics" class="w-full h-full object-cover transition-transform duration-[1.5s] group-hover:scale-105" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAaWGnX_NYT3S_lOflL2NJZGbWge4AAkvra4ymvF8ag-c1UKsOAIB-rsLVQXW5xIlPZipDiK8-ysPyv22xdgsvzs4EOXSSCcrT4Lb2YCe0u5orxRaZEA5TgxeoKq15zaWKSlmnHyPGjPd_7yglpfO13eZmbU5KaxFJ1KGO0UAxoO9BpsyCYgbgINMoSz3epGe5ZdwBWRH-5KCzjoLuXimFTLcd5bqg9T1YofTxgy2hWBMJzKkafyEniq8dP6hMmfNCLVcCHHHx0hRU"/>
|
||||||
|
<div class="absolute inset-0 bg-black/10 group-hover:bg-black/0 transition-colors duration-500"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-center space-x-4 mb-4">
|
||||||
|
<span class="text-[10px] uppercase tracking-[0.2em] text-text-muted border border-text-muted/30 px-2 py-1 rounded-full">Guide</span>
|
||||||
|
<span class="text-[10px] uppercase tracking-[0.2em] text-text-muted">Jul 15</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-display text-3xl text-text-main dark:text-white mb-4 leading-tight group-hover:underline decoration-1 underline-offset-4">The Art of Packaging</h3>
|
||||||
|
<p class="text-sm font-light text-text-muted dark:text-gray-400 leading-loose max-w-sm">
|
||||||
|
A practical guide for potters who want to package and send their handmade ceramics with care and confidence.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="group cursor-pointer">
|
||||||
|
<div class="relative h-[500px] overflow-hidden mb-8 shadow-md">
|
||||||
|
<img alt="Pottery tools" class="w-full h-full object-cover transition-transform duration-[1.5s] group-hover:scale-105" src="https://lh3.googleusercontent.com/aida-public/AB6AXuB8NOE5fGfN4d87cbcB27_Sh-nrlZlqxsTlYKbCZk98SoL-gHsPSWFNuxd1DxBq0g8Qysh0RBZ_btu-_WaH68UjV8SXPUalyxREvUqao4oXmra--pWAsaooWwKvWCzReYZ8kj7G-KIYIAo5mqudzB8n9C6-HVTNPPx9QgZHr_YsojMxlmmVcQ5bqk7-Lp0KtSAiVIPD2-1UE1dMGnkVSLUXKdgA65JIh8M3TtNkaJTGONuFKoTERrYOWe7u2BILnqyukTzlNcvK7Sc"/>
|
||||||
|
<div class="absolute inset-0 bg-black/10 group-hover:bg-black/0 transition-colors duration-500"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-center space-x-4 mb-4">
|
||||||
|
<span class="text-[10px] uppercase tracking-[0.2em] text-text-muted border border-text-muted/30 px-2 py-1 rounded-full">Wellness</span>
|
||||||
|
<span class="text-[10px] uppercase tracking-[0.2em] text-text-muted">Jun 11</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-display text-3xl text-text-main dark:text-white mb-4 leading-tight group-hover:underline decoration-1 underline-offset-4">Finding Motivation in Clay</h3>
|
||||||
|
<p class="text-sm font-light text-text-muted dark:text-gray-400 leading-loose max-w-sm">
|
||||||
|
10 gentle, practical tips to help potters find motivation during slow or uncertain moments in the creative process.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="py-20 bg-white dark:bg-background-dark">
|
||||||
|
<div class="max-w-[1920px] mx-auto px-4">
|
||||||
|
<div class="flex justify-between items-center mb-8 px-2">
|
||||||
|
<h4 class="font-display text-2xl text-text-main dark:text-white">@ikkai_ceramics</h4>
|
||||||
|
<a class="text-xs uppercase tracking-widest text-text-muted hover:text-primary transition-colors" href="#">Follow</a>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-6 gap-px bg-stone-100 dark:bg-stone-800 border border-stone-100 dark:border-stone-800">
|
||||||
|
<div class="aspect-square relative group overflow-hidden">
|
||||||
|
<img alt="Gallery image 1" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105 grayscale hover:grayscale-0" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDGY1PXPxlhIz9qU-XbDrdJjrRnX1pFo8YpH3HM1Crq9C6iVApx-qFkpjTj_MDOXXrX4jprk69hA0fmwR2EdURQyKaBLDAdkIE3vLKCyTRMhgyGerlpsy6_KZkZs-9hiaoWZPBFzvBIGWZ0i7sfbbtkQdBGJfK30yftDOPjI1NJfzBtsKNMbYOnXfmm-6u7uiovrM54rtRNWzsxmcvhRKQebZIUrERvGGYsUvUVARYEzSs4thyJnMYROk0LmoCrJ03_QjDvLzy_zjw"/>
|
||||||
|
</div>
|
||||||
|
<div class="aspect-square relative group overflow-hidden">
|
||||||
|
<img alt="Gallery image 2" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105 grayscale hover:grayscale-0" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBLafDd9RRuru_JkGfpxms6q-dFwzNrjKIazUT33dREB4eWcGSGtNYEKzTCgaRJYIGNvhE1z1tYSrr3ZMrMSQs_3oIJz2hrlYYq49EJaI8VD2YrM9akd50BsF3voaGW1yZFHM5S36ZbrCx3A8Id2wkDnlJ7TnUYdo76-TErMa6h94HxQYBSwLQESFBrPfDEi5Qf6MDfE-6i3HJNIYGS79zemso4U8mMRi-HA17y4ilifDoI2B4vc3ROE3HFbTVP6JxJxjklnlbMt28"/>
|
||||||
|
</div>
|
||||||
|
<div class="aspect-square relative group overflow-hidden">
|
||||||
|
<img alt="Gallery image 3" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105 grayscale hover:grayscale-0" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDGyeyp1BDBjdasLDmyEWUfA0eeSJ8qMmrlPS0IgsyNdW_-0NEbFZhO72z3kox8ys3T2PZOprxzbnDwBQfjdwLiISmT3eHil4LQgRNt35AucCh1b-BFmUjXB9vuQ-JAFY122ABF9AmGWIhKCH7HHJj-Pibiz4NlcEGf_-59GAtt_y4g6OzzZpBKzfZAXd2_h-2I3ZbMaNDYEVK9dZSwVSrUNxQEFRFLUoqoNm8_VqgfpwpXmGwSmEZfy-lfjnXDBF1AS3ipD7JR04E"/>
|
||||||
|
</div>
|
||||||
|
<div class="aspect-square relative group overflow-hidden">
|
||||||
|
<img alt="Gallery image 4" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105 grayscale hover:grayscale-0" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBxM-yqwsAyWN-4sNS6FCNyry1QP6_yKpP7av1uPQTEArxL29_Ir1vfmlNJ50UMMYBRXHwSYUS9dwgO9hsccM8QKk0DybH5Hsa0k1oA1PSD2fIButt6JbICrrLhqC51S37PtN7vpPxtlqFPXQyaGEQl8r8eZbbROIqtUCGdWks_prak9UNTbeph0gHDa0Xlw8HtXRSgQCqONjRuWVWC9ynnqpqXXgLIeCvCVBUiQuXxCnhLsMDWOcw8sjaVSFKo1tRi9IUFRaXEGu0"/>
|
||||||
|
</div>
|
||||||
|
<div class="aspect-square relative group overflow-hidden">
|
||||||
|
<img alt="Gallery image 5" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105 grayscale hover:grayscale-0" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDD63SnScOyJcWSHLRucaOZ46wQpGV0c-ljb4DvnaURof8aTmuMD75dT1UUKXzDwWBUKGqu3nNlUUOXnEfjLE8pwAdvRKHhunOunZW5qbw35eY1vH14HXGqQSe90m7RUxku70QRlVS338tKQEAJ9TasOSte56oSEmKzUC0q9VF9P8GTl-0R8CcmvQ9hfwRIe34s2QUEwE96LYTREHdWZI6RRZojG8MTeV1qGFgFgjwEqnYbIGCFdW5TFMyTvkuPd1R0IBNfWZzhJkM"/>
|
||||||
|
</div>
|
||||||
|
<div class="aspect-square relative group overflow-hidden">
|
||||||
|
<img alt="Gallery image 6" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105 grayscale hover:grayscale-0" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBQAOyWAgHl3Uf9zP9-cmp1ZPrAw5wi7GP1div8GHtarOvO68Kn8069PqiCFYs91kLD1YWcb9tk3y9Fm12AmMfRpIIvNTvAWxkZ7xL0BWM_UZ5BPmvSVuRDXKcvg5_qQVXJOy5ub3Yu3oBqKhR617MhwY4F_Am0cNClmSgPaYHALRi-CB3_hlLdgXQhI0dP5j7yNqlrTxHHv34vRQWvg2_Htkum0XcSQHuK9-A89-Cgcz5-V-FzCjxKPzAROoN0OKL9YxjRXHkwQjk"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<footer class="bg-primary dark:bg-black text-stone-400 py-24 px-6 md:px-12">
|
||||||
|
<div class="max-w-[1920px] mx-auto">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-12 gap-12 border-b border-stone-700 pb-20 mb-12">
|
||||||
|
<div class="md:col-span-4">
|
||||||
|
<a class="font-display text-4xl text-white block mb-8" href="#">IKKAI</a>
|
||||||
|
<p class="font-body text-sm font-light leading-loose max-w-sm mb-8">
|
||||||
|
Handcrafted ceramics for the modern home. Created with intention, fired with patience, and delivered with care.
|
||||||
|
</p>
|
||||||
|
<form class="flex border-b border-stone-600 pb-2 max-w-xs">
|
||||||
|
<input class="bg-transparent border-none text-white placeholder-stone-500 focus:ring-0 text-sm w-full p-0" placeholder="Join our newsletter" type="email"/>
|
||||||
|
<button class="text-xs uppercase tracking-widest text-white hover:text-stone-300" type="submit">Subscribe</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="md:col-span-2 md:col-start-7">
|
||||||
|
<h4 class="text-white text-xs font-bold uppercase tracking-widest mb-8">Shop</h4>
|
||||||
|
<ul class="space-y-4 text-xs font-light tracking-widest uppercase">
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">All Ceramics</a></li>
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">New Arrivals</a></li>
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">Best Sellers</a></li>
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">Gift Cards</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="md:col-span-2">
|
||||||
|
<h4 class="text-white text-xs font-bold uppercase tracking-widest mb-8">Company</h4>
|
||||||
|
<ul class="space-y-4 text-xs font-light tracking-widest uppercase">
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">Our Story</a></li>
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">Sustainability</a></li>
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">Careers</a></li>
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">Press</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="md:col-span-2">
|
||||||
|
<h4 class="text-white text-xs font-bold uppercase tracking-widest mb-8">Support</h4>
|
||||||
|
<ul class="space-y-4 text-xs font-light tracking-widest uppercase">
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">FAQ</a></li>
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">Shipping</a></li>
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">Returns</a></li>
|
||||||
|
<li><a class="hover:text-white transition-colors" href="#">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center text-[10px] tracking-widest uppercase">
|
||||||
|
<p>© 2024 IKKAI Ceramics. All rights reserved.</p>
|
||||||
|
<div class="flex space-x-6 mt-4 md:mt-0">
|
||||||
|
<a class="hover:text-white" href="#">Privacy</a>
|
||||||
|
<a class="hover:text-white" href="#">Terms</a>
|
||||||
|
<a class="hover:text-white" href="#">Cookies</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
|
After Width: | Height: | Size: 7.2 MiB |
|
|
@ -0,0 +1,96 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>HOTCHPOTSH — The Art of the Spin</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,200,0,0" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="prototype-header">
|
||||||
|
<div class="logo">HOTCHPOTSH</div>
|
||||||
|
<div class="nav-minimal">Prototype 02 / Product Spin</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="scroll-container">
|
||||||
|
|
||||||
|
<!-- Intro Section -->
|
||||||
|
<section class="intro-section">
|
||||||
|
<div class="intro-content">
|
||||||
|
<span class="label">Excellence in Form</span>
|
||||||
|
<h1>Tracing the Hand of the Maker</h1>
|
||||||
|
<p>A study of rotation, material, and light. Scroll to explore the silhouette of our signature Stoneware Mug.</p>
|
||||||
|
<div class="scroll-indicator">
|
||||||
|
<span class="material-symbols-outlined animate-bounce">expand_more</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Sticky Product Wrapper -->
|
||||||
|
<div class="product-stage">
|
||||||
|
<div class="cup-wrapper">
|
||||||
|
<!-- Canvas for smooth image sequence -->
|
||||||
|
<canvas id="cup-canvas"></canvas>
|
||||||
|
<!-- Hidden video for source buffering -->
|
||||||
|
<video id="cup-video" src="cup_spin.mp4" playsinline muted preload="auto" style="display: none;"></video>
|
||||||
|
<div class="stage-overlay"></div>
|
||||||
|
|
||||||
|
<!-- Buffering Status -->
|
||||||
|
<div id="buffer-status" class="buffer-info">
|
||||||
|
<span class="loader-dots"></span>
|
||||||
|
<span class="status-text">Refining silhouette...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scrolling Text Sections -->
|
||||||
|
<div class="content-section" data-step="1">
|
||||||
|
<div class="text-block">
|
||||||
|
<span class="step-num">01</span>
|
||||||
|
<h2>The Form</h2>
|
||||||
|
<p>Hand-thrown on the wheel, embracing imperfect symmetry. Every curve is a witness to the tactile dialogue between hands and clay.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-section" data-step="2">
|
||||||
|
<div class="text-block">
|
||||||
|
<span class="step-num">02</span>
|
||||||
|
<h2>The Clay</h2>
|
||||||
|
<p>Locally sourced stoneware, rich in texture and history. We use a high-iron body that produces subtle speckling during the long firing process.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-section" data-step="3">
|
||||||
|
<div class="text-block">
|
||||||
|
<span class="step-num">03</span>
|
||||||
|
<h2>The Glaze</h2>
|
||||||
|
<p>Fired at 1200°C to achieve a unique, stone-like finish. Our satin-matte glaze is applied by dipping, creating natural variations in depth.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-section" data-step="4">
|
||||||
|
<div class="text-block">
|
||||||
|
<span class="step-num">04</span>
|
||||||
|
<h2>Daily Ritual</h2>
|
||||||
|
<p>Designed to be held, used, and cherished every morning. A vessel not just for coffee, but for a moment of quiet reflection.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Outro Section -->
|
||||||
|
<section class="outro-section">
|
||||||
|
<div class="outro-content">
|
||||||
|
<h2>Limited Release</h2>
|
||||||
|
<p>The Studio Collection / Batch 014</p>
|
||||||
|
<button class="btn-minimal">Shop Collection</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
from PIL import Image
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def remove_white_background(input_path, output_path, threshold=240):
|
||||||
|
img = Image.open(input_path).convert("RGBA")
|
||||||
|
data = np.array(img)
|
||||||
|
|
||||||
|
# RGB values
|
||||||
|
r, g, b, a = data.T
|
||||||
|
|
||||||
|
# Define white areas (pixels > threshold)
|
||||||
|
white_areas = (r > threshold) & (g > threshold) & (b > threshold)
|
||||||
|
|
||||||
|
# Set alpha to 0 for white areas
|
||||||
|
data[..., 3][white_areas.T] = 0
|
||||||
|
|
||||||
|
# Create new image
|
||||||
|
new_img = Image.fromarray(data)
|
||||||
|
new_img.save(output_path)
|
||||||
|
print(f"Saved transparent image to {output_path}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Use relative paths so it works in WSL/Linux too
|
||||||
|
remove_white_background("cup.png", "cup_transparent.png")
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
|
const canvas = document.querySelector("#cup-canvas");
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
const video = document.querySelector("#cup-video");
|
||||||
|
const status = document.querySelector("#buffer-status");
|
||||||
|
|
||||||
|
const frames = [];
|
||||||
|
let isBuffered = false;
|
||||||
|
|
||||||
|
// 1. Canvas Setup
|
||||||
|
function resizeCanvas() {
|
||||||
|
if (video.videoWidth) {
|
||||||
|
canvas.width = video.videoWidth;
|
||||||
|
canvas.height = video.videoHeight;
|
||||||
|
} else {
|
||||||
|
canvas.width = 1000;
|
||||||
|
canvas.height = 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Buffering Logic (Capturing frames into memory)
|
||||||
|
video.addEventListener("loadedmetadata", () => {
|
||||||
|
resizeCanvas();
|
||||||
|
startBuffering();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function startBuffering() {
|
||||||
|
video.currentTime = 0;
|
||||||
|
video.playbackRate = 1; // Standard speed for better capture quality
|
||||||
|
await video.play();
|
||||||
|
|
||||||
|
function capture() {
|
||||||
|
if (video.paused || video.ended) {
|
||||||
|
finishBuffering();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw video frame to worker canvas and store as ImageBitmap
|
||||||
|
createImageBitmap(video).then(bitmap => {
|
||||||
|
frames.push(bitmap);
|
||||||
|
|
||||||
|
// Draw first frame immediately
|
||||||
|
if (frames.length === 1) renderFrame(0);
|
||||||
|
|
||||||
|
requestAnimationFrame(capture);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(capture);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishBuffering() {
|
||||||
|
isBuffered = true;
|
||||||
|
status.style.opacity = "0";
|
||||||
|
video.pause();
|
||||||
|
initScrollAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFrame(index) {
|
||||||
|
if (!frames[index]) return;
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
context.drawImage(frames[index], 0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Optimized Scroll Animation
|
||||||
|
function initScrollAnimation() {
|
||||||
|
const totalFrames = frames.length - 1;
|
||||||
|
const scrollObj = { frame: 0 };
|
||||||
|
|
||||||
|
// GSAP Timeline for the whole experience
|
||||||
|
const tl = gsap.timeline({
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: ".scroll-container",
|
||||||
|
start: "top top",
|
||||||
|
end: "bottom bottom",
|
||||||
|
scrub: 0.5, // Slight lag-behind for smoothness
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Frame scrubbing
|
||||||
|
tl.to(scrollObj, {
|
||||||
|
frame: totalFrames,
|
||||||
|
ease: "none",
|
||||||
|
onUpdate: () => renderFrame(Math.floor(scrollObj.frame))
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Intro Fade
|
||||||
|
gsap.to(".intro-content", {
|
||||||
|
opacity: 0,
|
||||||
|
y: -50,
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: ".intro-section",
|
||||||
|
start: "top top",
|
||||||
|
end: "bottom center",
|
||||||
|
scrub: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sub-animations for depth
|
||||||
|
tl.to(canvas, {
|
||||||
|
scale: 0.95,
|
||||||
|
rotateY: 5,
|
||||||
|
ease: "sine.inOut"
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Content text reveal
|
||||||
|
const sections = document.querySelectorAll(".content-section");
|
||||||
|
sections.forEach((section) => {
|
||||||
|
const text = section.querySelector(".text-block");
|
||||||
|
gsap.fromTo(text,
|
||||||
|
{ opacity: 0, y: 40 },
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 1,
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: section,
|
||||||
|
start: "top 70%",
|
||||||
|
end: "top 30%",
|
||||||
|
toggleActions: "play reverse play reverse",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exit
|
||||||
|
gsap.to(".product-stage", {
|
||||||
|
opacity: 0,
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: ".outro-section",
|
||||||
|
start: "top center",
|
||||||
|
end: "bottom bottom",
|
||||||
|
scrub: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,334 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;1,400&family=Inter:wght@300;400;500&family=JetBrains+Mono:wght@300&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg-color: #f8f6f3;
|
||||||
|
--text-color: #1c1917;
|
||||||
|
--accent-color: #44403c;
|
||||||
|
--muted-color: #a8a29e;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
overflow-x: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional: Grain Texture Overlay for that premium tactile feel */
|
||||||
|
body::after {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: url("https://grainy-gradients.vercel.app/noise.svg");
|
||||||
|
opacity: 0.04;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.prototype-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 3rem 4rem;
|
||||||
|
/* More air */
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 100;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-weight: 500;
|
||||||
|
border-bottom: 1px solid var(--text-color);
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scroll Container */
|
||||||
|
.scroll-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sections */
|
||||||
|
section {
|
||||||
|
position: relative;
|
||||||
|
height: 120vh;
|
||||||
|
/* More scroll space */
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 4rem;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Intro */
|
||||||
|
.intro-content {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-content .label {
|
||||||
|
display: block;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.6em;
|
||||||
|
color: var(--muted-color);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-content h1 {
|
||||||
|
font-family: 'Playfair Display', serif;
|
||||||
|
font-size: 6rem;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
line-height: 1.05;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-content p {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--accent-color);
|
||||||
|
line-height: 1.8;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto 4rem;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-indicator {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sticky Product Stage */
|
||||||
|
.product-stage {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
perspective: 1500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cup-wrapper {
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cup-canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
filter: drop-shadow(0 40px 80px rgba(0, 0, 0, 0.08));
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buffer-info {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -40px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
color: var(--muted-color);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
transition: opacity 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-dots::after {
|
||||||
|
content: " .";
|
||||||
|
animation: dots 1.5s steps(5, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dots {
|
||||||
|
20% {
|
||||||
|
content: " .";
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
content: " ..";
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
content: " ...";
|
||||||
|
}
|
||||||
|
|
||||||
|
80% {
|
||||||
|
content: " ....";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cup-video {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at center, transparent 30%, var(--bg-color) 70%);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content Sections */
|
||||||
|
.content-section {
|
||||||
|
position: relative;
|
||||||
|
height: 150vh;
|
||||||
|
/* Slow down the scroll feel */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 12%;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section:nth-child(odd) {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section:nth-child(even) {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-block {
|
||||||
|
max-width: 450px;
|
||||||
|
opacity: 1;
|
||||||
|
/* Handled by GSAP now */
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-num {
|
||||||
|
display: block;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--muted-color);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-num::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 40px;
|
||||||
|
top: 50%;
|
||||||
|
width: 60px;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--muted-color);
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-block h2 {
|
||||||
|
font-family: 'Playfair Display', serif;
|
||||||
|
font-size: 4.5rem;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-block p {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 2;
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-weight: 300;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Outro */
|
||||||
|
.outro-content {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outro-content h2 {
|
||||||
|
font-family: 'Playfair Display', serif;
|
||||||
|
font-size: 4rem;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-minimal {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding: 1rem 3rem;
|
||||||
|
border: 1px solid var(--text-color);
|
||||||
|
background: transparent;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-minimal:hover {
|
||||||
|
background: var(--text-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
.animate-bounce {
|
||||||
|
animation: bounce 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
20%,
|
||||||
|
50%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.intro-content h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cup-wrapper {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-block h2 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prototype-header {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 251 KiB |