/* archive.jsx — past events archive section + interactive primitives */ const { useState: useStateA, useEffect: useEffectA, useRef: useRefA } = React; /* --- IntersectionObserver reveal hook (local copy; scopes are separate) --- */ function useReveal() { const ref = useRefA(null); useEffectA(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { threshold: 0.18 }); io.observe(el); return () => io.disconnect(); }, []); return ref; } /* --- Count-up number animation --- */ function CountUp({ end, duration = 1800, suffix = '', prefix = '', decimals = 0 }) { const [val, setVal] = useStateA(0); const ref = useRefA(null); const started = useRefA(false); useEffectA(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting && !started.current) { started.current = true; const start = performance.now(); const tick = (t) => { const p = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(end * eased); if (p < 1) requestAnimationFrame(tick); else setVal(end); }; requestAnimationFrame(tick); io.disconnect(); } }); }, { threshold: 0.4 }); io.observe(el); return () => io.disconnect(); }, [end, duration]); const formatted = decimals > 0 ? val.toFixed(decimals) : Math.round(val).toLocaleString('ru-RU'); return {prefix}{formatted}{suffix}; } /* --- Tilt wrapper for cards --- */ function Tilt({ children, max = 6, className = '', style = {} }) { const ref = useRefA(null); const onMove = (e) => { const el = ref.current; if (!el) return; const r = el.getBoundingClientRect(); const x = (e.clientX - r.left) / r.width; const y = (e.clientY - r.top) / r.height; const rx = (0.5 - y) * max; const ry = (x - 0.5) * max; el.style.transform = `perspective(900px) rotateX(${rx}deg) rotateY(${ry}deg)`; }; const onLeave = () => { if (ref.current) ref.current.style.transform = ''; }; return (
{children}
); } /* --- Magnetic button hook --- */ function useMagnetic(strength = 18) { const ref = useRefA(null); useEffectA(() => { const el = ref.current; if (!el) return; let raf; const onMove = (e) => { const r = el.getBoundingClientRect(); const x = e.clientX - (r.left + r.width / 2); const y = e.clientY - (r.top + r.height / 2); cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { el.style.transform = `translate(${(x/r.width) * strength}px, ${(y/r.height) * strength}px)`; }); }; const onLeave = () => { cancelAnimationFrame(raf); el.style.transform = ''; }; el.addEventListener('mousemove', onMove); el.addEventListener('mouseleave', onLeave); return () => { el.removeEventListener('mousemove', onMove); el.removeEventListener('mouseleave', onLeave); }; }, [strength]); return ref; } /* --- Archive data --- */ const ARCHIVE = [ { id:'AR-024', when:'Май 2026', title:'Шаббат-ужин · Резиденция Альфаси', loc:'Кейсария', guests:24, photos:38, scene:'scene-dinner', size:'size-lg', cat:'Ужины', desc:'Закрытый вечер в частной резиденции у моря. Шеф Эял Шани, разговор о поколениях капитала и о том, как дети наследуют не деньги, а этику.' }, { id:'AR-023', when:'Апр 2026', title:'Регата 24h · Хайфский залив', loc:'Хайфа', guests:18, photos:124, scene:'scene-sea', size:'size-md', cat:'Спорт', desc:'Три яхты, два дня, ночёвка под звёздами. Команды формируются жеребьёвкой — лучший способ узнать партнёра до подписания term sheet.' }, { id:'AR-022', when:'Мар 2026', title:'Винный салон · Долина Эла', loc:'Иудейские горы', guests:32, photos:62, scene:'scene-vineyard', size:'size-md', cat:'Гастрономия', desc:'Резервные коллекции трёх виноделен. Авирам Кац открывает разговор о терруаре севера и инвестициях в винодельческий бизнес.' }, { id:'AR-021', when:'Фев 2026', title:'Открытие коллекции Шапиро', loc:'Тель‑Авив', guests:41, photos:89, scene:'scene-gallery', size:'size-sm', cat:'Культура', desc:'Приватный preview новой галереи Эстер Шапиро. Современное израильское искусство, разговор с куратором Тейт Модерн.' }, { id:'AR-020', when:'Янв 2026', title:'Пустынный завтрак · Махтеш Рамон', loc:'Негев', guests:16, photos:47, scene:'scene-desert', size:'size-sm', cat:'Путешествия', desc:'Восход в кратере, бедуинский завтрак, разговор о Негеве как следующей экономической границе Израиля.' }, { id:'AR-019', when:'Дек 2025', title:'Хануккальный приём · Старый город', loc:'Иерусалим', guests:52, photos:96, scene:'scene-jerusalem', size:'size-sm', cat:'Праздники', desc:'Восемь свечей, восемь спикеров, восемь историй о том, как свет переживает темноту. Закрытое мероприятие в Еврейском квартале.' }, { id:'AR-018', when:'Ноя 2025', title:'Tikkun Olam · вечер благотворения', loc:'Герцлия', guests:74, photos:148, scene:'scene-shabbat', size:'size-wide', cat:'Филантропия', desc:'Резиденты клуба собрали ₪4.2 млн на программу образования для детей репатриантов. Спикер — президент Israel Education Fund.' }, ]; const CATS = ['Все', 'Ужины', 'Гастрономия', 'Спорт', 'Культура', 'Путешествия', 'Праздники', 'Филантропия']; /* --- Archive section --- */ function Archive() { const [filter, setFilter] = useStateA('Все'); const [active, setActive] = useStateA(null); // selected event for lightbox const r = useReveal(); useEffectA(() => { const onKey = (e) => { if (e.key === 'Escape') setActive(null); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, []); useEffectA(() => { document.body.style.overflow = active ? 'hidden' : ''; }, [active]); const items = filter === 'Все' ? ARCHIVE : ARCHIVE.filter(a => a.cat === filter); return (
§ 04 — Архив

Что уже было
в этом году.

Мы не публикуем фото в соцсетях — но архив открыт для тех, кто думает о вступлении. Семь моментов из двадцати четырёх событий 2025—2026.

{CATS.map(c => ( ))}
{items.map(a => ( setActive(a)} /> ))}
Полный архив доступен резидентам · 2019—2026
Запросить доступ →
setActive(null)} />
); } function ArchiveCard({ a, onOpen }) { return (
FRAME {a.id}
{a.photos} КАДРОВ
{a.when}
{a.loc} {a.guests} гостей
); } function Lightbox({ event, onClose }) { if (!event) { return
; } return (
e.stopPropagation()}>
FRAME {event.id}
{event.photos} КАДРОВ
{event.when} · {event.loc}

{event.title.replace(/ /g, ' ')}

{event.guests}Гостей
{event.photos}Фото в архиве
{event.cat}Категория
); } Object.assign(window, { Archive, CountUp, Tilt, useMagnetic });