// Free local bookmarks are kept separate from authenticated account progress.
const CALENDAR_LOCAL_KEY = "fbo-calendar-local-v1";
const CALENDAR_PROGRESS = { saved: "Saved", applied: "Applied", interview: "Interview", offer: "Offer" };
function calendarLondonDay(now = new Date()) {
  const parts = new Intl.DateTimeFormat("en-GB", { timeZone: "Europe/London", year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(now);
  return ["year", "month", "day"].map(key => parts.find(p => p.type === key).value).join("-");
}
function calendarCanRemind(programme, today = calendarLondonDay()) {
  const verified = programme.deadlineVerifiedOn;
  const age = calendarValidDay(verified) ? Date.parse(`${today}T12:00:00Z`) - Date.parse(`${verified}T12:00:00Z`) : NaN;
  return age >= 0 && age <= 7 * 86400000 && programme.reminderEligible === true && programme.fixedDeadlineConfirmed === true && programme.status === "open" && programme.dateEvidence === "confirmed" && programme.datePrecision === "day" && !programme.estimated && !programme.rolling
    && /^\d{4}-\d{2}-\d{2}$/.test(programme.deadline || "") && programme.deadline > today;
}
function calendarValidDay(value) { return typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(`${value}T12:00:00Z`)) && new Date(`${value}T12:00:00Z`).toISOString().slice(0, 10) === value; }
function calendarDeadlinePassed(programme, today = calendarLondonDay()) { return !programme.rolling && !programme.estimated && programme.dateEvidence === "confirmed" && calendarValidDay(programme.deadline) && programme.deadline < today; }
function calendarDate(value) {
  if (!value || !/^\d{4}-\d{2}-\d{2}(?:T|$)/.test(value)) return "Not confirmed";
  const date = new Date(value.length === 10 ? `${value}T12:00:00Z` : value);
  return Number.isNaN(date.getTime()) ? "Not confirmed" : date.toLocaleDateString("en-GB", { timeZone: "Europe/London", day: "numeric", month: "short", year: "numeric" });
}
function calendarSafeUrl(value) { try { const u = new URL(value); return u.protocol === "https:" ? u.href : null; } catch { return null; } }
function calendarEntries(value, programmes) {
  // Retain saved records when a later calendar snapshot removes a programme.
  // A retired record must still be visible, exportable and removable.
  return (Array.isArray(value) ? value : []).filter(e => e && typeof e.programmeId === "string" && /^[a-z0-9][a-z0-9_-]{0,159}$/i.test(e.programmeId) && Object.hasOwn(CALENDAR_PROGRESS, e.status))
    .map(e => ({ programmeId: e.programmeId, status: e.status, remindDays: [1, 3, 7].includes(e.remindDays) ? e.remindDays : null,
      taskDate: calendarValidDay(e.taskDate) ? e.taskDate : null, preparationStage: ["OT", "VI", "AC", "INTV"].includes(e.preparationStage) ? e.preparationStage : null,
      reminderTarget: ["task", "deadline"].includes(e.reminderTarget) ? e.reminderTarget : null }));
}
function calendarExport(entries, programmes, format) {
  const rows = Object.values(entries).map(entry => { const p = programmes.find(p => p.id === entry.programmeId); return { ...entry, employer: p ? p.employer : "", programme: p ? p.title : "", officialDeadline: p ? p.deadline : null, officialUrl: p ? p.sourceUrl : null }; });
  if (format !== "csv") return JSON.stringify({ exportedAt: new Date().toISOString(), timezone: "Europe/London", entries: rows }, null, 2);
  const fields = ["programmeId", "employer", "programme", "status", "preparationStage", "taskDate", "officialDeadline", "reminderTarget", "remindDays", "officialUrl"];
  const cell = value => { let text = value == null ? "" : String(value); if (/^[=+@\-\t\r]/.test(text)) text = `'${text}`; return `"${text.replace(/"/g, '""')}"`; };
  return [fields.map(cell).join(","), ...rows.map(row => fields.map(field => cell(row[field])).join(","))].join("\r\n");
}
function calendarFilter(programmes, { query = "", type = "", stage = "", view = "all" }, entries = {}) {
  const words = query.trim().toLocaleLowerCase().split(/\s+/).filter(Boolean);
  return programmes.filter(p => (!type || p.type === type) && (!stage || (p.stages || []).includes(stage))
    && (view !== "saved" || entries[p.id]) && (view !== "open" || (p.status === "open" && !calendarDeadlinePassed(p)))
    && words.every(word => `${p.employer} ${p.title} ${p.location || ""} ${p.division || ""}`.toLocaleLowerCase().includes(word)));
}
async function calendarRequest(body) {
  const response = await fetch("/api/calendar", { method: body ? "POST" : "GET", credentials: "same-origin", cache: "no-store", headers: body ? { "Content-Type": "application/json" } : {}, ...(body ? { body: JSON.stringify(body) } : {}) });
  const data = await response.json().catch(() => ({}));
  if (!response.ok) { const error = new Error(data.error || "Your account progress could not be reached. Please try again."); error.status = response.status; throw error; }
  if (!Array.isArray(data.entries) || !Number.isSafeInteger(data.version)) throw new Error("We could not read your account progress. Please try again.");
  return data;
}
function useCalendarProgress(member, programmes) {
  const readLocal = () => { try { return calendarEntries(JSON.parse(localStorage.getItem(CALENDAR_LOCAL_KEY) || "[]"), programmes); } catch { return []; } };
  const [local, setLocal] = useState(readLocal);
  const [cloud, setCloud] = useState(null), [error, setError] = useState(""), [notice, setNotice] = useState("");
  const [loading, setLoading] = useState(false), [busy, setBusy] = useState("");
  const [mode, setMode] = useState("auto");
  const generation = useRef(0), pending = useRef(false), snapshot = useRef(null);
  const owner = member && member.user ? member.user.id : null;
  const identity = useRef(owner); identity.current = owner;
  const refresh = async () => {
    if (!owner || pending.current) return;
    const turn = ++generation.current; setLoading(true); setError("");
    try { const data = await calendarRequest(); if (turn === generation.current && identity.current === owner) { const next = { ...data, owner }; snapshot.current = next; setCloud(next); } }
    catch (e) { if (turn === generation.current && identity.current === owner) setError(e.message); }
    finally { if (turn === generation.current && identity.current === owner) setLoading(false); }
  };
  useEffect(() => {
    ++generation.current; snapshot.current = null; pending.current = false; setCloud(null); setMode("auto"); setBusy(""); setError(""); setNotice(""); setLoading(false);
    if (owner) refresh();
    const focus = () => { if (owner) refresh(); };
    window.addEventListener("focus", focus);
    return () => { ++generation.current; window.removeEventListener("focus", focus); };
  }, [owner]);
  useEffect(() => {
    const sync = e => { if (e.key === CALENDAR_LOCAL_KEY || e.key === null) setLocal(readLocal()); };
    window.addEventListener("storage", sync); return () => window.removeEventListener("storage", sync);
  }, [programmes]);
  const state = cloud && cloud.owner === owner ? cloud : null;
  const membership = state && state.membership;
  const active = !!(membership && membership.active);
  const accountMode = !!state && (mode === "account" || (mode === "auto" && (active || state.entries.length > 0)));
  const readOnly = accountMode && !active;
  const entries = Object.fromEntries((accountMode ? calendarEntries(state.entries, programmes) : local).map(e => [e.programmeId, e]));
  const update = async (programmeId, status, changes = {}) => {
    if (pending.current) return;
    setError(""); setNotice("");
    const nextEntry = { programmeId, status, taskDate: null, preparationStage: null, remindDays: null, reminderTarget: null, ...entries[programmeId], ...changes, status };
    if (readOnly && status !== "removed" && !(changes.remindDays === null && Object.keys(changes).every(key => ["remindDays", "reminderTarget"].includes(key)))) { setError("Your pass has ended. You can view, export or remove account records, and turn reminders off. Free browser progress is still available."); return; }
    if (!accountMode) {
      const next = readLocal().filter(e => e.programmeId !== programmeId);
      if (status !== "removed") next.push({ ...nextEntry, remindDays: null, reminderTarget: null });
      try { localStorage.setItem(CALENDAR_LOCAL_KEY, JSON.stringify(next)); setLocal(next); setNotice(status === "removed" ? "Removed from this browser." : "Saved in this browser."); }
      catch { setError("Your browser could not save this project. Allow local storage, then try again."); }
      return;
    }
    pending.current = true; setBusy(programmeId); setLoading(false); const turn = ++generation.current;
    try {
      const data = await calendarRequest({ ...nextEntry, version: snapshot.current.version });
      if (turn === generation.current && identity.current === owner) { const next = { ...data, owner }; snapshot.current = next; setCloud(next); setNotice("Saved to your account."); }
    } catch (e) {
      if (turn !== generation.current || identity.current !== owner) return;
      if (e.status === 409) {
        try { const data = await calendarRequest(); if (turn === generation.current && identity.current === owner) { const next = { ...data, owner }; snapshot.current = next; setCloud(next); setError("Another device updated your progress. We’ve loaded its changes. Please try your change again."); } }
        catch { if (turn === generation.current && identity.current === owner) setError("Your progress changed on another device. Refresh account progress before trying again."); }
      } else setError(e.message);
    } finally { if (turn === generation.current && identity.current === owner) { pending.current = false; setBusy(""); } }
  };
  return { entries, local, membership, active, accountMode, readOnly, setMode, error, notice, loading, busy, refresh, update, accountReady: !!state };
}
window.FBOCalendarClient = { calendarLondonDay, calendarCanRemind, calendarDate, calendarSafeUrl, calendarValidDay, calendarDeadlinePassed, calendarEntries, calendarFilter, calendarExport, calendarRequest };

function CalendarProgramme({ programme: p, entry, localEntry, progress }) {
  const [taskDraft, setTaskDraft] = useState(entry && entry.taskDate || "");
  useEffect(() => { setTaskDraft(entry && entry.taskDate || ""); }, [entry && entry.taskDate]);
  const [targetDraft, setTargetDraft] = useState(entry && entry.reminderTarget || "");
  const [daysDraft, setDaysDraft] = useState(entry && entry.remindDays || "");
  useEffect(() => { setTargetDraft(entry && entry.reminderTarget || ""); setDaysDraft(entry && entry.remindDays || ""); }, [entry && entry.reminderTarget, entry && entry.remindDays]);
  const source = calendarSafeUrl(p.sourceUrl);
  const canRemind = calendarCanRemind(p);
  const taskCanRemind = entry && taskDraft === (entry.taskDate || "") && calendarValidDay(entry.taskDate) && entry.taskDate > calendarLondonDay();
  const targetCanRemind = targetDraft === "task" ? taskCanRemind : targetDraft === "deadline" ? canRemind : false;
  const reminderChanged = entry && (targetDraft !== (entry.reminderTarget || "") || String(daysDraft) !== String(entry.remindDays || ""));
  const passed = calendarDeadlinePassed(p);
  const status = passed ? "Deadline passed" : p.status === "open" ? "Recorded open" : p.status === "closed" ? "Recorded closed" : "Status unconfirmed";
  const matches = p.matches || [];
  return <article className="calendar-programme">
    <div className="calendar-programme-main">
      <div className="calendar-employer"><span>{p.employer}</span><span>{p.type}</span></div>
      <div className="calendar-programme-heading"><div><h2>{p.title}</h2><p>{[p.location, p.cycle].filter(Boolean).join(" · ")}</p></div><span className={`calendar-status ${p.status === "open" && !passed ? "calendar-status-open" : ""}`}>{status}</span></div>
      <div className="calendar-dates"><div><span>Applications open</span><strong>{calendarDate(p.opens)}{p.estimated && p.opens ? " · estimated" : ""}</strong></div><div><span>Application deadline</span><strong>{p.rolling ? "Rolling recruitment" : calendarDate(p.deadline)}{p.estimated && p.deadline && !p.rolling ? " · estimated" : ""}</strong></div><div><span>Source last verified</span><strong>{calendarDate(p.lastVerified || p.lastChecked)}</strong></div></div>
      <div className="calendar-actions"><button className={`btn ${entry ? "btn-ghost" : "btn-primary"}`} disabled={!!progress.busy || (progress.readOnly && !entry)} onClick={() => progress.update(p.id, entry ? "removed" : "saved", !entry && localEntry ? { taskDate: localEntry.taskDate, preparationStage: localEntry.preparationStage } : {})}>{entry ? "Remove saved project" : progress.accountMode && localEntry ? "Save browser bookmark to account" : progress.accountMode ? "Save to my account" : "Save in this browser"}</button>{source && <a className="calendar-text-link" href={source} target="_blank" rel="noopener noreferrer">Official opportunity <span aria-hidden="true">↗</span></a>}</div>
      {entry && <div className="calendar-progress-controls"><label>My progress<select aria-label="My progress" value={entry.status} disabled={!!progress.busy || progress.readOnly} onChange={e => progress.update(p.id, e.target.value)}>{Object.entries(CALENDAR_PROGRESS).map(([key, label]) => <option key={key} value={key}>{label}</option>)}</select></label><label>My current preparation stage<select aria-label="My current preparation stage" value={entry.preparationStage || ""} disabled={!!progress.busy || progress.readOnly} onChange={e => progress.update(p.id, entry.status, { preparationStage: e.target.value || null })}><option value="">Not selected</option>{["OT", "VI", "AC", "INTV"].map(s => <option key={s} value={s}>{D.stageLabel[s] || s}</option>)}</select></label><label>My assessment / interview date<input type="date" aria-label="My assessment / interview date" value={taskDraft} disabled={!!progress.busy || progress.readOnly} onChange={e => setTaskDraft(e.target.value)}/>{taskDraft !== (entry.taskDate || "") && <button className="calendar-text-link" disabled={!!progress.busy || progress.readOnly || (!!taskDraft && !calendarValidDay(taskDraft))} onClick={() => progress.update(p.id, entry.status, { taskDate: taskDraft || null, ...(entry.reminderTarget === "task" ? { remindDays: null } : {}) })}>Save my date</button>}</label><label>Remind me about<select aria-label="Remind me about" value={targetDraft} disabled={!progress.active || !progress.accountMode || !!progress.busy} onChange={e => setTargetDraft(e.target.value)}><option value="">Choose a date</option><option value="task" disabled={!taskCanRemind}>My assessment / interview date</option><option value="deadline" disabled={!canRemind}>Official application deadline</option></select></label><label>Email reminder<select aria-label="Email reminder" value={daysDraft} disabled={!!progress.busy || !progress.accountMode || (!progress.active && !entry.remindDays) || (!targetCanRemind && !entry.remindDays)} onChange={e => setDaysDraft(e.target.value ? Number(e.target.value) : "")}><option value="">Off</option><option value="1" disabled={progress.readOnly || !targetCanRemind}>1 day before</option><option value="3" disabled={progress.readOnly || !targetCanRemind}>3 days before</option><option value="7" disabled={progress.readOnly || !targetCanRemind}>7 days before</option></select></label>{reminderChanged && <div className="calendar-reminder-save"><button className="btn btn-primary" disabled={!!progress.busy || (!!daysDraft && !targetCanRemind) || (!daysDraft && !entry.remindDays)} onClick={() => progress.update(p.id, entry.status, { reminderTarget: daysDraft ? targetDraft : null, remindDays: daysDraft || null })}>Save reminder settings</button><span>Changes apply after saving.</span></div>}<p>Your assessment / interview date is entered by you, not verified by the employer. Save date changes before setting a reminder. {!progress.active ? "The 90-day pass adds account sync and email reminders. Expired account records remain readable and exportable." : "Choose the reminder target explicitly. Official reminders need a future deadline verified within the past 7 days; rolling, estimated or stale official dates are excluded. You can still enter your own task date. Email reminders are checked once daily in Europe/London time, not in real time."}</p></div>}
    </div>
    <details className="calendar-programme-detail"><summary>Check eligibility, evidence &amp; relevant practice <span aria-hidden="true">+</span></summary><div className="calendar-detail-body">
      <div className="calendar-evidence"><h3>Before you apply</h3>{p.eligibility && typeof p.eligibility === "object" ? <dl className="calendar-eligibility">{Object.entries(p.eligibility).filter(([, value]) => value).map(([key, value]) => <div key={key}><dt>{{ studyStage: "Study stage", graduation: "Graduation", degree: "Degree", grades: "Grades", visa: "Visa", workRights: "Work rights" }[key] || key}</dt><dd>{String(value)}</dd></div>)}</dl> : <p>{p.eligibility || "Eligibility is not fully recorded. Check the employer’s current requirements."}</p>}{!!(p.stages || []).length && <p><strong>Recorded stages:</strong> {p.stages.map(s => D.stageLabel[s] || s).join(" → ")}</p>}<p>{p.verification === "checked" ? "A source check is recorded on the date above." : "This entry retains a previous source check; its status has not been freshly reconfirmed."} Recruitment can change or close early. A recorded opening is not a guarantee of availability.</p>{p.sourceEvidence && <p>{p.sourceEvidence}</p>}{p.limitations && <p>{Array.isArray(p.limitations) ? p.limitations.join(" ") : p.limitations}</p>}</div>
      <div className="calendar-practice"><h3>Practice for this application</h3>{!matches.length ? <p>{p.noMatchReason || "We do not currently have a supported practice match for this programme. Browse the catalogue only if a pack’s region, role and stage fit your invitation."}</p> : matches.map(m => <div className="calendar-practice-row" key={m.packId}><span className="eyebrow">{m.matchType === "role_relevant" ? "ROLE-RELEVANT PRACTICE" : "GENERAL EMPLOYER PRACTICE"} · {m.stageLabel || D.stageLabel[m.stage] || m.stage}</span><h4><a href={`#/shop?firm=${encodeURIComponent(m.firmId)}&pack=${encodeURIComponent(m.packId)}`}>{m.title} <span aria-hidden="true">↗</span></a></h4><p>{m.reason}</p>{m.limitation && <p className="calendar-limitation">{m.limitation}</p>}{m.conditionalOnInvitation && <p className="calendar-limitation">Use only if the stage in your invitation matches this practice.</p>}</div>)}</div>
    </div></details>
  </article>;
}
function CalendarPage({ nav, member = {}, pay = {} }) {
  const data = window.FBO_CALENDAR || { programmes: [], scope: "Calendar data is currently unavailable." };
  const programmes = data.programmes || [];
  const progress = useCalendarProgress(member, programmes);
  const [query, setQuery] = useState(""), [type, setType] = useState(""), [stage, setStage] = useState(""), [view, setView] = useState("all");
  const archived = Object.keys(progress.entries).filter(id => !programmes.some(p => p.id === id)).map(id => ({ id, employer: "Saved record", title: `Previously saved programme · ${id}`, type: "Archived record", status: "unknown", stages: [], matches: [], eligibility: "This programme is no longer in the current calendar selection. Your saved progress is retained; verify any future plans against the original employer source.", noMatchReason: "Current opportunity and practice information is unavailable for this saved record." }));
  const allProgrammes = [...programmes, ...archived];
  const visible = calendarFilter(allProgrammes, { query, type, stage, view }, progress.entries);
  const types = [...new Set(allProgrammes.map(p => p.type).filter(Boolean))].sort();
  const stages = [...new Set(programmes.flatMap(p => p.stages || []))];
  const local = Object.fromEntries(progress.local.map(e => [e.programmeId, e]));
  const exportProgress = format => {
    const blob = new Blob([calendarExport(progress.entries, programmes, format)], { type: format === "csv" ? "text/csv;charset=utf-8" : "application/json" });
    const url = URL.createObjectURL(blob), link = document.createElement("a");
    link.href = url; link.download = `fbo-applications-${calendarLondonDay()}.${format}`; document.body.appendChild(link); link.click(); link.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000);
  };
  return <div className="container calendar-page page-enter page-in">
    <header className="calendar-page-header"><div><div className="eyebrow-rule">FREE UK RECRUITMENT CALENDAR</div><h1 className="h-section">Find the opportunity.<br/><em>Prepare for the stage.</em></h1><p>Explore graduate and internship opportunities, check the source, and connect your next hiring stage to relevant practice.</p></div><div className="calendar-header-note"><span className="eyebrow">YOUR APPLICATION DESK</span><p>Save projects for free in this browser. The 90-day pass adds account sync and deadline reminders.</p><button className="calendar-text-link" onClick={() => nav("membership")}>See the £49 pass <span aria-hidden="true">↗</span></button></div></header>
    <div className="calendar-source-note"><strong>{programmes.length} selected programmes</strong><span>{data.scope || data.sourceScope} {data.verificationSummary}</span></div>
    <section className="calendar-workspace" aria-label="Your saved application progress"><div><strong>{progress.accountMode ? "Your account workspace" : "Your browser workspace"}</strong><p>{progress.accountMode ? progress.active ? `Synced to ${member.user.email}. Pass ends ${calendarDate(progress.membership.endsAt)}.` : `There is no active pass. Account records remain readable and exportable for ${member.user.email}. You can remove records or turn reminders off.` : member.user && progress.loading ? "Checking your account membership…" : "Free bookmarks, progress and personal dates stay on this browser. They are not automatically copied to an account."}</p></div><div className="calendar-workspace-actions">{member.user ? <button className="btn btn-ghost" disabled={progress.loading || !!progress.busy} onClick={progress.refresh}>{progress.loading ? "Checking…" : "Refresh account progress"}</button> : pay.accounts ? <button className="btn btn-ghost" onClick={() => nav("login")}>Sign in</button> : null}<button className="calendar-text-link" onClick={() => { setView(view === "saved" ? "all" : "saved"); }}>{view === "saved" ? "View all projects" : `My saved projects (${Object.keys(progress.entries).length})`}</button></div></section>
    <div className="calendar-workspace-toolbar">{progress.accountReady && <div className="calendar-workspace-switch" role="group" aria-label="Progress storage"><button aria-pressed={!progress.accountMode} onClick={() => progress.setMode("local")}>This browser</button><button aria-pressed={progress.accountMode} onClick={() => progress.setMode("account")}>My account{progress.readOnly ? " · read only" : ""}</button></div>}<div className="calendar-export"><span>Export displayed workspace</span><button className="calendar-text-link" onClick={() => exportProgress("json")}>JSON</button><button className="calendar-text-link" onClick={() => exportProgress("csv")}>CSV</button></div></div>
    {progress.active && progress.accountMode && progress.local.some(e => !progress.entries[e.programmeId]) && <p className="calendar-inline-note">You also have browser bookmarks. Choose “Save browser bookmark to account” on a project to copy its personal date and preparation stage across.</p>}
    {progress.error && <div className="calendar-alert" role="alert">{progress.error}</div>}<p className="calendar-live-message" role="status" aria-live="polite">{progress.busy ? "Saving account progress…" : progress.notice}</p>
    <section className="calendar-filter-bar" aria-label="Filter recruitment projects"><label className="calendar-search">Search projects<input type="search" className="input" placeholder="Employer, programme or location" value={query} onChange={e => setQuery(e.target.value)}/></label><label>Programme type<select aria-label="Programme type" value={type} onChange={e => setType(e.target.value)}><option value="">All programmes</option>{types.map(t => <option key={t} value={t}>{t}</option>)}</select></label><label>Recorded hiring stage<select aria-label="Recorded hiring stage" value={stage} onChange={e => setStage(e.target.value)}><option value="">All stages</option>{stages.map(s => <option key={s} value={s}>{D.stageLabel[s] || s}</option>)}</select></label><label>Show<select aria-label="Show" value={view} onChange={e => setView(e.target.value)}><option value="all">All projects</option><option value="open">Recorded open</option><option value="saved">My saved projects</option></select></label></section>
    <div className="calendar-results-heading"><p role="status">Showing <strong>{visible.length}</strong> of {allProgrammes.length} {archived.length ? "current and archived records" : "programmes"}</p><span>Dates shown in Europe/London time</span></div>
    <div className="calendar-programmes">{visible.map(p => <CalendarProgramme key={p.id} programme={p} entry={progress.entries[p.id]} localEntry={local[p.id]} progress={progress}/>)}</div>
    {!visible.length && <div className="calendar-empty"><h2>{view === "saved" ? "Your next application starts here." : "No projects match these filters."}</h2><p>{view === "saved" ? "Save a programme to keep its official link and your progress together." : "Try a different employer, programme type or hiring stage. An empty result does not mean the employer has no opportunities."}</p><button className="btn btn-ghost" onClick={() => { setQuery(""); setType(""); setStage(""); setView("all"); }}>Show all projects</button></div>}
    <footer className="calendar-page-footnote">FBO is independent of the employers listed. Practice matches explain their scope; general employer practice is not a claim that a pack covers this role or the whole recruitment process. Always use the employer’s current vacancy and invitation as the final reference.</footer>
  </div>;
}
Object.assign(window, { CalendarPage, useCalendarProgress });
