refraction almost completed

This commit is contained in:
2026-03-07 13:38:13 -08:00
parent 3c07a13547
commit d44483de5e
37 changed files with 8506 additions and 7145 deletions

View File

@@ -3,6 +3,28 @@
"use strict";
const { getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
const holidayDataUi = window.HolidayDataUi || {};
const holidayRenderUi = window.HolidayRenderUi || {};
if (
typeof holidayDataUi.buildAllHolidays !== "function"
|| typeof holidayDataUi.buildCalendarData !== "function"
|| typeof holidayDataUi.buildGodsMap !== "function"
|| typeof holidayDataUi.buildHebrewMap !== "function"
|| typeof holidayDataUi.buildPlanetMap !== "function"
|| typeof holidayDataUi.buildSignsMap !== "function"
|| typeof holidayDataUi.calendarLabel !== "function"
|| typeof holidayDataUi.formatCalendarDateFromGregorian !== "function"
|| typeof holidayDataUi.formatGregorianReferenceDate !== "function"
|| typeof holidayDataUi.monthLabelForCalendar !== "function"
|| typeof holidayDataUi.normalizeSourceFilter !== "function"
|| typeof holidayDataUi.resolveHolidayGregorianDate !== "function"
|| typeof holidayRenderUi.holidaySearchText !== "function"
|| typeof holidayRenderUi.renderList !== "function"
|| typeof holidayRenderUi.renderHolidayDetail !== "function"
) {
throw new Error("HolidayDataUi and HolidayRenderUi modules must load before ui-holidays.js");
}
const state = {
initialized: false,
@@ -69,52 +91,6 @@
"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"),
@@ -180,583 +156,27 @@
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 "<div class=\"planet-text\">--</div>";
}
const buttons = [];
if (associations.planetId) {
buttons.push(
`<button class="alpha-nav-btn" data-nav="planet" data-planet-id="${associations.planetId}">${planetLabel(associations.planetId)} -></button>`
);
}
if (associations.zodiacSignId) {
buttons.push(
`<button class="alpha-nav-btn" data-nav="zodiac" data-sign-id="${associations.zodiacSignId}">${zodiacLabel(associations.zodiacSignId)} -></button>`
);
}
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(
`<button class="alpha-nav-btn" data-nav="number" data-number-value="${numberValue}">${label} -></button>`
);
}
}
}
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(
`<button class="alpha-nav-btn" data-nav="tarot-card" data-card-name="${associations.tarotCard}" data-trump-number="${tarotTrumpNumber ?? ""}">${tarotLabel} -></button>`
);
}
if (associations.godId || associations.godName) {
const label = godLabel(associations.godId, associations.godName);
buttons.push(
`<button class="alpha-nav-btn" data-nav="god" data-god-id="${associations.godId || ""}" data-god-name="${associations.godName || label}">${label} -></button>`
);
}
if (associations.hebrewLetterId) {
buttons.push(
`<button class="alpha-nav-btn" data-nav="alphabet" data-alphabet="hebrew" data-hebrew-letter-id="${associations.hebrewLetterId}">${hebrewLabel(associations.hebrewLetterId)} -></button>`
);
}
if (associations.kabbalahPathNumber != null) {
buttons.push(
`<button class="alpha-nav-btn" data-nav="kabbalah" data-path-no="${associations.kabbalahPathNumber}">Path ${associations.kabbalahPathNumber} -></button>`
);
}
if (associations.iChingPlanetaryInfluence) {
buttons.push(
`<button class="alpha-nav-btn" data-nav="iching" data-planetary-influence="${associations.iChingPlanetaryInfluence}">I Ching - ${associations.iChingPlanetaryInfluence} -></button>`
);
}
if (!buttons.length) {
return "<div class=\"planet-text\">--</div>";
}
return `<div class="alpha-nav-btns">${buttons.join("")}</div>`;
}
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 getRenderContext(elements = getElements()) {
return {
elements,
state,
cap,
normalizeSearchValue,
getDisplayTarotName,
resolveTarotTrumpNumber,
getTarotCardSearchAliases,
calendarLabel: holidayDataUi.calendarLabel,
monthLabelForCalendar: (calendarId, monthId) => holidayDataUi.monthLabelForCalendar(state.calendarData, calendarId, monthId),
normalizeSourceFilter: holidayDataUi.normalizeSourceFilter,
filterBySource,
resolveHolidayGregorianDate: (holiday) => holidayDataUi.resolveHolidayGregorianDate(holiday, {
selectedYear: state.selectedYear,
calendarData: state.calendarData
}),
formatGregorianReferenceDate: holidayDataUi.formatGregorianReferenceDate,
formatCalendarDateFromGregorian: holidayDataUi.formatCalendarDateFromGregorian,
selectByHolidayId
};
}
function getSelectedHoliday() {
@@ -764,7 +184,7 @@
}
function filterBySource(holidays) {
const source = normalizeSourceFilter(state.selectedSource);
const source = holidayDataUi.normalizeSourceFilter(state.selectedSource);
if (source === "all") {
return [...holidays];
}
@@ -773,7 +193,7 @@
function syncControls(elements) {
if (elements.sourceSelectEl) {
elements.sourceSelectEl.value = normalizeSourceFilter(state.selectedSource);
elements.sourceSelectEl.value = holidayDataUi.normalizeSourceFilter(state.selectedSource);
}
if (elements.yearInputEl) {
elements.yearInputEl.value = String(state.selectedYear);
@@ -787,99 +207,11 @@
}
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 = `
<div class="planet-list-name">${holiday?.name || holiday?.id || "Holiday"}</div>
<div class="planet-list-meta">${sourceCalendar} - ${sourceMonth} - ${sourceDate}</div>
`;
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}`;
}
holidayRenderUi.renderList(getRenderContext(elements));
}
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
? `<div class="alpha-nav-btns"><button class="alpha-nav-btn" data-nav="calendar-month" data-calendar-id="${holiday.calendarId || ""}" data-month-id="${holiday.monthId}">Open ${calendarLabel(holiday?.calendarId)} ${monthName} -></button></div>`
: "";
return `
<div class="planet-meta-grid">
<div class="planet-meta-card">
<strong>Holiday Facts</strong>
<div class="planet-text">
<dl class="alpha-dl">
<dt>Source Calendar</dt><dd>${calendarLabel(holiday?.calendarId)}</dd>
<dt>Source Month</dt><dd>${monthName}</dd>
<dt>Source Date</dt><dd>${holidayDate}</dd>
<dt>Reference Year</dt><dd>${state.selectedYear}</dd>
<dt>Conversion</dt><dd>${confidenceLabel}</dd>
</dl>
</div>
</div>
<div class="planet-meta-card">
<strong>Cross-Calendar Dates</strong>
<div class="planet-text">
<dl class="alpha-dl">
<dt>Gregorian</dt><dd>${gregorianRef}</dd>
<dt>Hebrew</dt><dd>${hebrewRef}</dd>
<dt>Islamic</dt><dd>${islamicRef}</dd>
</dl>
</div>
</div>
<div class="planet-meta-card">
<strong>Description</strong>
<div class="planet-text">${holiday?.description || "--"}</div>
${sourceMonthLink}
</div>
<div class="planet-meta-card">
<strong>Associations</strong>
${buildAssociationButtons(holiday?.associations)}
</div>
</div>
`;
return holidayRenderUi.renderHolidayDetail(holiday, getRenderContext());
}
function renderDetail(elements) {
@@ -897,7 +229,7 @@
}
detailNameEl.textContent = holiday?.name || holiday?.id || "Holiday";
detailSubEl.textContent = `${calendarLabel(holiday?.calendarId)} - ${monthLabelForCalendar(holiday?.calendarId, holiday?.monthId)}`;
detailSubEl.textContent = `${holidayDataUi.calendarLabel(holiday?.calendarId)} - ${holidayDataUi.monthLabelForCalendar(state.calendarData, holiday?.calendarId, holiday?.monthId)}`;
detailBodyEl.innerHTML = renderHolidayDetail(holiday);
attachNavHandlers(detailBodyEl);
}
@@ -905,7 +237,7 @@
function applyFilters(elements) {
const sourceFiltered = filterBySource(state.holidays);
state.filteredHolidays = state.searchQuery
? sourceFiltered.filter((holiday) => holidaySearchText(holiday).includes(state.searchQuery))
? sourceFiltered.filter((holiday) => holidayRenderUi.holidaySearchText(holiday, getRenderContext()).includes(state.searchQuery))
: sourceFiltered;
if (!state.filteredHolidays.some((holiday) => holiday.id === state.selectedHolidayId)) {
@@ -924,12 +256,12 @@
}
const targetCalendar = String(target.calendarId || "").trim().toLowerCase();
const activeFilter = normalizeSourceFilter(state.selectedSource);
const activeFilter = holidayDataUi.normalizeSourceFilter(state.selectedSource);
if (activeFilter !== "all" && activeFilter !== targetCalendar) {
state.selectedSource = targetCalendar || "all";
}
if (state.searchQuery && !holidaySearchText(target).includes(state.searchQuery)) {
if (state.searchQuery && !holidayRenderUi.holidaySearchText(target, getRenderContext()).includes(state.searchQuery)) {
state.searchQuery = "";
}
@@ -941,7 +273,7 @@
function bindControls(elements) {
if (elements.sourceSelectEl) {
elements.sourceSelectEl.addEventListener("change", () => {
state.selectedSource = normalizeSourceFilter(elements.sourceSelectEl.value);
state.selectedSource = holidayDataUi.normalizeSourceFilter(elements.sourceSelectEl.value);
applyFilters(elements);
});
}
@@ -1073,19 +405,13 @@
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.planetsById = holidayDataUi.buildPlanetMap(referenceData.planets);
state.signsById = holidayDataUi.buildSignsMap(referenceData.signs);
state.godsById = holidayDataUi.buildGodsMap(state.magickDataset);
state.hebrewById = holidayDataUi.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();
state.calendarData = holidayDataUi.buildCalendarData(referenceData);
state.holidays = holidayDataUi.buildAllHolidays(state.referenceData);
if (!state.selectedHolidayId || !state.holidays.some((holiday) => holiday.id === state.selectedHolidayId)) {
state.selectedHolidayId = state.holidays[0]?.id || null;
}