/* ui-calendar.js — Month and celestial holiday browser */ (function () { "use strict"; const { getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {}; const calendarDatesUi = window.TarotCalendarDates || {}; const calendarDetailUi = window.TarotCalendarDetail || {}; const { addDays, buildSignDateBounds, formatCalendarDateFromGregorian, formatDateLabel, formatGregorianReferenceDate, formatIsoDate, getDaysInMonth, getGregorianMonthStartDate, getGregorianReferenceDateForCalendarMonth, getMonthStartWeekday, intersectDateRanges, isMonthDayInRange, isoToDateAtNoon, normalizeCalendarText, parseDayRangeFromText, parseMonthDayStartToken, parseMonthDayToken, parseMonthDayTokensFromText, parseMonthRange, resolveCalendarDayToGregorian, resolveHolidayGregorianDate } = calendarDatesUi; const state = { initialized: false, referenceData: null, magickDataset: null, selectedCalendar: "gregorian", calendarData: {}, months: [], filteredMonths: [], holidays: [], calendarHolidays: [], selectedMonthId: null, searchQuery: "", selectedYear: new Date().getFullYear(), selectedDayMonthId: null, selectedDayCalendarId: null, selectedDayEntries: [], planetsById: new Map(), signsById: new Map(), godsById: new Map(), hebrewById: new Map(), dayLinksCache: new Map() }; const TAROT_TRUMP_NUMBER_BY_NAME = { "the fool": 0, fool: 0, "the magus": 1, magus: 1, magician: 1, "the high priestess": 2, "high priestess": 2, "the empress": 3, empress: 3, "the emperor": 4, emperor: 4, "the hierophant": 5, hierophant: 5, "the lovers": 6, lovers: 6, "the chariot": 7, chariot: 7, strength: 8, lust: 8, "the hermit": 9, hermit: 9, fortune: 10, "wheel of fortune": 10, justice: 11, "the hanged man": 12, "hanged man": 12, death: 13, temperance: 14, art: 14, "the devil": 15, devil: 15, "the tower": 16, tower: 16, "the star": 17, star: 17, "the moon": 18, moon: 18, "the sun": 19, sun: 19, aeon: 20, judgement: 20, judgment: 20, universe: 21, world: 21, "the world": 21 }; function normalizeText(value) { return String(value || "").trim(); } function normalizeSearchValue(value) { return normalizeText(value).toLowerCase(); } function cap(value) { const text = normalizeText(value); return text ? text.charAt(0).toUpperCase() + text.slice(1) : text; } function normalizeTarotName(value) { return normalizeText(value) .toLowerCase() .replace(/\s+/g, " "); } function resolveTarotTrumpNumber(cardName) { const key = normalizeTarotName(cardName); if (!key) { return null; } if (Object.prototype.hasOwnProperty.call(TAROT_TRUMP_NUMBER_BY_NAME, key)) { return TAROT_TRUMP_NUMBER_BY_NAME[key]; } const withoutLeadingThe = key.replace(/^the\s+/, ""); if (Object.prototype.hasOwnProperty.call(TAROT_TRUMP_NUMBER_BY_NAME, withoutLeadingThe)) { return TAROT_TRUMP_NUMBER_BY_NAME[withoutLeadingThe]; } return null; } function getDisplayTarotName(cardName, trumpNumber) { if (!cardName) { return ""; } if (typeof getTarotCardDisplayName !== "function") { return cardName; } if (Number.isFinite(Number(trumpNumber))) { return getTarotCardDisplayName(cardName, { trumpNumber: Number(trumpNumber) }) || cardName; } return getTarotCardDisplayName(cardName) || cardName; } function normalizeMinorTarotCardName(value) { return normalizeTarotName(value) .split(" ") .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } function normalizeDayFilterEntry(dayNumber, gregorianIso) { const nextDayNumber = Math.trunc(Number(dayNumber)); const nextIso = normalizeText(gregorianIso); if (!Number.isFinite(nextDayNumber) || nextDayNumber <= 0 || !nextIso) { return null; } return { dayNumber: nextDayNumber, gregorianIso: nextIso }; } function sortDayFilterEntries(entries) { const deduped = new Map(); (Array.isArray(entries) ? entries : []).forEach((entry) => { const normalized = normalizeDayFilterEntry(entry?.dayNumber, entry?.gregorianIso); if (normalized) { deduped.set(normalized.dayNumber, normalized); } }); return [...deduped.values()].sort((left, right) => left.dayNumber - right.dayNumber); } function getElements() { return { monthListEl: document.getElementById("calendar-month-list"), monthCountEl: document.getElementById("calendar-month-count"), listTitleEl: document.getElementById("calendar-list-title"), calendarTypeEl: document.getElementById("calendar-type-select"), calendarYearWrapEl: document.getElementById("calendar-year-wrap"), yearInputEl: document.getElementById("calendar-year-input"), searchInputEl: document.getElementById("calendar-search-input"), searchClearEl: document.getElementById("calendar-search-clear"), detailNameEl: document.getElementById("calendar-detail-name"), detailSubEl: document.getElementById("calendar-detail-sub"), detailBodyEl: document.getElementById("calendar-detail-body") }; } function ensureDayFilterContext(month) { if (!month) { return; } const sameContext = state.selectedDayMonthId === month.id && state.selectedDayCalendarId === state.selectedCalendar; if (!sameContext) { state.selectedDayMonthId = month.id; state.selectedDayCalendarId = state.selectedCalendar; state.selectedDayEntries = []; } } function clearSelectedDayFilter() { state.selectedDayMonthId = null; state.selectedDayCalendarId = null; state.selectedDayEntries = []; } function toggleDayFilterEntry(month, dayNumber, gregorianIso) { ensureDayFilterContext(month); const next = normalizeDayFilterEntry(dayNumber, gregorianIso); if (!next) { return; } const entries = [...state.selectedDayEntries]; const existingIndex = entries.findIndex((entry) => entry.dayNumber === next.dayNumber); if (existingIndex >= 0) { entries.splice(existingIndex, 1); } else { entries.push(next); } state.selectedDayEntries = sortDayFilterEntries(entries); } function toggleDayRangeFilter(month, startDay, endDay) { ensureDayFilterContext(month); const start = Math.trunc(Number(startDay)); const end = Math.trunc(Number(endDay)); if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end <= 0) { return; } const minDay = Math.min(start, end); const maxDay = Math.max(start, end); const rows = getMonthDayLinkRows(month) .filter((row) => row.isResolved && row.day >= minDay && row.day <= maxDay) .map((row) => normalizeDayFilterEntry(row.day, row.gregorianDate)) .filter(Boolean); if (!rows.length) { return; } const existingSet = new Set(state.selectedDayEntries.map((entry) => entry.dayNumber)); const allSelected = rows.every((row) => existingSet.has(row.dayNumber)); if (allSelected) { const removeSet = new Set(rows.map((row) => row.dayNumber)); state.selectedDayEntries = state.selectedDayEntries.filter((entry) => !removeSet.has(entry.dayNumber)); return; } rows.forEach((row) => { if (!existingSet.has(row.dayNumber)) { state.selectedDayEntries.push(row); } }); state.selectedDayEntries = sortDayFilterEntries(state.selectedDayEntries); } function getSelectedDayFilterContext(month) { if (!month) { return null; } if (state.selectedDayMonthId !== month.id) { return null; } if (state.selectedDayCalendarId !== state.selectedCalendar) { return null; } if (!Array.isArray(state.selectedDayEntries) || !state.selectedDayEntries.length) { return null; } const entries = state.selectedDayEntries .map((entry) => { const normalized = normalizeDayFilterEntry(entry.dayNumber, entry.gregorianIso); if (!normalized) { return null; } return { ...normalized, gregorianDate: isoToDateAtNoon(normalized.gregorianIso) }; }) .filter(Boolean); if (!entries.length) { return null; } return { entries, dayNumbers: new Set(entries.map((entry) => entry.dayNumber)) }; } function buildDecanWindow(sign, decanIndex) { const bounds = buildSignDateBounds(sign); const index = Number(decanIndex); if (!bounds || !Number.isFinite(index)) { return null; } const start = addDays(bounds.start, (index - 1) * 10); const nominalEnd = addDays(start, 9); const end = nominalEnd.getTime() > bounds.end.getTime() ? bounds.end : nominalEnd; return { start, end, label: `${formatDateLabel(start)}–${formatDateLabel(end)}` }; } function listMonthNumbersBetween(start, end) { const result = []; const seen = new Set(); const cursor = new Date(start.getFullYear(), start.getMonth(), 1); const limit = new Date(end.getFullYear(), end.getMonth(), 1); while (cursor.getTime() <= limit.getTime()) { const monthNo = cursor.getMonth() + 1; if (!seen.has(monthNo)) { seen.add(monthNo); result.push(monthNo); } cursor.setMonth(cursor.getMonth() + 1); } return result; } function buildDecanTarotRowsForMonth(month) { const monthOrder = Number(month?.order); if (!Number.isFinite(monthOrder)) { return []; } const rows = []; const seen = new Set(); const decansBySign = state.referenceData?.decansBySign || {}; Object.entries(decansBySign).forEach(([signId, decans]) => { const sign = state.signsById.get(signId); if (!sign || !Array.isArray(decans)) { return; } decans.forEach((decan) => { const window = buildDecanWindow(sign, decan?.index); if (!window) { return; } const monthsTouched = listMonthNumbersBetween(window.start, window.end); if (!monthsTouched.includes(monthOrder)) { return; } const cardName = normalizeMinorTarotCardName(decan?.tarotMinorArcana); if (!cardName) { return; } const key = `${cardName}|${signId}|${decan.index}`; if (seen.has(key)) { return; } seen.add(key); const startDegree = (Number(decan.index) - 1) * 10; const endDegree = startDegree + 10; const signName = sign?.name?.en || sign?.name || signId; rows.push({ cardName, signId, signName, signSymbol: sign?.symbol || "", decanIndex: Number(decan.index), startDegree, endDegree, startTime: window.start.getTime(), endTime: window.end.getTime(), startMonth: window.start.getMonth() + 1, startDay: window.start.getDate(), endMonth: window.end.getMonth() + 1, endDay: window.end.getDate(), dateRange: window.label }); }); }); rows.sort((left, right) => { if (left.startTime !== right.startTime) { return left.startTime - right.startTime; } if (left.decanIndex !== right.decanIndex) { return left.decanIndex - right.decanIndex; } return left.cardName.localeCompare(right.cardName); }); return rows; } function buildPlanetMap(planetsObj) { const map = new Map(); if (!planetsObj || typeof planetsObj !== "object") { return map; } Object.values(planetsObj).forEach((planet) => { if (planet?.id) { map.set(planet.id, planet); } }); return map; } function buildSignsMap(signs) { const map = new Map(); if (!Array.isArray(signs)) { return map; } signs.forEach((sign) => { if (sign?.id) { map.set(sign.id, sign); } }); return map; } function buildGodsMap(magickDataset) { const gods = magickDataset?.grouped?.gods?.gods; const map = new Map(); if (!Array.isArray(gods)) { return map; } gods.forEach((god) => { if (god?.id) { map.set(god.id, god); } }); return map; } function findGodIdByName(name) { if (!name) { return null; } const normalized = normalizeText(name).toLowerCase().replace(/^the\s+/, ""); for (const [id, god] of state.godsById) { const godName = normalizeText(god?.name).toLowerCase().replace(/^the\s+/, ""); if (godName === normalized || id.toLowerCase() === normalized) { return id; } } return null; } function buildHebrewMap(magickDataset) { const map = new Map(); const letters = magickDataset?.grouped?.alphabets?.hebrew; if (!Array.isArray(letters)) { return map; } letters.forEach((letter) => { if (letter?.hebrewLetterId) { map.set(letter.hebrewLetterId, letter); } }); return map; } function sortMonths(months) { return [...months].sort((left, right) => Number(left?.order || 0) - Number(right?.order || 0)); } function getSelectedMonth() { return state.months.find((month) => month.id === state.selectedMonthId) || null; } function getMonthSubtitle(month) { if (state.selectedCalendar === "hebrew" || state.selectedCalendar === "islamic") { const native = month.nativeName ? ` · ${month.nativeName}` : ""; const days = month.days ? ` · ${month.days} days` : ""; return `${month.season || ""}${native}${days}`; } if (state.selectedCalendar === "wheel-of-year") { return [month.date, month.type, month.season].filter(Boolean).join(" · "); } return parseMonthRange(month); } function getMonthDayLinkRows(month) { const cacheKey = `${state.selectedCalendar}|${state.selectedYear}|${month?.id || ""}`; if (state.dayLinksCache.has(cacheKey)) { return state.dayLinksCache.get(cacheKey); } let dayCount = null; if (state.selectedCalendar === "gregorian") { dayCount = getDaysInMonth(state.selectedYear, Number(month?.order)); } else if (state.selectedCalendar === "hebrew" || state.selectedCalendar === "islamic") { const baseDays = Number(month?.days); const variantDays = Number(month?.daysVariant); if (Number.isFinite(baseDays) && Number.isFinite(variantDays)) { dayCount = Math.max(Math.trunc(baseDays), Math.trunc(variantDays)); } else if (Number.isFinite(baseDays)) { dayCount = Math.trunc(baseDays); } else if (Number.isFinite(variantDays)) { dayCount = Math.trunc(variantDays); } } if (!Number.isFinite(dayCount) || dayCount <= 0) { state.dayLinksCache.set(cacheKey, []); return []; } const rows = []; for (let day = 1; day <= dayCount; day += 1) { const gregorianDate = resolveCalendarDayToGregorian(month, day); rows.push({ day, gregorianDate: formatIsoDate(gregorianDate), isResolved: Boolean(gregorianDate && !Number.isNaN(gregorianDate.getTime())) }); } state.dayLinksCache.set(cacheKey, rows); return rows; } function renderList(elements) { const { monthListEl, monthCountEl, listTitleEl } = elements; if (!monthListEl) { return; } monthListEl.innerHTML = ""; state.filteredMonths.forEach((month) => { const isSelected = month.id === state.selectedMonthId; const itemEl = document.createElement("div"); itemEl.className = `planet-list-item${isSelected ? " is-selected" : ""}`; itemEl.setAttribute("role", "option"); itemEl.setAttribute("aria-selected", isSelected ? "true" : "false"); itemEl.dataset.monthId = month.id; itemEl.innerHTML = `
${month.name || month.id}
${getMonthSubtitle(month)}
`; itemEl.addEventListener("click", () => { selectByMonthId(month.id, elements); }); monthListEl.appendChild(itemEl); }); if (monthCountEl) { monthCountEl.textContent = state.searchQuery ? `${state.filteredMonths.length} of ${state.months.length} months` : `${state.months.length} months`; } if (listTitleEl) { listTitleEl.textContent = "Calendar > Months"; } } function associationSearchText(associations) { if (!associations || typeof associations !== "object") { return ""; } const tarotAliases = associations.tarotCard && typeof getTarotCardSearchAliases === "function" ? getTarotCardSearchAliases(associations.tarotCard, { trumpNumber: associations.tarotTrumpNumber }) : []; return [ associations.planetId, associations.zodiacSignId, associations.numberValue, associations.tarotCard, associations.tarotTrumpNumber, ...tarotAliases, associations.godId, associations.godName, associations.hebrewLetterId, associations.kabbalahPathNumber, associations.iChingPlanetaryInfluence ].filter(Boolean).join(" "); } function eventSearchText(event) { return normalizeSearchValue([ event?.name, event?.date, event?.dateRange, event?.description, associationSearchText(event?.associations) ].filter(Boolean).join(" ")); } function holidaySearchText(holiday) { return normalizeSearchValue([ holiday?.name, holiday?.kind, holiday?.date, holiday?.dateRange, holiday?.dateText, holiday?.monthDayStart, holiday?.calendarId, holiday?.description, associationSearchText(holiday?.associations) ].filter(Boolean).join(" ")); } function buildHolidayList(month) { const calendarId = state.selectedCalendar; const monthOrder = Number(month?.order); const fromRepo = state.calendarHolidays.filter((holiday) => { const holidayCalendarId = normalizeText(holiday?.calendarId).toLowerCase(); if (holidayCalendarId !== calendarId) { return false; } const isDirectMonthMatch = normalizeText(holiday?.monthId).toLowerCase() === normalizeText(month?.id).toLowerCase(); if (isDirectMonthMatch) { return true; } if (calendarId === "gregorian" && holiday?.dateRule && Number.isFinite(monthOrder)) { const computedDate = resolveHolidayGregorianDate(holiday); return computedDate instanceof Date && !Number.isNaN(computedDate.getTime()) && (computedDate.getMonth() + 1) === Math.trunc(monthOrder); } return false; }); if (fromRepo.length) { return [...fromRepo].sort((left, right) => { const leftDate = resolveHolidayGregorianDate(left); const rightDate = resolveHolidayGregorianDate(right); const leftDay = Number.isFinite(Number(left?.day)) ? Number(left.day) : ((leftDate instanceof Date && !Number.isNaN(leftDate.getTime())) ? leftDate.getDate() : NaN); const rightDay = Number.isFinite(Number(right?.day)) ? Number(right.day) : ((rightDate instanceof Date && !Number.isNaN(rightDate.getTime())) ? rightDate.getDate() : NaN); if (Number.isFinite(leftDay) && Number.isFinite(rightDay) && leftDay !== rightDay) { return leftDay - rightDay; } return normalizeText(left?.name).localeCompare(normalizeText(right?.name)); }); } const seen = new Set(); const ordered = []; (month?.holidayIds || []).forEach((holidayId) => { const holiday = state.holidays.find((item) => item.id === holidayId); if (holiday && !seen.has(holiday.id)) { seen.add(holiday.id); ordered.push(holiday); } }); state.holidays.forEach((holiday) => { if (holiday?.monthId === month.id && !seen.has(holiday.id)) { seen.add(holiday.id); ordered.push(holiday); } }); return ordered; } function buildMonthSearchText(month) { const monthHolidays = buildHolidayList(month); const holidayText = monthHolidays.map((holiday) => holidaySearchText(holiday)).join(" "); if (state.selectedCalendar === "gregorian") { const events = Array.isArray(month?.events) ? month.events : []; return normalizeSearchValue([ month?.name, month?.id, month?.start, month?.end, month?.coreTheme, month?.seasonNorth, month?.seasonSouth, associationSearchText(month?.associations), events.map((event) => eventSearchText(event)).join(" "), holidayText ].filter(Boolean).join(" ")); } const wheelAssocText = month?.associations ? [ Array.isArray(month.associations.themes) ? month.associations.themes.join(" ") : "", Array.isArray(month.associations.deities) ? month.associations.deities.join(" ") : "", month.associations.element, month.associations.direction ].filter(Boolean).join(" ") : ""; return normalizeSearchValue([ month?.name, month?.id, month?.nativeName, month?.meaning, month?.season, month?.description, month?.zodiacSign, month?.tribe, month?.element, month?.type, month?.date, month?.hebrewLetter, holidayText, wheelAssocText ].filter(Boolean).join(" ")); } function matchesSearch(searchText) { if (!state.searchQuery) { return true; } return searchText.includes(state.searchQuery); } function syncSearchControls(elements) { if (elements.searchInputEl) { elements.searchInputEl.value = state.searchQuery; } if (elements.searchClearEl) { elements.searchClearEl.disabled = !state.searchQuery; } } function renderDetail(elements) { calendarDetailUi.renderDetail?.(elements); } function applySearchFilter(elements) { state.filteredMonths = state.searchQuery ? state.months.filter((month) => matchesSearch(buildMonthSearchText(month))) : [...state.months]; if (!state.filteredMonths.some((month) => month.id === state.selectedMonthId)) { state.selectedMonthId = state.filteredMonths[0]?.id || null; } syncSearchControls(elements); renderList(elements); renderDetail(elements); } function selectByMonthId(monthId, elements = getElements()) { const target = state.months.find((month) => month.id === monthId); if (!target) { return false; } if (state.searchQuery && !state.filteredMonths.some((month) => month.id === target.id)) { state.searchQuery = ""; state.filteredMonths = [...state.months]; } if (state.selectedMonthId !== target.id) { clearSelectedDayFilter(); } state.selectedMonthId = target.id; syncSearchControls(elements); renderList(elements); renderDetail(elements); return true; } function selectCalendarType(calendarId, elements = getElements()) { if (!state.calendarData || !Array.isArray(state.calendarData[calendarId])) { return false; } if (elements.calendarTypeEl) { elements.calendarTypeEl.value = calendarId; } loadCalendarType(calendarId, elements); return true; } function bindYearInput(elements) { if (!elements.yearInputEl) { return; } elements.yearInputEl.value = String(state.selectedYear); elements.yearInputEl.addEventListener("change", () => { const nextYear = Number(elements.yearInputEl.value); if (!Number.isFinite(nextYear)) { elements.yearInputEl.value = String(state.selectedYear); return; } state.selectedYear = Math.min(2500, Math.max(1900, Math.round(nextYear))); elements.yearInputEl.value = String(state.selectedYear); state.dayLinksCache = new Map(); clearSelectedDayFilter(); renderDetail(elements); }); } function bindSearchInput(elements) { if (elements.searchInputEl) { elements.searchInputEl.addEventListener("input", () => { state.searchQuery = normalizeSearchValue(elements.searchInputEl.value); applySearchFilter(elements); }); } if (elements.searchClearEl && elements.searchInputEl) { elements.searchClearEl.addEventListener("click", () => { state.searchQuery = ""; elements.searchInputEl.value = ""; applySearchFilter(elements); elements.searchInputEl.focus(); }); } } function loadCalendarType(calendarId, elements) { const months = state.calendarData[calendarId]; if (!Array.isArray(months)) { return; } state.selectedCalendar = calendarId; state.dayLinksCache = new Map(); clearSelectedDayFilter(); state.months = sortMonths(months); state.filteredMonths = [...state.months]; state.selectedMonthId = state.months[0]?.id || null; state.searchQuery = ""; if (elements.calendarYearWrapEl) { elements.calendarYearWrapEl.hidden = false; } syncSearchControls(elements); applySearchFilter(elements); } function bindCalendarTypeSelect(elements) { if (!elements.calendarTypeEl) { return; } elements.calendarTypeEl.value = state.selectedCalendar; elements.calendarTypeEl.addEventListener("change", () => { loadCalendarType(String(elements.calendarTypeEl.value || "gregorian"), elements); }); } function ensureCalendarSection(referenceData, magickDataset) { if (!referenceData) { return; } calendarDatesUi.init?.({ getSelectedYear: () => state.selectedYear, getSelectedCalendar: () => state.selectedCalendar, getIslamicMonths: () => state.calendarData?.islamic || [] }); calendarDetailUi.init?.({ getState: () => state, getElements, getSelectedMonth, getSelectedDayFilterContext, clearSelectedDayFilter, toggleDayFilterEntry, toggleDayRangeFilter, getMonthSubtitle, getMonthDayLinkRows, buildDecanTarotRowsForMonth, buildHolidayList, matchesSearch, eventSearchText, holidaySearchText, getDisplayTarotName, cap, formatGregorianReferenceDate, getDaysInMonth, getMonthStartWeekday, getGregorianMonthStartDate, formatCalendarDateFromGregorian, parseMonthDayToken, parseMonthDayTokensFromText, parseMonthDayStartToken, parseDayRangeFromText, parseMonthRange, formatIsoDate, resolveHolidayGregorianDate, isMonthDayInRange, intersectDateRanges, getGregorianReferenceDateForCalendarMonth, normalizeCalendarText, findGodIdByName }); state.referenceData = referenceData; state.magickDataset = magickDataset || null; state.dayLinksCache = new Map(); clearSelectedDayFilter(); state.holidays = Array.isArray(referenceData.celestialHolidays) ? referenceData.celestialHolidays : []; state.calendarHolidays = Array.isArray(referenceData.calendarHolidays) ? referenceData.calendarHolidays : []; state.planetsById = buildPlanetMap(referenceData.planets); state.signsById = buildSignsMap(referenceData.signs); state.godsById = buildGodsMap(state.magickDataset); state.hebrewById = buildHebrewMap(state.magickDataset); state.calendarData = { gregorian: Array.isArray(referenceData.calendarMonths) ? referenceData.calendarMonths : [], hebrew: Array.isArray(referenceData.hebrewCalendar?.months) ? referenceData.hebrewCalendar.months : [], islamic: Array.isArray(referenceData.islamicCalendar?.months) ? referenceData.islamicCalendar.months : [], "wheel-of-year": Array.isArray(referenceData.wheelOfYear?.months) ? referenceData.wheelOfYear.months : [] }; const currentCalMonths = state.calendarData[state.selectedCalendar] || state.calendarData.gregorian || []; state.months = sortMonths(currentCalMonths); state.filteredMonths = [...state.months]; const elements = getElements(); if (elements.calendarYearWrapEl) { elements.calendarYearWrapEl.hidden = false; } if (!state.months.length) { if (elements.detailNameEl) { elements.detailNameEl.textContent = "Calendar"; } if (elements.detailSubEl) { elements.detailSubEl.textContent = "No month data available."; } if (elements.detailBodyEl) { elements.detailBodyEl.innerHTML = ""; } if (elements.monthListEl) { elements.monthListEl.innerHTML = ""; } return; } if (!state.selectedMonthId || !state.months.some((month) => month.id === state.selectedMonthId)) { state.selectedMonthId = state.months[0].id; } if (!state.initialized) { state.initialized = true; bindYearInput(elements); bindSearchInput(elements); bindCalendarTypeSelect(elements); } applySearchFilter(elements); } window.CalendarSectionUi = { ensureCalendarSection, selectByMonthId, selectCalendarType }; })();