// pages.jsx — Home, Gallery, ProductDetail, About, Order
const { useState: usePageState, useEffect: usePageEffect, useMemo: usePageMemo, useRef: usePageRef } = React;
// ───────────────────────────────────────────────────────────────────
// HOME
// ───────────────────────────────────────────────────────────────────
const HomePage = ({ lang, setRoute, onOpenProduct, onOpenTelegram, accent, pattern }) => {
const c = COPY[lang];
const featured = PRODUCTS.filter((p) => ['p01', 'p04', 'p06', 'p07'].includes(p.id));
return (
{/* Hero */}
{c.hero.eyebrow}
{c.hero.title_a} {' '}
{c.hero.title_b}
{c.hero.title_c}
{c.hero.body}
setRoute('gallery')}
className="inline-flex items-center gap-2 h-12 px-6 rounded-full"
style={{ background: 'var(--ink)', color: 'var(--bg)', fontFamily: 'PT Sans', fontWeight: 700, letterSpacing: '0.04em' }}>
{c.hero.cta_browse} →
{/* Hero stats */}
{[
{ v: c.hero.stat_a_v, l: c.hero.stat_a_l },
{ v: c.hero.stat_b_v, l: c.hero.stat_b_l },
{ v: c.hero.stat_c_v, l: c.hero.stat_c_l }].
map((s, i) =>
)}
{/* Featured */}
{c.featured.title}
setRoute('gallery')} className="ulink eyebrow whitespace-nowrap" style={{ color: 'var(--ink)' }}>
{c.featured.link} →
{/* Philosophy */}
{c.philosophy.eyebrow}
{c.philosophy.title}
{c.philosophy.body}
setRoute('about')} className="ulink eyebrow" style={{ color: 'var(--ink)' }}>
{lang === 'ru' ? 'О мастерской' : 'About the studio'} →
{/* Process */}
{c.process.eyebrow}
Как рождается штука
{c.process.steps.map((s, i) =>
)}
{/* Closing CTA */}
{lang === 'ru' ? 'Возьмите штуку себе домой' : 'Take a piece home'}
{lang === 'ru' ?
<>Найдите ту, что останется с вами.> :
<>Find the one that stays with you.>}
setRoute('gallery')}
className="inline-flex items-center gap-2 h-12 px-6 rounded-full"
style={{ background: 'var(--bg)', color: 'var(--ink)', fontFamily: 'PT Sans', fontWeight: 700, letterSpacing: '0.04em' }}>
{c.hero.cta_browse} →
{lang === 'ru' ? 'Написать нам' : 'Message us'}
);
};
// ───────────────────────────────────────────────────────────────────
// GALLERY
// ───────────────────────────────────────────────────────────────────
const GalleryPage = ({ lang, onOpenProduct, layout, accent, pattern }) => {
const c = COPY[lang];
const [cat, setCat] = usePageState('all');
const [sort, setSort] = usePageState('default');
const items = usePageMemo(() => {
let list = cat === 'all' ? PRODUCTS : PRODUCTS.filter((p) => p.cat === cat);
if (sort === 'low') list = [...list].sort((a, b) => a.price - b.price);
if (sort === 'high') list = [...list].sort((a, b) => b.price - a.price);
return list;
}, [cat, sort]);
const gridCols = layout === 'editorial' ? 'grid-cols-1 md:grid-cols-12' : 'grid-cols-2 md:grid-cols-3 lg:grid-cols-4';
return (
{c.nav.gallery}
{lang === 'ru' ? <>Все штуки ,> : <>Every piece ,>}
{lang === 'ru' ? 'по одной за раз.' : 'one at a time.'}
{c.product.pricing}
{/* Filters */}
{CATEGORIES.map((k) => {
const active = cat === k.id;
const count = k.id === 'all' ? PRODUCTS.length : PRODUCTS.filter((p) => p.cat === k.id).length;
return (
setCat(k.id)}
className="inline-flex items-center gap-1.5 px-3.5 h-9 rounded-full text-[15px] font-bold"
style={{
background: active ? 'var(--ink)' : 'transparent',
color: active ? 'var(--bg)' : 'var(--ink-2)',
border: '0.5px solid ' + (active ? 'var(--ink)' : 'var(--hair-2)'),
letterSpacing: '0.02em',
transition: 'all .2s ease', fontSize: "15px"
}}>
{k[lang]}
{count}
);
})}
{lang === 'ru' ? 'Сортировка' : 'Sort'}
setSort(e.target.value)}
className="text-[15px] bg-transparent h-9 rounded-full"
style={{ border: '0.5px solid var(--hair-2)', color: 'var(--ink)', paddingLeft: '12px', paddingRight: '32px', fontSize: '15px' }}>
{lang === 'ru' ? 'По порядку' : 'Default'}
{lang === 'ru' ? 'Сначала дешевле' : 'Price ↑'}
{lang === 'ru' ? 'Сначала дороже' : 'Price ↓'}
{/* Grid */}
{items.length} {c.product.results}
{layout === 'editorial' ?
{items.map((p, i) => {
const span = i % 5 === 0 ? 'md:col-span-7' : i % 5 === 1 ? 'md:col-span-5' : i % 5 === 2 ? 'md:col-span-4' : i % 5 === 3 ? 'md:col-span-4' : 'md:col-span-4';
return (
);
})}
:
}
{items.length === 0 &&
{lang === 'ru' ? 'В этой категории пока ничего.' : 'Nothing in this category yet.'}
}
);
};
// ───────────────────────────────────────────────────────────────────
// PRODUCT DETAIL (slide-over)
// ───────────────────────────────────────────────────────────────────
const ProductDetail = ({ productId, lang, onClose, onOpenTelegram, onOpenProduct }) => {
if (!productId) return null;
const p = PRODUCTS.find((x) => x.id === productId);
if (!p) return null;
const c = COPY[lang];
const related = PRODUCTS.filter((x) => x.id !== p.id && x.cat === p.cat).slice(0, 3);
const [activeImg, setActiveImg] = usePageState(0);
const [lightbox, setLightbox] = usePageState(false);
// lock body scroll
usePageEffect(() => {
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {document.body.style.overflow = prev;};
}, [productId]);
const imgs = p.imgs || [];
const hasImgs = imgs.length > 0;
return (
{/* top bar */}
← {c.product.back}
{CATEGORIES.find((k) => k.id === p.cat)[lang]}
{/* image col */}
{hasImgs ? (
<>
setLightbox(true)}>
{imgs.length > 1 &&
{imgs.map((src, i) =>
setActiveImg(i)} className="cursor-pointer" style={{ opacity: i === activeImg ? 1 : 0.45, border: i === activeImg ? '2px solid var(--ink)' : '2px solid transparent', borderRadius: 8, overflow: 'hidden', transition: 'opacity .2s, border-color .2s' }}>
)}
}
>
) : (
<>
{['object', 'bag', 'print', 'textile'].slice(0, 4).map((_, i) =>
)}
>
)}
{/* lightbox */}
{lightbox && hasImgs &&
setLightbox(false)}>
setLightbox(false)}>×
}
{/* info col */}
{p.edition ? p.edition[lang] : ''}
{p.title[lang].split(' ')[0]}{' '}
{p.title[lang].split(' ').slice(1).join(' ')}
{p.subtitle[lang]}
{p.price.toLocaleString('ru-RU')} ₽
onOpenTelegram(p)} className="mt-6 w-full inline-flex items-center justify-center gap-2 h-12 rounded-full"
style={{ background: 'var(--ink)', color: 'var(--bg)', fontFamily: 'PT Sans', fontWeight: 700, letterSpacing: '0.04em' }}>
{c.product.cta}
{[
['material', p.material[lang]],
['dims', p.dims[lang]],
...(p.edition ? [['edition', p.edition[lang]]] : [])].
map(([k, v]) =>
{c.product[k]}
{v}
)}
{c.product.story}
{p.story[lang]}
{/* related */}
{related.length > 0 &&
{lang === 'ru' ? 'В этой же категории' : 'In the same category'}
}
);
};
// ───────────────────────────────────────────────────────────────────
// ABOUT
// ───────────────────────────────────────────────────────────────────
const AboutPage = ({ lang, onOpenTelegram, accent, pattern, setRoute }) => {
const c = COPY[lang];
return (
{c.about.eyebrow}
{c.about.title_a} {' '}
{c.about.title_b}
{c.about.title_c}
{c.about.lead}
{c.about.body.map((line, i) =>
{line}
)}
{c.values.eyebrow}
{lang === 'ru' ? 'Во что верим' : 'What we believe in'}
{c.values.items.map((v, i) =>
0{i + 1} / 03
{v.t}
{v.d}
)}
{c.process.eyebrow}
Как рождается штука
{c.process.steps.map((s, i) =>
)}
{lang === 'ru' ? <>Штуки ждут своих > : <>Pieces waiting for you >}
{lang === 'ru' ? 'Загляните в каталог — возможно, одна из них уже выбрала вас.' : 'Browse the catalogue — one of them may have already chosen you.'}
setRoute('gallery')}
className="inline-flex items-center gap-2 h-12 px-6 rounded-full whitespace-nowrap"
style={{ background: 'var(--ink)', color: 'var(--bg)', fontFamily: 'PT Sans', fontWeight: 700, letterSpacing: '0.04em' }}>
{lang === 'ru' ? 'В каталог' : 'Browse'} →
);
};
// ───────────────────────────────────────────────────────────────────
// ORDER
// ───────────────────────────────────────────────────────────────────
const OrderPage = ({ lang, onOpenTelegram, setRoute, accent, pattern }) => {
const c = COPY[lang];
const [openFaq, setOpenFaq] = usePageState(0);
return (
{c.order.eyebrow}
{c.order.title.split(',')[0]},
{c.order.title.split(',')[1].trim()} .
{/* Steps */}
{c.order.steps.map((s, i) =>
{s.n}
{s.t}
{s.d}
{i === 0 &&
setRoute('gallery')} className="mt-6 ulink eyebrow" style={{ color: 'var(--ink)' }}>
{lang === 'ru' ? 'Смотреть каталог' : 'Browse catalog'} →
}
{i === 1 &&
{lang === 'ru' ? 'Написать нам' : 'Message us'} →
}
)}
{/* FAQ */}
FAQ
{lang === 'ru' ? 'Частые вопросы' : 'Frequently asked'}
{lang === 'ru' ? 'Если ответа здесь нет — напишите нам, ответим в течение дня.' : 'If your question is not here — message us on Telegram, we reply within a day.'}
{c.order.faq.map((f, i) => {
const open = openFaq === i;
return (
setOpenFaq(open ? -1 : i)}
className="w-full flex items-center justify-between gap-6 py-6 text-left">
{f.q}
+
);
})}
{/* CTA */}
{lang === 'ru' ? 'Готовы выбрать?' : 'Ready to pick one?'}
{lang === 'ru' ? 'Откройте каталог или напишите нам. Расскажем, что в наличии и в работе.' : 'Open the catalog or message us. We will tell you what is in stock and in progress.'}
setRoute('gallery')} className="inline-flex items-center gap-2 h-12 px-6 rounded-full"
style={{ background: 'var(--bg)', color: 'var(--ink)', fontFamily: 'PT Sans', fontWeight: 700, letterSpacing: '0.04em' }}>
{c.hero.cta_browse} →
{c.nav.tg}
);
};
Object.assign(window, { HomePage, GalleryPage, ProductDetail, AboutPage, OrderPage });