// Lev Chadash — v2 shared const { useState, useEffect, useMemo, useRef } = React; function Brand() { return ( לב חדש Lev Chadash ); } function SiteHeader({ active = 'home' }) { const { t } = useLang(); const [open, setOpen] = useState(false); // il menu si chiude quando si cambia pagina o sezione useEffect(() => { const close = () => setOpen(false); window.addEventListener('hashchange', close); return () => window.removeEventListener('hashchange', close); }, []); const links = [ ['home', '#/', t.nav.home], ['cal', '#vita', t.nav.cal], ['chi', '#/chi-siamo', t.nav.chi], ['ep', '#/ebraismo', t.nav.ep], ]; return (
); } function todayLabels(locale) { const now = new Date(); const civil = now.toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' }); let hebrew = ''; try { hebrew = now.toLocaleDateString(locale + '-u-ca-hebrew', { day: 'numeric', month: 'long', year: 'numeric' }).replace(/\s?(AM|A\.M\.)$/, ''); } catch (e) {} return hebrew ? `${civil} · ${hebrew}` : civil; } function SiteFooter() { const { t } = useLang(); const f = t.footer; const comHref = ['#/chi-siamo', '#/ebraismo', '#/approfondimenti', '#vita']; const stayHref = ['mailto:info@levchadash.it', 'https://www.levchadash.it/payment.php']; const movHref = ['https://www.fiepitalia.it', 'https://wupj.org', 'https://eupj.org', 'https://eupj.org/governance/european-beit-din/']; return ( ); } function Photo({ caption, style, className = '', src }) { const { t } = useLang(); const [broken, setBroken] = useState(false); useEffect(() => setBroken(false), [src]); if (src && !broken) { return (
{caption} setBroken(true)} />
); } return
; } // Orario di accensione dei lumi di Shabbat — Milano, Piazza Napoli. // Fonte: /shabbat.json, scritto ogni giorno sul server da Hebcal (18 minuti prima del tramonto). // Se il file non c'è, calcola il tramonto qui (precisione ~1-2 minuti), sempre -18 minuti. const LC_TZ = 'Europe/Rome'; const LC_LAT = 45.4547, LC_LON = 9.1521, LC_CANDLE_MIN = 18; let lcShabbatData = null; const lcShabbatSubs = new Set(); fetch('shabbat.json', { cache: 'no-cache' }) .then((r) => (r.ok ? r.json() : null)) .then((j) => { if (j && Array.isArray(j.items)) { lcShabbatData = j; lcShabbatSubs.forEach((f) => f()); } }) .catch(() => {}); function lcRomeParts(d) { const p = Object.fromEntries(new Intl.DateTimeFormat('en-US', { timeZone: LC_TZ, year: 'numeric', month: 'numeric', day: 'numeric', weekday: 'short' }) .formatToParts(d).map((x) => [x.type, x.value])); return { y: +p.year, m: +p.month, d: +p.day, wd: p.weekday }; } // Equazione del tramonto (NOAA semplificata) function lcSunset(y, m, d) { const rad = Math.PI / 180; const jd = Date.UTC(y, m - 1, d, 0) / 86400000 + 2440587.5; const n = Math.ceil(jd - 2451545.0 + 0.0008); const js = n - LC_LON / 360; const M = (357.5291 + 0.98560028 * js) % 360; const C = 1.9148 * Math.sin(M * rad) + 0.02 * Math.sin(2 * M * rad) + 0.0003 * Math.sin(3 * M * rad); const L = (M + C + 180 + 102.9372) % 360; const jt = 2451545.0 + js + 0.0053 * Math.sin(M * rad) - 0.0069 * Math.sin(2 * L * rad); const dec = Math.asin(Math.sin(L * rad) * Math.sin(23.4397 * rad)); const w0 = Math.acos((Math.sin(-0.833 * rad) - Math.sin(LC_LAT * rad) * Math.sin(dec)) / (Math.cos(LC_LAT * rad) * Math.cos(dec))); return new Date((jt + w0 / rad / 360 - 2440587.5) * 86400000); } function lcNextCandles(now) { if (lcShabbatData) { const next = lcShabbatData.items .filter((i) => i.category === 'candles') .map((i) => new Date(i.date)) .filter((d) => d > now && lcRomeParts(d).wd === 'Fri') .sort((a, b) => a - b)[0]; if (next) return next; } for (let i = 0; i < 8; i++) { const p = lcRomeParts(new Date(now.getTime() + i * 86400000)); if (p.wd !== 'Fri') continue; const t = new Date(lcSunset(p.y, p.m, p.d).getTime() - LC_CANDLE_MIN * 60000); t.setSeconds(0, 0); if (t > now) return t; } return null; } // Zmanim di Milano (Piazza Napoli), 14 giorni in avanti: /zmanim.json scritto dal server da Hebcal. let lcZmanimData = null; const lcZmanimSubs = new Set(); fetch('zmanim.json', { cache: 'no-cache' }) .then((r) => (r.ok ? r.json() : null)) .then((j) => { if (j && j.giorni) { lcZmanimData = j; lcZmanimSubs.forEach((f) => f()); } }) .catch(() => {}); function lcRomeISO(d) { return new Intl.DateTimeFormat('en-CA', { timeZone: LC_TZ, year: 'numeric', month: '2-digit', day: '2-digit' }).format(d); } function useZmanim() { const [, setV] = useState(0); useEffect(() => { const f = () => setV((n) => n + 1); lcZmanimSubs.add(f); const id = setInterval(f, 10 * 60 * 1000); // a mezzanotte passa da solo al giorno nuovo return () => { lcZmanimSubs.delete(f); clearInterval(id); }; }, []); if (!lcZmanimData) return null; const oggi = new Date(); const domani = new Date(oggi.getTime() + 24 * 3600 * 1000); const g = lcZmanimData.giorni; return { oggi: g[lcRomeISO(oggi)] ? { data: oggi, z: g[lcRomeISO(oggi)] } : null, domani: g[lcRomeISO(domani)] ? { data: domani, z: g[lcRomeISO(domani)] } : null }; } function useShabbatCountdown(locale = 'it-IT') { const [now, setNow] = useState(new Date()); const [, setV] = useState(0); useEffect(() => { const id = setInterval(() => setNow(new Date()), 30 * 1000); const f = () => setV((n) => n + 1); lcShabbatSubs.add(f); return () => { clearInterval(id); lcShabbatSubs.delete(f); }; }, []); const next = lcNextCandles(now) || now; const ms = next.getTime() - now.getTime(); const totalMin = Math.max(0, Math.floor(ms / 60000)); const days = Math.floor(totalMin / (60 * 24)); const hours = Math.floor((totalMin - days * 60 * 24) / 60); const mins = totalMin - days * 60 * 24 - hours * 60; const dateLabel = next.toLocaleDateString(locale, { timeZone: LC_TZ, weekday: 'long', day: 'numeric', month: 'long' }); const time = next.toLocaleTimeString('it-IT', { timeZone: LC_TZ, hour: '2-digit', minute: '2-digit' }); return { days, hours, mins, dateLabel, time }; } Object.assign(window, { useZmanim, lcRomeISO, Brand, SiteHeader, SiteFooter, Photo, useShabbatCountdown });