/* ============================================================ calendar-app.jsx — root: state, persistence, preview/real date ============================================================ */ const { useState: useS, useEffect: useE, useMemo } = React; const LS = { title: "songs365.title", note: "songs365.note", opened: "songs365.opened", sim: "songs365.simDay", real: "songs365.useReal", unlocked: "songs365.unlocked", showNotes: "songs365.showNotes", }; const load = (k, fb) => { try { const v = localStorage.getItem(k); return v == null ? fb : JSON.parse(v); } catch { return fb; } }; const save = (k, v) => { try { localStorage.setItem(k, JSON.stringify(v)); } catch {} }; const DEFAULT_TITLE = "Three Hundred\n& Sixty-Five"; const DEFAULT_NOTE = "One year. One song every morning, just for you — some to dance to, some to cry to, all of them because they made me think of us. Press play, then come find me. Happy anniversary. ♡"; function App() { const [unlocked, setUnlocked] = useS(() => load(LS.unlocked, false)); const [showNotes, setShowNotes] = useS(() => load(LS.showNotes, true)); const [title, setTitle] = useS(() => load(LS.title, DEFAULT_TITLE)); const [note, setNote] = useS(() => load(LS.note, DEFAULT_NOTE)); const [opened, setOpened] = useS(() => new Set(load(LS.opened, []))); const [simDay, setSimDay] = useS(() => load(LS.sim, 100)); const [useReal, setUseReal] = useS(() => load(LS.real, false)); const [active, setActive] = useS(null); // the day whose modal is open const realDayEarly = useMemo(() => window.daysBetween(window.START, new Date()) + 1, []); const [activeMonth, setActiveMonth] = useS(() => { const cur = window.MONTH_GROUPS.find(g => g.days.some(d => d.day === realDayEarly)); return (cur || window.MONTH_GROUPS[0]).key; }); useE(() => save(LS.unlocked, unlocked), [unlocked]); useE(() => save(LS.showNotes, showNotes), [showNotes]); useE(() => save(LS.title, title), [title]); useE(() => save(LS.note, note), [note]); useE(() => save(LS.opened, [...opened]), [opened]); useE(() => save(LS.sim, simDay), [simDay]); useE(() => save(LS.real, useReal), [useReal]); const realDay = useMemo(() => window.daysBetween(window.START, new Date()) + 1, []); const currentDay = (window.IS_AUTHOR && useReal === false) ? simDay : realDay; const unlockedCount = Math.min(Math.max(currentDay, 0), window.TOTAL_DAYS); const statusFor = (d) => { if (d.filler) return "filler"; if (d.day > currentDay) return "locked"; return opened.has(d.day) ? "played" : "open"; }; const openDay = (d) => { setActive(d); if (!opened.has(d.day)) { const next = new Set(opened); next.add(d.day); setOpened(next); } }; const openedCount = [...opened].filter(n => n <= currentDay).length; const group = window.MONTH_GROUPS.find(g => g.key === activeMonth) || window.MONTH_GROUPS[0]; const unlockedIn = group.days.filter(d => !d.filler && d.day <= currentDay).length; if (!unlocked) { return { setShowNotes(notes); setUnlocked(true); }} />; } return (
{activeMonth === window.MONTH_GROUPS[window.MONTH_GROUPS.length - 1].key ? "thanks for listening" : "the tape keeps going"}
{active && ( setActive(null)} showNotes={showNotes} /> )}
); } ReactDOM.createRoot(document.getElementById("root")).render();