// 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}

{/* 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) =>
{s.v}
{s.l}
)}
{/* Featured */}

{c.featured.title}

{featured.map((p) => )}
{/* Philosophy */}
{c.philosophy.eyebrow}

{c.philosophy.title}

{c.philosophy.body}

{/* Process */}
{c.process.eyebrow}

Как рождается штука

{c.process.steps.map((s, i) =>
{s.n}
{s.t}

{s.d}

)}
{/* Closing CTA */}
{lang === 'ru' ? 'Возьмите штуку себе домой' : 'Take a piece home'}

{lang === 'ru' ? <>Найдите ту, что останется с вами. : <>Find the one that stays with you.}

); }; // ─────────────────────────────────────────────────────────────────── // 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 ( ); })}
{lang === 'ru' ? 'Сортировка' : 'Sort'}
{/* 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.map((p) => )}
} {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 (
{CATEGORIES.find((k) => k.id === p.cat)[lang]}
{/* image col */}
{hasImgs ? ( <>
setLightbox(true)}> {p.title[lang]}
{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)}>
} {/* 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')} ₽
{[ ['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'}
{related.map((rp) => )}
} ); }; // ─────────────────────────────────────────────────────────────────── // 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) =>
{s.n}
{s.t}

{s.d}

)}

{lang === 'ru' ? <>Штуки ждут своих : <>Pieces waiting for you}

{lang === 'ru' ? 'Загляните в каталог — возможно, одна из них уже выбрала вас.' : 'Browse the catalogue — one of them may have already chosen you.'}

); }; // ─────────────────────────────────────────────────────────────────── // 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 && } {i === 1 && }
)}
{/* 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 (

{f.a}

); })}
{/* 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.'}

); }; Object.assign(window, { HomePage, GalleryPage, ProductDetail, AboutPage, OrderPage });