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

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 });