/* Maison Silya — Homepage: "A Private Viewing".
The homepage is a descent through a darkened gallery into the light,
in five movements. A fixed chapter rail tracks the descent and adapts
to each room. Pieces are presented as spotlit works floating in black. */
/* A piece presented as a lit work in a dark room: warm halo behind,
key light across the surface, a soft contact shadow grounding it. */
function LitPlate({ tone = 'stone-300', label = 'The piece', ratio = '4 / 5', image }) {
return (
{image && }
{!image && {label}}
);
}
function ChapterRail({ active, dark, show, onJump }) {
const items = [['I', t('home.r1')], ['II', t('home.r2')], ['III', t('home.r3')], ['IV', t('home.r4')], ['V', t('home.r5')]];
const base = dark ? 'rgba(247,243,236,0.55)' : 'rgba(34,29,22,0.45)';
const on = dark ? C('ivory') : C('ink');
return (
);
}
function HomeScreen({ onNav, onPiece }) {
const pieces = [
{ name: 'Monolith I', prov: t('p.prov.nepal'), detail: '300 × 400 cm · Wool & silk', edition: t('ed.8'), tone: 'stone-300', image: 'media/monolith-i.jpg', line: t('p.monolith.line') },
{ name: 'Sediment II', prov: t('p.prov.marrakech'), detail: '250 × 350 cm · Undyed wool', tone: 'stone-200', image: 'media/sediment-ii.jpg', line: t('p.sediment.line') },
{ name: 'Aurelian', prov: (window.__MS_LANG === 'fr' ? 'Tuftée main' : 'Hand-tufted'), detail: '260 × 360 cm · Laine & or', tone: 'champagne', image: 'media/aurelian.jpg', line: t('p.vein.line'), tufted: true },
];
const roman = ['I', 'II', 'III'];
const goTuftedSingulieres = () => { onNav('tufted'); setTimeout(() => { location.hash = 'tufted/singulieres'; }, 60); };
const openP = (p) => { if (p.tufted) goTuftedSingulieres(); else onPiece(msPiece(p.name, p)); };
const [active, setActive] = React.useState(1);
const [dark, setDark] = React.useState(true);
const [show, setShow] = React.useState(false);
React.useEffect(() => {
const sc = document.getElementById('ms-scroll');
if (!sc) return;
const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
/* III "La Présentation" begins at ALBA (the first piece shown), not at
Monolith I — otherwise ALBA was wrongly attributed to movement II and
III only lit from the second piece on. ALBA + the three pieces are one
viewing, so III spans them all. */
const anchors = [ ['mvt-2', 2, true], ['mvt-alba', 3, true], ['mvt-4', 4, false], ['mvt-5', 5, false] ];
let raf = 0;
const run = () => {
raf = 0;
const vh = window.innerHeight || 800;
const y = sc.scrollTop + vh * 0.45;
let act = 1, dk = true;
anchors.forEach(([id, n, d]) => { const el = document.getElementById(id); if (el && el.offsetTop <= y) { act = n; dk = d; } });
setActive(act); setDark(dk); setShow(sc.scrollTop > vh * 0.55);
if (!reduce) {
const vc = vh / 2;
document.querySelectorAll('.ms-home [data-par]').forEach((el) => {
const r = el.getBoundingClientRect();
const off = (r.top + r.height / 2 - vc) / vh;
el.style.transform = `translateY(${off * 26}px)`;
});
}
};
const onScroll = () => { if (!raf) raf = requestAnimationFrame(run); };
sc.addEventListener('scroll', onScroll, { passive: true });
run();
return () => { sc.removeEventListener('scroll', onScroll); if (raf) cancelAnimationFrame(raf); };
}, []);
const jump = (n) => {
const sc = document.getElementById('ms-scroll');
if (!sc) return;
if (n === 1) { sc.scrollTo({ top: 0, behavior: 'smooth' }); return; }
const el = document.getElementById('mvt-' + n);
if (el) sc.scrollTo({ top: el.offsetTop, behavior: 'smooth' });
};
return (
{/* I — The Unveiling */}
{/* II — The Manifesto, in the dark */}