/* ============================================================ calendar-components.jsx — IntroNote, Tile, SongModal, PreviewBar ============================================================ */ const { useState, useEffect, useRef } = React; const IS_AUTHOR = !!(window.omelette && window.omelette.writeFile); Object.assign(window, { IS_AUTHOR }); /* ---------- password gate ---------- */ function Gate({ onUnlock }) { const [value, setValue] = useState(""); const [error, setError] = useState(false); const submit = (e) => { e.preventDefault(); const v = value.trim().toLowerCase(); if (v === "goodmorning") onUnlock(true); else if (v === "withlove") onUnlock(false); else { setError(true); setValue(""); } }; return (

a mixtape, just for you

{ setValue(e.target.value); setError(false); }} /> {error &&

that's not it — try again

}
); } /* ---------- tiny cassette mark (simple shapes only) ---------- */ function Cassette({ size = 54 }) { return ( ); } /* ---------- editable + persisted text ---------- */ function Editable({ value, onChange, className, style, as = "div", placeholder }) { const ref = useRef(null); useEffect(() => { if (ref.current && ref.current.innerText !== value) ref.current.innerText = value; }, [value]); const Tag = as; return ( IS_AUTHOR && onChange(e.currentTarget.innerText)} /> ); } /* ---------- intro note ---------- */ function IntroNote({ title, setTitle, note, setNote, unlocked, opened }) { return (
DO NOT SHUFFLE · ANNIVERSARY EDITION
{opened} played · {unlocked} unlocked · {window.TOTAL_DAYS - unlocked} still wrapped
hello!!!
); } /* ---------- a single day tile — a "window" onto the month's shared picture ---------- */ function Tile({ d, status, onOpen, currentDay }) { if (d.filler) { return