// app/ui-cycles.js (function () { "use strict"; const state = { initialized: false, referenceData: null, entries: [], filteredEntries: [], searchQuery: "", selectedId: "" }; function getElements() { return { searchInputEl: document.getElementById("cycles-search-input"), searchClearEl: document.getElementById("cycles-search-clear"), countEl: document.getElementById("cycles-count"), listEl: document.getElementById("cycles-list"), detailNameEl: document.getElementById("cycles-detail-name"), detailTypeEl: document.getElementById("cycles-detail-type"), detailSummaryEl: document.getElementById("cycles-detail-summary"), detailBodyEl: document.getElementById("cycles-detail-body") }; } function normalizeSearchValue(value) { return String(value || "").trim().toLowerCase(); } function normalizeLookupToken(value) { return String(value || "") .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, " ") .trim(); } function normalizeCycle(raw, index) { const name = String(raw?.name || "").trim(); if (!name) { return null; } const id = String(raw?.id || `cycle-${index + 1}`).trim(); const category = String(raw?.category || "Uncategorized").trim(); const period = String(raw?.period || "").trim(); const description = String(raw?.description || "").trim(); const significance = String(raw?.significance || "").trim(); const related = Array.isArray(raw?.related) ? raw.related.map((item) => String(item || "").trim()).filter(Boolean) : []; const periodDaysRaw = Number(raw?.periodDays); const periodDays = Number.isFinite(periodDaysRaw) ? periodDaysRaw : null; const searchText = normalizeSearchValue([ name, category, period, description, significance, related.join(" ") ].join(" ")); return { id, name, category, period, periodDays, description, significance, related, searchText }; } function buildEntries(referenceData) { const rows = Array.isArray(referenceData?.astronomyCycles?.cycles) ? referenceData.astronomyCycles.cycles : []; return rows .map((row, index) => normalizeCycle(row, index)) .filter(Boolean) .sort((left, right) => left.name.localeCompare(right.name)); } function formatDays(value) { if (!Number.isFinite(value)) { return ""; } return value >= 1000 ? Math.round(value).toLocaleString() : value.toFixed(3).replace(/\.0+$/, ""); } function escapeHtml(value) { return String(value || "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function escapeAttr(value) { return escapeHtml(value).replaceAll("`", "`"); } function cssEscape(value) { if (typeof CSS !== "undefined" && typeof CSS.escape === "function") { return CSS.escape(value); } return String(value || "").replace(/[^a-zA-Z0-9_\-]/g, "\\$&"); } function setSelectedCycle(nextId) { const normalized = String(nextId || "").trim(); if (normalized && state.entries.some((entry) => entry.id === normalized)) { state.selectedId = normalized; return; } state.selectedId = state.filteredEntries[0]?.id || state.entries[0]?.id || ""; } function selectedEntry() { return state.filteredEntries.find((entry) => entry.id === state.selectedId) || state.entries.find((entry) => entry.id === state.selectedId) || state.filteredEntries[0] || state.entries[0] || null; } function findEntryByReference(reference) { const token = normalizeLookupToken(reference); if (!token) { return null; } return state.entries.find((entry) => { const idToken = normalizeLookupToken(entry.id); const nameToken = normalizeLookupToken(entry.name); return token === idToken || token === nameToken; }) || null; } function applyFilter() { const query = state.searchQuery; if (!query) { state.filteredEntries = state.entries.slice(); } else { state.filteredEntries = state.entries.filter((entry) => entry.searchText.includes(query)); } if (!state.filteredEntries.some((entry) => entry.id === state.selectedId)) { state.selectedId = state.filteredEntries[0]?.id || ""; } } function syncControls(elements) { const { searchInputEl, searchClearEl } = elements; if (searchInputEl) { searchInputEl.value = state.searchQuery; } if (searchClearEl) { searchClearEl.disabled = !state.searchQuery; } } function renderList(elements) { const { listEl, countEl } = elements; if (!(listEl instanceof HTMLElement)) { return; } if (countEl) { countEl.textContent = state.searchQuery ? `${state.filteredEntries.length} of ${state.entries.length}` : `${state.entries.length}`; } if (!state.filteredEntries.length) { listEl.innerHTML = '
Period: ${escapeHtml(entry.period)}
`); } if (Number.isFinite(entry.periodDays)) { body.push(`Approx days: ${escapeHtml(formatDays(entry.periodDays))}
`); } if (entry.significance) { body.push(`Significance: ${escapeHtml(entry.significance)}
`); } if (entry.related.length) { const relatedButtons = entry.related .map((label) => { const relatedEntry = findEntryByReference(label); if (!relatedEntry) { return ``; } return ``; }) .join(""); body.push([ "