/* Galleries: People (editorial stack), Food (mosaic), Predmet (airy grid). Each sets its own --accent and uses a distinct layout. Real photos open in the lightbox; empty cells are fillable placeholders. */ /* Scales a display title to exactly fit its container width (caps at max). Fixes long words like "Предметка" overflowing the vw-based clamp. */ function FitTitle({ text, max = 260, min = 38 }) { const ref = React.useRef(null); React.useEffect(() => { const el = ref.current; if (!el) return; const fit = () => { const avail = el.parentElement ? el.parentElement.clientWidth : 0; if (!avail) return; let lo = min, hi = max; el.style.whiteSpace = "nowrap"; for (let i = 0; i < 20; i++) { const mid = (lo + hi) / 2; el.style.fontSize = mid + "px"; if (el.scrollWidth <= avail) lo = mid; else hi = mid; } el.style.fontSize = Math.floor(lo) + "px"; }; fit(); const ro = new ResizeObserver(fit); ro.observe(document.body); if (document.fonts && document.fonts.ready) document.fonts.ready.then(fit); return () => ro.disconnect(); }, [text]); return

{text}

; } function GalleryIntro({ g }) { const real = g.photos.length, total = real + g.slots; return (
{g.kicker}
{g.mood}

{g.lead}

{String(total).padStart(2, "0")} работ · {real} в галерее
); } function Slot({ id, label, ratio, style = {} }) { return ( ); } /* ---------------- PEOPLE — editorial alternating stack ---------------- */ function PeopleGallery() { const g = window.SITE.galleries.people; const P = g.photos; const ph = (i, aspect, cls) => ; const duo = { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 34, alignItems: "start" }; return (
{/* opener — first photo with text, kept as-is */}

«Хороший портрет — это про характер, а не про позу.»

Живой свет · мягкая ретушьваш темп
{ph(1, "4/5")} {ph(2, "4/5")}
{ph(5, "3/2")} {ph(6, "3/2")}
{ph(3, "4/5")} {ph(4, "4/5")}
{ph(7, "4/5")} {ph(8, "4/5")}
{ph(9, "4/5")} {ph(10, "4/5")}
{ph(11, "4/5")} {ph(12, "3/2")}
); } /* ---------------- FOOD — warm mosaic ---------------- */ function FoodGallery() { const g = window.SITE.galleries.food; const P = g.photos; // grid spans: [colSpan, rowSpan] — all cells are real photos now const cells = [ { i: 0, c: "span 3", r: "span 3" }, { i: 1, c: "span 3", r: "span 2" }, { i: 2, c: "span 2", r: "span 2" }, { i: 3, c: "span 1", r: "span 2" }, { i: 4, c: "span 2", r: "span 3" }, { i: 5, c: "span 4", r: "span 2" }, { i: 6, c: "span 3", r: "span 2" }, { i: 7, c: "span 3", r: "span 2" }, ]; return (
{cells.map((cell, k) => (
))}
); } /* ---------------- PREDMET — airy minimal grid ---------------- */ function PredmetGallery() { const g = window.SITE.galleries.predmet; const P = g.photos; const cells = [ { col: "1 / 7", ratio: "4/5" }, { col: "8 / 13", ratio: "4/5" }, { col: "1 / 8", ratio: "16/10" }, { col: "9 / 13", ratio: "3/4" }, { col: "1 / 5", ratio: "4/5" }, { col: "6 / 13", ratio: "16/10" }, { col: "1 / 5", ratio: "3/4" }, { col: "6 / 13", ratio: "16/10" }, { col: "1 / 7", ratio: "4/5" }, { col: "8 / 13", ratio: "4/5" }, { col: "1 / 8", ratio: "16/10" }, { col: "9 / 13", ratio: "3/4" }, ]; return (
{cells.map((cell, k) => (
))}
); } function GalleryFoot({ navTo, next }) { const nav = window.__navigate; return ( ); } Object.assign(window, { PeopleGallery, FoodGallery, PredmetGallery });