Files
TaroTime/app/ui-calendar.js
2026-03-07 05:17:50 -08:00

1013 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 = `
<div class="planet-list-name">${month.name || month.id}</div>
<div class="planet-list-meta">${getMonthSubtitle(month)}</div>
`;
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
};
})();