/* ui-holidays.js - Standalone holiday repository browser */ (function () { "use strict"; const { getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {}; const state = { initialized: false, referenceData: null, magickDataset: null, selectedYear: new Date().getFullYear(), selectedSource: "all", searchQuery: "", holidays: [], filteredHolidays: [], selectedHolidayId: null, planetsById: new Map(), signsById: new Map(), godsById: new Map(), hebrewById: new Map(), calendarData: {} }; 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 }; const HEBREW_MONTH_ALIAS_BY_ID = { nisan: ["nisan"], iyar: ["iyar"], sivan: ["sivan"], tammuz: ["tamuz", "tammuz"], av: ["av"], elul: ["elul"], tishrei: ["tishri", "tishrei"], cheshvan: ["heshvan", "cheshvan", "marcheshvan"], kislev: ["kislev"], tevet: ["tevet"], shvat: ["shevat", "shvat"], adar: ["adar", "adar i", "adar 1"], "adar-ii": ["adar ii", "adar 2"] }; const MONTH_NAME_TO_INDEX = { january: 0, february: 1, march: 2, april: 3, may: 4, june: 5, july: 6, august: 7, september: 8, october: 9, november: 10, december: 11 }; const GREGORIAN_MONTH_ID_TO_ORDER = { january: 1, february: 2, march: 3, april: 4, may: 5, june: 6, july: 7, august: 8, september: 9, october: 10, november: 11, december: 12 }; function getElements() { return { sourceSelectEl: document.getElementById("holiday-source-select"), yearInputEl: document.getElementById("holiday-year-input"), searchInputEl: document.getElementById("holiday-search-input"), searchClearEl: document.getElementById("holiday-search-clear"), countEl: document.getElementById("holiday-count"), listEl: document.getElementById("holiday-list"), detailNameEl: document.getElementById("holiday-detail-name"), detailSubEl: document.getElementById("holiday-detail-sub"), detailBodyEl: document.getElementById("holiday-detail-body") }; } function normalizeText(value) { return String(value || "").trim(); } function normalizeSearchValue(value) { return String(value || "").trim().toLowerCase(); } function cap(value) { const text = normalizeText(value); return text ? text.charAt(0).toUpperCase() + text.slice(1) : text; } function normalizeTarotName(value) { return String(value || "") .trim() .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 normalizeCalendarText(value) { return String(value || "") .normalize("NFKD") .replace(/[\u0300-\u036f]/g, "") .replace(/['`]/g, "") .toLowerCase() .replace(/[^a-z0-9]+/g, " ") .trim(); } function readNumericPart(parts, partType) { const raw = parts.find((part) => part.type === partType)?.value; if (!raw) { return null; } const digits = String(raw).replace(/[^0-9]/g, ""); if (!digits) { return null; } const parsed = Number(digits); return Number.isFinite(parsed) ? parsed : null; } function getGregorianMonthOrderFromId(monthId) { if (!monthId) { return null; } const key = String(monthId).trim().toLowerCase(); const value = GREGORIAN_MONTH_ID_TO_ORDER[key]; return Number.isFinite(value) ? value : null; } function parseMonthDayStartToken(token) { const match = String(token || "").match(/(\d{2})-(\d{2})/); if (!match) { return null; } const month = Number(match[1]); const day = Number(match[2]); if (!Number.isFinite(month) || !Number.isFinite(day)) { return null; } return { month, day }; } function createDateAtNoon(year, monthIndex, dayOfMonth) { return new Date(Math.trunc(year), monthIndex, Math.trunc(dayOfMonth), 12, 0, 0, 0); } function computeWesternEasterDate(year) { const y = Math.trunc(Number(year)); if (!Number.isFinite(y)) { return null; } // Meeus/Jones/Butcher Gregorian algorithm. const a = y % 19; const b = Math.floor(y / 100); const c = y % 100; const d = Math.floor(b / 4); const e = b % 4; const f = Math.floor((b + 8) / 25); const g = Math.floor((b - f + 1) / 3); const h = (19 * a + b - d - g + 15) % 30; const i = Math.floor(c / 4); const k = c % 4; const l = (32 + 2 * e + 2 * i - h - k) % 7; const m = Math.floor((a + 11 * h + 22 * l) / 451); const month = Math.floor((h + l - 7 * m + 114) / 31); const day = ((h + l - 7 * m + 114) % 31) + 1; return createDateAtNoon(y, month - 1, day); } function computeNthWeekdayOfMonth(year, monthIndex, weekday, ordinal) { const y = Math.trunc(Number(year)); if (!Number.isFinite(y)) { return null; } const first = createDateAtNoon(y, monthIndex, 1); const firstWeekday = first.getDay(); const offset = (weekday - firstWeekday + 7) % 7; const dayOfMonth = 1 + offset + (Math.trunc(ordinal) - 1) * 7; const daysInMonth = new Date(y, monthIndex + 1, 0).getDate(); if (dayOfMonth > daysInMonth) { return null; } return createDateAtNoon(y, monthIndex, dayOfMonth); } function resolveGregorianDateRule(rule) { const key = String(rule || "").trim().toLowerCase(); if (!key) { return null; } if (key === "gregorian-easter-sunday") { return computeWesternEasterDate(state.selectedYear); } if (key === "gregorian-good-friday") { const easter = computeWesternEasterDate(state.selectedYear); if (!(easter instanceof Date) || Number.isNaN(easter.getTime())) { return null; } return createDateAtNoon(easter.getFullYear(), easter.getMonth(), easter.getDate() - 2); } if (key === "gregorian-thanksgiving-us") { // US Thanksgiving: 4th Thursday of November. return computeNthWeekdayOfMonth(state.selectedYear, 10, 4, 4); } return null; } function parseFirstMonthDayFromText(dateText) { const text = String(dateText || "").replace(/~/g, " "); const firstSegment = text.split("/")[0] || text; const match = firstSegment.match(/(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2})/i); if (!match) { return null; } const monthIndex = MONTH_NAME_TO_INDEX[String(match[1]).toLowerCase()]; const day = Number(match[2]); if (!Number.isFinite(monthIndex) || !Number.isFinite(day)) { return null; } return { monthIndex, day }; } function findHebrewMonthDayInGregorianYear(monthId, day, year) { const aliases = HEBREW_MONTH_ALIAS_BY_ID[String(monthId || "").toLowerCase()] || []; const targetDay = Number(day); if (!aliases.length || !Number.isFinite(targetDay) || !Number.isFinite(year)) { return null; } const normalizedAliases = aliases.map((alias) => normalizeCalendarText(alias)).filter(Boolean); const formatter = new Intl.DateTimeFormat("en-u-ca-hebrew", { day: "numeric", month: "long", year: "numeric" }); const cursor = new Date(Math.trunc(year), 0, 1, 12, 0, 0, 0); const end = new Date(Math.trunc(year), 11, 31, 12, 0, 0, 0); while (cursor.getTime() <= end.getTime()) { const parts = formatter.formatToParts(cursor); const currentDay = readNumericPart(parts, "day"); const monthName = normalizeCalendarText(parts.find((part) => part.type === "month")?.value); if (currentDay === Math.trunc(targetDay) && normalizedAliases.includes(monthName)) { return new Date(cursor); } cursor.setDate(cursor.getDate() + 1); } return null; } function getIslamicMonthOrderById(monthId) { const month = (state.calendarData?.islamic || []).find((item) => item?.id === monthId); const order = Number(month?.order); return Number.isFinite(order) ? Math.trunc(order) : null; } function findIslamicMonthDayInGregorianYear(monthId, day, year) { const monthOrder = getIslamicMonthOrderById(monthId); const targetDay = Number(day); if (!Number.isFinite(monthOrder) || !Number.isFinite(targetDay) || !Number.isFinite(year)) { return null; } const formatter = new Intl.DateTimeFormat("en-u-ca-islamic", { day: "numeric", month: "numeric", year: "numeric" }); const cursor = new Date(Math.trunc(year), 0, 1, 12, 0, 0, 0); const end = new Date(Math.trunc(year), 11, 31, 12, 0, 0, 0); while (cursor.getTime() <= end.getTime()) { const parts = formatter.formatToParts(cursor); const currentDay = readNumericPart(parts, "day"); const currentMonth = readNumericPart(parts, "month"); if (currentDay === Math.trunc(targetDay) && currentMonth === monthOrder) { return new Date(cursor); } cursor.setDate(cursor.getDate() + 1); } return null; } function resolveHolidayGregorianDate(holiday) { if (!holiday || typeof holiday !== "object") { return null; } const calendarId = String(holiday.calendarId || "").trim().toLowerCase(); const monthId = String(holiday.monthId || "").trim().toLowerCase(); const day = Number(holiday.day); if (calendarId === "gregorian") { if (holiday?.dateRule) { const ruledDate = resolveGregorianDateRule(holiday.dateRule); if (ruledDate) { return ruledDate; } } const monthDay = parseMonthDayStartToken(holiday.monthDayStart) || parseMonthDayStartToken(holiday.dateText); if (monthDay) { return new Date(state.selectedYear, monthDay.month - 1, monthDay.day, 12, 0, 0, 0); } const order = getGregorianMonthOrderFromId(monthId); if (Number.isFinite(order) && Number.isFinite(day)) { return new Date(state.selectedYear, order - 1, Math.trunc(day), 12, 0, 0, 0); } return null; } if (calendarId === "hebrew") { return findHebrewMonthDayInGregorianYear(monthId, day, state.selectedYear); } if (calendarId === "islamic") { return findIslamicMonthDayInGregorianYear(monthId, day, state.selectedYear); } if (calendarId === "wheel-of-year") { const monthDay = parseMonthDayStartToken(holiday.monthDayStart) || parseFirstMonthDayFromText(holiday.dateText); if (monthDay?.month && monthDay?.day) { return new Date(state.selectedYear, monthDay.month - 1, monthDay.day, 12, 0, 0, 0); } if (monthDay?.monthIndex != null && monthDay?.day) { return new Date(state.selectedYear, monthDay.monthIndex, monthDay.day, 12, 0, 0, 0); } } return null; } function formatGregorianReferenceDate(date) { if (!(date instanceof Date) || Number.isNaN(date.getTime())) { return "--"; } return date.toLocaleDateString(undefined, { weekday: "long", year: "numeric", month: "long", day: "numeric" }); } function formatCalendarDateFromGregorian(date, calendarId) { if (!(date instanceof Date) || Number.isNaN(date.getTime())) { return "--"; } const locale = calendarId === "hebrew" ? "en-u-ca-hebrew" : (calendarId === "islamic" ? "en-u-ca-islamic" : "en"); return new Intl.DateTimeFormat(locale, { weekday: "long", year: "numeric", month: "long", day: "numeric" }).format(date); } function buildPlanetMap(planetsObj) { const map = new Map(); if (!planetsObj || typeof planetsObj !== "object") { return map; } Object.values(planetsObj).forEach((planet) => { if (!planet?.id) { return; } 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) { return; } 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) { return; } map.set(god.id, god); }); return map; } 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) { return; } map.set(letter.hebrewLetterId, letter); }); return map; } function calendarLabel(calendarId) { const key = String(calendarId || "").trim().toLowerCase(); if (key === "hebrew") return "Hebrew"; if (key === "islamic") return "Islamic"; if (key === "wheel-of-year") return "Wheel of the Year"; return "Gregorian"; } function monthLabelForCalendar(calendarId, monthId) { const months = state.calendarData?.[calendarId]; if (!Array.isArray(months)) { return monthId || "--"; } const month = months.find((entry) => String(entry?.id || "").toLowerCase() === String(monthId || "").toLowerCase()); return month?.name || monthId || "--"; } function normalizeSourceFilter(value) { const key = String(value || "").trim().toLowerCase(); if (key === "gregorian" || key === "hebrew" || key === "islamic" || key === "wheel-of-year") { return key; } return "all"; } function buildAllHolidays() { if (Array.isArray(state.referenceData?.calendarHolidays) && state.referenceData.calendarHolidays.length) { return [...state.referenceData.calendarHolidays].sort((left, right) => { const calCmp = calendarLabel(left?.calendarId).localeCompare(calendarLabel(right?.calendarId)); if (calCmp !== 0) return calCmp; const leftDay = Number(left?.day); const rightDay = Number(right?.day); if (Number.isFinite(leftDay) && Number.isFinite(rightDay) && leftDay !== rightDay) { return leftDay - rightDay; } return String(left?.name || "").localeCompare(String(right?.name || "")); }); } const legacy = Array.isArray(state.referenceData?.celestialHolidays) ? state.referenceData.celestialHolidays : []; return legacy.map((holiday) => ({ ...holiday, calendarId: "gregorian", dateText: holiday?.date || holiday?.dateRange || "" })); } function planetLabel(planetId) { if (!planetId) { return "Planet"; } const planet = state.planetsById.get(planetId); if (!planet) { return cap(planetId); } return `${planet.symbol || ""} ${planet.name || cap(planetId)}`.trim(); } function zodiacLabel(signId) { if (!signId) { return "Zodiac"; } const sign = state.signsById.get(signId); if (!sign) { return cap(signId); } return `${sign.symbol || ""} ${sign.name || cap(signId)}`.trim(); } function godLabel(godId, godName) { if (godName) { return godName; } if (!godId) { return "Deity"; } const god = state.godsById.get(godId); return god?.name || cap(godId); } function hebrewLabel(hebrewLetterId) { if (!hebrewLetterId) { return "Hebrew Letter"; } const letter = state.hebrewById.get(hebrewLetterId); if (!letter) { return cap(hebrewLetterId); } return `${letter.char || ""} ${letter.name || cap(hebrewLetterId)}`.trim(); } function computeDigitalRoot(value) { let current = Math.abs(Math.trunc(Number(value))); if (!Number.isFinite(current)) { return null; } while (current >= 10) { current = String(current) .split("") .reduce((sum, digit) => sum + Number(digit), 0); } return current; } function buildAssociationButtons(associations) { if (!associations || typeof associations !== "object") { return "
--
"; } const buttons = []; if (associations.planetId) { buttons.push( `` ); } if (associations.zodiacSignId) { buttons.push( `` ); } if (Number.isFinite(Number(associations.numberValue))) { const rawNumber = Math.trunc(Number(associations.numberValue)); if (rawNumber >= 0) { const numberValue = computeDigitalRoot(rawNumber); if (numberValue != null) { const label = rawNumber === numberValue ? `Number ${numberValue}` : `Number ${numberValue} (from ${rawNumber})`; buttons.push( `` ); } } } if (associations.tarotCard) { const trumpNumber = resolveTarotTrumpNumber(associations.tarotCard); const explicitTrumpNumber = Number(associations.tarotTrumpNumber); const tarotTrumpNumber = Number.isFinite(explicitTrumpNumber) ? explicitTrumpNumber : trumpNumber; const tarotLabel = getDisplayTarotName(associations.tarotCard, tarotTrumpNumber); buttons.push( `` ); } if (associations.godId || associations.godName) { const label = godLabel(associations.godId, associations.godName); buttons.push( `` ); } if (associations.hebrewLetterId) { buttons.push( `` ); } if (associations.kabbalahPathNumber != null) { buttons.push( `` ); } if (associations.iChingPlanetaryInfluence) { buttons.push( `` ); } if (!buttons.length) { return "
--
"; } return `
${buttons.join("")}
`; } 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 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 getSelectedHoliday() { return state.holidays.find((holiday) => holiday.id === state.selectedHolidayId) || null; } function filterBySource(holidays) { const source = normalizeSourceFilter(state.selectedSource); if (source === "all") { return [...holidays]; } return holidays.filter((holiday) => String(holiday?.calendarId || "").trim().toLowerCase() === source); } function syncControls(elements) { if (elements.sourceSelectEl) { elements.sourceSelectEl.value = normalizeSourceFilter(state.selectedSource); } if (elements.yearInputEl) { elements.yearInputEl.value = String(state.selectedYear); } if (elements.searchInputEl) { elements.searchInputEl.value = state.searchQuery; } if (elements.searchClearEl) { elements.searchClearEl.disabled = !state.searchQuery; } } function renderList(elements) { const { listEl, countEl } = elements; if (!listEl) { return; } listEl.innerHTML = ""; state.filteredHolidays.forEach((holiday) => { const isSelected = holiday.id === state.selectedHolidayId; 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.holidayId = holiday.id; const sourceCalendar = calendarLabel(holiday.calendarId); const sourceMonth = monthLabelForCalendar(holiday.calendarId, holiday.monthId); const sourceDate = holiday?.dateText || holiday?.date || holiday?.dateRange || "--"; itemEl.innerHTML = `
${holiday?.name || holiday?.id || "Holiday"}
${sourceCalendar} - ${sourceMonth} - ${sourceDate}
`; itemEl.addEventListener("click", () => { selectByHolidayId(holiday.id, elements); }); listEl.appendChild(itemEl); }); if (countEl) { const sourceFiltered = filterBySource(state.holidays); const activeFilter = normalizeSourceFilter(state.selectedSource); const sourceLabel = activeFilter === "all" ? "" : ` (${calendarLabel(activeFilter)})`; countEl.textContent = state.searchQuery ? `${state.filteredHolidays.length} of ${sourceFiltered.length} holidays${sourceLabel}` : `${sourceFiltered.length} holidays${sourceLabel}`; } } function renderHolidayDetail(holiday) { const gregorianDate = resolveHolidayGregorianDate(holiday); const gregorianRef = formatGregorianReferenceDate(gregorianDate); const hebrewRef = formatCalendarDateFromGregorian(gregorianDate, "hebrew"); const islamicRef = formatCalendarDateFromGregorian(gregorianDate, "islamic"); const confidence = String(holiday?.conversionConfidence || holiday?.datePrecision || "approximate").toLowerCase(); const confidenceLabel = (!(gregorianDate instanceof Date) || Number.isNaN(gregorianDate.getTime())) ? "unresolved" : (confidence === "exact" ? "exact" : "approximate"); const monthName = monthLabelForCalendar(holiday?.calendarId, holiday?.monthId); const holidayDate = holiday?.dateText || holiday?.date || holiday?.dateRange || "--"; const sourceMonthLink = holiday?.monthId ? `
` : ""; return `
Holiday Facts
Source Calendar
${calendarLabel(holiday?.calendarId)}
Source Month
${monthName}
Source Date
${holidayDate}
Reference Year
${state.selectedYear}
Conversion
${confidenceLabel}
Cross-Calendar Dates
Gregorian
${gregorianRef}
Hebrew
${hebrewRef}
Islamic
${islamicRef}
Description
${holiday?.description || "--"}
${sourceMonthLink}
Associations ${buildAssociationButtons(holiday?.associations)}
`; } function renderDetail(elements) { const { detailNameEl, detailSubEl, detailBodyEl } = elements; if (!detailNameEl || !detailSubEl || !detailBodyEl) { return; } const holiday = getSelectedHoliday(); if (!holiday) { detailNameEl.textContent = "--"; detailSubEl.textContent = "Select a holiday to explore"; detailBodyEl.innerHTML = ""; return; } detailNameEl.textContent = holiday?.name || holiday?.id || "Holiday"; detailSubEl.textContent = `${calendarLabel(holiday?.calendarId)} - ${monthLabelForCalendar(holiday?.calendarId, holiday?.monthId)}`; detailBodyEl.innerHTML = renderHolidayDetail(holiday); attachNavHandlers(detailBodyEl); } function applyFilters(elements) { const sourceFiltered = filterBySource(state.holidays); state.filteredHolidays = state.searchQuery ? sourceFiltered.filter((holiday) => holidaySearchText(holiday).includes(state.searchQuery)) : sourceFiltered; if (!state.filteredHolidays.some((holiday) => holiday.id === state.selectedHolidayId)) { state.selectedHolidayId = state.filteredHolidays[0]?.id || null; } syncControls(elements); renderList(elements); renderDetail(elements); } function selectByHolidayId(holidayId, elements = getElements()) { const target = state.holidays.find((holiday) => holiday.id === holidayId); if (!target) { return false; } const targetCalendar = String(target.calendarId || "").trim().toLowerCase(); const activeFilter = normalizeSourceFilter(state.selectedSource); if (activeFilter !== "all" && activeFilter !== targetCalendar) { state.selectedSource = targetCalendar || "all"; } if (state.searchQuery && !holidaySearchText(target).includes(state.searchQuery)) { state.searchQuery = ""; } state.selectedHolidayId = target.id; applyFilters(elements); return true; } function bindControls(elements) { if (elements.sourceSelectEl) { elements.sourceSelectEl.addEventListener("change", () => { state.selectedSource = normalizeSourceFilter(elements.sourceSelectEl.value); applyFilters(elements); }); } if (elements.yearInputEl) { 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); renderDetail(elements); }); } if (elements.searchInputEl) { elements.searchInputEl.addEventListener("input", () => { state.searchQuery = normalizeSearchValue(elements.searchInputEl.value); applyFilters(elements); }); } if (elements.searchClearEl && elements.searchInputEl) { elements.searchClearEl.addEventListener("click", () => { state.searchQuery = ""; elements.searchInputEl.value = ""; applyFilters(elements); elements.searchInputEl.focus(); }); } } function attachNavHandlers(detailBodyEl) { if (!detailBodyEl) { return; } detailBodyEl.querySelectorAll("[data-nav]").forEach((button) => { button.addEventListener("click", () => { const navType = button.dataset.nav; if (navType === "planet" && button.dataset.planetId) { document.dispatchEvent(new CustomEvent("nav:planet", { detail: { planetId: button.dataset.planetId } })); return; } if (navType === "zodiac" && button.dataset.signId) { document.dispatchEvent(new CustomEvent("nav:zodiac", { detail: { signId: button.dataset.signId } })); return; } if (navType === "number" && button.dataset.numberValue) { document.dispatchEvent(new CustomEvent("nav:number", { detail: { value: Number(button.dataset.numberValue) } })); return; } if (navType === "tarot-card" && button.dataset.cardName) { const trumpNumber = Number(button.dataset.trumpNumber); document.dispatchEvent(new CustomEvent("nav:tarot-trump", { detail: { cardName: button.dataset.cardName, trumpNumber: Number.isFinite(trumpNumber) ? trumpNumber : undefined } })); return; } if (navType === "god") { document.dispatchEvent(new CustomEvent("nav:gods", { detail: { godId: button.dataset.godId || undefined, godName: button.dataset.godName || undefined } })); return; } if (navType === "alphabet" && button.dataset.hebrewLetterId) { document.dispatchEvent(new CustomEvent("nav:alphabet", { detail: { alphabet: "hebrew", hebrewLetterId: button.dataset.hebrewLetterId } })); return; } if (navType === "kabbalah" && button.dataset.pathNo) { document.dispatchEvent(new CustomEvent("nav:kabbalah-path", { detail: { pathNo: Number(button.dataset.pathNo) } })); return; } if (navType === "iching" && button.dataset.planetaryInfluence) { document.dispatchEvent(new CustomEvent("nav:iching", { detail: { planetaryInfluence: button.dataset.planetaryInfluence } })); return; } if (navType === "calendar-month" && button.dataset.monthId) { document.dispatchEvent(new CustomEvent("nav:calendar-month", { detail: { calendarId: button.dataset.calendarId || undefined, monthId: button.dataset.monthId } })); } }); }); } function ensureHolidaySection(referenceData, magickDataset) { if (!referenceData) { return; } state.referenceData = referenceData; state.magickDataset = magickDataset || null; 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 : [] }; state.holidays = buildAllHolidays(); if (!state.selectedHolidayId || !state.holidays.some((holiday) => holiday.id === state.selectedHolidayId)) { state.selectedHolidayId = state.holidays[0]?.id || null; } const elements = getElements(); if (!state.initialized) { state.initialized = true; bindControls(elements); } applyFilters(elements); } window.HolidaySectionUi = { ensureHolidaySection, selectByHolidayId }; })();